nandi/gleanpublic⑂ Fork 0
⑂ cc6316c
Commits
⬇ Clone ▾
git clone https://git.rickub.com/nandi/glean.git
git clone ssh://git@rickub.com/nandi/glean.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Landing page overhaul and productionize tailwindcssUnverified

Julien Robert committed 2026-04-20T13:50:18+02:00 Browse files
cc6316c parent: fd2d54c
modified .gitignore +2 -1
@@ -25,5 +25,6 @@ go.work.sum
2525 *.db
2626 *.db.journal
2727
28-# tailwind output
28+# tailwind
2929 static/output.css
30+node_modules/
@@ -25,5 +25,6 @@ go.work.sum
25 *.db25 *.db
26 *.db.journal26 *.db.journal
27 27
28-# tailwind output28+# tailwind
29 static/output.css29 static/output.css
30+node_modules/
modified Makefile +10 -4
@@ -1,13 +1,19 @@
1-.PHONY: dev run build test clean
1+.PHONY: dev run build test clean css css-watch
22
3-dev:
3+dev: css
44 @if [ -f .env ]; then set -a; . ./.env; set +a; fi && go run .
55
6-build:
6+build: css
77 go build -o glean .
88
9+css:
10+ npx tailwindcss -i ./static/input.css -o ./static/output.css --minify
11+
12+css-watch:
13+ npx tailwindcss -i ./static/input.css -o ./static/output.css --watch
14+
915 test:
1016 go test ./...
1117
1218 clean:
13- rm -f glean glean.db
19+ rm -f glean glean.db static/output.css
@@ -1,13 +1,19 @@
1-.PHONY: dev run build test clean1+.PHONY: dev run build test clean css css-watch
2 2
3-dev:3+dev: css
4 @if [ -f .env ]; then set -a; . ./.env; set +a; fi && go run .4 @if [ -f .env ]; then set -a; . ./.env; set +a; fi && go run .
5 5
6-build:6+build: css
7 go build -o glean .7 go build -o glean .
8 8
9+css:
10+ npx tailwindcss -i ./static/input.css -o ./static/output.css --minify
11+
12+css-watch:
13+ npx tailwindcss -i ./static/input.css -o ./static/output.css --watch
14+
9 test:15 test:
10 go test ./...16 go test ./...
11 17
12 clean:18 clean:
13- rm -f glean glean.db19+ rm -f glean glean.db static/output.css
modified docs/design.md +548 -242
@@ -1,315 +1,621 @@
1-# Design System Inspired by Spotify
1+# Design System Inspired by Starbucks
22
33 ## 1. Visual Theme & Atmosphere
44
5-Spotify's web interface is a dark, immersive music player that wraps listeners in a near-black cocoon (`#121212`, `#181818`, `#1f1f1f`) where album art and content become the primary source of color. The design philosophy is "content-first darkness" — the UI recedes into shadow so that music, podcasts, and playlists can glow. Every surface is a shade of charcoal, creating a theater-like environment where the only true color comes from the brand Accent Purple (`#a855f7`) and the album artwork itself.
5+Starbucks' design system is a **warm, confident retail flagship** wearing the green of their storefront apron across every surface. The canvas alternates between a neutral-warm cream (`#f2f0eb`) and a ceramic off-white (`#edebe9`) — colors that reference actual store materials: the paper napkins, the café walls, the wood finishes — while the signature **Starbucks Green** (`#006241`) anchors the brand moment on hero bands, CTAs, and the Rewards experience. The greens come in four calibrated shades (Starbucks, Accent, House, Uplift) each mapped to a specific surface role, and gold (`#cba258`) appears only around Rewards-status ceremony — not as a general accent.
66
7-The typography uses SpotifyMixUI and SpotifyMixUITitle — proprietary fonts from the CircularSp family (Circular by Lineto, customized for Spotify) with an extensive fallback stack that includes Arabic, Hebrew, Cyrillic, Greek, Devanagari, and CJK fonts, reflecting Spotify's global reach. The type system is compact and functional: 700 (bold) for emphasis and navigation, 600 (semibold) for secondary emphasis, and 400 (regular) for body. Buttons use uppercase with positive letter-spacing (1.4px–2px) for a systematic, label-like quality.
7+Typography carries most of the brand voice. The proprietary **SoDoSans** typeface (custom to Starbucks) sits across nearly every surface with a tight `-0.16px` letter-spacing — it reads confident and friendly rather than fashion-magazine severe. What's unusual: the Rewards page switches to a warm serif (`"Lander Tall", "Iowan Old Style", Georgia`) for specific headline moments, subtly echoing the nostalgic feel of a coffeehouse chalkboard. And the Careers pages use a handwritten script (`"Kalam", "Comic Sans MS", cursive`) for personal cup-name touches. Three typefaces, three contexts — the system is disciplined about when each appears.
88
9-What distinguishes Spotify is its pill-and-circle geometry. Primary buttons use 500px–9999px radius (full pill), circular play buttons use 50% radius, and search inputs are 500px pills. Combined with heavy shadows (`rgba(0,0,0,0.5) 0px 8px 24px`) on elevated elements and a unique inset border-shadow combo (`rgb(18,18,18) 0px 1px 0px, rgb(124,124,124) 0px 0px 0px 1px inset`), the result is an interface that feels like a premium audio device — tactile, rounded, and built for touch.
9+The surfaces breathe through rounded geometry. Every button is a 50px full-pill. Cards take a 12px rounded-rectangle. The "Frap" floating CTA — a 56px circular order button in Green Accent (`#00754A`) — is the product's signature depth move: it floats bottom-right with a layered shadow stack (`0 0 6px rgba(0,0,0,0.24)` base + `0 8px 12px rgba(0,0,0,0.14)` ambient) and compresses via `scale(0.95)` on press. Elevations are otherwise restrained — card shadows stay at a whispered `0.14/0.24` alpha, global nav gets a quiet three-layer shadow stack. The whole system feels like clean café signage: legible, warm, and never shouting.
1010
1111 **Key Characteristics:**
1212
13-- Near-black immersive dark theme (`#121212`–`#1f1f1f`) — UI disappears behind content
14-- Accent Purple (`#a855f7`) as singular brand accent — never decorative, always functional
15-- SpotifyMixUI/CircularSp font family with global script support
16-- Pill buttons (500px–9999px) and circular controls (50%) — rounded, touch-optimized
17-- Uppercase button labels with wide letter-spacing (1.4px–2px)
18-- Heavy shadows on elevated elements (`rgba(0,0,0,0.5) 0px 8px 24px`)
19-- Semantic colors: negative red (`#f3727f`), warning orange (`#ffa42b`), announcement blue (`#539df5`)
20-- Album art as the primary color source — the UI is achromatic by design
21-- Light/dark theme toggle — persisted in localStorage, dark is default
13+- Four-tier green brand system (Starbucks / Accent / House / Uplift) each mapped to a distinct surface role — not a single "brand green"
14+- Gold reserved for Rewards-status moments only; never a general-purpose accent
15+- Warm-neutral canvas (`#f2f0eb` / `#edebe9`) instead of cold white — references café materials
16+- Custom proprietary typeface (SoDoSans) with tight `-0.16px` letter-spacing as the universal voice
17+- Context-specific type switches: serif (Lander Tall) for Rewards, script (Kalam) for Careers cup-names
18+- Full-pill buttons (`50px` radius) universal, `scale(0.95)` active press the signature micro-interaction
19+- Floating "Frap" circular CTA (`56px`, Green Accent fill, layered shadow stack) — the product's signature elevation element
20+- Gift-card surfaces designed as **photographed physical product** — every card is a distinct illustrated photograph rather than a generated graphic
21+- 12px card radius + whisper-soft shadows keep content cards flat-plus-hint-of-lift
22+- Rem-based spacing scale anchored at 1.6rem (~16px) = `--space-3`, stepping to 6.4rem (~64px)
23+
24+**Color-block page rhythm:** Cream hero → White content sections → Dark-green (`#1E3932`) feature band with white text → Cream utility zone → Dark-green (`#1E3932`) footer with gold / white text — an espresso-dark bookend around the bright body.
2225
2326 ## 2. Color Palette & Roles
2427
25-All theme-dependent colors use CSS custom properties defined on `:root` (dark, default) and `[data-theme="light"]` (light). Accent and semantic colors are constant across themes.
26-
27-### Tailwind Token Mapping
28-
29-The `spot.*` namespace provides semantic tokens. Theme-dependent tokens resolve to CSS variables.
30-
31-| Token | CSS Variable | Dark (default) | Light | Use |
32-| ---------------------- | --------------------- | -------------------------------- | -------------------------------- | -------------------------------- |
33-| `spot.purple` | — | `#a855f7` | `#a855f7` | Primary accent |
34-| `spot.purple-border` | — | `#9333ea` | `#9333ea` | Accent border variant |
35-| `spot.bg` | `--spot-bg` | `#121212` | `#f5f5f5` | Page background |
36-| `spot.surface` | `--spot-surface` | `#181818` | `#ffffff` | Cards, containers, sidebar |
37-| `spot.hover` | `--spot-hover` | `#1f1f1f` | `#f3f4f6` | Interactive surface, input bg |
38-| `spot.hover-50` | `--spot-hover-50` | `rgba(31,31,31,0.5)` | `rgba(243,244,246,0.5)` | Card hover with transparency |
39-| `spot.text` | `--spot-text` | `#ffffff` | `#111827` | Primary text, headings |
40-| `spot.secondary` | `--spot-secondary` | `#b3b3b3` | `#6b7280` | Secondary text, muted labels |
41-| `spot.body` | `--spot-body` | `#cbcbcb` | `#374151` | Article body text |
42-| `spot.muted` | `--spot-muted` | `#4d4d4d` | `#9ca3af` | Very muted text, rank numbers |
43-| `spot.divider` | `--spot-divider` | `rgba(77,77,77,0.2)` | `#e5e7eb` | Subtle border dividers |
44-| `spot.divider-30` | `--spot-divider-30` | `rgba(77,77,77,0.3)` | `#d1d5db` | Slightly stronger dividers, hr |
45-| `spot.outline` | `--spot-outline` | `#7c7c7c` | `#d1d5db` | Visible borders on buttons/inputs|
46-| `spot.placeholder` | `--spot-placeholder` | `#4d4d4d` | `#9ca3af` | Input placeholder text |
47-| `spot.active-pill-bg` | `--spot-active-bg` | `#ffffff` | `#111827` | Active filter pill background |
48-| `spot.active-pill-text`| `--spot-active-text` | `#121212` | `#ffffff` | Active filter pill text |
49-| `spot.shadow` | `--spot-shadow` | `rgba(0,0,0,0.3) 0px 8px 8px` | `rgba(0,0,0,0.08) 0px 2px 8px` | Card elevation shadow |
50-| `spot.shadow-heavy` | `--spot-shadow-heavy` | `rgba(0,0,0,0.5) 0px 8px 24px` | `rgba(0,0,0,0.1) 0px 4px 16px` | Dialog/elevated panel shadow |
51-| `spot.red` | — | `#f3727f` | `#f3727f` | Error states |
52-| `spot.orange` | — | `#ffa42b` | `#ffa42b` | Warning states |
53-| `spot.blue` | — | `#539df5` | `#539df5` | Info states |
54-
55-### Shadows
56-
57-- **Card** (`var(--spot-shadow)`): Cards, dropdowns
58-- **Heavy** (`var(--spot-shadow-heavy)`): Dialogs, menus, elevated panels
59-- **Inset Border** (`rgb(18,18,18) 0px 1px 0px, rgb(124,124,124) 0px 0px 0px 1px inset`): Input border-shadow combo (dark only)
60-
61-## 3. Theme Toggle
62-
63-The app supports dark (default) and light themes. Theme preference is stored in `localStorage` under the key `theme`.
64-
65-### Implementation
66-
67-- CSS custom properties are defined on `:root` for dark and `[data-theme="light"]` for light
68-- A `<script>` block runs before render to set `data-theme` on `<html>`, preventing flash of wrong theme
69-- Toggle buttons appear in the sidebar footer (desktop) and mobile top bar
70-- Icon: moon (when in dark mode) / sun (when in light mode)
71-- Default: dark
72-
73-### CSS Variables
74-
75-```css
76-:root {
77- --spot-bg: #121212;
78- --spot-surface: #181818;
79- --spot-hover: #1f1f1f;
80- --spot-hover-50: rgba(31,31,31,0.5);
81- --spot-text: #ffffff;
82- --spot-secondary: #b3b3b3;
83- --spot-body: #cbcbcb;
84- --spot-muted: #4d4d4d;
85- --spot-divider: rgba(77,77,77,0.2);
86- --spot-divider-30: rgba(77,77,77,0.3);
87- --spot-outline: #7c7c7c;
88- --spot-placeholder: #4d4d4d;
89- --spot-active-bg: #ffffff;
90- --spot-active-text: #121212;
91- --spot-shadow: rgba(0,0,0,0.3) 0px 8px 8px;
92- --spot-shadow-heavy: rgba(0,0,0,0.5) 0px 8px 24px;
93-}
94-[data-theme="light"] {
95- --spot-bg: #f5f5f5;
96- --spot-surface: #ffffff;
97- --spot-hover: #f3f4f6;
98- --spot-hover-50: rgba(243,244,246,0.5);
99- --spot-text: #111827;
100- --spot-secondary: #6b7280;
101- --spot-body: #374151;
102- --spot-muted: #9ca3af;
103- --spot-divider: #e5e7eb;
104- --spot-divider-30: #d1d5db;
105- --spot-outline: #d1d5db;
106- --spot-placeholder: #9ca3af;
107- --spot-active-bg: #111827;
108- --spot-active-text: #ffffff;
109- --spot-shadow: rgba(0,0,0,0.08) 0px 2px 8px;
110- --spot-shadow-heavy: rgba(0,0,0,0.1) 0px 4px 16px;
111-}
112-```
113-
114-## 4. Typography Rules
115-
116-### Font Families
117-
118-- **Title**: `SpotifyMixUITitle`, fallbacks: `CircularSp-Arab, CircularSp-Hebr, CircularSp-Cyrl, CircularSp-Grek, CircularSp-Deva, Helvetica Neue, helvetica, arial, Hiragino Sans, Hiragino Kaku Gothic ProN, Meiryo, MS Gothic`
119-- **UI / Body**: `SpotifyMixUI`, same fallback stack
28+**Source pages analyzed:** homepage, rewards, gift cards, product detail (Pink Energy Drink), product nutrition (Cold Brew).
29+
30+### Primary
31+
32+- **Starbucks Green** (`#006241`): The historic brand green. Used on h1 headings, primary section headers on the Rewards page, and as the main brand signal wherever a single dominant color is needed.
33+- **Green Accent** (`#00754A`): A slightly brighter, more luminous green. The primary filled-CTA color ("Explore our afternoon menu", "See the spring menu") and the fill of the floating Frap circular button.
34+- **House Green** (`#1E3932`): The deep near-black brand green. Footer surface, feature-band backgrounds, reward-status dark surfaces, and the headline "Free coffee is just the beginning" hero band on Rewards.
35+- **Green Uplift** (`#2b5148`): A secondary mid-dark green used sparingly on decorative accents and dark-gradient moments.
36+- **Green Light** (`#d4e9e2`): A pale mint wash used for form-valid-state tints and light green utility surfaces.
37+
38+### Secondary & Accent
39+
40+- **Gold** (`#cba258`): Reserved almost exclusively for Rewards-status ceremony — Gold-tier callouts, partnership badges (SkyMiles, Bonvoy), and premium-feeling accents. Never a general-purpose brand color.
41+- **Gold Light** (`#dfc49d`): Softer gold for background washes on gold-tier sections.
42+- **Gold Lightest** (`#faf6ee`): Cream-gold page-surface wash used under partnership sections on the Rewards page — ties the gold accent back into the warm neutral system.
43+
44+### Surface & Background
45+
46+- **White** (`#ffffff`): Primary card and modal surface. Also card fill on gift-card tiles.
47+- **Neutral Cool** (`#f9f9f9`): Subtle cool-gray surface used on dropdown menus ("Account" dropdown), form-card wraps, and quiet utility containers.
48+- **Neutral Warm** (`#f2f0eb`): The warm cream **primary page canvas** for Rewards utility zones and hero bands.
49+- **Ceramic** (`#edebe9`): A slightly warmer/darker cream for zone separators, soft page-section washes, and Rewards partnership band.
50+- **Black** (`#000000`): Deep ink reserved for the dark top-of-page CTA strip ("Join now") and high-contrast top-nav sign-in buttons.
51+
52+### Neutrals & Text
53+
54+- **Text Black** (`rgba(0, 0, 0, 0.87)`): Primary heading and body text color on light surfaces. Not pure black — an 87%-opacity black that reads warmer.
55+- **Text Black Soft** (`rgba(0, 0, 0, 0.58)`): Secondary/metadata text on light surfaces.
56+- **Text White** (`rgba(255, 255, 255, 1)`): Primary heading/body text on dark green surfaces.
57+- **Text White Soft** (`rgba(255, 255, 255, 0.70)`): Secondary text on dark-green surfaces — footer link descriptions, caption text.
58+- **Rewards Green** (`#33433d`): A dedicated muted slate-green used only on Rewards-page text blocks — a slightly "dustier" reading color than Text Black that signals "reward surface" without using full Starbucks Green.
59+
60+### Semantic & Accent
61+
62+- **Red** (`#c82014`): Error and destructive state (form invalid, destructive actions).
63+- **Yellow** (`#fbbc05`): Warning state, legacy brand touch.
64+- **Green Light** (`#d4e9e2` at 33% opacity = `hsl(160 32% 87% / 33%)`): Form valid-field tint background.
65+- **Red Tint** (`hsl(4 82% 43% / 5%)`): Invalid-field tint on forms.
66+
67+### Black / White Alpha Ladders
68+
69+Two parallel translucent scales for overlay and secondary-text use:
70+
71+- `rgba(0,0,0,0.06)` through `rgba(0,0,0,0.90)` in 10% steps — for dark overlays on light surfaces
72+- `rgba(255,255,255,0.10)` through `rgba(255,255,255,0.90)` in 10% steps — for light overlays on dark surfaces
73+
74+### Gradient System
75+
76+No structural gradient tokens observed. Surface hierarchy is solid-color-block throughout — the system relies on its five-tier cream/green surface palette rather than gradients.
77+
78+## 3. Typography Rules
79+
80+### Font Family
81+
82+- **Primary:** `SoDoSans, "Helvetica Neue", Helvetica, Arial, sans-serif` — Starbucks' proprietary corporate typeface, used across nearly every surface
83+- **Loading Fallback:** `"Helvetica Neue", Helvetica, Arial, sans-serif` — what users see before SoDoSans loads
84+- **Rewards Serif:** `"Lander Tall", "Iowan Old Style", Georgia, serif` — used on specific Rewards-page headline moments for a warm editorial feel
85+- **Careers Script:** `"Kalam", "Comic Sans MS", cursive` — used exclusively for Careers-page "cup name" decorative touches, referencing the hand-written names on Starbucks cups
86+
87+No OpenType stylistic sets explicitly activated at `:root`.
12088
12189 ### Hierarchy
12290
123-| Role | Font | Size | Weight | Line Height | Letter Spacing | Notes |
124-| ---------------- | ----------------- | ---------------- | ------- | ------------ | -------------- | ---------------------------- |
125-| Section Title | SpotifyMixUITitle | 24px (1.50rem) | 700 | normal | normal | Bold title weight |
126-| Feature Heading | SpotifyMixUI | 18px (1.13rem) | 600 | 1.30 (tight) | normal | Semibold section heads |
127-| Body Bold | SpotifyMixUI | 16px (1.00rem) | 700 | normal | normal | Emphasized text |
128-| Body | SpotifyMixUI | 16px (1.00rem) | 400 | normal | normal | Standard body |
129-| Button Uppercase | SpotifyMixUI | 14px (0.88rem) | 600–700 | 1.00 (tight) | 1.4px–2px | `text-transform: uppercase` |
130-| Button | SpotifyMixUI | 14px (0.88rem) | 700 | normal | 0.14px | Standard button |
131-| Nav Link Bold | SpotifyMixUI | 14px (0.88rem) | 700 | normal | normal | Navigation |
132-| Nav Link | SpotifyMixUI | 14px (0.88rem) | 400 | normal | normal | Inactive nav |
133-| Caption Bold | SpotifyMixUI | 14px (0.88rem) | 700 | 1.50–1.54 | normal | Bold metadata |
134-| Caption | SpotifyMixUI | 14px (0.88rem) | 400 | normal | normal | Metadata |
135-| Small Bold | SpotifyMixUI | 12px (0.75rem) | 700 | 1.50 | normal | Tags, counts |
136-| Small | SpotifyMixUI | 12px (0.75rem) | 400 | normal | normal | Fine print |
137-| Badge | SpotifyMixUI | 10.5px (0.66rem) | 600 | 1.33 | normal | `text-transform: capitalize` |
138-| Micro | SpotifyMixUI | 10px (0.63rem) | 400 | normal | normal | Smallest text |
91+| Role | Size | Weight | Line Height | Letter Spacing | Notes |
92+| ------------------- | -------------- | ------- | --------------- | -------------- | -------------------------------------------- |
93+| Display (text-10) | 5.0rem / 80px | 400–600 | 1.2 | -0.16px | Largest Rewards/hero display |
94+| Jumbo (text-9) | 3.6rem / 58px | 400–600 | 1.2 | -0.16px | Secondary hero headings |
95+| Hero Large (text-8) | 2.8rem / 45px | 400–600 | 1.2–1.5 | -0.16px | Landing section headlines |
96+| H1 | 24px | 600 | 36px | -0.16px | Starbucks-Green primary heading |
97+| H2 | 24px | 400 | 36px | -0.16px | Regular-weight section title in Text Black |
98+| Body Large | 19px | 400–600 | 33.25px (~1.75) | -0.16px | Hero intro copy, feature-band body |
99+| Body (text-3) | 1.6rem / 16px | 400 | 1.5 (24px) | -0.01em | Default body copy |
100+| Small (text-2) | 1.4rem / ~14px | 400–600 | 1.5 | -0.01em | Button label, metadata, form labels |
101+| Micro (text-1) | 1.3rem / ~13px | 400 | 1.5 | -0.01em | Active float-label state, caption micro-copy |
102+| Button Label | 14–16px | 400–600 | 1.2 | -0.01em | All pill-button labels |
103+
104+**Letter-spacing tokens:**
105+
106+- `letterSpacingNormal`: `-0.01em` (default — tight, characteristic)
107+- `letterSpacingLoose`: `0.1em` (emphasized caps)
108+- `letterSpacingLooser`: `0.15em` (uppercase-style labels, extreme emphasis)
109+
110+**Line-height tokens:**
111+
112+- `lineHeightNormal`: `1.5` (body)
113+- `lineHeightCompact`: `1.2` (display/buttons)
139114
140115 ### Principles
141116
142-- **Bold/regular binary**: Most text is either 700 (bold) or 400 (regular), with 600 used sparingly. This creates a clear visual hierarchy through weight contrast rather than size variation.
143-- **Uppercase buttons as system**: Button labels use uppercase + wide letter-spacing (1.4px–2px), creating a systematic "label" voice distinct from content text.
144-- **Compact sizing**: The range is 10px–24px — narrower than most systems. Spotify's type is compact and functional, designed for scanning playlists, not reading articles.
145-- **Global script support**: The extensive fallback stack (Arabic, Hebrew, Cyrillic, Greek, Devanagari, CJK) reflects Spotify's 180+ market reach.
117+- **Tight negative tracking (`-0.01em`)** is applied almost universally — the entire product reads slightly compressed, which gives SoDoSans its confident presence without feeling squeezed.
118+- **Weight shifts carry hierarchy, not size shifts.** H1 and H2 share the same 24px/36px size; only weight (600 vs 400) and color (Starbucks-Green vs Text Black) separate them.
119+- **Size tokens use rem, anchored to `1rem = 10px`** on this site (via a `font-size: 62.5%` root trick). So `1.6rem` = 16px, `2.4rem` = 24px, etc. The scale is semantic (textSize-1 through textSize-10), not arbitrary pixel values.
120+- **Context-specific typeface swaps** — serif on Rewards, script on Careers — are deliberate and localized. Never mix them with the primary sans within the same surface.
121+- **Body text never goes pure black** — it sits at `rgba(0,0,0,0.87)` to match the warm-neutral canvas temperature.
122+
123+### Note on Font Substitutes
124+
125+SoDoSans is proprietary to Starbucks (licensed from House Industries, not publicly available). Reasonable open-source substitutes:
126+
127+- **Inter** (Google Fonts) — similar humanist geometric proportions, wide weight range
128+- **Manrope** — slightly rounder, similar confident feel
129+- **Nunito Sans** — warmer, good for a "café" brand substitute
130+
131+If substituting, verify the tight `-0.01em` / `-0.16px` tracking still reads well; some open-source fonts need `-0.005em` instead.
146132
147-## 5. Component Stylings
133+Lander Tall (the Rewards serif) is custom — open-source substitutes: **Iowan Old Style** (already in fallback), **Lora**, or **Source Serif Pro**. Kalam (Careers script) is available on Google Fonts directly.
134+
135+## 4. Component Stylings
148136
149137 ### Buttons
150138
151-**Accent Pill**
139+**1. Primary Filled — "Explore our afternoon menu / Sign up for free"**
140+
141+- Background: `#00754A` (Green Accent)
142+- Text: `#ffffff`
143+- Border: `1px solid #00754A`
144+- Radius: `50px` (full pill)
145+- Padding: `7px 16px`
146+- Font: SoDoSans, 16px, weight 600, letter-spacing `-0.01em`
147+- Active state: `transform: scale(0.95)` via `--buttonActiveScale`
148+- Transition: `all 0.2s ease`
149+
150+**2. Primary Outlined — "Give them a try / Start an order"**
151+
152+- Background: transparent
153+- Text: `#00754A` (Green Accent)
154+- Border: `1px solid #00754A`
155+- Same radius/padding/active/transition as Primary Filled
156+
157+**3. Black Filled — "Join now"**
158+
159+- Background: `#000000`
160+- Text: `#ffffff`
161+- Border: `1px solid #000000`
162+- Radius: `50px`, Padding: `7px 16px`
163+- Font: 14px, weight 600
164+- Used on the top-of-page join strip and similar conversion moments
152165
153-- Background: `#a855f7`
154-- Text: `var(--spot-bg)` (dark in dark mode, light in light mode)
155-- Padding: 8px 16px
156-- Radius: 9999px (full pill)
157-- Use: Primary CTAs, add buttons
166+**4. Dark Outlined — "Sign in"**
158167
159-**Dark Pill**
168+- Background: transparent
169+- Text: `rgba(0, 0, 0, 0.87)` (Text Black)
170+- Border: `1px solid rgba(0, 0, 0, 0.87)`
171+- Radius: `50px`, Padding: `7px 16px`
172+- Font: 14px, weight 600
173+
174+**5. Green-on-Green Inverted — "See the spring menu"**
160175
161-- Background: `var(--spot-hover)`
162-- Text: `var(--spot-text)` or `var(--spot-secondary)`
163-- Padding: 8px 16px
164-- Radius: 9999px (full pill)
165-- Use: Navigation pills, secondary actions
176+- Background: `#ffffff`
177+- Text: `#00754A`
178+- Border: `1px solid #ffffff`
179+- Used when the surface behind the button is the dark green House Green band — white button with green text instead of a filled green pill on green bg
166180
167-**Outlined Pill**
181+**6. Outlined on Dark — "Learn more / Order now"**
168182
169183 - Background: transparent
170-- Text: `var(--spot-text)`
171-- Border: `1px solid var(--spot-outline)`
172-- Radius: 9999px
173-- Use: Follow buttons, secondary actions
184+- Text: `#ffffff`
185+- Border: `1px solid #ffffff`
186+- Used on dark-green feature bands for secondary action paired with a white filled CTA
187+
188+**7. Consent Agree (dark-green variant)**
189+
190+- Background: `rgb(0, 130, 72)` (a specific variant green used in the cookie-consent module)
191+- Text: `#ffffff`
192+- No border, `50px` radius, `7px 16px` padding, 14px / weight 400
193+- Slightly brighter than Green Accent — reserved for the consent-banner Agree action
174194
175-**Circular Play**
195+**8. Frap — Floating Circular Order Button**
176196
177-- Background: `#a855f7`
178-- Text: `var(--spot-bg)`
179-- Padding: 12px
180-- Radius: 50% (circle)
181-- Use: Play/pause controls
197+- Background: `#00754A` (Green Accent)
198+- Icon: `#ffffff`
199+- Size: `5.6rem / 56px` (standard), `4rem / 40px` (mini variant)
200+- Radius: `50%` (full circle)
201+- Fixed bottom-right, `-0.8rem` touch offset for extra tap comfort
202+- Shadow stack: base `0 0 6px rgba(0,0,0,0.24)` + ambient `0 8px 12px rgba(0,0,0,0.14)`
203+- Active state: ambient shadow fades to `0 8px 12px rgba(0,0,0,0)`
204+- This is the product's signature elevation element — it floats over every scrolled surface
205+
206+**9. Full-width Feedback Tab — "Provide feedback"**
207+
208+- Background: `#00754A`
209+- Text: `#ffffff`
210+- Radius: `12px 12px 0px 0px` (top-rounded only)
211+- Padding: `8px 16px`
212+- Font: 14px, weight 400
213+- Positioned fixed bottom-right-inside, attached to the viewport edge
182214
183215 ### Cards & Containers
184216
185-- Background: `var(--spot-surface)`
186-- Radius: 6px–8px
187-- No visible borders on most cards
188-- Hover: `var(--spot-hover-50)` background
189-- Shadow: `var(--spot-shadow)` on elevated
217+**Content Card (default)**
218+
219+- Background: `#ffffff` (`--cardBackgroundColor`)
220+- Radius: `12px` (`--cardBorderRadius`)
221+- Shadow: `0px 0px .5px 0px rgba(0,0,0,0.14), 0px 1px 1px 0px rgba(0,0,0,0.24)` (`--cardBoxShadow`)
222+- Used for: feature cards, menu-item tiles, reward-status panels
223+
224+**Gift Card Tile**
225+
226+- Background: illustrated photography fills the card (no solid bg)
227+- Radius: similar to cards (`~12px`, slightly tighter on corners)
228+- Shadow: lighter than default card — these are treated like physical cards laid on the canvas
229+- Labeled by category above the card grid (Spring, Thank You, Birthday, Celebration, Mother's Day, Appreciation, Encouragement, Milestones, Anytime)
230+
231+**Rewards Status Cards (Rewards page signature)**
232+
233+- Three-column grid: Bronze / Gold / Silver-ish — each a dark-green (`#1E3932`) panel with:
234+ - Colored gradient/color header ring
235+ - Numbered "Level" badge
236+ - Status title in large SoDoSans weight 600
237+ - Stars / benefits list in white/translucent-white text
238+ - Bottom "As you earn more stars…" progression caption
239+
240+**Partnership Card (Rewards)**
241+
242+- Background: `#faf6ee` (Gold Lightest) warm-cream surface
243+- Content: partner logos ("SkyMiles", "Bonvoy") centered, with descriptive text below
244+- Radius and shadow follow default card spec
245+
246+**Dropdown Menu (Account dropdown, top-nav)**
247+
248+- Background: `#f9f9f9` (Neutral Cool)
249+- Menu items at `24px / weight 400` in Text Black
250+- No border — just background surface shift against white nav
251+
252+**Modal**
253+
254+- Padding: `2.4rem` (`--modalPadding`)
255+- Top padding: `8.8rem` (`--modalTopPadding`) — leaves room for close button / header
256+- Combined vertical padding: `11.2rem`
257+- Radius inherits from card spec (`12px`)
258+
259+### Inputs & Forms
260+
261+**Floating Label Input**
262+
263+- Label floats above the input border when focused/filled
264+- Desktop label font size: `1.9rem` default, animates to `1.4rem` when active
265+- Mobile label font size: `1.6rem` default, animates to `1.3rem` active
266+- Label horizontal offset: `12px` from left
267+- Active label translate: up to `-12px` with `-50%` Y translation
268+- Field padding: `12px`
269+- Form horizontal padding: `1.6rem`
270+- Validation: valid-field gets `rgba(green-light, 0.33)` tint; invalid-field gets `rgba(red, 0.05)` tint
271+- Transition: `0.3s option-label-marker-expansion cubic-bezier(0.32, 2.32, 0.61, 0.27)` on checked-input
190272
191-### Inputs
273+**Option Icon (checkbox/radio)**
192274
193-- Background: `var(--spot-hover)`
194-- Text: `var(--spot-text)`
195-- Radius: 500px (pill)
196-- Focus ring: `#a855f7`
197-- Placeholder: `var(--spot-placeholder)`
275+- Padding: `3px` inner
276+- Uses the checked-input cubic-bezier animation above (a slightly "springy" 2.32 overshoot curve)
198277
199278 ### Navigation
200279
201-- Sidebar: `var(--spot-bg)` background
202-- Active items: 14px weight 700, `var(--spot-text)`
203-- Inactive items: 14px weight 400, `var(--spot-secondary)`
204-- Circular icon buttons (50% radius)
205-- Brand logo top-left in purple
280+**Global Nav (top bar)**
206281
207-## 6. Layout Principles
282+- Fixed position with progressive heights: `64px` xs → `72px` mobile → `83px` tablet → `99px` desktop
283+- Shadow stack: `0 1px 3px rgba(0,0,0,0.1), 0 2px 2px rgba(0,0,0,0.06), 0 0 2px rgba(0,0,0,0.07)` — three-layer soft lift
284+- Left: Starbucks wordmark logo, offsetting by `99px` (md) / `131px` (lg) from left edge
285+- Primary links inline in SoDoSans weight 400–600: Menu · Rewards · Gift Cards
286+- Right: Find a store link + Sign in (outlined) + Join now (black filled)
287+
288+**Sub-nav (second bar, e.g., Rewards internal)**
289+
290+- Height: `53px` (global subnav) / `48px` (internal subnav)
291+- Typically horizontal tab group beneath the global nav
292+
293+**Mobile Nav**
294+
295+- Collapses to a hamburger drawer below tablet breakpoint
296+- Frap floating button persists at bottom-right regardless of nav state
297+
298+### Image Treatment
299+
300+- **Hero photography**: Product photos (beverages in clear glass with colored backgrounds — coral, sage, warm amber) occupy ~40vw of a split-hero layout; text occupies the other 60vw (`--headerCrateProportion: 40vw` / `--contentCrateProportion: 60vw`)
301+- **Gift card illustrations**: Each card is a distinct illustrated photograph (painted-feel, hand-drawn-looking, warm color palette). Never generic generated graphics.
302+- **Rewards ceremony imagery**: Photographs of Starbucks Rewards App screens held in-hand, angled compositions — product-in-context photography.
303+- **Menu thumbnails**: Square or 4:3 product photography with clean white/cream backdrops, slight soft drop-shadow around the glass.
304+- **Image fade-in**: `opacity 0.3s ease-in` transition on image load (`--imageFadeTransition`).
305+
306+### Feature Band (dark-green hero strip)
307+
308+Full-width `#1E3932` (House Green) band with:
309+
310+- Left: white headline + subhead + CTA row
311+- Right: product photography or illustration
312+- Split ratio ~40/60 or 50/50 depending on section
313+- White text throughout with `rgba(255,255,255,0.70)` for secondary copy
314+- CTAs follow Green-on-Green Inverted (white filled) + Outlined on Dark (white outline) pairing
315+
316+### Expander / Accordion
317+
318+- Duration: `300ms` (`--expanderDuration`)
319+- Timing curve: `cubic-bezier(0.25, 0.46, 0.45, 0.94)` — a measured ease-out
320+- Used for FAQ sections on Rewards and gift page
321+
322+### Cookie Consent Module
323+
324+Dark-green modal card at top of page with "Agree" (green-filled) and "Manage preferences" (outlined) buttons. Appears on first visit; dismissible.
325+
326+### Product Detail Components (PDP signature cluster)
327+
328+A repeating component cluster used on menu product pages (e.g., `/menu/product/40498/iced` for a drink detail, `/menu/product/.../nutrition` for nutrition facts). These extend the component inventory without changing tokens.
329+
330+**Size Options Selector**
331+
332+- Horizontal row of 4 cup-icon buttons (Tall / Grande / Venti / Trenta)
333+- Each item: cup silhouette icon on top, size name below (16/700 in Starbucks-Green), fluid-ounce caption (13/400 in Text Black Soft)
334+- Active state: a green circular ring outline (`2px solid #00754A`) around the selected cup icon
335+- Inactive: no ring, same typography
336+- Full-width row, equal spacing
337+- Radius of container: `12px` or flat; individual icons are `50%` circular
338+- Padding: `16px 24px` internal
339+
340+**Add-in / Milk Select (outlined rectangle)**
341+
342+- Background: `#ffffff`
343+- Border: `1px solid #d6dbde` (Input Border)
344+- Radius: `4px`
345+- Full-width in its column
346+- Floating label above top border: "Add-ins" / "Milk" / "Add-ins" — 13/700 in Text Black, uppercase, `0.325px` letter-spacing
347+- Value displayed centered (e.g., "Ice", "Coconut", "Strawberry Fruit Inclusions scoop"): 16/400 Text Black
348+- Chevron-down icon right side in Text Black Soft
349+- Focus: border shifts to Green Accent (`#00754A`)
350+
351+**Numeric Stepper**
352+
353+- Embedded inside an Add-in row when a quantity is required (e.g., Strawberry Fruit Inclusions scoop)
354+- `−` minus button + count number + `+` plus button, all inline right of the label
355+- Buttons: circular `32×32px` with `1px solid #d6dbde` border, neutral gray icon
356+- Count number: 16/700 Text Black centered
357+
358+**Customize Button**
359+
360+- Background: `#ffffff`
361+- Text: `#00754A` (Green Accent)
362+- Border: `1.5px solid #00754A`
363+- Radius: `50px` (full pill)
364+- Padding: `14px 40px` (generously larger than default pills — this is a secondary primary action)
365+- Label: "Customize" with a gold sparkle ✨ icon inset left
366+- Used for: entering the drink-customization flow after size/milk selection
367+
368+**Add to Order Button (PDP)**
369+
370+- Background: `#00754A` (Green Accent)
371+- Text: `#ffffff`
372+- Radius: `50px`
373+- Padding: `14px 32px`
374+- Pinned top-right of product card and/or aligned right within the store-availability band
375+- Same scale(0.95) active behavior as other primary CTAs
376+
377+**Rewards Cost Pill — "200★ item"**
378+
379+- Background: transparent
380+- Border: `1px solid #cba258` (Gold)
381+- Text: `#cba258` (Gold)
382+- Radius: `50px` (full pill)
383+- Padding: `4px 12px`
384+- Content: "200★ item" where `★` is a small filled star glyph — indicates the Rewards Stars required to redeem this item
385+- Font: Proxima Nova 13/700 with `0.5px` letter-spacing
386+- Used only on products that are Rewards-redeemable
387+
388+**Product Description Band**
389+
390+- Full-width dark-green band (`#1E3932` House Green)
391+- Contains top-to-bottom:
392+ 1. Rewards Cost Pill (gold) if applicable
393+ 2. Product description body copy in white (16/400/1.5)
394+ 3. Nutritional summary inline ("140 calories, 25g sugar, 2.5g fat") with info-icon tooltip — 14/700 white
395+ 4. "Full nutrition & ingredients list" outlined-white-on-green pill button
396+- Padding: `32px` vertical
397+- Appears beneath the primary product header band
398+
399+**Ingredients / Nutrition Table**
400+
401+- Two-column layout on the Nutrition page
402+- Left column: "Ingredients" header + list or "Not available for this item" placeholder text block with an explanatory paragraph in Text Black Soft 14/400
403+- Right column: "Nutrition" header + label/value rows
404+- Each row: nutrient label (Proxima Nova 14/400) on the left, value (e.g., "140 calories", "25g", "205 mg\*\*") on the right, separated by a `1px solid #e7e7e7` hairline below
405+- Footnote for caffeine/asterisk markers in 13/400 Text Black Soft at the bottom
406+- Reusable pattern for nutrition facts regulation-compliant tables
407+
408+**Store Availability Selector**
409+
410+- Appears on dark-green feature band above the size-options row
411+- Full-width rounded rectangle with transparent-white interior
412+- Text: "For item availability, choose a store" in white, 14/400
413+- Right side: chevron-down affordance + shopping-bag SVG icon in white outline
414+- Radius: `4px`
415+- Height: ~48px
416+
417+**PDP Breadcrumb**
418+
419+- "Menu / Refreshers / Pink Energy Drink" trail above the product title
420+- Separator: `/` slash character in Text Black Soft
421+- Current page is unlinked, prior pages are underlined green-accent links
422+- Font: 14/400 Proxima Nova
423+- Appears on all PDP pages
424+
425+**Back Chevron Link (PDP nutrition / detail sub-pages)**
426+
427+- "← Back" text link above section headings on the nutrition page
428+- Text in Green Accent (`#00754A`) 14/700 Proxima Nova
429+- Left chevron `<` in the same green
430+- Alternative to full breadcrumb on deep sub-pages
431+
432+## 5. Layout Principles
208433
209434 ### Spacing System
210435
211-- Base unit: 8px
212-- Scale: 1px, 2px, 3px, 4px, 5px, 6px, 8px, 10px, 12px, 14px, 15px, 16px, 20px
436+Rem-based semantic scale (anchored `1rem = 10px`):
437+
438+| Token | Rem | Pixels | Typical Use |
439+| ----------- | -------- | ------ | ----------------------------------------- |
440+| `--space-1` | `0.4rem` | 4px | Tightest inline padding |
441+| `--space-2` | `0.8rem` | 8px | Small gap, button vertical padding |
442+| `--space-3` | `1.6rem` | 16px | Default — card padding, outer gutter xs |
443+| `--space-4` | `2.4rem` | 24px | Section inner spacing, outer gutter md |
444+| `--space-5` | `3.2rem` | 32px | Major between-section spacing |
445+| `--space-6` | `4rem` | 40px | Large gaps, outer gutter lg, header crate |
446+| `--space-7` | `4.8rem` | 48px | Section-to-section spacing |
447+| `--space-8` | `5.6rem` | 56px | Very large breathing — Frap height |
448+| `--space-9` | `6.4rem` | 64px | Widest section padding |
449+
450+**Gutter tokens:**
451+
452+- `--outerGutter: 1.6rem` (16px, default / mobile)
453+- `--outerGutterMedium: 2.4rem` (24px, tablet)
454+- `--outerGutterLarge: 4.0rem` (40px, desktop)
455+
456+**Universal rhythm constant:** `1.6rem` (16px) appears across every page as the default outer gutter, card padding baseline, and text size 3 body — the system's most frequent spacing unit.
213457
214458 ### Grid & Container
215459
216-- Sidebar (fixed) + main content area
217-- Grid-based album/playlist cards
218-- Responsive content area fills remaining space
460+- Column width scale: `--columnWidthSmall: 343px` / `Medium: 500px` / `Large: 720px` / `XLarge: 1440px`
461+- Gift-card grid uses a 3-5-up responsive grid of `~343px` tiles
462+- Rewards status section: 3-up dark-green panels at `lg+` breakpoints
463+- Hero: asymmetric split 40% (image) / 60% (content) via `--headerCrateProportion` / `--contentCrateProportion`
219464
220465 ### Whitespace Philosophy
221466
222-- **Dark compression**: Spotify packs content densely — playlist grids, track lists, and navigation are all tightly spaced. The dark background provides visual rest between elements without needing large gaps.
223-- **Content density over breathing room**: This is an app, not a marketing site. Every pixel serves the listening experience.
467+Whitespace carries the feeling of "plenty of space in the café." Section padding leans generous (40–64px). Content blocks are separated by whitespace rather than dividers. The cream canvas (`#f2f0eb`) is itself a visual breath between white cards and green feature bands.
224468
225469 ### Border Radius Scale
226470
227-- Minimal (2px): Badges, explicit tags
228-- Subtle (4px): Inputs, small elements
229-- Standard (6px): Album art containers, cards
230-- Comfortable (8px): Sections, dialogs
231-- Medium (10px–20px): Panels, overlay elements
232-- Large (100px): Large pill buttons
233-- Pill (500px): Primary buttons, search input
234-- Full Pill (9999px): Navigation pills, search
235-- Circle (50%): Play buttons, avatars, icons
471+| Value | Use |
472+| --------------- | ----------------------------------------------------------------------------------- |
473+| `12px` | Cards, modals, menu-item tiles (`--cardBorderRadius`) |
474+| `12px 12px 0 0` | Full-width feedback tab (top-rounded only) |
475+| `50px` | All buttons — full-pill radius (`--buttonBorderRadius`) |
476+| `50%` | Circular icons, Frap floating button, avatar thumbnails |
477+| Specialty | `3.3333%/5.298%` elliptical for Starbucks-Visa-Card mockups (`--svcRoundedCorners`) |
478+
479+## 6. Depth & Elevation
480+
481+| Level | Treatment | Use |
482+| -------------------- | --------------------------------------------------------------------------------- | ----------------------------------------------------- |
483+| Card | `0 0 0.5px rgba(0,0,0,0.14), 0 1px 1px rgba(0,0,0,0.24)` | Default content cards — a whisper-soft dual-shadow |
484+| Global Nav | `0 1px 3px rgba(0,0,0,0.1), 0 2px 2px rgba(0,0,0,0.06), 0 0 2px rgba(0,0,0,0.07)` | Triple-layer soft lift on the fixed top bar |
485+| Frap Base | `0 0 6px rgba(0,0,0,0.24)` | Base halo around the floating circular CTA |
486+| Frap Ambient | `0 8px 12px rgba(0,0,0,0.14)` | Stacked directional ambient — floats the Frap forward |
487+| Gift Card | Light drop shadow around illustrated photograph | Physical-card feel for gift tiles |
488+| Starbucks Card (SVC) | `drop-shadow(0 4px 1px rgba(0,0,0,0.11)) drop-shadow(0 0 2px rgba(0,0,0,0.24))` | Stacked SVG drop shadows for Starbucks Card visuals |
236489
237-## 7. Depth & Elevation
490+**Shadow philosophy:** Whisper-soft, layered over solid — the system never reaches for a single heavy drop shadow. Instead, it stacks 2–3 low-alpha shadows with different offsets to simulate real-world ambient + direct lighting. The Frap button is the most elevated element on any page.
238491
239-| Level | Treatment | Use |
240-| ------------------ | ---------------------------- | ------------------------------ |
241-| Base (Level 0) | `var(--spot-bg)` background | Deepest layer, page background |
242-| Surface (Level 1) | `var(--spot-surface)` | Cards, sidebar, containers |
243-| Elevated (Level 2) | `var(--spot-shadow)` | Dropdown menus, hover cards |
244-| Dialog (Level 3) | `var(--spot-shadow-heavy)` | Modals, overlays, menus |
492+### Decorative Depth
245493
246-## 8. Do's and Don'ts
494+- **No gradient system** — surfaces are solid color-block
495+- **Color-block banding** carries perceived depth (dark-green bands read as "recessed feature zones" between cream/white body sections)
496+- **SVG filter shadows** on Starbucks-Card visuals add a slight 3D physicality without a box-shadow
497+
498+## 7. Do's and Don'ts
247499
248500 ### Do
249501
250-- Use semantic color tokens (`spot-bg`, `spot-surface`, `spot-text`, etc.) — they adapt to theme
251-- Apply Accent Purple (`#a855f7`) only for play controls, active states, and primary CTAs
252-- Use pill shape (500px–9999px) for all buttons — circular (50%) for play controls
253-- Apply uppercase + wide letter-spacing (1.4px–2px) on button labels
254-- Keep typography compact (10px–24px range) — this is an app, not a magazine
255-- Use theme-aware shadows via CSS variables
256-- Test all components in both dark and light themes
502+- Use Neutral Warm (`#f2f0eb`) or Ceramic (`#edebe9`) as page canvas instead of pure white — the warm cream is the signature
503+- Map the green tiers to their intended surface role — Starbucks Green for headings, Green Accent for CTAs, House Green for deep bands, Uplift for decorative
504+- Keep tracking tight at `-0.01em` / `-0.16px` on SoDoSans across the whole system
505+- Use 50px full-pill radius on every button without exception
506+- Apply `transform: scale(0.95)` as the universal button active state
507+- Reserve Gold for Rewards-status ceremony moments only
508+- Use SoDoSans for nearly everything; switch to Lander Tall serif only for Rewards editorial headlines; reserve Kalam script for Careers "cup name" moments
509+- Layer 2–3 low-alpha shadows instead of one heavier drop shadow for elevation
510+- Use the Frap circular CTA as the persistent floating order entry on every shopping surface
511+- Let the cream canvas breathe between content cards — use whitespace, not dividers
257512
258513 ### Don't
259514
260-- Don't use Accent Purple decoratively or on backgrounds — it's functional only
261-- Don't hardcode theme-dependent colors — use CSS variable-backed tokens
262-- Don't skip the pill/circle geometry on buttons — square buttons break the identity
263-- Don't use hardcoded shadow values — use `shadow-spot` and `shadow-spot-heavy`
264-- Don't add additional brand colors — purple + achromatic grays is the complete palette
265-- Don't use `text-white` or `bg-white` directly — use `text-spot-text` and `bg-spot-active-pill-bg`
266-- Don't expose raw gray borders — use `border-spot-divider` or `border-spot-outline`
515+- Don't use pure white as the page canvas — the warm cream temperature is load-bearing
516+- Don't pick "one brand green" — the four-green system is intentional; using only `#006241` everywhere flattens the brand
517+- Don't use Gold as a general-purpose accent — it's a Rewards signal only
518+- Don't square the corners on buttons — the 50px pill is universal
519+- Don't introduce gradient fills — the system is color-block throughout
520+- Don't weight-contrast h1 and h2 by size — the hierarchy comes from weight + color (600 Starbucks-Green vs 400 Text Black)
521+- Don't use pure black for body text — `rgba(0,0,0,0.87)` matches the warm canvas
522+- Don't skip the `scale(0.95)` active feedback on buttons — it's a signature micro-interaction
523+- Don't stack single heavy shadows; always layer 2–3 low-alpha ones
524+- Don't introduce serifs or scripts into the main shopping flow — they belong to Rewards and Careers contexts respectively
267525
268-## 9. Responsive Behavior
526+## 8. Responsive Behavior
269527
270528 ### Breakpoints
271529
272-| Name | Width | Key Changes |
273-| ------------- | ----------- | --------------------- |
274-| Mobile Small | <425px | Compact mobile layout |
275-| Mobile | 425–576px | Standard mobile |
276-| Tablet | 576–768px | 2-column grid |
277-| Tablet Large | 768–896px | Expanded layout |
278-| Desktop Small | 896–1024px | Sidebar visible |
279-| Desktop | 1024–1280px | Full desktop layout |
280-| Large Desktop | >1280px | Expanded grid |
530+Inferred from component width tokens and progressive nav heights:
531+
532+| Name | Width | Key Changes |
533+| ------- | ----------- | ------------------------------------------------------------------------------- |
534+| xs | < 480px | Global nav 64px; hamburger menu; single-column layouts; pill buttons full-width |
535+| Mobile | 480–767px | Global nav 72px; gift-card grid 2-up; card padding tightens |
536+| Tablet | 768–1023px | Global nav 83px; gift-card grid 3-up; hero split begins to appear |
537+| Desktop | 1024–1439px | Global nav 99px; gift-card grid 4-up; full asymmetric hero 40/60 |
538+| XLarge | 1440px+ | Content caps at `--columnWidthXLarge`; gift-card grid 5-up; extra cream margin |
539+
540+### Touch Targets
541+
542+- Pill buttons at `7px 16px` padding measure ~32px tall — below 44px WCAG AAA minimum for touch-only surfaces. On mobile, button padding may be visually expanded to meet the minimum.
543+- Frap floating circular button at `56px` is well above minimum.
544+- Frap uses `--frapTouchOffset: calc(-1 * .8rem)` to extend tap area 8px beyond visual edge.
545+- Form float-label inputs grow their label font size on mobile (1.6rem base vs 1.9rem desktop) — easier to tap and read at arm's-length.
281546
282547 ### Collapsing Strategy
283548
284-- Sidebar: full → collapsed → hidden
285-- Album grid: 5 columns → 3 → 2 → 1
286-- Search: pill input maintained, width adjusts
287-- Navigation: sidebar → bottom bar on mobile
288-- Theme toggle: always accessible in sidebar footer / mobile header
549+- **Global nav height scales progressively**: 64 → 72 → 83 → 99px across breakpoints, not a single value
550+- **Hero split collapses**: 40/60 asymmetric split → stacked (image top, content below) at mobile
551+- **Gift-card grid**: 5-up → 4-up → 3-up → 2-up → 1-up across breakpoints with adjusted card widths
552+- **Feature bands**: Stay full-width but text + imagery stack vertically on mobile
553+- **Outer gutter scales**: 16px → 24px → 40px as viewport grows
554+- **Rewards 3-column status panels**: Stack to single column on mobile
289555
290-## 10. Agent Prompt Guide
556+### Image Behavior
557+
558+- Hero product photography crops tighter vertically on mobile; content becomes the visual anchor
559+- Gift-card illustrations preserve aspect ratio; card grid reflows
560+- `opacity 0.3s ease-in` fade-in transition on image load (prevents jarring pop-in)
561+- Rewards app-in-hand photography scales proportionally; never stretches
562+
563+## 9. Agent Prompt Guide
291564
292565 ### Quick Color Reference
293566
294-| Role | Token | Value (dark) |
295-| -------------- | -------------------- | -------------- |
296-| Background | `bg-spot-bg` | `#121212` |
297-| Surface | `bg-spot-surface` | `#181818` |
298-| Hover | `bg-spot-hover` | `#1f1f1f` |
299-| Text primary | `text-spot-text` | `#ffffff` |
300-| Text secondary | `text-spot-secondary`| `#b3b3b3` |
301-| Text body | `text-spot-body` | `#cbcbcb` |
302-| Text muted | `text-spot-muted` | `#4d4d4d` |
303-| Accent | `text-spot-purple` | `#a855f7` |
304-| Divider | `border-spot-divider`| `rgba(...)` |
305-| Outline | `border-spot-outline`| `#7c7c7c` |
306-| Error | `text-spot-red` | `#f3727f` |
567+- Primary CTA: "Green Accent (`#00754A`)"
568+- Primary CTA text: "White (`#ffffff`)"
569+- Brand heading: "Starbucks Green (`#006241`)"
570+- Feature band / footer: "House Green (`#1E3932`)"
571+- Page canvas: "Neutral Warm (`#f2f0eb`)"
572+- Card canvas: "White (`#ffffff`)"
573+- Heading text on light: "Text Black (`rgba(0,0,0,0.87)`)"
574+- Body text on light: "Text Black Soft (`rgba(0,0,0,0.58)`)"
575+- Body text on dark-green: "Text White Soft (`rgba(255,255,255,0.70)`)"
576+- Rewards accent: "Gold (`#cba258`)"
577+- Rewards text: "Rewards Green (`#33433d`)"
578+- Destructive: "Red (`#c82014`)"
579+
580+### Example Component Prompts
581+
582+1. "Create a primary Starbucks CTA pill button with Green Accent (`#00754A`) background, white text 'Explore our afternoon menu', SoDoSans font at 16px weight 600 with `-0.01em` letter-spacing, `50px` border-radius (full pill), `7px 16px` padding. Apply `transform: scale(0.95)` as the active state with a `0.2s ease` transition."
583+
584+2. "Design a content card with White (`#ffffff`) background at `12px` border-radius, layered shadow `0 0 0.5px rgba(0,0,0,0.14), 0 1px 1px rgba(0,0,0,0.24)`. Pad contents `16–24px` (`--space-3` to `--space-4`). Place on a Neutral Warm (`#f2f0eb`) page canvas with `16px` gap to siblings."
585+
586+3. "Build the Frap floating circular order button — `56px` diameter, Green Accent (`#00754A`) fill, white shopping-bag icon centered. Layered shadow: `0 0 6px rgba(0,0,0,0.24)` + `0 8px 12px rgba(0,0,0,0.14)`. Fixed position bottom-right with `-0.8rem` touch offset. Active state collapses the ambient shadow to `0 8px 12px rgba(0,0,0,0)` with `scale(0.95)`."
587+
588+4. "Build a dark-green feature band — full-width section with House Green (`#1E3932`) background. Left column: white SoDoSans h2 at 24px weight 600, followed by a Text White Soft (`rgba(255,255,255,0.70)`) body paragraph and a CTA row with two buttons (White-filled with Green Accent text for primary, Outlined-on-Dark white border for secondary). Right column: product photography. Split ratio 40/60, stacked vertically below `768px`."
589+
590+5. "Create a Rewards status card — House Green (`#1E3932`) panel with `12px` border-radius, colored gradient top stripe (Bronze/Silver/Gold tier). Title in SoDoSans 24px weight 600 in white. Benefits list as white bullets with `rgba(255,255,255,0.70)` secondary captions. Bottom progression text in Text White Soft. Stack 3 panels in a grid at `lg+`, single column on mobile."
591+
592+6. "Design a gift-card tile — card radius matches `12px`, fills with an illustrated photograph (hand-drawn watercolor-painted feel) as the entire surface. Subtle drop shadow makes it feel like a physical card on the cream canvas. Group under a category label ('Spring', 'Thank You', 'Birthday') in SoDoSans 24px weight 400 above the grid."
593+
594+7. "Create a Starbucks product-detail header — House Green (`#1E3932`) band with breadcrumb 'Menu / Refreshers / Pink Energy Drink' in 14/400 white above the product title in SoDoSans 32/700 uppercase white. Product photograph centered below title. Below photo: a 4-up size selector row — each cup-icon button shows a vertical cup silhouette, size name ('Tall' / 'Grande' / 'Venti' / 'Trenta') in 16/700 white, and fluid-ounce in 13/400 Text White Soft. Selected size wraps the cup icon in a `2px solid #00754A` circular ring."
595+
596+8. "Build a Starbucks customize flow — under the size selector, 3 stacked outlined-rectangle input rows (white bg, `1px solid #d6dbde` border, `4px` radius). Each has a floating label ('Add-ins', 'Milk', 'Add-ins') above the top border in 13/700 Text Black uppercase. Value centered (e.g., 'Ice', 'Coconut'). Right side: chevron-down in Text Black Soft. For the scoop row, embed a numeric stepper (`−` `1` `+` with circular `32px` outlined buttons). Below all three fields: outlined green 'Customize' pill with gold sparkle icon, `50px` radius, `14px 40px` padding. Pair with a Green Accent filled 'Add to Order' pill in the same row."
597+
598+9. "Design a Starbucks product description band — full-width House Green (`#1E3932`) below product header. Top: a gold-outlined '200★ item' Rewards Cost Pill (`50px` radius, `4px 12px` padding, gold `#cba258` border and text). Below: product description in white 16/400/1.5. Nutritional inline summary in white 14/700 ('140 calories, 25g sugar, 2.5g fat') with info-icon tooltip. Outlined-white-on-green pill button 'Full nutrition &amp; ingredients list'. 32px vertical padding."
599+
600+10. "Create a Starbucks nutrition facts table — two-column layout inside a White card. Left column: 'Ingredients' header (24/400 Text Black), followed by ingredient list or 'Not available for this item' placeholder paragraph in 14/400 Text Black Soft. Right column: 'Nutrition' header, then label/value rows (nutrient name left, value right) separated by `1px solid #e7e7e7` hairlines. Typography: labels in 14/400 Text Black, values in 14/700 Text Black right-aligned. Footnote asterisk markers in 13/400 Text Black Soft at the bottom."
307601
308602 ### Iteration Guide
309603
310-1. Use semantic tokens (`spot-bg`, `spot-surface`, `spot-text`) — they handle theme switching
311-2. Accent Purple (`spot-purple`) for functional highlights only (active, CTA)
312-3. Pill everything — 500px for large, 9999px for small, 50% for circular
313-4. Uppercase + wide tracking on buttons — the systematic label voice
314-5. Theme-aware shadows via `shadow-spot` and `shadow-spot-heavy`
315-6. Never hardcode `text-white` or `bg-white` — use semantic tokens
604+When refining existing screens generated with this design system:
605+
606+1. Focus on ONE component at a time
607+2. Reference specific color names and hex codes from this document
608+3. Use natural language descriptions ("warm cream canvas," "four-tier green system") alongside exact values
609+4. Preserve the 50px pill + `scale(0.95)` active state universally
610+5. Check that greens are mapped to their correct role (Green Accent for CTA, Starbucks Green for heading, House Green for band)
611+6. Don't introduce gradients — the system is color-block
612+7. Keep SoDoSans tracking at `-0.01em` / `-0.16px` across the board
613+
614+### Known Gaps
615+
616+- SoDoSans is a proprietary typeface not available on Google Fonts — when implementing publicly, use Inter or Manrope as a substitute and document the swap
617+- Lander Tall (Rewards serif) is also custom — substitute with Iowan Old Style, Lora, or Source Serif Pro
618+- Specific per-component animation timings beyond the few documented (`--duration: 0.4s`, `--iconTransition: all ease-out 0.2s`, `--expanderDuration: 300ms`) are not captured for every interactive surface
619+- Form error-state full styling (red border weight, icon placement) visible on the tint token but not exhaustively extracted
620+- Careers-page specific components (cup-name card, search radio grid) are referenced in token names but not covered by this extraction
621+- Starbucks Visa Card / Starbucks-Card (SVC) detailed mockup specs are hinted at by `--svcRoundedCorners` and `--svcShadowFilter` tokens but not fully documented
@@ -1,315 +1,621 @@
1-# Design System Inspired by Spotify1+# Design System Inspired by Starbucks
2 2
3 ## 1. Visual Theme & Atmosphere3 ## 1. Visual Theme & Atmosphere
4 4
5-Spotify's web interface is a dark, immersive music player that wraps listeners in a near-black cocoon (`#121212`, `#181818`, `#1f1f1f`) where album art and content become the primary source of color. The design philosophy is "content-first darkness" — the UI recedes into shadow so that music, podcasts, and playlists can glow. Every surface is a shade of charcoal, creating a theater-like environment where the only true color comes from the brand Accent Purple (`#a855f7`) and the album artwork itself.5+Starbucks' design system is a **warm, confident retail flagship** wearing the green of their storefront apron across every surface. The canvas alternates between a neutral-warm cream (`#f2f0eb`) and a ceramic off-white (`#edebe9`) — colors that reference actual store materials: the paper napkins, the café walls, the wood finishes — while the signature **Starbucks Green** (`#006241`) anchors the brand moment on hero bands, CTAs, and the Rewards experience. The greens come in four calibrated shades (Starbucks, Accent, House, Uplift) each mapped to a specific surface role, and gold (`#cba258`) appears only around Rewards-status ceremony — not as a general accent.
6 6
7-The typography uses SpotifyMixUI and SpotifyMixUITitle — proprietary fonts from the CircularSp family (Circular by Lineto, customized for Spotify) with an extensive fallback stack that includes Arabic, Hebrew, Cyrillic, Greek, Devanagari, and CJK fonts, reflecting Spotify's global reach. The type system is compact and functional: 700 (bold) for emphasis and navigation, 600 (semibold) for secondary emphasis, and 400 (regular) for body. Buttons use uppercase with positive letter-spacing (1.4px–2px) for a systematic, label-like quality.7+Typography carries most of the brand voice. The proprietary **SoDoSans** typeface (custom to Starbucks) sits across nearly every surface with a tight `-0.16px` letter-spacing — it reads confident and friendly rather than fashion-magazine severe. What's unusual: the Rewards page switches to a warm serif (`"Lander Tall", "Iowan Old Style", Georgia`) for specific headline moments, subtly echoing the nostalgic feel of a coffeehouse chalkboard. And the Careers pages use a handwritten script (`"Kalam", "Comic Sans MS", cursive`) for personal cup-name touches. Three typefaces, three contexts — the system is disciplined about when each appears.
8 8
9-What distinguishes Spotify is its pill-and-circle geometry. Primary buttons use 500px–9999px radius (full pill), circular play buttons use 50% radius, and search inputs are 500px pills. Combined with heavy shadows (`rgba(0,0,0,0.5) 0px 8px 24px`) on elevated elements and a unique inset border-shadow combo (`rgb(18,18,18) 0px 1px 0px, rgb(124,124,124) 0px 0px 0px 1px inset`), the result is an interface that feels like a premium audio device — tactile, rounded, and built for touch.9+The surfaces breathe through rounded geometry. Every button is a 50px full-pill. Cards take a 12px rounded-rectangle. The "Frap" floating CTA — a 56px circular order button in Green Accent (`#00754A`) — is the product's signature depth move: it floats bottom-right with a layered shadow stack (`0 0 6px rgba(0,0,0,0.24)` base + `0 8px 12px rgba(0,0,0,0.14)` ambient) and compresses via `scale(0.95)` on press. Elevations are otherwise restrained — card shadows stay at a whispered `0.14/0.24` alpha, global nav gets a quiet three-layer shadow stack. The whole system feels like clean café signage: legible, warm, and never shouting.
10 10
11 **Key Characteristics:**11 **Key Characteristics:**
12 12
13-- Near-black immersive dark theme (`#121212`–`#1f1f1f`) — UI disappears behind content13+- Four-tier green brand system (Starbucks / Accent / House / Uplift) each mapped to a distinct surface role — not a single "brand green"
14-- Accent Purple (`#a855f7`) as singular brand accent — never decorative, always functional14+- Gold reserved for Rewards-status moments only; never a general-purpose accent
15-- SpotifyMixUI/CircularSp font family with global script support15+- Warm-neutral canvas (`#f2f0eb` / `#edebe9`) instead of cold white — references café materials
16-- Pill buttons (500px–9999px) and circular controls (50%) — rounded, touch-optimized16+- Custom proprietary typeface (SoDoSans) with tight `-0.16px` letter-spacing as the universal voice
17-- Uppercase button labels with wide letter-spacing (1.4px–2px)17+- Context-specific type switches: serif (Lander Tall) for Rewards, script (Kalam) for Careers cup-names
18-- Heavy shadows on elevated elements (`rgba(0,0,0,0.5) 0px 8px 24px`)18+- Full-pill buttons (`50px` radius) universal, `scale(0.95)` active press the signature micro-interaction
19-- Semantic colors: negative red (`#f3727f`), warning orange (`#ffa42b`), announcement blue (`#539df5`)19+- Floating "Frap" circular CTA (`56px`, Green Accent fill, layered shadow stack) — the product's signature elevation element
20-- Album art as the primary color source — the UI is achromatic by design20+- Gift-card surfaces designed as **photographed physical product** — every card is a distinct illustrated photograph rather than a generated graphic
21-- Light/dark theme toggle — persisted in localStorage, dark is default21+- 12px card radius + whisper-soft shadows keep content cards flat-plus-hint-of-lift
22+- Rem-based spacing scale anchored at 1.6rem (~16px) = `--space-3`, stepping to 6.4rem (~64px)
23+
24+**Color-block page rhythm:** Cream hero → White content sections → Dark-green (`#1E3932`) feature band with white text → Cream utility zone → Dark-green (`#1E3932`) footer with gold / white text — an espresso-dark bookend around the bright body.
22 25
23 ## 2. Color Palette & Roles26 ## 2. Color Palette & Roles
24 27
25-All theme-dependent colors use CSS custom properties defined on `:root` (dark, default) and `[data-theme="light"]` (light). Accent and semantic colors are constant across themes.28+**Source pages analyzed:** homepage, rewards, gift cards, product detail (Pink Energy Drink), product nutrition (Cold Brew).
26-29+
27-### Tailwind Token Mapping30+### Primary
28-31+
29-The `spot.*` namespace provides semantic tokens. Theme-dependent tokens resolve to CSS variables.32+- **Starbucks Green** (`#006241`): The historic brand green. Used on h1 headings, primary section headers on the Rewards page, and as the main brand signal wherever a single dominant color is needed.
30-33+- **Green Accent** (`#00754A`): A slightly brighter, more luminous green. The primary filled-CTA color ("Explore our afternoon menu", "See the spring menu") and the fill of the floating Frap circular button.
31-| Token | CSS Variable | Dark (default) | Light | Use |34+- **House Green** (`#1E3932`): The deep near-black brand green. Footer surface, feature-band backgrounds, reward-status dark surfaces, and the headline "Free coffee is just the beginning" hero band on Rewards.
32-| ---------------------- | --------------------- | -------------------------------- | -------------------------------- | -------------------------------- |35+- **Green Uplift** (`#2b5148`): A secondary mid-dark green used sparingly on decorative accents and dark-gradient moments.
33-| `spot.purple` | — | `#a855f7` | `#a855f7` | Primary accent |36+- **Green Light** (`#d4e9e2`): A pale mint wash used for form-valid-state tints and light green utility surfaces.
34-| `spot.purple-border` | — | `#9333ea` | `#9333ea` | Accent border variant |37+
35-| `spot.bg` | `--spot-bg` | `#121212` | `#f5f5f5` | Page background |38+### Secondary & Accent
36-| `spot.surface` | `--spot-surface` | `#181818` | `#ffffff` | Cards, containers, sidebar |39+
37-| `spot.hover` | `--spot-hover` | `#1f1f1f` | `#f3f4f6` | Interactive surface, input bg |40+- **Gold** (`#cba258`): Reserved almost exclusively for Rewards-status ceremony — Gold-tier callouts, partnership badges (SkyMiles, Bonvoy), and premium-feeling accents. Never a general-purpose brand color.
38-| `spot.hover-50` | `--spot-hover-50` | `rgba(31,31,31,0.5)` | `rgba(243,244,246,0.5)` | Card hover with transparency |41+- **Gold Light** (`#dfc49d`): Softer gold for background washes on gold-tier sections.
39-| `spot.text` | `--spot-text` | `#ffffff` | `#111827` | Primary text, headings |42+- **Gold Lightest** (`#faf6ee`): Cream-gold page-surface wash used under partnership sections on the Rewards page — ties the gold accent back into the warm neutral system.
40-| `spot.secondary` | `--spot-secondary` | `#b3b3b3` | `#6b7280` | Secondary text, muted labels |43+
41-| `spot.body` | `--spot-body` | `#cbcbcb` | `#374151` | Article body text |44+### Surface & Background
42-| `spot.muted` | `--spot-muted` | `#4d4d4d` | `#9ca3af` | Very muted text, rank numbers |45+
43-| `spot.divider` | `--spot-divider` | `rgba(77,77,77,0.2)` | `#e5e7eb` | Subtle border dividers |46+- **White** (`#ffffff`): Primary card and modal surface. Also card fill on gift-card tiles.
44-| `spot.divider-30` | `--spot-divider-30` | `rgba(77,77,77,0.3)` | `#d1d5db` | Slightly stronger dividers, hr |47+- **Neutral Cool** (`#f9f9f9`): Subtle cool-gray surface used on dropdown menus ("Account" dropdown), form-card wraps, and quiet utility containers.
45-| `spot.outline` | `--spot-outline` | `#7c7c7c` | `#d1d5db` | Visible borders on buttons/inputs|48+- **Neutral Warm** (`#f2f0eb`): The warm cream **primary page canvas** for Rewards utility zones and hero bands.
46-| `spot.placeholder` | `--spot-placeholder` | `#4d4d4d` | `#9ca3af` | Input placeholder text |49+- **Ceramic** (`#edebe9`): A slightly warmer/darker cream for zone separators, soft page-section washes, and Rewards partnership band.
47-| `spot.active-pill-bg` | `--spot-active-bg` | `#ffffff` | `#111827` | Active filter pill background |50+- **Black** (`#000000`): Deep ink reserved for the dark top-of-page CTA strip ("Join now") and high-contrast top-nav sign-in buttons.
48-| `spot.active-pill-text`| `--spot-active-text` | `#121212` | `#ffffff` | Active filter pill text |51+
49-| `spot.shadow` | `--spot-shadow` | `rgba(0,0,0,0.3) 0px 8px 8px` | `rgba(0,0,0,0.08) 0px 2px 8px` | Card elevation shadow |52+### Neutrals & Text
50-| `spot.shadow-heavy` | `--spot-shadow-heavy` | `rgba(0,0,0,0.5) 0px 8px 24px` | `rgba(0,0,0,0.1) 0px 4px 16px` | Dialog/elevated panel shadow |53+
51-| `spot.red` | — | `#f3727f` | `#f3727f` | Error states |54+- **Text Black** (`rgba(0, 0, 0, 0.87)`): Primary heading and body text color on light surfaces. Not pure black — an 87%-opacity black that reads warmer.
52-| `spot.orange` | — | `#ffa42b` | `#ffa42b` | Warning states |55+- **Text Black Soft** (`rgba(0, 0, 0, 0.58)`): Secondary/metadata text on light surfaces.
53-| `spot.blue` | — | `#539df5` | `#539df5` | Info states |56+- **Text White** (`rgba(255, 255, 255, 1)`): Primary heading/body text on dark green surfaces.
54-57+- **Text White Soft** (`rgba(255, 255, 255, 0.70)`): Secondary text on dark-green surfaces — footer link descriptions, caption text.
55-### Shadows58+- **Rewards Green** (`#33433d`): A dedicated muted slate-green used only on Rewards-page text blocks — a slightly "dustier" reading color than Text Black that signals "reward surface" without using full Starbucks Green.
56-59+
57-- **Card** (`var(--spot-shadow)`): Cards, dropdowns60+### Semantic & Accent
58-- **Heavy** (`var(--spot-shadow-heavy)`): Dialogs, menus, elevated panels61+
59-- **Inset Border** (`rgb(18,18,18) 0px 1px 0px, rgb(124,124,124) 0px 0px 0px 1px inset`): Input border-shadow combo (dark only)62+- **Red** (`#c82014`): Error and destructive state (form invalid, destructive actions).
60-63+- **Yellow** (`#fbbc05`): Warning state, legacy brand touch.
61-## 3. Theme Toggle64+- **Green Light** (`#d4e9e2` at 33% opacity = `hsl(160 32% 87% / 33%)`): Form valid-field tint background.
62-65+- **Red Tint** (`hsl(4 82% 43% / 5%)`): Invalid-field tint on forms.
63-The app supports dark (default) and light themes. Theme preference is stored in `localStorage` under the key `theme`.66+
64-67+### Black / White Alpha Ladders
65-### Implementation68+
66-69+Two parallel translucent scales for overlay and secondary-text use:
67-- CSS custom properties are defined on `:root` for dark and `[data-theme="light"]` for light70+
68-- A `<script>` block runs before render to set `data-theme` on `<html>`, preventing flash of wrong theme71+- `rgba(0,0,0,0.06)` through `rgba(0,0,0,0.90)` in 10% steps — for dark overlays on light surfaces
69-- Toggle buttons appear in the sidebar footer (desktop) and mobile top bar72+- `rgba(255,255,255,0.10)` through `rgba(255,255,255,0.90)` in 10% steps — for light overlays on dark surfaces
70-- Icon: moon (when in dark mode) / sun (when in light mode)73+
71-- Default: dark74+### Gradient System
72-75+
73-### CSS Variables76+No structural gradient tokens observed. Surface hierarchy is solid-color-block throughout — the system relies on its five-tier cream/green surface palette rather than gradients.
74-77+
75-```css78+## 3. Typography Rules
76-:root {79+
77- --spot-bg: #121212;80+### Font Family
78- --spot-surface: #181818;81+
79- --spot-hover: #1f1f1f;82+- **Primary:** `SoDoSans, "Helvetica Neue", Helvetica, Arial, sans-serif` — Starbucks' proprietary corporate typeface, used across nearly every surface
80- --spot-hover-50: rgba(31,31,31,0.5);83+- **Loading Fallback:** `"Helvetica Neue", Helvetica, Arial, sans-serif` — what users see before SoDoSans loads
81- --spot-text: #ffffff;84+- **Rewards Serif:** `"Lander Tall", "Iowan Old Style", Georgia, serif` — used on specific Rewards-page headline moments for a warm editorial feel
82- --spot-secondary: #b3b3b3;85+- **Careers Script:** `"Kalam", "Comic Sans MS", cursive` — used exclusively for Careers-page "cup name" decorative touches, referencing the hand-written names on Starbucks cups
83- --spot-body: #cbcbcb;86+
84- --spot-muted: #4d4d4d;87+No OpenType stylistic sets explicitly activated at `:root`.
85- --spot-divider: rgba(77,77,77,0.2);
86- --spot-divider-30: rgba(77,77,77,0.3);
87- --spot-outline: #7c7c7c;
88- --spot-placeholder: #4d4d4d;
89- --spot-active-bg: #ffffff;
90- --spot-active-text: #121212;
91- --spot-shadow: rgba(0,0,0,0.3) 0px 8px 8px;
92- --spot-shadow-heavy: rgba(0,0,0,0.5) 0px 8px 24px;
93-}
94-[data-theme="light"] {
95- --spot-bg: #f5f5f5;
96- --spot-surface: #ffffff;
97- --spot-hover: #f3f4f6;
98- --spot-hover-50: rgba(243,244,246,0.5);
99- --spot-text: #111827;
100- --spot-secondary: #6b7280;
101- --spot-body: #374151;
102- --spot-muted: #9ca3af;
103- --spot-divider: #e5e7eb;
104- --spot-divider-30: #d1d5db;
105- --spot-outline: #d1d5db;
106- --spot-placeholder: #9ca3af;
107- --spot-active-bg: #111827;
108- --spot-active-text: #ffffff;
109- --spot-shadow: rgba(0,0,0,0.08) 0px 2px 8px;
110- --spot-shadow-heavy: rgba(0,0,0,0.1) 0px 4px 16px;
111-}
112-```
113-
114-## 4. Typography Rules
115-
116-### Font Families
117-
118-- **Title**: `SpotifyMixUITitle`, fallbacks: `CircularSp-Arab, CircularSp-Hebr, CircularSp-Cyrl, CircularSp-Grek, CircularSp-Deva, Helvetica Neue, helvetica, arial, Hiragino Sans, Hiragino Kaku Gothic ProN, Meiryo, MS Gothic`
119-- **UI / Body**: `SpotifyMixUI`, same fallback stack
120 88
121 ### Hierarchy89 ### Hierarchy
122 90
123-| Role | Font | Size | Weight | Line Height | Letter Spacing | Notes |91+| Role | Size | Weight | Line Height | Letter Spacing | Notes |
124-| ---------------- | ----------------- | ---------------- | ------- | ------------ | -------------- | ---------------------------- |92+| ------------------- | -------------- | ------- | --------------- | -------------- | -------------------------------------------- |
125-| Section Title | SpotifyMixUITitle | 24px (1.50rem) | 700 | normal | normal | Bold title weight |93+| Display (text-10) | 5.0rem / 80px | 400–600 | 1.2 | -0.16px | Largest Rewards/hero display |
126-| Feature Heading | SpotifyMixUI | 18px (1.13rem) | 600 | 1.30 (tight) | normal | Semibold section heads |94+| Jumbo (text-9) | 3.6rem / 58px | 400–600 | 1.2 | -0.16px | Secondary hero headings |
127-| Body Bold | SpotifyMixUI | 16px (1.00rem) | 700 | normal | normal | Emphasized text |95+| Hero Large (text-8) | 2.8rem / 45px | 400–600 | 1.2–1.5 | -0.16px | Landing section headlines |
128-| Body | SpotifyMixUI | 16px (1.00rem) | 400 | normal | normal | Standard body |96+| H1 | 24px | 600 | 36px | -0.16px | Starbucks-Green primary heading |
129-| Button Uppercase | SpotifyMixUI | 14px (0.88rem) | 600–700 | 1.00 (tight) | 1.4px–2px | `text-transform: uppercase` |97+| H2 | 24px | 400 | 36px | -0.16px | Regular-weight section title in Text Black |
130-| Button | SpotifyMixUI | 14px (0.88rem) | 700 | normal | 0.14px | Standard button |98+| Body Large | 19px | 400–600 | 33.25px (~1.75) | -0.16px | Hero intro copy, feature-band body |
131-| Nav Link Bold | SpotifyMixUI | 14px (0.88rem) | 700 | normal | normal | Navigation |99+| Body (text-3) | 1.6rem / 16px | 400 | 1.5 (24px) | -0.01em | Default body copy |
132-| Nav Link | SpotifyMixUI | 14px (0.88rem) | 400 | normal | normal | Inactive nav |100+| Small (text-2) | 1.4rem / ~14px | 400–600 | 1.5 | -0.01em | Button label, metadata, form labels |
133-| Caption Bold | SpotifyMixUI | 14px (0.88rem) | 700 | 1.50–1.54 | normal | Bold metadata |101+| Micro (text-1) | 1.3rem / ~13px | 400 | 1.5 | -0.01em | Active float-label state, caption micro-copy |
134-| Caption | SpotifyMixUI | 14px (0.88rem) | 400 | normal | normal | Metadata |102+| Button Label | 14–16px | 400–600 | 1.2 | -0.01em | All pill-button labels |
135-| Small Bold | SpotifyMixUI | 12px (0.75rem) | 700 | 1.50 | normal | Tags, counts |103+
136-| Small | SpotifyMixUI | 12px (0.75rem) | 400 | normal | normal | Fine print |104+**Letter-spacing tokens:**
137-| Badge | SpotifyMixUI | 10.5px (0.66rem) | 600 | 1.33 | normal | `text-transform: capitalize` |105+
138-| Micro | SpotifyMixUI | 10px (0.63rem) | 400 | normal | normal | Smallest text |106+- `letterSpacingNormal`: `-0.01em` (default — tight, characteristic)
107+- `letterSpacingLoose`: `0.1em` (emphasized caps)
108+- `letterSpacingLooser`: `0.15em` (uppercase-style labels, extreme emphasis)
109+
110+**Line-height tokens:**
111+
112+- `lineHeightNormal`: `1.5` (body)
113+- `lineHeightCompact`: `1.2` (display/buttons)
139 114
140 ### Principles115 ### Principles
141 116
142-- **Bold/regular binary**: Most text is either 700 (bold) or 400 (regular), with 600 used sparingly. This creates a clear visual hierarchy through weight contrast rather than size variation.117+- **Tight negative tracking (`-0.01em`)** is applied almost universally — the entire product reads slightly compressed, which gives SoDoSans its confident presence without feeling squeezed.
143-- **Uppercase buttons as system**: Button labels use uppercase + wide letter-spacing (1.4px–2px), creating a systematic "label" voice distinct from content text.118+- **Weight shifts carry hierarchy, not size shifts.** H1 and H2 share the same 24px/36px size; only weight (600 vs 400) and color (Starbucks-Green vs Text Black) separate them.
144-- **Compact sizing**: The range is 10px–24px — narrower than most systems. Spotify's type is compact and functional, designed for scanning playlists, not reading articles.119+- **Size tokens use rem, anchored to `1rem = 10px`** on this site (via a `font-size: 62.5%` root trick). So `1.6rem` = 16px, `2.4rem` = 24px, etc. The scale is semantic (textSize-1 through textSize-10), not arbitrary pixel values.
145-- **Global script support**: The extensive fallback stack (Arabic, Hebrew, Cyrillic, Greek, Devanagari, CJK) reflects Spotify's 180+ market reach.120+- **Context-specific typeface swaps** — serif on Rewards, script on Careers — are deliberate and localized. Never mix them with the primary sans within the same surface.
121+- **Body text never goes pure black** — it sits at `rgba(0,0,0,0.87)` to match the warm-neutral canvas temperature.
122+
123+### Note on Font Substitutes
124+
125+SoDoSans is proprietary to Starbucks (licensed from House Industries, not publicly available). Reasonable open-source substitutes:
126+
127+- **Inter** (Google Fonts) — similar humanist geometric proportions, wide weight range
128+- **Manrope** — slightly rounder, similar confident feel
129+- **Nunito Sans** — warmer, good for a "café" brand substitute
130+
131+If substituting, verify the tight `-0.01em` / `-0.16px` tracking still reads well; some open-source fonts need `-0.005em` instead.
146 132
147-## 5. Component Stylings133+Lander Tall (the Rewards serif) is custom — open-source substitutes: **Iowan Old Style** (already in fallback), **Lora**, or **Source Serif Pro**. Kalam (Careers script) is available on Google Fonts directly.
134+
135+## 4. Component Stylings
148 136
149 ### Buttons137 ### Buttons
150 138
151-**Accent Pill**139+**1. Primary Filled — "Explore our afternoon menu / Sign up for free"**
140+
141+- Background: `#00754A` (Green Accent)
142+- Text: `#ffffff`
143+- Border: `1px solid #00754A`
144+- Radius: `50px` (full pill)
145+- Padding: `7px 16px`
146+- Font: SoDoSans, 16px, weight 600, letter-spacing `-0.01em`
147+- Active state: `transform: scale(0.95)` via `--buttonActiveScale`
148+- Transition: `all 0.2s ease`
149+
150+**2. Primary Outlined — "Give them a try / Start an order"**
151+
152+- Background: transparent
153+- Text: `#00754A` (Green Accent)
154+- Border: `1px solid #00754A`
155+- Same radius/padding/active/transition as Primary Filled
156+
157+**3. Black Filled — "Join now"**
158+
159+- Background: `#000000`
160+- Text: `#ffffff`
161+- Border: `1px solid #000000`
162+- Radius: `50px`, Padding: `7px 16px`
163+- Font: 14px, weight 600
164+- Used on the top-of-page join strip and similar conversion moments
152 165
153-- Background: `#a855f7`166+**4. Dark Outlined — "Sign in"**
154-- Text: `var(--spot-bg)` (dark in dark mode, light in light mode)
155-- Padding: 8px 16px
156-- Radius: 9999px (full pill)
157-- Use: Primary CTAs, add buttons
158 167
159-**Dark Pill**168+- Background: transparent
169+- Text: `rgba(0, 0, 0, 0.87)` (Text Black)
170+- Border: `1px solid rgba(0, 0, 0, 0.87)`
171+- Radius: `50px`, Padding: `7px 16px`
172+- Font: 14px, weight 600
173+
174+**5. Green-on-Green Inverted — "See the spring menu"**
160 175
161-- Background: `var(--spot-hover)`176+- Background: `#ffffff`
162-- Text: `var(--spot-text)` or `var(--spot-secondary)`177+- Text: `#00754A`
163-- Padding: 8px 16px178+- Border: `1px solid #ffffff`
164-- Radius: 9999px (full pill)179+- Used when the surface behind the button is the dark green House Green band — white button with green text instead of a filled green pill on green bg
165-- Use: Navigation pills, secondary actions
166 180
167-**Outlined Pill**181+**6. Outlined on Dark — "Learn more / Order now"**
168 182
169 - Background: transparent183 - Background: transparent
170-- Text: `var(--spot-text)`184+- Text: `#ffffff`
171-- Border: `1px solid var(--spot-outline)`185+- Border: `1px solid #ffffff`
172-- Radius: 9999px186+- Used on dark-green feature bands for secondary action paired with a white filled CTA
173-- Use: Follow buttons, secondary actions187+
188+**7. Consent Agree (dark-green variant)**
189+
190+- Background: `rgb(0, 130, 72)` (a specific variant green used in the cookie-consent module)
191+- Text: `#ffffff`
192+- No border, `50px` radius, `7px 16px` padding, 14px / weight 400
193+- Slightly brighter than Green Accent — reserved for the consent-banner Agree action
174 194
175-**Circular Play**195+**8. Frap — Floating Circular Order Button**
176 196
177-- Background: `#a855f7`197+- Background: `#00754A` (Green Accent)
178-- Text: `var(--spot-bg)`198+- Icon: `#ffffff`
179-- Padding: 12px199+- Size: `5.6rem / 56px` (standard), `4rem / 40px` (mini variant)
180-- Radius: 50% (circle)200+- Radius: `50%` (full circle)
181-- Use: Play/pause controls201+- Fixed bottom-right, `-0.8rem` touch offset for extra tap comfort
202+- Shadow stack: base `0 0 6px rgba(0,0,0,0.24)` + ambient `0 8px 12px rgba(0,0,0,0.14)`
203+- Active state: ambient shadow fades to `0 8px 12px rgba(0,0,0,0)`
204+- This is the product's signature elevation element — it floats over every scrolled surface
205+
206+**9. Full-width Feedback Tab — "Provide feedback"**
207+
208+- Background: `#00754A`
209+- Text: `#ffffff`
210+- Radius: `12px 12px 0px 0px` (top-rounded only)
211+- Padding: `8px 16px`
212+- Font: 14px, weight 400
213+- Positioned fixed bottom-right-inside, attached to the viewport edge
182 214
183 ### Cards & Containers215 ### Cards & Containers
184 216
185-- Background: `var(--spot-surface)`217+**Content Card (default)**
186-- Radius: 6px–8px218+
187-- No visible borders on most cards219+- Background: `#ffffff` (`--cardBackgroundColor`)
188-- Hover: `var(--spot-hover-50)` background220+- Radius: `12px` (`--cardBorderRadius`)
189-- Shadow: `var(--spot-shadow)` on elevated221+- Shadow: `0px 0px .5px 0px rgba(0,0,0,0.14), 0px 1px 1px 0px rgba(0,0,0,0.24)` (`--cardBoxShadow`)
222+- Used for: feature cards, menu-item tiles, reward-status panels
223+
224+**Gift Card Tile**
225+
226+- Background: illustrated photography fills the card (no solid bg)
227+- Radius: similar to cards (`~12px`, slightly tighter on corners)
228+- Shadow: lighter than default card — these are treated like physical cards laid on the canvas
229+- Labeled by category above the card grid (Spring, Thank You, Birthday, Celebration, Mother's Day, Appreciation, Encouragement, Milestones, Anytime)
230+
231+**Rewards Status Cards (Rewards page signature)**
232+
233+- Three-column grid: Bronze / Gold / Silver-ish — each a dark-green (`#1E3932`) panel with:
234+ - Colored gradient/color header ring
235+ - Numbered "Level" badge
236+ - Status title in large SoDoSans weight 600
237+ - Stars / benefits list in white/translucent-white text
238+ - Bottom "As you earn more stars…" progression caption
239+
240+**Partnership Card (Rewards)**
241+
242+- Background: `#faf6ee` (Gold Lightest) warm-cream surface
243+- Content: partner logos ("SkyMiles", "Bonvoy") centered, with descriptive text below
244+- Radius and shadow follow default card spec
245+
246+**Dropdown Menu (Account dropdown, top-nav)**
247+
248+- Background: `#f9f9f9` (Neutral Cool)
249+- Menu items at `24px / weight 400` in Text Black
250+- No border — just background surface shift against white nav
251+
252+**Modal**
253+
254+- Padding: `2.4rem` (`--modalPadding`)
255+- Top padding: `8.8rem` (`--modalTopPadding`) — leaves room for close button / header
256+- Combined vertical padding: `11.2rem`
257+- Radius inherits from card spec (`12px`)
258+
259+### Inputs & Forms
260+
261+**Floating Label Input**
262+
263+- Label floats above the input border when focused/filled
264+- Desktop label font size: `1.9rem` default, animates to `1.4rem` when active
265+- Mobile label font size: `1.6rem` default, animates to `1.3rem` active
266+- Label horizontal offset: `12px` from left
267+- Active label translate: up to `-12px` with `-50%` Y translation
268+- Field padding: `12px`
269+- Form horizontal padding: `1.6rem`
270+- Validation: valid-field gets `rgba(green-light, 0.33)` tint; invalid-field gets `rgba(red, 0.05)` tint
271+- Transition: `0.3s option-label-marker-expansion cubic-bezier(0.32, 2.32, 0.61, 0.27)` on checked-input
190 272
191-### Inputs273+**Option Icon (checkbox/radio)**
192 274
193-- Background: `var(--spot-hover)`275+- Padding: `3px` inner
194-- Text: `var(--spot-text)`276+- Uses the checked-input cubic-bezier animation above (a slightly "springy" 2.32 overshoot curve)
195-- Radius: 500px (pill)
196-- Focus ring: `#a855f7`
197-- Placeholder: `var(--spot-placeholder)`
198 277
199 ### Navigation278 ### Navigation
200 279
201-- Sidebar: `var(--spot-bg)` background280+**Global Nav (top bar)**
202-- Active items: 14px weight 700, `var(--spot-text)`
203-- Inactive items: 14px weight 400, `var(--spot-secondary)`
204-- Circular icon buttons (50% radius)
205-- Brand logo top-left in purple
206 281
207-## 6. Layout Principles282+- Fixed position with progressive heights: `64px` xs → `72px` mobile → `83px` tablet → `99px` desktop
283+- Shadow stack: `0 1px 3px rgba(0,0,0,0.1), 0 2px 2px rgba(0,0,0,0.06), 0 0 2px rgba(0,0,0,0.07)` — three-layer soft lift
284+- Left: Starbucks wordmark logo, offsetting by `99px` (md) / `131px` (lg) from left edge
285+- Primary links inline in SoDoSans weight 400–600: Menu · Rewards · Gift Cards
286+- Right: Find a store link + Sign in (outlined) + Join now (black filled)
287+
288+**Sub-nav (second bar, e.g., Rewards internal)**
289+
290+- Height: `53px` (global subnav) / `48px` (internal subnav)
291+- Typically horizontal tab group beneath the global nav
292+
293+**Mobile Nav**
294+
295+- Collapses to a hamburger drawer below tablet breakpoint
296+- Frap floating button persists at bottom-right regardless of nav state
297+
298+### Image Treatment
299+
300+- **Hero photography**: Product photos (beverages in clear glass with colored backgrounds — coral, sage, warm amber) occupy ~40vw of a split-hero layout; text occupies the other 60vw (`--headerCrateProportion: 40vw` / `--contentCrateProportion: 60vw`)
301+- **Gift card illustrations**: Each card is a distinct illustrated photograph (painted-feel, hand-drawn-looking, warm color palette). Never generic generated graphics.
302+- **Rewards ceremony imagery**: Photographs of Starbucks Rewards App screens held in-hand, angled compositions — product-in-context photography.
303+- **Menu thumbnails**: Square or 4:3 product photography with clean white/cream backdrops, slight soft drop-shadow around the glass.
304+- **Image fade-in**: `opacity 0.3s ease-in` transition on image load (`--imageFadeTransition`).
305+
306+### Feature Band (dark-green hero strip)
307+
308+Full-width `#1E3932` (House Green) band with:
309+
310+- Left: white headline + subhead + CTA row
311+- Right: product photography or illustration
312+- Split ratio ~40/60 or 50/50 depending on section
313+- White text throughout with `rgba(255,255,255,0.70)` for secondary copy
314+- CTAs follow Green-on-Green Inverted (white filled) + Outlined on Dark (white outline) pairing
315+
316+### Expander / Accordion
317+
318+- Duration: `300ms` (`--expanderDuration`)
319+- Timing curve: `cubic-bezier(0.25, 0.46, 0.45, 0.94)` — a measured ease-out
320+- Used for FAQ sections on Rewards and gift page
321+
322+### Cookie Consent Module
323+
324+Dark-green modal card at top of page with "Agree" (green-filled) and "Manage preferences" (outlined) buttons. Appears on first visit; dismissible.
325+
326+### Product Detail Components (PDP signature cluster)
327+
328+A repeating component cluster used on menu product pages (e.g., `/menu/product/40498/iced` for a drink detail, `/menu/product/.../nutrition` for nutrition facts). These extend the component inventory without changing tokens.
329+
330+**Size Options Selector**
331+
332+- Horizontal row of 4 cup-icon buttons (Tall / Grande / Venti / Trenta)
333+- Each item: cup silhouette icon on top, size name below (16/700 in Starbucks-Green), fluid-ounce caption (13/400 in Text Black Soft)
334+- Active state: a green circular ring outline (`2px solid #00754A`) around the selected cup icon
335+- Inactive: no ring, same typography
336+- Full-width row, equal spacing
337+- Radius of container: `12px` or flat; individual icons are `50%` circular
338+- Padding: `16px 24px` internal
339+
340+**Add-in / Milk Select (outlined rectangle)**
341+
342+- Background: `#ffffff`
343+- Border: `1px solid #d6dbde` (Input Border)
344+- Radius: `4px`
345+- Full-width in its column
346+- Floating label above top border: "Add-ins" / "Milk" / "Add-ins" — 13/700 in Text Black, uppercase, `0.325px` letter-spacing
347+- Value displayed centered (e.g., "Ice", "Coconut", "Strawberry Fruit Inclusions scoop"): 16/400 Text Black
348+- Chevron-down icon right side in Text Black Soft
349+- Focus: border shifts to Green Accent (`#00754A`)
350+
351+**Numeric Stepper**
352+
353+- Embedded inside an Add-in row when a quantity is required (e.g., Strawberry Fruit Inclusions scoop)
354+- `−` minus button + count number + `+` plus button, all inline right of the label
355+- Buttons: circular `32×32px` with `1px solid #d6dbde` border, neutral gray icon
356+- Count number: 16/700 Text Black centered
357+
358+**Customize Button**
359+
360+- Background: `#ffffff`
361+- Text: `#00754A` (Green Accent)
362+- Border: `1.5px solid #00754A`
363+- Radius: `50px` (full pill)
364+- Padding: `14px 40px` (generously larger than default pills — this is a secondary primary action)
365+- Label: "Customize" with a gold sparkle ✨ icon inset left
366+- Used for: entering the drink-customization flow after size/milk selection
367+
368+**Add to Order Button (PDP)**
369+
370+- Background: `#00754A` (Green Accent)
371+- Text: `#ffffff`
372+- Radius: `50px`
373+- Padding: `14px 32px`
374+- Pinned top-right of product card and/or aligned right within the store-availability band
375+- Same scale(0.95) active behavior as other primary CTAs
376+
377+**Rewards Cost Pill — "200★ item"**
378+
379+- Background: transparent
380+- Border: `1px solid #cba258` (Gold)
381+- Text: `#cba258` (Gold)
382+- Radius: `50px` (full pill)
383+- Padding: `4px 12px`
384+- Content: "200★ item" where `★` is a small filled star glyph — indicates the Rewards Stars required to redeem this item
385+- Font: Proxima Nova 13/700 with `0.5px` letter-spacing
386+- Used only on products that are Rewards-redeemable
387+
388+**Product Description Band**
389+
390+- Full-width dark-green band (`#1E3932` House Green)
391+- Contains top-to-bottom:
392+ 1. Rewards Cost Pill (gold) if applicable
393+ 2. Product description body copy in white (16/400/1.5)
394+ 3. Nutritional summary inline ("140 calories, 25g sugar, 2.5g fat") with info-icon tooltip — 14/700 white
395+ 4. "Full nutrition & ingredients list" outlined-white-on-green pill button
396+- Padding: `32px` vertical
397+- Appears beneath the primary product header band
398+
399+**Ingredients / Nutrition Table**
400+
401+- Two-column layout on the Nutrition page
402+- Left column: "Ingredients" header + list or "Not available for this item" placeholder text block with an explanatory paragraph in Text Black Soft 14/400
403+- Right column: "Nutrition" header + label/value rows
404+- Each row: nutrient label (Proxima Nova 14/400) on the left, value (e.g., "140 calories", "25g", "205 mg\*\*") on the right, separated by a `1px solid #e7e7e7` hairline below
405+- Footnote for caffeine/asterisk markers in 13/400 Text Black Soft at the bottom
406+- Reusable pattern for nutrition facts regulation-compliant tables
407+
408+**Store Availability Selector**
409+
410+- Appears on dark-green feature band above the size-options row
411+- Full-width rounded rectangle with transparent-white interior
412+- Text: "For item availability, choose a store" in white, 14/400
413+- Right side: chevron-down affordance + shopping-bag SVG icon in white outline
414+- Radius: `4px`
415+- Height: ~48px
416+
417+**PDP Breadcrumb**
418+
419+- "Menu / Refreshers / Pink Energy Drink" trail above the product title
420+- Separator: `/` slash character in Text Black Soft
421+- Current page is unlinked, prior pages are underlined green-accent links
422+- Font: 14/400 Proxima Nova
423+- Appears on all PDP pages
424+
425+**Back Chevron Link (PDP nutrition / detail sub-pages)**
426+
427+- "← Back" text link above section headings on the nutrition page
428+- Text in Green Accent (`#00754A`) 14/700 Proxima Nova
429+- Left chevron `<` in the same green
430+- Alternative to full breadcrumb on deep sub-pages
431+
432+## 5. Layout Principles
208 433
209 ### Spacing System434 ### Spacing System
210 435
211-- Base unit: 8px436+Rem-based semantic scale (anchored `1rem = 10px`):
212-- Scale: 1px, 2px, 3px, 4px, 5px, 6px, 8px, 10px, 12px, 14px, 15px, 16px, 20px437+
438+| Token | Rem | Pixels | Typical Use |
439+| ----------- | -------- | ------ | ----------------------------------------- |
440+| `--space-1` | `0.4rem` | 4px | Tightest inline padding |
441+| `--space-2` | `0.8rem` | 8px | Small gap, button vertical padding |
442+| `--space-3` | `1.6rem` | 16px | Default — card padding, outer gutter xs |
443+| `--space-4` | `2.4rem` | 24px | Section inner spacing, outer gutter md |
444+| `--space-5` | `3.2rem` | 32px | Major between-section spacing |
445+| `--space-6` | `4rem` | 40px | Large gaps, outer gutter lg, header crate |
446+| `--space-7` | `4.8rem` | 48px | Section-to-section spacing |
447+| `--space-8` | `5.6rem` | 56px | Very large breathing — Frap height |
448+| `--space-9` | `6.4rem` | 64px | Widest section padding |
449+
450+**Gutter tokens:**
451+
452+- `--outerGutter: 1.6rem` (16px, default / mobile)
453+- `--outerGutterMedium: 2.4rem` (24px, tablet)
454+- `--outerGutterLarge: 4.0rem` (40px, desktop)
455+
456+**Universal rhythm constant:** `1.6rem` (16px) appears across every page as the default outer gutter, card padding baseline, and text size 3 body — the system's most frequent spacing unit.
213 457
214 ### Grid & Container458 ### Grid & Container
215 459
216-- Sidebar (fixed) + main content area460+- Column width scale: `--columnWidthSmall: 343px` / `Medium: 500px` / `Large: 720px` / `XLarge: 1440px`
217-- Grid-based album/playlist cards461+- Gift-card grid uses a 3-5-up responsive grid of `~343px` tiles
218-- Responsive content area fills remaining space462+- Rewards status section: 3-up dark-green panels at `lg+` breakpoints
463+- Hero: asymmetric split 40% (image) / 60% (content) via `--headerCrateProportion` / `--contentCrateProportion`
219 464
220 ### Whitespace Philosophy465 ### Whitespace Philosophy
221 466
222-- **Dark compression**: Spotify packs content densely — playlist grids, track lists, and navigation are all tightly spaced. The dark background provides visual rest between elements without needing large gaps.467+Whitespace carries the feeling of "plenty of space in the café." Section padding leans generous (40–64px). Content blocks are separated by whitespace rather than dividers. The cream canvas (`#f2f0eb`) is itself a visual breath between white cards and green feature bands.
223-- **Content density over breathing room**: This is an app, not a marketing site. Every pixel serves the listening experience.
224 468
225 ### Border Radius Scale469 ### Border Radius Scale
226 470
227-- Minimal (2px): Badges, explicit tags471+| Value | Use |
228-- Subtle (4px): Inputs, small elements472+| --------------- | ----------------------------------------------------------------------------------- |
229-- Standard (6px): Album art containers, cards473+| `12px` | Cards, modals, menu-item tiles (`--cardBorderRadius`) |
230-- Comfortable (8px): Sections, dialogs474+| `12px 12px 0 0` | Full-width feedback tab (top-rounded only) |
231-- Medium (10px–20px): Panels, overlay elements475+| `50px` | All buttons — full-pill radius (`--buttonBorderRadius`) |
232-- Large (100px): Large pill buttons476+| `50%` | Circular icons, Frap floating button, avatar thumbnails |
233-- Pill (500px): Primary buttons, search input477+| Specialty | `3.3333%/5.298%` elliptical for Starbucks-Visa-Card mockups (`--svcRoundedCorners`) |
234-- Full Pill (9999px): Navigation pills, search478+
235-- Circle (50%): Play buttons, avatars, icons479+## 6. Depth & Elevation
480+
481+| Level | Treatment | Use |
482+| -------------------- | --------------------------------------------------------------------------------- | ----------------------------------------------------- |
483+| Card | `0 0 0.5px rgba(0,0,0,0.14), 0 1px 1px rgba(0,0,0,0.24)` | Default content cards — a whisper-soft dual-shadow |
484+| Global Nav | `0 1px 3px rgba(0,0,0,0.1), 0 2px 2px rgba(0,0,0,0.06), 0 0 2px rgba(0,0,0,0.07)` | Triple-layer soft lift on the fixed top bar |
485+| Frap Base | `0 0 6px rgba(0,0,0,0.24)` | Base halo around the floating circular CTA |
486+| Frap Ambient | `0 8px 12px rgba(0,0,0,0.14)` | Stacked directional ambient — floats the Frap forward |
487+| Gift Card | Light drop shadow around illustrated photograph | Physical-card feel for gift tiles |
488+| Starbucks Card (SVC) | `drop-shadow(0 4px 1px rgba(0,0,0,0.11)) drop-shadow(0 0 2px rgba(0,0,0,0.24))` | Stacked SVG drop shadows for Starbucks Card visuals |
236 489
237-## 7. Depth & Elevation490+**Shadow philosophy:** Whisper-soft, layered over solid — the system never reaches for a single heavy drop shadow. Instead, it stacks 2–3 low-alpha shadows with different offsets to simulate real-world ambient + direct lighting. The Frap button is the most elevated element on any page.
238 491
239-| Level | Treatment | Use |492+### Decorative Depth
240-| ------------------ | ---------------------------- | ------------------------------ |
241-| Base (Level 0) | `var(--spot-bg)` background | Deepest layer, page background |
242-| Surface (Level 1) | `var(--spot-surface)` | Cards, sidebar, containers |
243-| Elevated (Level 2) | `var(--spot-shadow)` | Dropdown menus, hover cards |
244-| Dialog (Level 3) | `var(--spot-shadow-heavy)` | Modals, overlays, menus |
245 493
246-## 8. Do's and Don'ts494+- **No gradient system** — surfaces are solid color-block
495+- **Color-block banding** carries perceived depth (dark-green bands read as "recessed feature zones" between cream/white body sections)
496+- **SVG filter shadows** on Starbucks-Card visuals add a slight 3D physicality without a box-shadow
497+
498+## 7. Do's and Don'ts
247 499
248 ### Do500 ### Do
249 501
250-- Use semantic color tokens (`spot-bg`, `spot-surface`, `spot-text`, etc.) — they adapt to theme502+- Use Neutral Warm (`#f2f0eb`) or Ceramic (`#edebe9`) as page canvas instead of pure white — the warm cream is the signature
251-- Apply Accent Purple (`#a855f7`) only for play controls, active states, and primary CTAs503+- Map the green tiers to their intended surface role — Starbucks Green for headings, Green Accent for CTAs, House Green for deep bands, Uplift for decorative
252-- Use pill shape (500px–9999px) for all buttons — circular (50%) for play controls504+- Keep tracking tight at `-0.01em` / `-0.16px` on SoDoSans across the whole system
253-- Apply uppercase + wide letter-spacing (1.4px–2px) on button labels505+- Use 50px full-pill radius on every button without exception
254-- Keep typography compact (10px–24px range) — this is an app, not a magazine506+- Apply `transform: scale(0.95)` as the universal button active state
255-- Use theme-aware shadows via CSS variables507+- Reserve Gold for Rewards-status ceremony moments only
256-- Test all components in both dark and light themes508+- Use SoDoSans for nearly everything; switch to Lander Tall serif only for Rewards editorial headlines; reserve Kalam script for Careers "cup name" moments
509+- Layer 2–3 low-alpha shadows instead of one heavier drop shadow for elevation
510+- Use the Frap circular CTA as the persistent floating order entry on every shopping surface
511+- Let the cream canvas breathe between content cards — use whitespace, not dividers
257 512
258 ### Don't513 ### Don't
259 514
260-- Don't use Accent Purple decoratively or on backgrounds — it's functional only515+- Don't use pure white as the page canvas — the warm cream temperature is load-bearing
261-- Don't hardcode theme-dependent colors — use CSS variable-backed tokens516+- Don't pick "one brand green" — the four-green system is intentional; using only `#006241` everywhere flattens the brand
262-- Don't skip the pill/circle geometry on buttons — square buttons break the identity517+- Don't use Gold as a general-purpose accent — it's a Rewards signal only
263-- Don't use hardcoded shadow values — use `shadow-spot` and `shadow-spot-heavy`518+- Don't square the corners on buttons — the 50px pill is universal
264-- Don't add additional brand colors — purple + achromatic grays is the complete palette519+- Don't introduce gradient fills — the system is color-block throughout
265-- Don't use `text-white` or `bg-white` directly — use `text-spot-text` and `bg-spot-active-pill-bg`520+- Don't weight-contrast h1 and h2 by size — the hierarchy comes from weight + color (600 Starbucks-Green vs 400 Text Black)
266-- Don't expose raw gray borders — use `border-spot-divider` or `border-spot-outline`521+- Don't use pure black for body text — `rgba(0,0,0,0.87)` matches the warm canvas
522+- Don't skip the `scale(0.95)` active feedback on buttons — it's a signature micro-interaction
523+- Don't stack single heavy shadows; always layer 2–3 low-alpha ones
524+- Don't introduce serifs or scripts into the main shopping flow — they belong to Rewards and Careers contexts respectively
267 525
268-## 9. Responsive Behavior526+## 8. Responsive Behavior
269 527
270 ### Breakpoints528 ### Breakpoints
271 529
272-| Name | Width | Key Changes |530+Inferred from component width tokens and progressive nav heights:
273-| ------------- | ----------- | --------------------- |531+
274-| Mobile Small | <425px | Compact mobile layout |532+| Name | Width | Key Changes |
275-| Mobile | 425–576px | Standard mobile |533+| ------- | ----------- | ------------------------------------------------------------------------------- |
276-| Tablet | 576–768px | 2-column grid |534+| xs | < 480px | Global nav 64px; hamburger menu; single-column layouts; pill buttons full-width |
277-| Tablet Large | 768–896px | Expanded layout |535+| Mobile | 480–767px | Global nav 72px; gift-card grid 2-up; card padding tightens |
278-| Desktop Small | 896–1024px | Sidebar visible |536+| Tablet | 768–1023px | Global nav 83px; gift-card grid 3-up; hero split begins to appear |
279-| Desktop | 1024–1280px | Full desktop layout |537+| Desktop | 1024–1439px | Global nav 99px; gift-card grid 4-up; full asymmetric hero 40/60 |
280-| Large Desktop | >1280px | Expanded grid |538+| XLarge | 1440px+ | Content caps at `--columnWidthXLarge`; gift-card grid 5-up; extra cream margin |
539+
540+### Touch Targets
541+
542+- Pill buttons at `7px 16px` padding measure ~32px tall — below 44px WCAG AAA minimum for touch-only surfaces. On mobile, button padding may be visually expanded to meet the minimum.
543+- Frap floating circular button at `56px` is well above minimum.
544+- Frap uses `--frapTouchOffset: calc(-1 * .8rem)` to extend tap area 8px beyond visual edge.
545+- Form float-label inputs grow their label font size on mobile (1.6rem base vs 1.9rem desktop) — easier to tap and read at arm's-length.
281 546
282 ### Collapsing Strategy547 ### Collapsing Strategy
283 548
284-- Sidebar: full → collapsed → hidden549+- **Global nav height scales progressively**: 64 → 72 → 83 → 99px across breakpoints, not a single value
285-- Album grid: 5 columns → 3 → 2 → 1550+- **Hero split collapses**: 40/60 asymmetric split → stacked (image top, content below) at mobile
286-- Search: pill input maintained, width adjusts551+- **Gift-card grid**: 5-up → 4-up → 3-up → 2-up → 1-up across breakpoints with adjusted card widths
287-- Navigation: sidebar → bottom bar on mobile552+- **Feature bands**: Stay full-width but text + imagery stack vertically on mobile
288-- Theme toggle: always accessible in sidebar footer / mobile header553+- **Outer gutter scales**: 16px → 24px → 40px as viewport grows
554+- **Rewards 3-column status panels**: Stack to single column on mobile
289 555
290-## 10. Agent Prompt Guide556+### Image Behavior
557+
558+- Hero product photography crops tighter vertically on mobile; content becomes the visual anchor
559+- Gift-card illustrations preserve aspect ratio; card grid reflows
560+- `opacity 0.3s ease-in` fade-in transition on image load (prevents jarring pop-in)
561+- Rewards app-in-hand photography scales proportionally; never stretches
562+
563+## 9. Agent Prompt Guide
291 564
292 ### Quick Color Reference565 ### Quick Color Reference
293 566
294-| Role | Token | Value (dark) |567+- Primary CTA: "Green Accent (`#00754A`)"
295-| -------------- | -------------------- | -------------- |568+- Primary CTA text: "White (`#ffffff`)"
296-| Background | `bg-spot-bg` | `#121212` |569+- Brand heading: "Starbucks Green (`#006241`)"
297-| Surface | `bg-spot-surface` | `#181818` |570+- Feature band / footer: "House Green (`#1E3932`)"
298-| Hover | `bg-spot-hover` | `#1f1f1f` |571+- Page canvas: "Neutral Warm (`#f2f0eb`)"
299-| Text primary | `text-spot-text` | `#ffffff` |572+- Card canvas: "White (`#ffffff`)"
300-| Text secondary | `text-spot-secondary`| `#b3b3b3` |573+- Heading text on light: "Text Black (`rgba(0,0,0,0.87)`)"
301-| Text body | `text-spot-body` | `#cbcbcb` |574+- Body text on light: "Text Black Soft (`rgba(0,0,0,0.58)`)"
302-| Text muted | `text-spot-muted` | `#4d4d4d` |575+- Body text on dark-green: "Text White Soft (`rgba(255,255,255,0.70)`)"
303-| Accent | `text-spot-purple` | `#a855f7` |576+- Rewards accent: "Gold (`#cba258`)"
304-| Divider | `border-spot-divider`| `rgba(...)` |577+- Rewards text: "Rewards Green (`#33433d`)"
305-| Outline | `border-spot-outline`| `#7c7c7c` |578+- Destructive: "Red (`#c82014`)"
306-| Error | `text-spot-red` | `#f3727f` |579+
580+### Example Component Prompts
581+
582+1. "Create a primary Starbucks CTA pill button with Green Accent (`#00754A`) background, white text 'Explore our afternoon menu', SoDoSans font at 16px weight 600 with `-0.01em` letter-spacing, `50px` border-radius (full pill), `7px 16px` padding. Apply `transform: scale(0.95)` as the active state with a `0.2s ease` transition."
583+
584+2. "Design a content card with White (`#ffffff`) background at `12px` border-radius, layered shadow `0 0 0.5px rgba(0,0,0,0.14), 0 1px 1px rgba(0,0,0,0.24)`. Pad contents `16–24px` (`--space-3` to `--space-4`). Place on a Neutral Warm (`#f2f0eb`) page canvas with `16px` gap to siblings."
585+
586+3. "Build the Frap floating circular order button — `56px` diameter, Green Accent (`#00754A`) fill, white shopping-bag icon centered. Layered shadow: `0 0 6px rgba(0,0,0,0.24)` + `0 8px 12px rgba(0,0,0,0.14)`. Fixed position bottom-right with `-0.8rem` touch offset. Active state collapses the ambient shadow to `0 8px 12px rgba(0,0,0,0)` with `scale(0.95)`."
587+
588+4. "Build a dark-green feature band — full-width section with House Green (`#1E3932`) background. Left column: white SoDoSans h2 at 24px weight 600, followed by a Text White Soft (`rgba(255,255,255,0.70)`) body paragraph and a CTA row with two buttons (White-filled with Green Accent text for primary, Outlined-on-Dark white border for secondary). Right column: product photography. Split ratio 40/60, stacked vertically below `768px`."
589+
590+5. "Create a Rewards status card — House Green (`#1E3932`) panel with `12px` border-radius, colored gradient top stripe (Bronze/Silver/Gold tier). Title in SoDoSans 24px weight 600 in white. Benefits list as white bullets with `rgba(255,255,255,0.70)` secondary captions. Bottom progression text in Text White Soft. Stack 3 panels in a grid at `lg+`, single column on mobile."
591+
592+6. "Design a gift-card tile — card radius matches `12px`, fills with an illustrated photograph (hand-drawn watercolor-painted feel) as the entire surface. Subtle drop shadow makes it feel like a physical card on the cream canvas. Group under a category label ('Spring', 'Thank You', 'Birthday') in SoDoSans 24px weight 400 above the grid."
593+
594+7. "Create a Starbucks product-detail header — House Green (`#1E3932`) band with breadcrumb 'Menu / Refreshers / Pink Energy Drink' in 14/400 white above the product title in SoDoSans 32/700 uppercase white. Product photograph centered below title. Below photo: a 4-up size selector row — each cup-icon button shows a vertical cup silhouette, size name ('Tall' / 'Grande' / 'Venti' / 'Trenta') in 16/700 white, and fluid-ounce in 13/400 Text White Soft. Selected size wraps the cup icon in a `2px solid #00754A` circular ring."
595+
596+8. "Build a Starbucks customize flow — under the size selector, 3 stacked outlined-rectangle input rows (white bg, `1px solid #d6dbde` border, `4px` radius). Each has a floating label ('Add-ins', 'Milk', 'Add-ins') above the top border in 13/700 Text Black uppercase. Value centered (e.g., 'Ice', 'Coconut'). Right side: chevron-down in Text Black Soft. For the scoop row, embed a numeric stepper (`−` `1` `+` with circular `32px` outlined buttons). Below all three fields: outlined green 'Customize' pill with gold sparkle icon, `50px` radius, `14px 40px` padding. Pair with a Green Accent filled 'Add to Order' pill in the same row."
597+
598+9. "Design a Starbucks product description band — full-width House Green (`#1E3932`) below product header. Top: a gold-outlined '200★ item' Rewards Cost Pill (`50px` radius, `4px 12px` padding, gold `#cba258` border and text). Below: product description in white 16/400/1.5. Nutritional inline summary in white 14/700 ('140 calories, 25g sugar, 2.5g fat') with info-icon tooltip. Outlined-white-on-green pill button 'Full nutrition &amp; ingredients list'. 32px vertical padding."
599+
600+10. "Create a Starbucks nutrition facts table — two-column layout inside a White card. Left column: 'Ingredients' header (24/400 Text Black), followed by ingredient list or 'Not available for this item' placeholder paragraph in 14/400 Text Black Soft. Right column: 'Nutrition' header, then label/value rows (nutrient name left, value right) separated by `1px solid #e7e7e7` hairlines. Typography: labels in 14/400 Text Black, values in 14/700 Text Black right-aligned. Footnote asterisk markers in 13/400 Text Black Soft at the bottom."
307 601
308 ### Iteration Guide602 ### Iteration Guide
309 603
310-1. Use semantic tokens (`spot-bg`, `spot-surface`, `spot-text`) — they handle theme switching604+When refining existing screens generated with this design system:
311-2. Accent Purple (`spot-purple`) for functional highlights only (active, CTA)605+
312-3. Pill everything — 500px for large, 9999px for small, 50% for circular606+1. Focus on ONE component at a time
313-4. Uppercase + wide tracking on buttons — the systematic label voice607+2. Reference specific color names and hex codes from this document
314-5. Theme-aware shadows via `shadow-spot` and `shadow-spot-heavy`608+3. Use natural language descriptions ("warm cream canvas," "four-tier green system") alongside exact values
315-6. Never hardcode `text-white` or `bg-white` — use semantic tokens609+4. Preserve the 50px pill + `scale(0.95)` active state universally
610+5. Check that greens are mapped to their correct role (Green Accent for CTA, Starbucks Green for heading, House Green for band)
611+6. Don't introduce gradients — the system is color-block
612+7. Keep SoDoSans tracking at `-0.01em` / `-0.16px` across the board
613+
614+### Known Gaps
615+
616+- SoDoSans is a proprietary typeface not available on Google Fonts — when implementing publicly, use Inter or Manrope as a substitute and document the swap
617+- Lander Tall (Rewards serif) is also custom — substitute with Iowan Old Style, Lora, or Source Serif Pro
618+- Specific per-component animation timings beyond the few documented (`--duration: 0.4s`, `--iconTransition: all ease-out 0.2s`, `--expanderDuration: 300ms`) are not captured for every interactive surface
619+- Form error-state full styling (red border weight, icon placement) visible on the tint token but not exhaustively extracted
620+- Careers-page specific components (cup-name card, search radio grid) are referenced in token names but not covered by this extraction
621+- Starbucks Visa Card / Starbucks-Card (SVC) detailed mockup specs are hinted at by `--svcRoundedCorners` and `--svcShadowFilter` tokens but not fully documented
modified internal/tmpl/annotations.html +2 -2
@@ -6,7 +6,7 @@
66 {{if .ArticleURL}}
77 <div class="mb-4 text-sm text-spot-secondary">
88 Filtering by: <strong class="text-spot-text">{{.ArticleURL}}</strong>
9- <a href="/annotations" class="text-spot-purple ml-2 hover:brightness-110">Clear</a>
9+ <a href="/annotations" class="text-spot-green ml-2 hover:brightness-110">Clear</a>
1010 </div>
1111 {{end}}
1212
@@ -26,7 +26,7 @@
2626 <button hx-get="/annotations?offset={{.NextOffset}}{{if .ArticleURL}}&article={{.ArticleURL}}{{end}}"
2727 hx-target="#annotations-list" hx-swap="beforeend"
2828 hx-select=".annotation-card"
29- class="text-sm text-spot-secondary hover:text-spot-purple transition">Load more</button>
29+ class="text-sm text-spot-secondary hover:text-spot-green transition">Load more</button>
3030 </div>
3131 {{end}}
3232 {{end}}
@@ -6,7 +6,7 @@
6 {{if .ArticleURL}}6 {{if .ArticleURL}}
7 <div class="mb-4 text-sm text-spot-secondary">7 <div class="mb-4 text-sm text-spot-secondary">
8 Filtering by: <strong class="text-spot-text">{{.ArticleURL}}</strong>8 Filtering by: <strong class="text-spot-text">{{.ArticleURL}}</strong>
9- <a href="/annotations" class="text-spot-purple ml-2 hover:brightness-110">Clear</a>9+ <a href="/annotations" class="text-spot-green ml-2 hover:brightness-110">Clear</a>
10 </div>10 </div>
11 {{end}}11 {{end}}
12 12
@@ -26,7 +26,7 @@
26 <button hx-get="/annotations?offset={{.NextOffset}}{{if .ArticleURL}}&article={{.ArticleURL}}{{end}}"26 <button hx-get="/annotations?offset={{.NextOffset}}{{if .ArticleURL}}&article={{.ArticleURL}}{{end}}"
27 hx-target="#annotations-list" hx-swap="beforeend"27 hx-target="#annotations-list" hx-swap="beforeend"
28 hx-select=".annotation-card"28 hx-select=".annotation-card"
29- class="text-sm text-spot-secondary hover:text-spot-purple transition">Load more</button>29+ class="text-sm text-spot-secondary hover:text-spot-green transition">Load more</button>
30 </div>30 </div>
31 {{end}}31 {{end}}
32 {{end}}32 {{end}}
modified internal/tmpl/article_detail.html +7 -7
@@ -4,7 +4,7 @@
44
55 <article>
66 <h1 class="text-3xl font-bold font-title leading-tight">
7- {{if .Article.URL.Valid}}<a href="{{.Article.URL.String}}" target="_blank" rel="noopener noreferrer" class="text-spot-text hover:text-spot-purple transition">{{.Article.Title}}</a>
7+ {{if .Article.URL.Valid}}<a href="{{.Article.URL.String}}" target="_blank" rel="noopener noreferrer" class="text-spot-text hover:text-spot-green transition">{{.Article.Title}}</a>
88 {{else}}<span class="text-spot-text">{{.Article.Title}}</span>{{end}}
99 </h1>
1010
@@ -12,7 +12,7 @@
1212 {{if .Article.Author.Valid}}<span>{{.Article.Author.String}}</span>{{end}}
1313 {{if .Article.Published.Valid}}<span>{{.Article.Published.Time.Format "Jan 2, 2006 15:04"}}</span>{{end}}
1414 {{if .Feed}}
15- <a href="/articles?feed={{.Feed.FeedURL}}" class="hover:text-spot-purple transition">
15+ <a href="/articles?feed={{.Feed.FeedURL}}" class="hover:text-spot-green transition">
1616 {{if .Feed.FaviconURL.Valid}}<img src="{{.Feed.FaviconURL.String}}" class="w-4 h-4 inline-block mr-1 align-text-bottom rounded-sm">{{end}}
1717 {{if .Feed.Title.Valid}}{{.Feed.Title.String}}{{else}}{{.Feed.FeedURL}}{{end}}
1818 </a>
@@ -57,7 +57,7 @@
5757 {{sanitizeHTML .Article.Summary.String}}
5858 </div>
5959 {{else}}
60- <p class="text-spot-secondary">No content available. <a href="{{.Article.URL.String}}" class="text-spot-purple hover:underline">Read the original article.</a></p>
60+ <p class="text-spot-secondary">No content available. <a href="{{.Article.URL.String}}" class="text-spot-green hover:underline">Read the original article.</a></p>
6161 {{end}}
6262 </article>
6363
@@ -74,15 +74,15 @@
7474 <input type="hidden" name="article_url" value="{{if .Article.URL.Valid}}{{.Article.URL.String}}{{end}}">
7575 <div>
7676 <textarea name="note" rows="2" placeholder="Add a note..."
77- class="w-full bg-spot-hover text-spot-text rounded-lg px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-spot-purple placeholder:text-spot-placeholder resize-none"></textarea>
77+ class="w-full bg-spot-hover text-spot-text rounded-lg px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-spot-green placeholder:text-spot-placeholder resize-none"></textarea>
7878 </div>
7979 <div class="flex gap-2">
8080 <input type="text" name="quote" placeholder="Quote (optional)"
81- class="flex-1 bg-spot-hover text-spot-text rounded-lg px-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-spot-purple placeholder:text-spot-placeholder">
81+ class="flex-1 bg-spot-hover text-spot-text rounded-lg px-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-spot-green placeholder:text-spot-placeholder">
8282 <input type="text" name="tags" placeholder="Tags (comma separated)"
83- class="flex-1 bg-spot-hover text-spot-text rounded-lg px-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-spot-purple placeholder:text-spot-placeholder">
83+ class="flex-1 bg-spot-hover text-spot-text rounded-lg px-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-spot-green placeholder:text-spot-placeholder">
8484 </div>
85- <button type="submit" class="bg-spot-purple text-spot-bg rounded-pill px-5 py-2 text-sm font-bold uppercase tracking-button hover:brightness-110 transition">Annotate</button>
85+ <button type="submit" class="bg-spot-green text-spot-bg rounded-pill px-5 py-2 text-sm font-bold uppercase tracking-button hover:brightness-110 transition">Annotate</button>
8686 </form>
8787
8888 <div id="annotations-list" class="space-y-3">
@@ -4,7 +4,7 @@
4 4
5 <article>5 <article>
6 <h1 class="text-3xl font-bold font-title leading-tight">6 <h1 class="text-3xl font-bold font-title leading-tight">
7- {{if .Article.URL.Valid}}<a href="{{.Article.URL.String}}" target="_blank" rel="noopener noreferrer" class="text-spot-text hover:text-spot-purple transition">{{.Article.Title}}</a>7+ {{if .Article.URL.Valid}}<a href="{{.Article.URL.String}}" target="_blank" rel="noopener noreferrer" class="text-spot-text hover:text-spot-green transition">{{.Article.Title}}</a>
8 {{else}}<span class="text-spot-text">{{.Article.Title}}</span>{{end}}8 {{else}}<span class="text-spot-text">{{.Article.Title}}</span>{{end}}
9 </h1>9 </h1>
10 10
@@ -12,7 +12,7 @@
12 {{if .Article.Author.Valid}}<span>{{.Article.Author.String}}</span>{{end}}12 {{if .Article.Author.Valid}}<span>{{.Article.Author.String}}</span>{{end}}
13 {{if .Article.Published.Valid}}<span>{{.Article.Published.Time.Format "Jan 2, 2006 15:04"}}</span>{{end}}13 {{if .Article.Published.Valid}}<span>{{.Article.Published.Time.Format "Jan 2, 2006 15:04"}}</span>{{end}}
14 {{if .Feed}}14 {{if .Feed}}
15- <a href="/articles?feed={{.Feed.FeedURL}}" class="hover:text-spot-purple transition">15+ <a href="/articles?feed={{.Feed.FeedURL}}" class="hover:text-spot-green transition">
16 {{if .Feed.FaviconURL.Valid}}<img src="{{.Feed.FaviconURL.String}}" class="w-4 h-4 inline-block mr-1 align-text-bottom rounded-sm">{{end}}16 {{if .Feed.FaviconURL.Valid}}<img src="{{.Feed.FaviconURL.String}}" class="w-4 h-4 inline-block mr-1 align-text-bottom rounded-sm">{{end}}
17 {{if .Feed.Title.Valid}}{{.Feed.Title.String}}{{else}}{{.Feed.FeedURL}}{{end}}17 {{if .Feed.Title.Valid}}{{.Feed.Title.String}}{{else}}{{.Feed.FeedURL}}{{end}}
18 </a>18 </a>
@@ -57,7 +57,7 @@
57 {{sanitizeHTML .Article.Summary.String}}57 {{sanitizeHTML .Article.Summary.String}}
58 </div>58 </div>
59 {{else}}59 {{else}}
60- <p class="text-spot-secondary">No content available. <a href="{{.Article.URL.String}}" class="text-spot-purple hover:underline">Read the original article.</a></p>60+ <p class="text-spot-secondary">No content available. <a href="{{.Article.URL.String}}" class="text-spot-green hover:underline">Read the original article.</a></p>
61 {{end}}61 {{end}}
62 </article>62 </article>
63 63
@@ -74,15 +74,15 @@
74 <input type="hidden" name="article_url" value="{{if .Article.URL.Valid}}{{.Article.URL.String}}{{end}}">74 <input type="hidden" name="article_url" value="{{if .Article.URL.Valid}}{{.Article.URL.String}}{{end}}">
75 <div>75 <div>
76 <textarea name="note" rows="2" placeholder="Add a note..."76 <textarea name="note" rows="2" placeholder="Add a note..."
77- class="w-full bg-spot-hover text-spot-text rounded-lg px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-spot-purple placeholder:text-spot-placeholder resize-none"></textarea>77+ class="w-full bg-spot-hover text-spot-text rounded-lg px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-spot-green placeholder:text-spot-placeholder resize-none"></textarea>
78 </div>78 </div>
79 <div class="flex gap-2">79 <div class="flex gap-2">
80 <input type="text" name="quote" placeholder="Quote (optional)"80 <input type="text" name="quote" placeholder="Quote (optional)"
81- class="flex-1 bg-spot-hover text-spot-text rounded-lg px-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-spot-purple placeholder:text-spot-placeholder">81+ class="flex-1 bg-spot-hover text-spot-text rounded-lg px-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-spot-green placeholder:text-spot-placeholder">
82 <input type="text" name="tags" placeholder="Tags (comma separated)"82 <input type="text" name="tags" placeholder="Tags (comma separated)"
83- class="flex-1 bg-spot-hover text-spot-text rounded-lg px-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-spot-purple placeholder:text-spot-placeholder">83+ class="flex-1 bg-spot-hover text-spot-text rounded-lg px-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-spot-green placeholder:text-spot-placeholder">
84 </div>84 </div>
85- <button type="submit" class="bg-spot-purple text-spot-bg rounded-pill px-5 py-2 text-sm font-bold uppercase tracking-button hover:brightness-110 transition">Annotate</button>85+ <button type="submit" class="bg-spot-green text-spot-bg rounded-pill px-5 py-2 text-sm font-bold uppercase tracking-button hover:brightness-110 transition">Annotate</button>
86 </form>86 </form>
87 87
88 <div id="annotations-list" class="space-y-3">88 <div id="annotations-list" class="space-y-3">
modified internal/tmpl/articles.html +3 -3
@@ -21,7 +21,7 @@
2121 <button hx-get="/articles?offset={{.NextOffset}}{{if .FeedURL}}&feed={{.FeedURL}}{{end}}{{if .Starred}}&starred=1{{end}}"
2222 hx-target="#article-list" hx-swap="beforeend"
2323 hx-select="article"
24- class="text-sm text-spot-secondary hover:text-spot-purple transition">
24+ class="text-sm text-spot-secondary hover:text-spot-green transition">
2525 Load more
2626 </button>
2727 </div>
@@ -33,10 +33,10 @@
3333 var currentIdx = -1;
3434
3535 function highlightArticle(idx) {
36- articles.forEach(function(a) { a.classList.remove('ring-2', 'ring-spot-purple'); });
36+ articles.forEach(function(a) { a.classList.remove('ring-2', 'ring-spot-green'); });
3737 if (idx >= 0 && idx < articles.length) {
3838 currentIdx = idx;
39- articles[idx].classList.add('ring-2', 'ring-spot-purple');
39+ articles[idx].classList.add('ring-2', 'ring-spot-green');
4040 articles[idx].scrollIntoView({ behavior: 'smooth', block: 'nearest' });
4141 }
4242 }
@@ -21,7 +21,7 @@
21 <button hx-get="/articles?offset={{.NextOffset}}{{if .FeedURL}}&feed={{.FeedURL}}{{end}}{{if .Starred}}&starred=1{{end}}"21 <button hx-get="/articles?offset={{.NextOffset}}{{if .FeedURL}}&feed={{.FeedURL}}{{end}}{{if .Starred}}&starred=1{{end}}"
22 hx-target="#article-list" hx-swap="beforeend"22 hx-target="#article-list" hx-swap="beforeend"
23 hx-select="article"23 hx-select="article"
24- class="text-sm text-spot-secondary hover:text-spot-purple transition">24+ class="text-sm text-spot-secondary hover:text-spot-green transition">
25 Load more25 Load more
26 </button>26 </button>
27 </div>27 </div>
@@ -33,10 +33,10 @@
33 var currentIdx = -1;33 var currentIdx = -1;
34 34
35 function highlightArticle(idx) {35 function highlightArticle(idx) {
36- articles.forEach(function(a) { a.classList.remove('ring-2', 'ring-spot-purple'); });36+ articles.forEach(function(a) { a.classList.remove('ring-2', 'ring-spot-green'); });
37 if (idx >= 0 && idx < articles.length) {37 if (idx >= 0 && idx < articles.length) {
38 currentIdx = idx;38 currentIdx = idx;
39- articles[idx].classList.add('ring-2', 'ring-spot-purple');39+ articles[idx].classList.add('ring-2', 'ring-spot-green');
40 articles[idx].scrollIntoView({ behavior: 'smooth', block: 'nearest' });40 articles[idx].scrollIntoView({ behavior: 'smooth', block: 'nearest' });
41 }41 }
42 }42 }
modified internal/tmpl/base.html +19 -125
@@ -8,134 +8,28 @@
88 <script>
99 (function(){var t=localStorage.getItem('theme')||'dark';document.documentElement.setAttribute('data-theme',t)})();
1010 </script>
11- <script src="https://cdn.tailwindcss.com"></script>
12- <script>
13- tailwind.config = {
14- theme: {
15- extend: {
16- colors: {
17- spot: {
18- purple: '#a855f7',
19- 'purple-border': '#9333ea',
20- bg: 'var(--spot-bg)',
21- surface: 'var(--spot-surface)',
22- hover: 'var(--spot-hover)',
23- 'hover-50': 'var(--spot-hover-50)',
24- text: 'var(--spot-text)',
25- secondary: 'var(--spot-secondary)',
26- body: 'var(--spot-body)',
27- muted: 'var(--spot-muted)',
28- divider: 'var(--spot-divider)',
29- 'divider-30': 'var(--spot-divider-30)',
30- outline: 'var(--spot-outline)',
31- placeholder: 'var(--spot-placeholder)',
32- 'active-pill-bg': 'var(--spot-active-bg)',
33- 'active-pill-text': 'var(--spot-active-text)',
34- red: '#f3727f',
35- orange: '#ffa42b',
36- blue: '#539df5',
37- }
38- },
39- borderRadius: {
40- pill: '9999px',
41- 'pill-lg': '500px',
42- },
43- boxShadow: {
44- 'spot': 'var(--spot-shadow)',
45- 'spot-heavy': 'var(--spot-shadow-heavy)',
46- },
47- letterSpacing: {
48- button: '1.4px',
49- },
50- }
51- }
52- }
53- </script>
11+ <link rel="stylesheet" href="/static/output.css">
5412 <script src="https://unpkg.com/htmx.org@2"></script>
55- <style>
56- :root {
57- --spot-bg: #121212;
58- --spot-surface: #181818;
59- --spot-hover: #1f1f1f;
60- --spot-hover-50: rgba(31,31,31,0.5);
61- --spot-text: #ffffff;
62- --spot-secondary: #b3b3b3;
63- --spot-body: #cbcbcb;
64- --spot-muted: #4d4d4d;
65- --spot-divider: rgba(77,77,77,0.2);
66- --spot-divider-30: rgba(77,77,77,0.3);
67- --spot-outline: #7c7c7c;
68- --spot-placeholder: #4d4d4d;
69- --spot-active-bg: #ffffff;
70- --spot-active-text: #121212;
71- --spot-shadow: rgba(0,0,0,0.3) 0px 8px 8px;
72- --spot-shadow-heavy: rgba(0,0,0,0.5) 0px 8px 24px;
73- }
74- [data-theme="light"] {
75- --spot-bg: #f5f5f5;
76- --spot-surface: #ffffff;
77- --spot-hover: #f3f4f6;
78- --spot-hover-50: rgba(243,244,246,0.5);
79- --spot-text: #111827;
80- --spot-secondary: #6b7280;
81- --spot-body: #374151;
82- --spot-muted: #9ca3af;
83- --spot-divider: #e5e7eb;
84- --spot-divider-30: #d1d5db;
85- --spot-outline: #d1d5db;
86- --spot-placeholder: #9ca3af;
87- --spot-active-bg: #111827;
88- --spot-active-text: #ffffff;
89- --spot-shadow: rgba(0,0,0,0.08) 0px 2px 8px;
90- --spot-shadow-heavy: rgba(0,0,0,0.1) 0px 4px 16px;
91- }
92- </style>
93- <style type="text/tailwindcss">
94- @layer components {
95- .article-body h1 { @apply text-2xl font-bold mt-8 mb-3 text-spot-text }
96- .article-body h2 { @apply text-xl font-semibold mt-6 mb-2 text-spot-text }
97- .article-body h3 { @apply text-lg font-semibold mt-5 mb-2 text-spot-text }
98- .article-body h4 { @apply text-base font-semibold mt-4 mb-1 text-spot-text }
99- .article-body p { @apply my-3 leading-7 text-spot-body }
100- .article-body ul { @apply list-disc pl-6 my-3 text-spot-body }
101- .article-body ol { @apply list-decimal pl-6 my-3 text-spot-body }
102- .article-body li { @apply my-1 leading-7 }
103- .article-body blockquote { @apply border-l-4 border-spot-divider pl-4 italic text-spot-secondary my-4 }
104- .article-body pre { @apply bg-spot-bg text-spot-body rounded-lg p-4 overflow-x-auto my-4 text-sm leading-6 }
105- .article-body code { @apply bg-spot-surface text-spot-body px-1.5 py-0.5 rounded text-sm font-mono }
106- .article-body pre code { @apply bg-transparent text-spot-body p-0 }
107- .article-body img { @apply rounded-lg max-w-full h-auto my-4 }
108- .article-body figure { @apply my-4 }
109- .article-body figcaption { @apply text-sm text-spot-secondary text-center mt-2 }
110- .article-body a { @apply text-spot-purple underline hover:brightness-110 }
111- .article-body table { @apply w-full border-collapse my-4 }
112- .article-body th { @apply border border-spot-divider px-3 py-2 bg-spot-surface font-semibold text-left text-spot-text }
113- .article-body td { @apply border border-spot-divider px-3 py-2 text-spot-body }
114- .article-body hr { @apply border-spot-divider my-6 }
115- .article-body iframe, .article-body video { @apply rounded-lg my-4 max-w-full }
116- .article-body iframe[src*="youtube.com"], .article-body iframe[src*="youtube-nocookie.com"], .article-body iframe[src*="vimeo.com"], .article-body iframe[src*="spotify.com"], .article-body iframe[src*="soundcloud.com"], .article-body iframe[src*="bandcamp.com"] { @apply w-full aspect-video }
117- .article-body del { @apply line-through text-spot-secondary }
118- .article-body mark { @apply bg-spot-orange/30 px-1 rounded }
119- }
120- </style>
13+ <link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
12114 <link rel="preconnect" href="https://fonts.googleapis.com">
12215 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
123- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
124- <style>
125- body { font-family: 'Inter', 'Helvetica Neue', helvetica, arial, sans-serif; }
126- .sidebar-link { transition: color 0.15s; }
127- .sidebar-link:hover { color: var(--spot-text); }
128- .line-clamp-2 { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
129- ::-webkit-scrollbar { width: 8px; }
130- ::-webkit-scrollbar-track { background: var(--spot-bg); }
131- ::-webkit-scrollbar-thumb { background: var(--spot-muted); border-radius: 4px; }
132- ::-webkit-scrollbar-thumb:hover { background: var(--spot-outline); }
133- </style>
16+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
13417 </head>
13518 <body class="bg-spot-bg text-spot-text min-h-screen flex">
13619 {{if .User}}
13720 <aside class="hidden lg:flex flex-col w-60 bg-spot-bg h-screen fixed left-0 top-0 px-3 py-4 z-20">
138- <a href="/" class="text-spot-purple font-bold text-xl font-title tracking-tight mb-8 px-3">Glean</a>
21+ <a href="/" class="text-spot-green font-bold text-xl font-title tracking-tight mb-8 px-3 flex items-center gap-2">
22+ <svg class="w-7 h-7" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
23+ <rect width="32" height="32" rx="8" fill="#00754A"/>
24+ <path d="M16 8 L16 22" stroke="#fff" stroke-width="2.5" stroke-linecap="round"/>
25+ <path d="M16 11 Q11 9 9 13" stroke="#fff" stroke-width="1.8" stroke-linecap="round" fill="none"/>
26+ <path d="M16 11 Q21 9 23 13" stroke="#fff" stroke-width="1.8" stroke-linecap="round" fill="none"/>
27+ <path d="M16 15 Q10 13 8 18" stroke="#fff" stroke-width="1.8" stroke-linecap="round" fill="none"/>
28+ <path d="M16 15 Q22 13 24 18" stroke="#fff" stroke-width="1.8" stroke-linecap="round" fill="none"/>
29+ <circle cx="16" cy="24" r="1.5" fill="#fff"/>
30+ </svg>
31+ Glean
32+ </a>
13933 <nav class="flex flex-col gap-1 text-sm">
14034 <a href="/dashboard" class="sidebar-link flex items-center gap-3 px-3 py-2 rounded-md {{activeClass .ActivePath "/dashboard"}}">
14135 <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-4 0a1 1 0 01-1-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 01-1 1"/></svg>
@@ -207,14 +101,14 @@
207101 <main id="main-content" class="{{if .User}}lg:ml-60 pb-20 lg:pb-0{{end}} flex-1 min-h-screen flex flex-col">
208102 {{if .User}}
209103 <div class="lg:hidden bg-spot-surface border-b border-spot-divider px-4 py-3 flex items-center justify-between sticky top-0 z-20">
210- <a href="/" class="text-spot-purple font-bold text-lg font-title">Glean</a>
104+ <a href="/" class="text-spot-green font-bold text-lg font-title">Glean</a>
211105 <a href="/profile/{{.User.DID}}" class="flex items-center gap-2">
212106 {{if .User.AvatarURL.Valid}}<img src="{{.User.AvatarURL.String}}" class="w-7 h-7 rounded-full">{{end}}
213107 <span class="text-xs text-spot-secondary">@{{.User.Handle}}</span>
214108 </a>
215109 </div>
216110 {{end}}
217- <div class="max-w-6xl mx-auto px-4 lg:px-8 py-6 flex-1">
111+ <div class="{{if not .User}}w-full{{else}}max-w-6xl mx-auto px-4 lg:px-8 py-6{{end}} flex-1">
218112 {{.Content}}
219113 </div>
220114 <footer class="mt-auto">
@@ -222,7 +116,7 @@
222116 <div class="max-w-6xl mx-auto px-4 lg:px-8 py-8">
223117 <div class="hidden md:grid md:grid-cols-[auto_1fr_auto] md:gap-12 md:items-start">
224118 <div>
225- <a href="/" class="text-spot-purple font-bold text-lg font-title tracking-tight">Glean</a>
119+ <a href="/" class="text-spot-green font-bold text-lg font-title tracking-tight">Glean</a>
226120 <p class="text-xs text-spot-secondary mt-1 max-w-[200px]">A social RSS reader<br>on the AT Protocol.</p>
227121 </div>
228122 <div class="grid grid-cols-3 gap-8">
@@ -273,7 +167,7 @@
273167 </div>
274168
275169 <div class="md:hidden flex flex-col gap-6">
276- <a href="/" class="text-spot-purple font-bold text-lg font-title tracking-tight">Glean</a>
170+ <a href="/" class="text-spot-green font-bold text-lg font-title tracking-tight">Glean</a>
277171 <div class="grid grid-cols-2 gap-6">
278172 <div class="flex flex-col gap-1.5">
279173 <div class="text-spot-text font-bold text-xs uppercase tracking-wide mb-1">Browse</div>
@@ -8,134 +8,28 @@
8 <script>8 <script>
9 (function(){var t=localStorage.getItem('theme')||'dark';document.documentElement.setAttribute('data-theme',t)})();9 (function(){var t=localStorage.getItem('theme')||'dark';document.documentElement.setAttribute('data-theme',t)})();
10 </script>10 </script>
11- <script src="https://cdn.tailwindcss.com"></script>11+ <link rel="stylesheet" href="/static/output.css">
12- <script>
13- tailwind.config = {
14- theme: {
15- extend: {
16- colors: {
17- spot: {
18- purple: '#a855f7',
19- 'purple-border': '#9333ea',
20- bg: 'var(--spot-bg)',
21- surface: 'var(--spot-surface)',
22- hover: 'var(--spot-hover)',
23- 'hover-50': 'var(--spot-hover-50)',
24- text: 'var(--spot-text)',
25- secondary: 'var(--spot-secondary)',
26- body: 'var(--spot-body)',
27- muted: 'var(--spot-muted)',
28- divider: 'var(--spot-divider)',
29- 'divider-30': 'var(--spot-divider-30)',
30- outline: 'var(--spot-outline)',
31- placeholder: 'var(--spot-placeholder)',
32- 'active-pill-bg': 'var(--spot-active-bg)',
33- 'active-pill-text': 'var(--spot-active-text)',
34- red: '#f3727f',
35- orange: '#ffa42b',
36- blue: '#539df5',
37- }
38- },
39- borderRadius: {
40- pill: '9999px',
41- 'pill-lg': '500px',
42- },
43- boxShadow: {
44- 'spot': 'var(--spot-shadow)',
45- 'spot-heavy': 'var(--spot-shadow-heavy)',
46- },
47- letterSpacing: {
48- button: '1.4px',
49- },
50- }
51- }
52- }
53- </script>
54 <script src="https://unpkg.com/htmx.org@2"></script>12 <script src="https://unpkg.com/htmx.org@2"></script>
55- <style>13+ <link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
56- :root {
57- --spot-bg: #121212;
58- --spot-surface: #181818;
59- --spot-hover: #1f1f1f;
60- --spot-hover-50: rgba(31,31,31,0.5);
61- --spot-text: #ffffff;
62- --spot-secondary: #b3b3b3;
63- --spot-body: #cbcbcb;
64- --spot-muted: #4d4d4d;
65- --spot-divider: rgba(77,77,77,0.2);
66- --spot-divider-30: rgba(77,77,77,0.3);
67- --spot-outline: #7c7c7c;
68- --spot-placeholder: #4d4d4d;
69- --spot-active-bg: #ffffff;
70- --spot-active-text: #121212;
71- --spot-shadow: rgba(0,0,0,0.3) 0px 8px 8px;
72- --spot-shadow-heavy: rgba(0,0,0,0.5) 0px 8px 24px;
73- }
74- [data-theme="light"] {
75- --spot-bg: #f5f5f5;
76- --spot-surface: #ffffff;
77- --spot-hover: #f3f4f6;
78- --spot-hover-50: rgba(243,244,246,0.5);
79- --spot-text: #111827;
80- --spot-secondary: #6b7280;
81- --spot-body: #374151;
82- --spot-muted: #9ca3af;
83- --spot-divider: #e5e7eb;
84- --spot-divider-30: #d1d5db;
85- --spot-outline: #d1d5db;
86- --spot-placeholder: #9ca3af;
87- --spot-active-bg: #111827;
88- --spot-active-text: #ffffff;
89- --spot-shadow: rgba(0,0,0,0.08) 0px 2px 8px;
90- --spot-shadow-heavy: rgba(0,0,0,0.1) 0px 4px 16px;
91- }
92- </style>
93- <style type="text/tailwindcss">
94- @layer components {
95- .article-body h1 { @apply text-2xl font-bold mt-8 mb-3 text-spot-text }
96- .article-body h2 { @apply text-xl font-semibold mt-6 mb-2 text-spot-text }
97- .article-body h3 { @apply text-lg font-semibold mt-5 mb-2 text-spot-text }
98- .article-body h4 { @apply text-base font-semibold mt-4 mb-1 text-spot-text }
99- .article-body p { @apply my-3 leading-7 text-spot-body }
100- .article-body ul { @apply list-disc pl-6 my-3 text-spot-body }
101- .article-body ol { @apply list-decimal pl-6 my-3 text-spot-body }
102- .article-body li { @apply my-1 leading-7 }
103- .article-body blockquote { @apply border-l-4 border-spot-divider pl-4 italic text-spot-secondary my-4 }
104- .article-body pre { @apply bg-spot-bg text-spot-body rounded-lg p-4 overflow-x-auto my-4 text-sm leading-6 }
105- .article-body code { @apply bg-spot-surface text-spot-body px-1.5 py-0.5 rounded text-sm font-mono }
106- .article-body pre code { @apply bg-transparent text-spot-body p-0 }
107- .article-body img { @apply rounded-lg max-w-full h-auto my-4 }
108- .article-body figure { @apply my-4 }
109- .article-body figcaption { @apply text-sm text-spot-secondary text-center mt-2 }
110- .article-body a { @apply text-spot-purple underline hover:brightness-110 }
111- .article-body table { @apply w-full border-collapse my-4 }
112- .article-body th { @apply border border-spot-divider px-3 py-2 bg-spot-surface font-semibold text-left text-spot-text }
113- .article-body td { @apply border border-spot-divider px-3 py-2 text-spot-body }
114- .article-body hr { @apply border-spot-divider my-6 }
115- .article-body iframe, .article-body video { @apply rounded-lg my-4 max-w-full }
116- .article-body iframe[src*="youtube.com"], .article-body iframe[src*="youtube-nocookie.com"], .article-body iframe[src*="vimeo.com"], .article-body iframe[src*="spotify.com"], .article-body iframe[src*="soundcloud.com"], .article-body iframe[src*="bandcamp.com"] { @apply w-full aspect-video }
117- .article-body del { @apply line-through text-spot-secondary }
118- .article-body mark { @apply bg-spot-orange/30 px-1 rounded }
119- }
120- </style>
121 <link rel="preconnect" href="https://fonts.googleapis.com">14 <link rel="preconnect" href="https://fonts.googleapis.com">
122 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>15 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
123- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">16+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
124- <style>
125- body { font-family: 'Inter', 'Helvetica Neue', helvetica, arial, sans-serif; }
126- .sidebar-link { transition: color 0.15s; }
127- .sidebar-link:hover { color: var(--spot-text); }
128- .line-clamp-2 { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
129- ::-webkit-scrollbar { width: 8px; }
130- ::-webkit-scrollbar-track { background: var(--spot-bg); }
131- ::-webkit-scrollbar-thumb { background: var(--spot-muted); border-radius: 4px; }
132- ::-webkit-scrollbar-thumb:hover { background: var(--spot-outline); }
133- </style>
134 </head>17 </head>
135 <body class="bg-spot-bg text-spot-text min-h-screen flex">18 <body class="bg-spot-bg text-spot-text min-h-screen flex">
136 {{if .User}}19 {{if .User}}
137 <aside class="hidden lg:flex flex-col w-60 bg-spot-bg h-screen fixed left-0 top-0 px-3 py-4 z-20">20 <aside class="hidden lg:flex flex-col w-60 bg-spot-bg h-screen fixed left-0 top-0 px-3 py-4 z-20">
138- <a href="/" class="text-spot-purple font-bold text-xl font-title tracking-tight mb-8 px-3">Glean</a>21+ <a href="/" class="text-spot-green font-bold text-xl font-title tracking-tight mb-8 px-3 flex items-center gap-2">
22+ <svg class="w-7 h-7" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
23+ <rect width="32" height="32" rx="8" fill="#00754A"/>
24+ <path d="M16 8 L16 22" stroke="#fff" stroke-width="2.5" stroke-linecap="round"/>
25+ <path d="M16 11 Q11 9 9 13" stroke="#fff" stroke-width="1.8" stroke-linecap="round" fill="none"/>
26+ <path d="M16 11 Q21 9 23 13" stroke="#fff" stroke-width="1.8" stroke-linecap="round" fill="none"/>
27+ <path d="M16 15 Q10 13 8 18" stroke="#fff" stroke-width="1.8" stroke-linecap="round" fill="none"/>
28+ <path d="M16 15 Q22 13 24 18" stroke="#fff" stroke-width="1.8" stroke-linecap="round" fill="none"/>
29+ <circle cx="16" cy="24" r="1.5" fill="#fff"/>
30+ </svg>
31+ Glean
32+ </a>
139 <nav class="flex flex-col gap-1 text-sm">33 <nav class="flex flex-col gap-1 text-sm">
140 <a href="/dashboard" class="sidebar-link flex items-center gap-3 px-3 py-2 rounded-md {{activeClass .ActivePath "/dashboard"}}">34 <a href="/dashboard" class="sidebar-link flex items-center gap-3 px-3 py-2 rounded-md {{activeClass .ActivePath "/dashboard"}}">
141 <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-4 0a1 1 0 01-1-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 01-1 1"/></svg>35 <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-4 0a1 1 0 01-1-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 01-1 1"/></svg>
@@ -207,14 +101,14 @@
207 <main id="main-content" class="{{if .User}}lg:ml-60 pb-20 lg:pb-0{{end}} flex-1 min-h-screen flex flex-col">101 <main id="main-content" class="{{if .User}}lg:ml-60 pb-20 lg:pb-0{{end}} flex-1 min-h-screen flex flex-col">
208 {{if .User}}102 {{if .User}}
209 <div class="lg:hidden bg-spot-surface border-b border-spot-divider px-4 py-3 flex items-center justify-between sticky top-0 z-20">103 <div class="lg:hidden bg-spot-surface border-b border-spot-divider px-4 py-3 flex items-center justify-between sticky top-0 z-20">
210- <a href="/" class="text-spot-purple font-bold text-lg font-title">Glean</a>104+ <a href="/" class="text-spot-green font-bold text-lg font-title">Glean</a>
211 <a href="/profile/{{.User.DID}}" class="flex items-center gap-2">105 <a href="/profile/{{.User.DID}}" class="flex items-center gap-2">
212 {{if .User.AvatarURL.Valid}}<img src="{{.User.AvatarURL.String}}" class="w-7 h-7 rounded-full">{{end}}106 {{if .User.AvatarURL.Valid}}<img src="{{.User.AvatarURL.String}}" class="w-7 h-7 rounded-full">{{end}}
213 <span class="text-xs text-spot-secondary">@{{.User.Handle}}</span>107 <span class="text-xs text-spot-secondary">@{{.User.Handle}}</span>
214 </a>108 </a>
215 </div>109 </div>
216 {{end}}110 {{end}}
217- <div class="max-w-6xl mx-auto px-4 lg:px-8 py-6 flex-1">111+ <div class="{{if not .User}}w-full{{else}}max-w-6xl mx-auto px-4 lg:px-8 py-6{{end}} flex-1">
218 {{.Content}}112 {{.Content}}
219 </div>113 </div>
220 <footer class="mt-auto">114 <footer class="mt-auto">
@@ -222,7 +116,7 @@
222 <div class="max-w-6xl mx-auto px-4 lg:px-8 py-8">116 <div class="max-w-6xl mx-auto px-4 lg:px-8 py-8">
223 <div class="hidden md:grid md:grid-cols-[auto_1fr_auto] md:gap-12 md:items-start">117 <div class="hidden md:grid md:grid-cols-[auto_1fr_auto] md:gap-12 md:items-start">
224 <div>118 <div>
225- <a href="/" class="text-spot-purple font-bold text-lg font-title tracking-tight">Glean</a>119+ <a href="/" class="text-spot-green font-bold text-lg font-title tracking-tight">Glean</a>
226 <p class="text-xs text-spot-secondary mt-1 max-w-[200px]">A social RSS reader<br>on the AT Protocol.</p>120 <p class="text-xs text-spot-secondary mt-1 max-w-[200px]">A social RSS reader<br>on the AT Protocol.</p>
227 </div>121 </div>
228 <div class="grid grid-cols-3 gap-8">122 <div class="grid grid-cols-3 gap-8">
@@ -273,7 +167,7 @@
273 </div>167 </div>
274 168
275 <div class="md:hidden flex flex-col gap-6">169 <div class="md:hidden flex flex-col gap-6">
276- <a href="/" class="text-spot-purple font-bold text-lg font-title tracking-tight">Glean</a>170+ <a href="/" class="text-spot-green font-bold text-lg font-title tracking-tight">Glean</a>
277 <div class="grid grid-cols-2 gap-6">171 <div class="grid grid-cols-2 gap-6">
278 <div class="flex flex-col gap-1.5">172 <div class="flex flex-col gap-1.5">
279 <div class="text-spot-text font-bold text-xs uppercase tracking-wide mb-1">Browse</div>173 <div class="text-spot-text font-bold text-xs uppercase tracking-wide mb-1">Browse</div>
modified internal/tmpl/dashboard.html +7 -7
@@ -19,7 +19,7 @@
1919
2020 {{if .Articles}}
2121 <div class="mt-4 text-center">
22- <a href="/articles" class="text-sm text-spot-secondary hover:text-spot-purple transition">View all articles &rarr;</a>
22+ <a href="/articles" class="text-sm text-spot-secondary hover:text-spot-green transition">View all articles &rarr;</a>
2323 </div>
2424 {{end}}
2525 </div>
@@ -29,7 +29,7 @@
2929 <div>
3030 <div class="flex items-center justify-between mb-4">
3131 <h2 class="text-lg font-semibold text-spot-text">Trending</h2>
32- <a href="/trending" class="text-xs text-spot-secondary hover:text-spot-purple uppercase tracking-button transition">See all</a>
32+ <a href="/trending" class="text-xs text-spot-secondary hover:text-spot-green uppercase tracking-button transition">See all</a>
3333 </div>
3434 <div class="space-y-3">
3535 {{range .Trending}}
@@ -53,14 +53,14 @@
5353 <div>
5454 <div class="flex items-center justify-between mb-4">
5555 <h2 class="text-lg font-semibold text-spot-text">Similar readers</h2>
56- <a href="/discover" class="text-xs text-spot-secondary hover:text-spot-purple uppercase tracking-button transition">See all</a>
56+ <a href="/discover" class="text-xs text-spot-secondary hover:text-spot-green uppercase tracking-button transition">See all</a>
5757 </div>
5858 <div class="space-y-3">
5959 {{range .PeopleRecommendations}}
6060 <div class="bg-spot-surface rounded-lg p-4 flex items-center gap-3 hover:bg-spot-hover-50 transition">
6161 {{if .avatar_url}}<img src="{{.avatar_url}}" class="w-10 h-10 rounded-full">{{end}}
6262 <div class="min-w-0 flex-1">
63- <a href="/profile/{{.handle}}" class="font-bold text-spot-text hover:text-spot-purple transition">@{{.handle}}</a>
63+ <a href="/profile/{{.handle}}" class="font-bold text-spot-text hover:text-spot-green transition">@{{.handle}}</a>
6464 {{if .display_name}}<div class="text-sm text-spot-secondary">{{.display_name}}</div>{{end}}
6565 </div>
6666 <span class="text-xs text-spot-secondary">{{.common_feeds}} shared</span>
@@ -74,7 +74,7 @@
7474 <div>
7575 <div class="flex items-center justify-between mb-4">
7676 <h2 class="text-lg font-semibold text-spot-text">Recommended feeds</h2>
77- <a href="/discover" class="text-xs text-spot-secondary hover:text-spot-purple uppercase tracking-button transition">See all</a>
77+ <a href="/discover" class="text-xs text-spot-secondary hover:text-spot-green uppercase tracking-button transition">See all</a>
7878 </div>
7979 <div class="space-y-3">
8080 {{range .FeedRecommendations}}
@@ -89,9 +89,9 @@
8989 <form hx-post="/feeds/add" hx-target="#feed-list" hx-swap="beforeend" class="flex gap-2">
9090 {{csrfInput .CSRFToken}}
9191 <input type="url" name="feed_url" placeholder="Feed URL"
92- class="flex-1 bg-spot-hover text-spot-text rounded-pill px-5 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-spot-purple placeholder:text-spot-placeholder"
92+ class="flex-1 bg-spot-hover text-spot-text rounded-pill px-5 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-spot-green placeholder:text-spot-placeholder"
9393 required>
94- <button type="submit" class="bg-spot-purple text-spot-bg rounded-pill px-5 py-2 text-sm font-bold uppercase tracking-button hover:brightness-110 transition">Add</button>
94+ <button type="submit" class="bg-spot-green text-spot-bg rounded-pill px-5 py-2 text-sm font-bold uppercase tracking-button hover:brightness-110 transition">Add</button>
9595 </form>
9696 </div>
9797 </div>
@@ -19,7 +19,7 @@
19 19
20 {{if .Articles}}20 {{if .Articles}}
21 <div class="mt-4 text-center">21 <div class="mt-4 text-center">
22- <a href="/articles" class="text-sm text-spot-secondary hover:text-spot-purple transition">View all articles &rarr;</a>22+ <a href="/articles" class="text-sm text-spot-secondary hover:text-spot-green transition">View all articles &rarr;</a>
23 </div>23 </div>
24 {{end}}24 {{end}}
25 </div>25 </div>
@@ -29,7 +29,7 @@
29 <div>29 <div>
30 <div class="flex items-center justify-between mb-4">30 <div class="flex items-center justify-between mb-4">
31 <h2 class="text-lg font-semibold text-spot-text">Trending</h2>31 <h2 class="text-lg font-semibold text-spot-text">Trending</h2>
32- <a href="/trending" class="text-xs text-spot-secondary hover:text-spot-purple uppercase tracking-button transition">See all</a>32+ <a href="/trending" class="text-xs text-spot-secondary hover:text-spot-green uppercase tracking-button transition">See all</a>
33 </div>33 </div>
34 <div class="space-y-3">34 <div class="space-y-3">
35 {{range .Trending}}35 {{range .Trending}}
@@ -53,14 +53,14 @@
53 <div>53 <div>
54 <div class="flex items-center justify-between mb-4">54 <div class="flex items-center justify-between mb-4">
55 <h2 class="text-lg font-semibold text-spot-text">Similar readers</h2>55 <h2 class="text-lg font-semibold text-spot-text">Similar readers</h2>
56- <a href="/discover" class="text-xs text-spot-secondary hover:text-spot-purple uppercase tracking-button transition">See all</a>56+ <a href="/discover" class="text-xs text-spot-secondary hover:text-spot-green uppercase tracking-button transition">See all</a>
57 </div>57 </div>
58 <div class="space-y-3">58 <div class="space-y-3">
59 {{range .PeopleRecommendations}}59 {{range .PeopleRecommendations}}
60 <div class="bg-spot-surface rounded-lg p-4 flex items-center gap-3 hover:bg-spot-hover-50 transition">60 <div class="bg-spot-surface rounded-lg p-4 flex items-center gap-3 hover:bg-spot-hover-50 transition">
61 {{if .avatar_url}}<img src="{{.avatar_url}}" class="w-10 h-10 rounded-full">{{end}}61 {{if .avatar_url}}<img src="{{.avatar_url}}" class="w-10 h-10 rounded-full">{{end}}
62 <div class="min-w-0 flex-1">62 <div class="min-w-0 flex-1">
63- <a href="/profile/{{.handle}}" class="font-bold text-spot-text hover:text-spot-purple transition">@{{.handle}}</a>63+ <a href="/profile/{{.handle}}" class="font-bold text-spot-text hover:text-spot-green transition">@{{.handle}}</a>
64 {{if .display_name}}<div class="text-sm text-spot-secondary">{{.display_name}}</div>{{end}}64 {{if .display_name}}<div class="text-sm text-spot-secondary">{{.display_name}}</div>{{end}}
65 </div>65 </div>
66 <span class="text-xs text-spot-secondary">{{.common_feeds}} shared</span>66 <span class="text-xs text-spot-secondary">{{.common_feeds}} shared</span>
@@ -74,7 +74,7 @@
74 <div>74 <div>
75 <div class="flex items-center justify-between mb-4">75 <div class="flex items-center justify-between mb-4">
76 <h2 class="text-lg font-semibold text-spot-text">Recommended feeds</h2>76 <h2 class="text-lg font-semibold text-spot-text">Recommended feeds</h2>
77- <a href="/discover" class="text-xs text-spot-secondary hover:text-spot-purple uppercase tracking-button transition">See all</a>77+ <a href="/discover" class="text-xs text-spot-secondary hover:text-spot-green uppercase tracking-button transition">See all</a>
78 </div>78 </div>
79 <div class="space-y-3">79 <div class="space-y-3">
80 {{range .FeedRecommendations}}80 {{range .FeedRecommendations}}
@@ -89,9 +89,9 @@
89 <form hx-post="/feeds/add" hx-target="#feed-list" hx-swap="beforeend" class="flex gap-2">89 <form hx-post="/feeds/add" hx-target="#feed-list" hx-swap="beforeend" class="flex gap-2">
90 {{csrfInput .CSRFToken}}90 {{csrfInput .CSRFToken}}
91 <input type="url" name="feed_url" placeholder="Feed URL"91 <input type="url" name="feed_url" placeholder="Feed URL"
92- class="flex-1 bg-spot-hover text-spot-text rounded-pill px-5 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-spot-purple placeholder:text-spot-placeholder"92+ class="flex-1 bg-spot-hover text-spot-text rounded-pill px-5 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-spot-green placeholder:text-spot-placeholder"
93 required>93 required>
94- <button type="submit" class="bg-spot-purple text-spot-bg rounded-pill px-5 py-2 text-sm font-bold uppercase tracking-button hover:brightness-110 transition">Add</button>94+ <button type="submit" class="bg-spot-green text-spot-bg rounded-pill px-5 py-2 text-sm font-bold uppercase tracking-button hover:brightness-110 transition">Add</button>
95 </form>95 </form>
96 </div>96 </div>
97 </div>97 </div>
modified internal/tmpl/discover.html +2 -2
@@ -41,7 +41,7 @@
4141 <div class="bg-spot-surface rounded-lg p-4 flex items-center gap-3 hover:bg-spot-hover-50 transition">
4242 {{if .avatar_url}}<img src="{{.avatar_url}}" class="w-10 h-10 rounded-full">{{end}}
4343 <div class="min-w-0 flex-1">
44- <a href="/profile/{{.handle}}" class="font-bold text-spot-text hover:text-spot-purple transition">@{{.handle}}</a>
44+ <a href="/profile/{{.handle}}" class="font-bold text-spot-text hover:text-spot-green transition">@{{.handle}}</a>
4545 {{if .display_name}}<div class="text-sm text-spot-secondary">{{.display_name}}</div>{{end}}
4646 </div>
4747 <span class="text-xs text-spot-secondary">{{.common_feeds}} shared feeds</span>
@@ -65,7 +65,7 @@
6565 <div class="flex items-center gap-3 shrink-0">
6666 <span class="text-xs text-spot-secondary">{{.SubscriberCount}} subscribers</span>
6767 <button hx-post="/feeds" hx-vals='{"feed_url": "{{.FeedURL}}"}' hx-target="this" hx-swap="outerHTML"
68- class="text-xs bg-spot-purple/20 text-spot-purple px-4 py-1.5 rounded-pill font-bold uppercase tracking-button hover:bg-spot-purple hover:text-spot-bg transition">Subscribe</button>
68+ class="text-xs bg-spot-green/20 text-spot-green px-4 py-1.5 rounded-pill font-bold uppercase tracking-button hover:bg-spot-green hover:text-spot-bg transition">Subscribe</button>
6969 </div>
7070 </div>
7171 {{else}}
@@ -41,7 +41,7 @@
41 <div class="bg-spot-surface rounded-lg p-4 flex items-center gap-3 hover:bg-spot-hover-50 transition">41 <div class="bg-spot-surface rounded-lg p-4 flex items-center gap-3 hover:bg-spot-hover-50 transition">
42 {{if .avatar_url}}<img src="{{.avatar_url}}" class="w-10 h-10 rounded-full">{{end}}42 {{if .avatar_url}}<img src="{{.avatar_url}}" class="w-10 h-10 rounded-full">{{end}}
43 <div class="min-w-0 flex-1">43 <div class="min-w-0 flex-1">
44- <a href="/profile/{{.handle}}" class="font-bold text-spot-text hover:text-spot-purple transition">@{{.handle}}</a>44+ <a href="/profile/{{.handle}}" class="font-bold text-spot-text hover:text-spot-green transition">@{{.handle}}</a>
45 {{if .display_name}}<div class="text-sm text-spot-secondary">{{.display_name}}</div>{{end}}45 {{if .display_name}}<div class="text-sm text-spot-secondary">{{.display_name}}</div>{{end}}
46 </div>46 </div>
47 <span class="text-xs text-spot-secondary">{{.common_feeds}} shared feeds</span>47 <span class="text-xs text-spot-secondary">{{.common_feeds}} shared feeds</span>
@@ -65,7 +65,7 @@
65 <div class="flex items-center gap-3 shrink-0">65 <div class="flex items-center gap-3 shrink-0">
66 <span class="text-xs text-spot-secondary">{{.SubscriberCount}} subscribers</span>66 <span class="text-xs text-spot-secondary">{{.SubscriberCount}} subscribers</span>
67 <button hx-post="/feeds" hx-vals='{"feed_url": "{{.FeedURL}}"}' hx-target="this" hx-swap="outerHTML"67 <button hx-post="/feeds" hx-vals='{"feed_url": "{{.FeedURL}}"}' hx-target="this" hx-swap="outerHTML"
68- class="text-xs bg-spot-purple/20 text-spot-purple px-4 py-1.5 rounded-pill font-bold uppercase tracking-button hover:bg-spot-purple hover:text-spot-bg transition">Subscribe</button>68+ class="text-xs bg-spot-green/20 text-spot-green px-4 py-1.5 rounded-pill font-bold uppercase tracking-button hover:bg-spot-green hover:text-spot-bg transition">Subscribe</button>
69 </div>69 </div>
70 </div>70 </div>
71 {{else}}71 {{else}}
modified internal/tmpl/feeds.html +8 -8
@@ -47,12 +47,12 @@
4747 {{if .Category.Valid}}<span class="text-xs text-spot-secondary">{{.Category.String}}</span>{{end}}
4848 </div>
4949 <div class="flex items-center gap-3">
50- {{if .UnreadCount}}<span class="text-xs bg-spot-purple/20 text-spot-purple px-2.5 py-0.5 rounded-full font-bold">{{.UnreadCount}}</span>{{end}}
50+ {{if .UnreadCount}}<span class="text-xs bg-spot-green/20 text-spot-green px-2.5 py-0.5 rounded-full font-bold">{{.UnreadCount}}</span>{{end}}
5151 <form hx-post="/feeds/set-interval" hx-swap="none" hx-vals='{"feed_url": "{{.FeedURL}}"}' class="inline-flex items-center gap-1">
5252 {{csrfInput $.CSRFToken}}
5353 <input type="hidden" name="feed_url" value="{{.FeedURL}}">
5454 <select name="interval" onchange="this.form.submit()"
55- class="text-xs bg-spot-hover text-spot-secondary rounded px-2 py-1 border-none focus:outline-none focus:ring-1 focus:ring-spot-purple">
55+ class="text-xs bg-spot-hover text-spot-secondary rounded px-2 py-1 border-none focus:outline-none focus:ring-1 focus:ring-spot-green">
5656 <option value="15" {{if eq .FetchInterval 15}}selected{{end}}>15m</option>
5757 <option value="30" {{if eq .FetchInterval 30}}selected{{end}}>30m</option>
5858 <option value="60" {{if eq .FetchInterval 60}}selected{{end}}>1h</option>
@@ -81,11 +81,11 @@
8181 <form hx-post="/feeds/add" hx-target="#feed-list" hx-swap="beforeend" class="space-y-3">
8282 {{csrfInput .CSRFToken}}
8383 <input type="url" name="feed_url" placeholder="https://example.com/feed.xml"
84- class="w-full bg-spot-hover text-spot-text rounded-pill px-5 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-spot-purple placeholder:text-spot-placeholder"
84+ class="w-full bg-spot-hover text-spot-text rounded-pill px-5 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-spot-green placeholder:text-spot-placeholder"
8585 required>
8686 <input type="text" name="category" placeholder="Category (optional)"
87- class="w-full bg-spot-hover text-spot-text rounded-pill px-5 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-spot-purple placeholder:text-spot-placeholder">
88- <button type="submit" class="w-full bg-spot-purple text-spot-bg rounded-pill px-5 py-2 text-sm font-bold uppercase tracking-button hover:brightness-110 transition">Add</button>
87+ class="w-full bg-spot-hover text-spot-text rounded-pill px-5 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-spot-green placeholder:text-spot-placeholder">
88+ <button type="submit" class="w-full bg-spot-green text-spot-bg rounded-pill px-5 py-2 text-sm font-bold uppercase tracking-button hover:brightness-110 transition">Add</button>
8989 </form>
9090 <form hx-post="/feeds/opml/upload" hx-encoding="multipart/form-data" class="flex flex-col gap-2">
9191 {{csrfInput .CSRFToken}}
@@ -99,7 +99,7 @@
9999 <div>
100100 <div class="flex items-center justify-between mb-4">
101101 <h2 class="text-sm font-bold text-spot-text uppercase tracking-wide">Recommended feeds</h2>
102- <a href="/discover" class="text-xs text-spot-secondary hover:text-spot-purple uppercase tracking-button transition">See all</a>
102+ <a href="/discover" class="text-xs text-spot-secondary hover:text-spot-green uppercase tracking-button transition">See all</a>
103103 </div>
104104 <div class="space-y-3">
105105 {{range .FeedRecommendations}}
@@ -113,14 +113,14 @@
113113 <div>
114114 <div class="flex items-center justify-between mb-4">
115115 <h2 class="text-sm font-bold text-spot-text uppercase tracking-wide">Similar readers</h2>
116- <a href="/discover" class="text-xs text-spot-secondary hover:text-spot-purple uppercase tracking-button transition">See all</a>
116+ <a href="/discover" class="text-xs text-spot-secondary hover:text-spot-green uppercase tracking-button transition">See all</a>
117117 </div>
118118 <div class="space-y-3">
119119 {{range .PeopleRecommendations}}
120120 <div class="bg-spot-surface rounded-lg p-4 flex items-center gap-3 hover:bg-spot-hover-50 transition">
121121 {{if .avatar_url}}<img src="{{.avatar_url}}" class="w-10 h-10 rounded-full">{{end}}
122122 <div class="min-w-0 flex-1">
123- <a href="/profile/{{.handle}}" class="font-bold text-spot-text hover:text-spot-purple transition">@{{.handle}}</a>
123+ <a href="/profile/{{.handle}}" class="font-bold text-spot-text hover:text-spot-green transition">@{{.handle}}</a>
124124 {{if .display_name}}<div class="text-sm text-spot-secondary">{{.display_name}}</div>{{end}}
125125 </div>
126126 <span class="text-xs text-spot-secondary">{{.common_feeds}} shared</span>
@@ -47,12 +47,12 @@
47 {{if .Category.Valid}}<span class="text-xs text-spot-secondary">{{.Category.String}}</span>{{end}}47 {{if .Category.Valid}}<span class="text-xs text-spot-secondary">{{.Category.String}}</span>{{end}}
48 </div>48 </div>
49 <div class="flex items-center gap-3">49 <div class="flex items-center gap-3">
50- {{if .UnreadCount}}<span class="text-xs bg-spot-purple/20 text-spot-purple px-2.5 py-0.5 rounded-full font-bold">{{.UnreadCount}}</span>{{end}}50+ {{if .UnreadCount}}<span class="text-xs bg-spot-green/20 text-spot-green px-2.5 py-0.5 rounded-full font-bold">{{.UnreadCount}}</span>{{end}}
51 <form hx-post="/feeds/set-interval" hx-swap="none" hx-vals='{"feed_url": "{{.FeedURL}}"}' class="inline-flex items-center gap-1">51 <form hx-post="/feeds/set-interval" hx-swap="none" hx-vals='{"feed_url": "{{.FeedURL}}"}' class="inline-flex items-center gap-1">
52 {{csrfInput $.CSRFToken}}52 {{csrfInput $.CSRFToken}}
53 <input type="hidden" name="feed_url" value="{{.FeedURL}}">53 <input type="hidden" name="feed_url" value="{{.FeedURL}}">
54 <select name="interval" onchange="this.form.submit()"54 <select name="interval" onchange="this.form.submit()"
55- class="text-xs bg-spot-hover text-spot-secondary rounded px-2 py-1 border-none focus:outline-none focus:ring-1 focus:ring-spot-purple">55+ class="text-xs bg-spot-hover text-spot-secondary rounded px-2 py-1 border-none focus:outline-none focus:ring-1 focus:ring-spot-green">
56 <option value="15" {{if eq .FetchInterval 15}}selected{{end}}>15m</option>56 <option value="15" {{if eq .FetchInterval 15}}selected{{end}}>15m</option>
57 <option value="30" {{if eq .FetchInterval 30}}selected{{end}}>30m</option>57 <option value="30" {{if eq .FetchInterval 30}}selected{{end}}>30m</option>
58 <option value="60" {{if eq .FetchInterval 60}}selected{{end}}>1h</option>58 <option value="60" {{if eq .FetchInterval 60}}selected{{end}}>1h</option>
@@ -81,11 +81,11 @@
81 <form hx-post="/feeds/add" hx-target="#feed-list" hx-swap="beforeend" class="space-y-3">81 <form hx-post="/feeds/add" hx-target="#feed-list" hx-swap="beforeend" class="space-y-3">
82 {{csrfInput .CSRFToken}}82 {{csrfInput .CSRFToken}}
83 <input type="url" name="feed_url" placeholder="https://example.com/feed.xml"83 <input type="url" name="feed_url" placeholder="https://example.com/feed.xml"
84- class="w-full bg-spot-hover text-spot-text rounded-pill px-5 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-spot-purple placeholder:text-spot-placeholder"84+ class="w-full bg-spot-hover text-spot-text rounded-pill px-5 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-spot-green placeholder:text-spot-placeholder"
85 required>85 required>
86 <input type="text" name="category" placeholder="Category (optional)"86 <input type="text" name="category" placeholder="Category (optional)"
87- class="w-full bg-spot-hover text-spot-text rounded-pill px-5 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-spot-purple placeholder:text-spot-placeholder">87+ class="w-full bg-spot-hover text-spot-text rounded-pill px-5 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-spot-green placeholder:text-spot-placeholder">
88- <button type="submit" class="w-full bg-spot-purple text-spot-bg rounded-pill px-5 py-2 text-sm font-bold uppercase tracking-button hover:brightness-110 transition">Add</button>88+ <button type="submit" class="w-full bg-spot-green text-spot-bg rounded-pill px-5 py-2 text-sm font-bold uppercase tracking-button hover:brightness-110 transition">Add</button>
89 </form>89 </form>
90 <form hx-post="/feeds/opml/upload" hx-encoding="multipart/form-data" class="flex flex-col gap-2">90 <form hx-post="/feeds/opml/upload" hx-encoding="multipart/form-data" class="flex flex-col gap-2">
91 {{csrfInput .CSRFToken}}91 {{csrfInput .CSRFToken}}
@@ -99,7 +99,7 @@
99 <div>99 <div>
100 <div class="flex items-center justify-between mb-4">100 <div class="flex items-center justify-between mb-4">
101 <h2 class="text-sm font-bold text-spot-text uppercase tracking-wide">Recommended feeds</h2>101 <h2 class="text-sm font-bold text-spot-text uppercase tracking-wide">Recommended feeds</h2>
102- <a href="/discover" class="text-xs text-spot-secondary hover:text-spot-purple uppercase tracking-button transition">See all</a>102+ <a href="/discover" class="text-xs text-spot-secondary hover:text-spot-green uppercase tracking-button transition">See all</a>
103 </div>103 </div>
104 <div class="space-y-3">104 <div class="space-y-3">
105 {{range .FeedRecommendations}}105 {{range .FeedRecommendations}}
@@ -113,14 +113,14 @@
113 <div>113 <div>
114 <div class="flex items-center justify-between mb-4">114 <div class="flex items-center justify-between mb-4">
115 <h2 class="text-sm font-bold text-spot-text uppercase tracking-wide">Similar readers</h2>115 <h2 class="text-sm font-bold text-spot-text uppercase tracking-wide">Similar readers</h2>
116- <a href="/discover" class="text-xs text-spot-secondary hover:text-spot-purple uppercase tracking-button transition">See all</a>116+ <a href="/discover" class="text-xs text-spot-secondary hover:text-spot-green uppercase tracking-button transition">See all</a>
117 </div>117 </div>
118 <div class="space-y-3">118 <div class="space-y-3">
119 {{range .PeopleRecommendations}}119 {{range .PeopleRecommendations}}
120 <div class="bg-spot-surface rounded-lg p-4 flex items-center gap-3 hover:bg-spot-hover-50 transition">120 <div class="bg-spot-surface rounded-lg p-4 flex items-center gap-3 hover:bg-spot-hover-50 transition">
121 {{if .avatar_url}}<img src="{{.avatar_url}}" class="w-10 h-10 rounded-full">{{end}}121 {{if .avatar_url}}<img src="{{.avatar_url}}" class="w-10 h-10 rounded-full">{{end}}
122 <div class="min-w-0 flex-1">122 <div class="min-w-0 flex-1">
123- <a href="/profile/{{.handle}}" class="font-bold text-spot-text hover:text-spot-purple transition">@{{.handle}}</a>123+ <a href="/profile/{{.handle}}" class="font-bold text-spot-text hover:text-spot-green transition">@{{.handle}}</a>
124 {{if .display_name}}<div class="text-sm text-spot-secondary">{{.display_name}}</div>{{end}}124 {{if .display_name}}<div class="text-sm text-spot-secondary">{{.display_name}}</div>{{end}}
125 </div>125 </div>
126 <span class="text-xs text-spot-secondary">{{.common_feeds}} shared</span>126 <span class="text-xs text-spot-secondary">{{.common_feeds}} shared</span>
modified internal/tmpl/index.html +167 -25
@@ -1,35 +1,177 @@
11 {{define "index.html"}}
2-<div class="max-w-2xl mx-auto mt-20 lg:mt-32 text-center">
3- <h1 class="text-5xl font-bold font-title mb-6 text-spot-text">Glean</h1>
4- <p class="text-lg text-spot-secondary mb-10 max-w-lg mx-auto leading-relaxed">A social RSS reader. Follow feeds, share what you read, discover new sources through your network.</p>
5- <div class="flex flex-col sm:flex-row justify-center gap-3">
6- <a href="/auth/login" class="bg-spot-purple text-spot-bg rounded-pill px-8 py-3 text-sm font-bold uppercase tracking-button hover:brightness-110 transition inline-flex items-center gap-2">
7- Sign in
8- </a>
9- <a href="/trending" class="border border-spot-outline text-spot-text rounded-pill px-8 py-3 text-sm font-bold uppercase tracking-button hover:border-spot-text transition">Browse trending</a>
2+<style>
3+.landing-hero { padding-top: 6rem; padding-bottom: 4rem; }
4+.landing-feed { display: flex; flex-direction: column; gap: 1px; }
5+.landing-item { transition: background 0.15s; }
6+.landing-item:hover { background: var(--spot-hover); }
7+.feed-line { height: 10px; border-radius: 5px; }
8+.feed-line-short { width: 40%; }
9+.feed-line-medium { width: 65%; }
10+.feed-dot { width: 8px; height: 8px; border-radius: 50%; }
11+@media (min-width: 768px) { .landing-hero { padding-top: 8rem; padding-bottom: 6rem; } }
12+</style>
13+
14+<section class="landing-hero">
15+ <div class="max-w-6xl mx-auto px-6">
16+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-16 items-center">
17+ <div>
18+ <div class="flex items-center gap-3 mb-8">
19+ <svg class="w-10 h-10" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
20+ <rect width="32" height="32" rx="8" fill="#00754A"/>
21+ <path d="M16 8 L16 22" stroke="#fff" stroke-width="2.5" stroke-linecap="round"/>
22+ <path d="M16 11 Q11 9 9 13" stroke="#fff" stroke-width="1.8" stroke-linecap="round" fill="none"/>
23+ <path d="M16 11 Q21 9 23 13" stroke="#fff" stroke-width="1.8" stroke-linecap="round" fill="none"/>
24+ <path d="M16 15 Q10 13 8 18" stroke="#fff" stroke-width="1.8" stroke-linecap="round" fill="none"/>
25+ <path d="M16 15 Q22 13 24 18" stroke="#fff" stroke-width="1.8" stroke-linecap="round" fill="none"/>
26+ <circle cx="16" cy="24" r="1.5" fill="#fff"/>
27+ </svg>
28+ <span class="font-bold text-xl text-spot-text" style="letter-spacing: -0.02em;">Glean</span>
29+ </div>
30+ <h1 class="text-3xl md:text-5xl font-bold mb-6 leading-[1.1] text-spot-text" style="letter-spacing: -0.03em;">
31+ Your feeds,<br>your network,<br>your rules.
32+ </h1>
33+ <p class="text-lg text-spot-secondary mb-8 max-w-md leading-relaxed">
34+ Follow RSS feeds from around the web. See what your network is reading. All tied to your AT Protocol identity.
35+ </p>
36+ <div class="flex flex-col sm:flex-row gap-3">
37+ <a href="/auth/login" class="bg-spot-green text-white rounded-pill px-7 py-3 text-sm font-bold uppercase tracking-button hover:brightness-110 transition active:scale-95 inline-flex items-center justify-center gap-2">
38+ Sign in
39+ </a>
40+ <a href="/trending" class="border border-spot-outline text-spot-text rounded-pill px-7 py-3 text-sm font-bold uppercase tracking-button hover:border-spot-text transition active:scale-95">
41+ See what's trending
42+ </a>
43+ </div>
44+ </div>
45+
46+ <div class="hidden lg:block">
47+ <div class="bg-spot-surface rounded-xl shadow-spot overflow-hidden">
48+ <div class="px-5 py-3 border-b border-spot-divider flex items-center gap-2">
49+ <div class="feed-dot bg-spot-red/60"></div>
50+ <div class="feed-dot bg-spot-orange/60"></div>
51+ <div class="feed-dot bg-spot-green/60"></div>
52+ <span class="text-xs text-spot-muted ml-3">Dashboard</span>
53+ </div>
54+ <div class="landing-feed p-4">
55+ <div class="landing-item rounded-lg px-4 py-3 flex items-start gap-3">
56+ <div class="w-8 h-8 rounded-full bg-spot-green/20 shrink-0 mt-0.5 flex items-center justify-center">
57+ <svg class="w-4 h-4 text-spot-green" fill="currentColor" viewBox="0 0 20 20"><path d="M5 3a1 1 0 000 2c5.523 0 10 4.477 10 10a1 1 0 102 0C17 8.373 11.627 3 5 3z"/><path d="M4 9a1 1 0 011-1 7 7 0 017 7 1 1 0 11-2 0 5 5 0 00-5-5 1 1 0 01-1-1z"/></svg>
58+ </div>
59+ <div class="flex-1 min-w-0">
60+ <div class="feed-line bg-spot-text/80 mb-2" style="width:70%"></div>
61+ <div class="feed-line bg-spot-secondary/40 feed-line-short"></div>
62+ </div>
63+ <div class="flex items-center gap-1 text-spot-green shrink-0">
64+ <svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M3.172 5.172a4 4 0 015.656 0L10 6.343l1.172-1.171a4 4 0 115.656 5.656L10 17.657l-6.828-6.829a4 4 0 010-5.656z"/></svg>
65+ <span class="text-xs">12</span>
66+ </div>
67+ </div>
68+ <div class="landing-item rounded-lg px-4 py-3 flex items-start gap-3">
69+ <div class="w-8 h-8 rounded-full bg-spot-blue/20 shrink-0 mt-0.5 flex items-center justify-center">
70+ <svg class="w-4 h-4 text-spot-blue" fill="currentColor" viewBox="0 0 20 20"><path d="M2 5a2 2 0 012-2h8a2 2 0 012 2v10a2 2 0 002 2H4a2 2 0 01-2-2V5z"/></svg>
71+ </div>
72+ <div class="flex-1 min-w-0">
73+ <div class="feed-line bg-spot-text/80 mb-2" style="width:55%"></div>
74+ <div class="feed-line bg-spot-secondary/40" style="width:80%"></div>
75+ </div>
76+ </div>
77+ <div class="landing-item rounded-lg px-4 py-3 flex items-start gap-3">
78+ <div class="w-8 h-8 rounded-full bg-spot-orange/20 shrink-0 mt-0.5 flex items-center justify-center">
79+ <svg class="w-4 h-4 text-spot-orange" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M12.395 2.553a1 1 0 00-1.45-.385c-.345.23-.614.558-.822.88-.214.33-.403.713-.57 1.116-.334.804-.614 1.768-.84 2.734a31.365 31.365 0 00-.613 3.58 2.64 2.64 0 01-.945-1.067c-.328-.68-.398-1.534-.398-2.654A1 1 0 005.05 6.05 6.981 6.981 0 003 11a7 7 0 1011.95-4.95c-.592-.591-.98-.985-1.348-1.467-.363-.476-.724-1.063-1.207-2.03zM12.12 15.12A3 3 0 017 13s.879.5 2.5.5c0-1 .5-4 1.25-4.5.5 1 .786 1.293 1.371 1.879A2.99 2.99 0 0113 13a2.99 2.99 0 01-.879 2.121z"/></svg>
80+ </div>
81+ <div class="flex-1 min-w-0">
82+ <div class="feed-line bg-spot-text/80 mb-2" style="width:45%"></div>
83+ <div class="feed-line bg-spot-secondary/40 feed-line-medium"></div>
84+ </div>
85+ <div class="flex items-center gap-1 text-spot-secondary shrink-0">
86+ <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z"/></svg>
87+ <span class="text-xs">3</span>
88+ </div>
89+ </div>
90+ <div class="landing-item rounded-lg px-4 py-3 flex items-start gap-3">
91+ <div class="w-8 h-8 rounded-full bg-spot-green/20 shrink-0 mt-0.5 flex items-center justify-center">
92+ <svg class="w-4 h-4 text-spot-green" fill="currentColor" viewBox="0 0 20 20"><path d="M5 3a1 1 0 000 2c5.523 0 10 4.477 10 10a1 1 0 102 0C17 8.373 11.627 3 5 3z"/></svg>
93+ </div>
94+ <div class="flex-1 min-w-0">
95+ <div class="feed-line bg-spot-text/80 mb-2" style="width:60%"></div>
96+ <div class="feed-line bg-spot-secondary/40 feed-line-short"></div>
97+ </div>
98+ </div>
99+ </div>
100+ <div class="px-5 py-3 border-t border-spot-divider flex items-center justify-between">
101+ <div class="flex items-center gap-2 text-xs text-spot-muted">
102+ <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"/></svg>
103+ 4 trending in your network
104+ </div>
105+ <span class="text-xs text-spot-green font-bold">42 unread</span>
106+ </div>
107+ </div>
108+ </div>
109+ </div>
10110 </div>
11- <div class="mt-24 grid grid-cols-1 sm:grid-cols-3 gap-4 text-left">
12- <div class="bg-spot-surface hover:bg-spot-hover rounded-lg p-6 transition shadow-spot">
13- <div class="text-spot-purple text-2xl mb-3">
14- <svg class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 5c7.18 0 13 5.82 13 13M6 11a7 7 0 017 7m-7-1a1 1 0 11-2 0 1 1 0 012 0z"/></svg>
111+</section>
112+
113+<section class="max-w-6xl mx-auto px-6 py-16">
114+ <div class="grid grid-cols-1 md:grid-cols-3 gap-8 md:gap-12">
115+ <div>
116+ <div class="flex items-center gap-2 mb-3">
117+ <svg class="w-5 h-5 text-spot-green" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 5c7.18 0 13 5.82 13 13M6 11a7 7 0 017 7m-7-1a1 1 0 11-2 0 1 1 0 012 0z"/></svg>
118+ <h3 class="font-bold text-spot-text">Subscribe</h3>
15119 </div>
16- <h3 class="font-bold text-spot-text mb-1">Follow any feed</h3>
17- <p class="text-sm text-spot-secondary">Subscribe to RSS and Atom feeds. Organize with categories. Import via OPML.</p>
120+ <p class="text-sm text-spot-secondary leading-relaxed">Add any RSS or Atom feed. Import your existing subscriptions from another reader via OPML. Group feeds by category.</p>
18121 </div>
19- <div class="bg-spot-surface hover:bg-spot-hover rounded-lg p-6 transition shadow-spot">
20- <div class="text-spot-purple text-2xl mb-3">
21- <svg class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
122+ <div>
123+ <div class="flex items-center gap-2 mb-3">
124+ <svg class="w-5 h-5 text-spot-green" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
125+ <h3 class="font-bold text-spot-text">Connect</h3>
22126 </div>
23- <h3 class="font-bold text-spot-text mb-1">Read together</h3>
24- <p class="text-sm text-spot-secondary">See what your network reads. Like and annotate articles. Share discoveries.</p>
127+ <p class="text-sm text-spot-secondary leading-relaxed">Follow people on the AT Protocol. See what they read and share. Get recommendations from readers with similar tastes.</p>
25128 </div>
26- <div class="bg-spot-surface hover:bg-spot-hover rounded-lg p-6 transition shadow-spot">
27- <div class="text-spot-purple text-2xl mb-3">
28- <svg class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
129+ <div>
130+ <div class="flex items-center gap-2 mb-3">
131+ <svg class="w-5 h-5 text-spot-green" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z"/></svg>
132+ <h3 class="font-bold text-spot-text">Annotate</h3>
29133 </div>
30- <h3 class="font-bold text-spot-text mb-1">Find new sources</h3>
31- <p class="text-sm text-spot-secondary">Get recommendations based on your reading. Find people with similar tastes.</p>
134+ <p class="text-sm text-spot-secondary leading-relaxed">Leave notes on any article. Quote passages that matter. Keep private annotations or share them with your network.</p>
32135 </div>
33136 </div>
34-</div>
137+</section>
138+
139+<section class="bg-spot-green-house">
140+ <div class="max-w-6xl mx-auto px-6 py-16">
141+ <div class="max-w-2xl">
142+ <h2 class="text-2xl md:text-3xl font-bold text-white mb-4" style="letter-spacing: -0.02em;">Owned by you, not by us.</h2>
143+ <p class="text-[rgba(255,255,255,0.70)] leading-relaxed mb-8">
144+ Glean stores subscriptions, annotations, and your social graph in your personal data repository on the AT Protocol. Bring your own PDS. Take your data anywhere. No lock-in, no ads, no tracking.
145+ </p>
146+ <div class="flex flex-wrap gap-x-8 gap-y-4 text-sm">
147+ <div class="flex items-center gap-2 text-[rgba(255,255,255,0.70)]">
148+ <svg class="w-4 h-4 text-spot-green" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>
149+ AT Protocol identity
150+ </div>
151+ <div class="flex items-center gap-2 text-[rgba(255,255,255,0.70)]">
152+ <svg class="w-4 h-4 text-spot-green" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>
153+ Portable data
154+ </div>
155+ <div class="flex items-center gap-2 text-[rgba(255,255,255,0.70)]">
156+ <svg class="w-4 h-4 text-spot-green" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>
157+ Open source
158+ </div>
159+ <div class="flex items-center gap-2 text-[rgba(255,255,255,0.70)]">
160+ <svg class="w-4 h-4 text-spot-green" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>
161+ Self-hostable
162+ </div>
163+ </div>
164+ </div>
165+ </div>
166+</section>
167+
168+<section class="py-16 text-center">
169+ <div class="max-w-md mx-auto px-6">
170+ <h2 class="text-2xl font-bold text-spot-text mb-3" style="letter-spacing: -0.02em;">Start reading</h2>
171+ <p class="text-spot-secondary mb-6 text-sm leading-relaxed">Sign in with your Bluesky handle or any AT Protocol account. Your feeds are waiting.</p>
172+ <a href="/auth/login" class="bg-spot-green text-white rounded-pill px-8 py-3 text-sm font-bold uppercase tracking-button hover:brightness-110 transition active:scale-95 inline-flex items-center justify-center gap-2">
173+ Get started
174+ </a>
175+ </div>
176+</section>
35177 {{end}}
@@ -1,35 +1,177 @@
1 {{define "index.html"}}1 {{define "index.html"}}
2-<div class="max-w-2xl mx-auto mt-20 lg:mt-32 text-center">2+<style>
3- <h1 class="text-5xl font-bold font-title mb-6 text-spot-text">Glean</h1>3+.landing-hero { padding-top: 6rem; padding-bottom: 4rem; }
4- <p class="text-lg text-spot-secondary mb-10 max-w-lg mx-auto leading-relaxed">A social RSS reader. Follow feeds, share what you read, discover new sources through your network.</p>4+.landing-feed { display: flex; flex-direction: column; gap: 1px; }
5- <div class="flex flex-col sm:flex-row justify-center gap-3">5+.landing-item { transition: background 0.15s; }
6- <a href="/auth/login" class="bg-spot-purple text-spot-bg rounded-pill px-8 py-3 text-sm font-bold uppercase tracking-button hover:brightness-110 transition inline-flex items-center gap-2">6+.landing-item:hover { background: var(--spot-hover); }
7- Sign in7+.feed-line { height: 10px; border-radius: 5px; }
8- </a>8+.feed-line-short { width: 40%; }
9- <a href="/trending" class="border border-spot-outline text-spot-text rounded-pill px-8 py-3 text-sm font-bold uppercase tracking-button hover:border-spot-text transition">Browse trending</a>9+.feed-line-medium { width: 65%; }
10+.feed-dot { width: 8px; height: 8px; border-radius: 50%; }
11+@media (min-width: 768px) { .landing-hero { padding-top: 8rem; padding-bottom: 6rem; } }
12+</style>
13+
14+<section class="landing-hero">
15+ <div class="max-w-6xl mx-auto px-6">
16+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-16 items-center">
17+ <div>
18+ <div class="flex items-center gap-3 mb-8">
19+ <svg class="w-10 h-10" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
20+ <rect width="32" height="32" rx="8" fill="#00754A"/>
21+ <path d="M16 8 L16 22" stroke="#fff" stroke-width="2.5" stroke-linecap="round"/>
22+ <path d="M16 11 Q11 9 9 13" stroke="#fff" stroke-width="1.8" stroke-linecap="round" fill="none"/>
23+ <path d="M16 11 Q21 9 23 13" stroke="#fff" stroke-width="1.8" stroke-linecap="round" fill="none"/>
24+ <path d="M16 15 Q10 13 8 18" stroke="#fff" stroke-width="1.8" stroke-linecap="round" fill="none"/>
25+ <path d="M16 15 Q22 13 24 18" stroke="#fff" stroke-width="1.8" stroke-linecap="round" fill="none"/>
26+ <circle cx="16" cy="24" r="1.5" fill="#fff"/>
27+ </svg>
28+ <span class="font-bold text-xl text-spot-text" style="letter-spacing: -0.02em;">Glean</span>
29+ </div>
30+ <h1 class="text-3xl md:text-5xl font-bold mb-6 leading-[1.1] text-spot-text" style="letter-spacing: -0.03em;">
31+ Your feeds,<br>your network,<br>your rules.
32+ </h1>
33+ <p class="text-lg text-spot-secondary mb-8 max-w-md leading-relaxed">
34+ Follow RSS feeds from around the web. See what your network is reading. All tied to your AT Protocol identity.
35+ </p>
36+ <div class="flex flex-col sm:flex-row gap-3">
37+ <a href="/auth/login" class="bg-spot-green text-white rounded-pill px-7 py-3 text-sm font-bold uppercase tracking-button hover:brightness-110 transition active:scale-95 inline-flex items-center justify-center gap-2">
38+ Sign in
39+ </a>
40+ <a href="/trending" class="border border-spot-outline text-spot-text rounded-pill px-7 py-3 text-sm font-bold uppercase tracking-button hover:border-spot-text transition active:scale-95">
41+ See what's trending
42+ </a>
43+ </div>
44+ </div>
45+
46+ <div class="hidden lg:block">
47+ <div class="bg-spot-surface rounded-xl shadow-spot overflow-hidden">
48+ <div class="px-5 py-3 border-b border-spot-divider flex items-center gap-2">
49+ <div class="feed-dot bg-spot-red/60"></div>
50+ <div class="feed-dot bg-spot-orange/60"></div>
51+ <div class="feed-dot bg-spot-green/60"></div>
52+ <span class="text-xs text-spot-muted ml-3">Dashboard</span>
53+ </div>
54+ <div class="landing-feed p-4">
55+ <div class="landing-item rounded-lg px-4 py-3 flex items-start gap-3">
56+ <div class="w-8 h-8 rounded-full bg-spot-green/20 shrink-0 mt-0.5 flex items-center justify-center">
57+ <svg class="w-4 h-4 text-spot-green" fill="currentColor" viewBox="0 0 20 20"><path d="M5 3a1 1 0 000 2c5.523 0 10 4.477 10 10a1 1 0 102 0C17 8.373 11.627 3 5 3z"/><path d="M4 9a1 1 0 011-1 7 7 0 017 7 1 1 0 11-2 0 5 5 0 00-5-5 1 1 0 01-1-1z"/></svg>
58+ </div>
59+ <div class="flex-1 min-w-0">
60+ <div class="feed-line bg-spot-text/80 mb-2" style="width:70%"></div>
61+ <div class="feed-line bg-spot-secondary/40 feed-line-short"></div>
62+ </div>
63+ <div class="flex items-center gap-1 text-spot-green shrink-0">
64+ <svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M3.172 5.172a4 4 0 015.656 0L10 6.343l1.172-1.171a4 4 0 115.656 5.656L10 17.657l-6.828-6.829a4 4 0 010-5.656z"/></svg>
65+ <span class="text-xs">12</span>
66+ </div>
67+ </div>
68+ <div class="landing-item rounded-lg px-4 py-3 flex items-start gap-3">
69+ <div class="w-8 h-8 rounded-full bg-spot-blue/20 shrink-0 mt-0.5 flex items-center justify-center">
70+ <svg class="w-4 h-4 text-spot-blue" fill="currentColor" viewBox="0 0 20 20"><path d="M2 5a2 2 0 012-2h8a2 2 0 012 2v10a2 2 0 002 2H4a2 2 0 01-2-2V5z"/></svg>
71+ </div>
72+ <div class="flex-1 min-w-0">
73+ <div class="feed-line bg-spot-text/80 mb-2" style="width:55%"></div>
74+ <div class="feed-line bg-spot-secondary/40" style="width:80%"></div>
75+ </div>
76+ </div>
77+ <div class="landing-item rounded-lg px-4 py-3 flex items-start gap-3">
78+ <div class="w-8 h-8 rounded-full bg-spot-orange/20 shrink-0 mt-0.5 flex items-center justify-center">
79+ <svg class="w-4 h-4 text-spot-orange" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M12.395 2.553a1 1 0 00-1.45-.385c-.345.23-.614.558-.822.88-.214.33-.403.713-.57 1.116-.334.804-.614 1.768-.84 2.734a31.365 31.365 0 00-.613 3.58 2.64 2.64 0 01-.945-1.067c-.328-.68-.398-1.534-.398-2.654A1 1 0 005.05 6.05 6.981 6.981 0 003 11a7 7 0 1011.95-4.95c-.592-.591-.98-.985-1.348-1.467-.363-.476-.724-1.063-1.207-2.03zM12.12 15.12A3 3 0 017 13s.879.5 2.5.5c0-1 .5-4 1.25-4.5.5 1 .786 1.293 1.371 1.879A2.99 2.99 0 0113 13a2.99 2.99 0 01-.879 2.121z"/></svg>
80+ </div>
81+ <div class="flex-1 min-w-0">
82+ <div class="feed-line bg-spot-text/80 mb-2" style="width:45%"></div>
83+ <div class="feed-line bg-spot-secondary/40 feed-line-medium"></div>
84+ </div>
85+ <div class="flex items-center gap-1 text-spot-secondary shrink-0">
86+ <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z"/></svg>
87+ <span class="text-xs">3</span>
88+ </div>
89+ </div>
90+ <div class="landing-item rounded-lg px-4 py-3 flex items-start gap-3">
91+ <div class="w-8 h-8 rounded-full bg-spot-green/20 shrink-0 mt-0.5 flex items-center justify-center">
92+ <svg class="w-4 h-4 text-spot-green" fill="currentColor" viewBox="0 0 20 20"><path d="M5 3a1 1 0 000 2c5.523 0 10 4.477 10 10a1 1 0 102 0C17 8.373 11.627 3 5 3z"/></svg>
93+ </div>
94+ <div class="flex-1 min-w-0">
95+ <div class="feed-line bg-spot-text/80 mb-2" style="width:60%"></div>
96+ <div class="feed-line bg-spot-secondary/40 feed-line-short"></div>
97+ </div>
98+ </div>
99+ </div>
100+ <div class="px-5 py-3 border-t border-spot-divider flex items-center justify-between">
101+ <div class="flex items-center gap-2 text-xs text-spot-muted">
102+ <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"/></svg>
103+ 4 trending in your network
104+ </div>
105+ <span class="text-xs text-spot-green font-bold">42 unread</span>
106+ </div>
107+ </div>
108+ </div>
109+ </div>
10 </div>110 </div>
11- <div class="mt-24 grid grid-cols-1 sm:grid-cols-3 gap-4 text-left">111+</section>
12- <div class="bg-spot-surface hover:bg-spot-hover rounded-lg p-6 transition shadow-spot">112+
13- <div class="text-spot-purple text-2xl mb-3">113+<section class="max-w-6xl mx-auto px-6 py-16">
14- <svg class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 5c7.18 0 13 5.82 13 13M6 11a7 7 0 017 7m-7-1a1 1 0 11-2 0 1 1 0 012 0z"/></svg>114+ <div class="grid grid-cols-1 md:grid-cols-3 gap-8 md:gap-12">
115+ <div>
116+ <div class="flex items-center gap-2 mb-3">
117+ <svg class="w-5 h-5 text-spot-green" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 5c7.18 0 13 5.82 13 13M6 11a7 7 0 017 7m-7-1a1 1 0 11-2 0 1 1 0 012 0z"/></svg>
118+ <h3 class="font-bold text-spot-text">Subscribe</h3>
15 </div>119 </div>
16- <h3 class="font-bold text-spot-text mb-1">Follow any feed</h3>120+ <p class="text-sm text-spot-secondary leading-relaxed">Add any RSS or Atom feed. Import your existing subscriptions from another reader via OPML. Group feeds by category.</p>
17- <p class="text-sm text-spot-secondary">Subscribe to RSS and Atom feeds. Organize with categories. Import via OPML.</p>
18 </div>121 </div>
19- <div class="bg-spot-surface hover:bg-spot-hover rounded-lg p-6 transition shadow-spot">122+ <div>
20- <div class="text-spot-purple text-2xl mb-3">123+ <div class="flex items-center gap-2 mb-3">
21- <svg class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z"/></svg>124+ <svg class="w-5 h-5 text-spot-green" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
125+ <h3 class="font-bold text-spot-text">Connect</h3>
22 </div>126 </div>
23- <h3 class="font-bold text-spot-text mb-1">Read together</h3>127+ <p class="text-sm text-spot-secondary leading-relaxed">Follow people on the AT Protocol. See what they read and share. Get recommendations from readers with similar tastes.</p>
24- <p class="text-sm text-spot-secondary">See what your network reads. Like and annotate articles. Share discoveries.</p>
25 </div>128 </div>
26- <div class="bg-spot-surface hover:bg-spot-hover rounded-lg p-6 transition shadow-spot">129+ <div>
27- <div class="text-spot-purple text-2xl mb-3">130+ <div class="flex items-center gap-2 mb-3">
28- <svg class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>131+ <svg class="w-5 h-5 text-spot-green" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z"/></svg>
132+ <h3 class="font-bold text-spot-text">Annotate</h3>
29 </div>133 </div>
30- <h3 class="font-bold text-spot-text mb-1">Find new sources</h3>134+ <p class="text-sm text-spot-secondary leading-relaxed">Leave notes on any article. Quote passages that matter. Keep private annotations or share them with your network.</p>
31- <p class="text-sm text-spot-secondary">Get recommendations based on your reading. Find people with similar tastes.</p>
32 </div>135 </div>
33 </div>136 </div>
34-</div>137+</section>
138+
139+<section class="bg-spot-green-house">
140+ <div class="max-w-6xl mx-auto px-6 py-16">
141+ <div class="max-w-2xl">
142+ <h2 class="text-2xl md:text-3xl font-bold text-white mb-4" style="letter-spacing: -0.02em;">Owned by you, not by us.</h2>
143+ <p class="text-[rgba(255,255,255,0.70)] leading-relaxed mb-8">
144+ Glean stores subscriptions, annotations, and your social graph in your personal data repository on the AT Protocol. Bring your own PDS. Take your data anywhere. No lock-in, no ads, no tracking.
145+ </p>
146+ <div class="flex flex-wrap gap-x-8 gap-y-4 text-sm">
147+ <div class="flex items-center gap-2 text-[rgba(255,255,255,0.70)]">
148+ <svg class="w-4 h-4 text-spot-green" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>
149+ AT Protocol identity
150+ </div>
151+ <div class="flex items-center gap-2 text-[rgba(255,255,255,0.70)]">
152+ <svg class="w-4 h-4 text-spot-green" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>
153+ Portable data
154+ </div>
155+ <div class="flex items-center gap-2 text-[rgba(255,255,255,0.70)]">
156+ <svg class="w-4 h-4 text-spot-green" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>
157+ Open source
158+ </div>
159+ <div class="flex items-center gap-2 text-[rgba(255,255,255,0.70)]">
160+ <svg class="w-4 h-4 text-spot-green" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>
161+ Self-hostable
162+ </div>
163+ </div>
164+ </div>
165+ </div>
166+</section>
167+
168+<section class="py-16 text-center">
169+ <div class="max-w-md mx-auto px-6">
170+ <h2 class="text-2xl font-bold text-spot-text mb-3" style="letter-spacing: -0.02em;">Start reading</h2>
171+ <p class="text-spot-secondary mb-6 text-sm leading-relaxed">Sign in with your Bluesky handle or any AT Protocol account. Your feeds are waiting.</p>
172+ <a href="/auth/login" class="bg-spot-green text-white rounded-pill px-8 py-3 text-sm font-bold uppercase tracking-button hover:brightness-110 transition active:scale-95 inline-flex items-center justify-center gap-2">
173+ Get started
174+ </a>
175+ </div>
176+</section>
35 {{end}}177 {{end}}
modified internal/tmpl/login.html +1 -1
@@ -7,7 +7,7 @@
77 <form action="/auth/start" method="POST" id="login-form">
88 {{csrfInput .CSRFToken}}
99 <input type="text" name="handle" placeholder="you.bsky.social"
10- class="w-full bg-spot-hover text-spot-text rounded-pill px-5 py-3 mb-5 text-sm focus:outline-none focus:ring-2 focus:ring-spot-purple placeholder:text-spot-placeholder"
10+ class="w-full bg-spot-hover text-spot-text rounded-pill px-5 py-3 mb-5 text-sm focus:outline-none focus:ring-2 focus:ring-spot-green placeholder:text-spot-placeholder"
1111 required>
1212 </form>
1313
@@ -7,7 +7,7 @@
7 <form action="/auth/start" method="POST" id="login-form">7 <form action="/auth/start" method="POST" id="login-form">
8 {{csrfInput .CSRFToken}}8 {{csrfInput .CSRFToken}}
9 <input type="text" name="handle" placeholder="you.bsky.social"9 <input type="text" name="handle" placeholder="you.bsky.social"
10- class="w-full bg-spot-hover text-spot-text rounded-pill px-5 py-3 mb-5 text-sm focus:outline-none focus:ring-2 focus:ring-spot-purple placeholder:text-spot-placeholder"10+ class="w-full bg-spot-hover text-spot-text rounded-pill px-5 py-3 mb-5 text-sm focus:outline-none focus:ring-2 focus:ring-spot-green placeholder:text-spot-placeholder"
11 required>11 required>
12 </form>12 </form>
13 13
modified internal/tmpl/partials/annotation-card.html +2 -2
@@ -1,7 +1,7 @@
11 {{define "annotation-card.html"}}
22 <div class="bg-spot-surface rounded-lg p-4">
33 {{if .Quote.Valid}}
4- <blockquote class="border-l-2 border-spot-purple pl-3 text-sm text-spot-body italic">{{.Quote.String}}</blockquote>
4+ <blockquote class="border-l-2 border-spot-green pl-3 text-sm text-spot-body italic">{{.Quote.String}}</blockquote>
55 {{end}}
66 {{if .Note.Valid}}
77 <p class="text-sm text-spot-body mt-2">{{.Note.String}}</p>
@@ -15,7 +15,7 @@
1515 <div class="text-sm text-spot-orange mt-1">{{repeat "&#9733;" (int .Rating.Int64)}}</div>
1616 {{end}}
1717 <div class="text-xs text-spot-muted mt-2">
18- <a href="/profile/{{.AuthorDID}}" class="hover:text-spot-purple transition">{{.AuthorHandle}}</a>
18+ <a href="/profile/{{.AuthorDID}}" class="hover:text-spot-green transition">{{.AuthorHandle}}</a>
1919 {{if .CreatedAt.Valid}}<span class="ml-2">{{.CreatedAt.Time.Format "Jan 2, 2006 15:04"}}</span>{{end}}
2020 </div>
2121 </div>
@@ -1,7 +1,7 @@
1 {{define "annotation-card.html"}}1 {{define "annotation-card.html"}}
2 <div class="bg-spot-surface rounded-lg p-4">2 <div class="bg-spot-surface rounded-lg p-4">
3 {{if .Quote.Valid}}3 {{if .Quote.Valid}}
4- <blockquote class="border-l-2 border-spot-purple pl-3 text-sm text-spot-body italic">{{.Quote.String}}</blockquote>4+ <blockquote class="border-l-2 border-spot-green pl-3 text-sm text-spot-body italic">{{.Quote.String}}</blockquote>
5 {{end}}5 {{end}}
6 {{if .Note.Valid}}6 {{if .Note.Valid}}
7 <p class="text-sm text-spot-body mt-2">{{.Note.String}}</p>7 <p class="text-sm text-spot-body mt-2">{{.Note.String}}</p>
@@ -15,7 +15,7 @@
15 <div class="text-sm text-spot-orange mt-1">{{repeat "&#9733;" (int .Rating.Int64)}}</div>15 <div class="text-sm text-spot-orange mt-1">{{repeat "&#9733;" (int .Rating.Int64)}}</div>
16 {{end}}16 {{end}}
17 <div class="text-xs text-spot-muted mt-2">17 <div class="text-xs text-spot-muted mt-2">
18- <a href="/profile/{{.AuthorDID}}" class="hover:text-spot-purple transition">{{.AuthorHandle}}</a>18+ <a href="/profile/{{.AuthorDID}}" class="hover:text-spot-green transition">{{.AuthorHandle}}</a>
19 {{if .CreatedAt.Valid}}<span class="ml-2">{{.CreatedAt.Time.Format "Jan 2, 2006 15:04"}}</span>{{end}}19 {{if .CreatedAt.Valid}}<span class="ml-2">{{.CreatedAt.Time.Format "Jan 2, 2006 15:04"}}</span>{{end}}
20 </div>20 </div>
21 </div>21 </div>
modified internal/tmpl/partials/article-card.html +1 -1
@@ -2,7 +2,7 @@
22 <article data-article-id="{{.ID}}" class="bg-spot-surface rounded-lg px-5 py-4 hover:bg-spot-hover-50 transition shadow-spot">
33 <div class="flex items-start justify-between gap-4">
44 <div class="min-w-0 flex-1">
5- <a href="/articles/{{.ID}}" class="font-bold text-spot-text hover:text-spot-purple transition text-lg leading-tight">{{.Title}}</a>
5+ <a href="/articles/{{.ID}}" class="font-bold text-spot-text hover:text-spot-green transition text-lg leading-tight">{{.Title}}</a>
66 <div class="text-sm text-spot-secondary mt-1 flex items-center gap-2">
77 {{if .Author.Valid}}{{if .Author.String}}<span>{{.Author.String}}</span>{{end}}{{end}}
88 {{if .Published.Valid}}<span>{{.Published.Time.Format "Jan 2, 2006 15:04"}}</span>{{end}}
@@ -2,7 +2,7 @@
2 <article data-article-id="{{.ID}}" class="bg-spot-surface rounded-lg px-5 py-4 hover:bg-spot-hover-50 transition shadow-spot">2 <article data-article-id="{{.ID}}" class="bg-spot-surface rounded-lg px-5 py-4 hover:bg-spot-hover-50 transition shadow-spot">
3 <div class="flex items-start justify-between gap-4">3 <div class="flex items-start justify-between gap-4">
4 <div class="min-w-0 flex-1">4 <div class="min-w-0 flex-1">
5- <a href="/articles/{{.ID}}" class="font-bold text-spot-text hover:text-spot-purple transition text-lg leading-tight">{{.Title}}</a>5+ <a href="/articles/{{.ID}}" class="font-bold text-spot-text hover:text-spot-green transition text-lg leading-tight">{{.Title}}</a>
6 <div class="text-sm text-spot-secondary mt-1 flex items-center gap-2">6 <div class="text-sm text-spot-secondary mt-1 flex items-center gap-2">
7 {{if .Author.Valid}}{{if .Author.String}}<span>{{.Author.String}}</span>{{end}}{{end}}7 {{if .Author.Valid}}{{if .Author.String}}<span>{{.Author.String}}</span>{{end}}{{end}}
8 {{if .Published.Valid}}<span>{{.Published.Time.Format "Jan 2, 2006 15:04"}}</span>{{end}}8 {{if .Published.Valid}}<span>{{.Published.Time.Format "Jan 2, 2006 15:04"}}</span>{{end}}
modified internal/tmpl/partials/article-list.html +1 -1
@@ -3,7 +3,7 @@
33 <article class="bg-spot-surface rounded-lg px-5 py-4 hover:bg-spot-hover-50 transition shadow-spot">
44 <div class="flex items-start justify-between gap-4">
55 <div class="min-w-0 flex-1">
6- <a href="/articles/{{.ID}}" class="font-bold text-spot-text hover:text-spot-purple transition text-lg leading-tight">{{.Title}}</a>
6+ <a href="/articles/{{.ID}}" class="font-bold text-spot-text hover:text-spot-green transition text-lg leading-tight">{{.Title}}</a>
77 <div class="text-sm text-spot-secondary mt-1 flex items-center gap-2">
88 {{if .Author.Valid}}{{if .Author.String}}<span>{{.Author.String}}</span>{{end}}{{end}}
99 {{if .Published.Valid}}<span>{{.Published.Time.Format "Jan 2, 2006 15:04"}}</span>{{end}}
@@ -3,7 +3,7 @@
3 <article class="bg-spot-surface rounded-lg px-5 py-4 hover:bg-spot-hover-50 transition shadow-spot">3 <article class="bg-spot-surface rounded-lg px-5 py-4 hover:bg-spot-hover-50 transition shadow-spot">
4 <div class="flex items-start justify-between gap-4">4 <div class="flex items-start justify-between gap-4">
5 <div class="min-w-0 flex-1">5 <div class="min-w-0 flex-1">
6- <a href="/articles/{{.ID}}" class="font-bold text-spot-text hover:text-spot-purple transition text-lg leading-tight">{{.Title}}</a>6+ <a href="/articles/{{.ID}}" class="font-bold text-spot-text hover:text-spot-green transition text-lg leading-tight">{{.Title}}</a>
7 <div class="text-sm text-spot-secondary mt-1 flex items-center gap-2">7 <div class="text-sm text-spot-secondary mt-1 flex items-center gap-2">
8 {{if .Author.Valid}}{{if .Author.String}}<span>{{.Author.String}}</span>{{end}}{{end}}8 {{if .Author.Valid}}{{if .Author.String}}<span>{{.Author.String}}</span>{{end}}{{end}}
9 {{if .Published.Valid}}<span>{{.Published.Time.Format "Jan 2, 2006 15:04"}}</span>{{end}}9 {{if .Published.Valid}}<span>{{.Published.Time.Format "Jan 2, 2006 15:04"}}</span>{{end}}
modified internal/tmpl/partials/profile-card.html +1 -1
@@ -1,7 +1,7 @@
11 {{define "profile-card.html"}}
22 <div class="bg-spot-surface rounded-lg p-4 flex items-center gap-3 hover:bg-spot-hover-50 transition">
33 <div class="min-w-0 flex-1">
4- <a href="/profile/{{.did}}" class="font-bold text-spot-text hover:text-spot-purple transition">@{{.handle}}</a>
4+ <a href="/profile/{{.did}}" class="font-bold text-spot-text hover:text-spot-green transition">@{{.handle}}</a>
55 {{if .display_name}}<div class="text-sm text-spot-secondary">{{.display_name}}</div>{{end}}
66 </div>
77 <span class="text-xs text-spot-secondary">{{.common_feeds}} shared feeds</span>
@@ -1,7 +1,7 @@
1 {{define "profile-card.html"}}1 {{define "profile-card.html"}}
2 <div class="bg-spot-surface rounded-lg p-4 flex items-center gap-3 hover:bg-spot-hover-50 transition">2 <div class="bg-spot-surface rounded-lg p-4 flex items-center gap-3 hover:bg-spot-hover-50 transition">
3 <div class="min-w-0 flex-1">3 <div class="min-w-0 flex-1">
4- <a href="/profile/{{.did}}" class="font-bold text-spot-text hover:text-spot-purple transition">@{{.handle}}</a>4+ <a href="/profile/{{.did}}" class="font-bold text-spot-text hover:text-spot-green transition">@{{.handle}}</a>
5 {{if .display_name}}<div class="text-sm text-spot-secondary">{{.display_name}}</div>{{end}}5 {{if .display_name}}<div class="text-sm text-spot-secondary">{{.display_name}}</div>{{end}}
6 </div>6 </div>
7 <span class="text-xs text-spot-secondary">{{.common_feeds}} shared feeds</span>7 <span class="text-xs text-spot-secondary">{{.common_feeds}} shared feeds</span>
added package-lock.json +986 -0
new file mode 100644
@@ -0,0 +1,986 @@
1+{
2+ "name": "glean",
3+ "lockfileVersion": 3,
4+ "requires": true,
5+ "packages": {
6+ "": {
7+ "dependencies": {
8+ "tailwindcss": "^3.4.19"
9+ }
10+ },
11+ "node_modules/@alloc/quick-lru": {
12+ "version": "5.2.0",
13+ "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
14+ "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
15+ "license": "MIT",
16+ "engines": {
17+ "node": ">=10"
18+ },
19+ "funding": {
20+ "url": "https://github.com/sponsors/sindresorhus"
21+ }
22+ },
23+ "node_modules/@jridgewell/gen-mapping": {
24+ "version": "0.3.13",
25+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
26+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
27+ "license": "MIT",
28+ "dependencies": {
29+ "@jridgewell/sourcemap-codec": "^1.5.0",
30+ "@jridgewell/trace-mapping": "^0.3.24"
31+ }
32+ },
33+ "node_modules/@jridgewell/resolve-uri": {
34+ "version": "3.1.2",
35+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
36+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
37+ "license": "MIT",
38+ "engines": {
39+ "node": ">=6.0.0"
40+ }
41+ },
42+ "node_modules/@jridgewell/sourcemap-codec": {
43+ "version": "1.5.5",
44+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
45+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
46+ "license": "MIT"
47+ },
48+ "node_modules/@jridgewell/trace-mapping": {
49+ "version": "0.3.31",
50+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
51+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
52+ "license": "MIT",
53+ "dependencies": {
54+ "@jridgewell/resolve-uri": "^3.1.0",
55+ "@jridgewell/sourcemap-codec": "^1.4.14"
56+ }
57+ },
58+ "node_modules/@nodelib/fs.scandir": {
59+ "version": "2.1.5",
60+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
61+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
62+ "license": "MIT",
63+ "dependencies": {
64+ "@nodelib/fs.stat": "2.0.5",
65+ "run-parallel": "^1.1.9"
66+ },
67+ "engines": {
68+ "node": ">= 8"
69+ }
70+ },
71+ "node_modules/@nodelib/fs.stat": {
72+ "version": "2.0.5",
73+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
74+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
75+ "license": "MIT",
76+ "engines": {
77+ "node": ">= 8"
78+ }
79+ },
80+ "node_modules/@nodelib/fs.walk": {
81+ "version": "1.2.8",
82+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
83+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
84+ "license": "MIT",
85+ "dependencies": {
86+ "@nodelib/fs.scandir": "2.1.5",
87+ "fastq": "^1.6.0"
88+ },
89+ "engines": {
90+ "node": ">= 8"
91+ }
92+ },
93+ "node_modules/any-promise": {
94+ "version": "1.3.0",
95+ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
96+ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
97+ "license": "MIT"
98+ },
99+ "node_modules/anymatch": {
100+ "version": "3.1.3",
101+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
102+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
103+ "license": "ISC",
104+ "dependencies": {
105+ "normalize-path": "^3.0.0",
106+ "picomatch": "^2.0.4"
107+ },
108+ "engines": {
109+ "node": ">= 8"
110+ }
111+ },
112+ "node_modules/anymatch/node_modules/picomatch": {
113+ "version": "2.3.2",
114+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
115+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
116+ "license": "MIT",
117+ "engines": {
118+ "node": ">=8.6"
119+ },
120+ "funding": {
121+ "url": "https://github.com/sponsors/jonschlinkert"
122+ }
123+ },
124+ "node_modules/arg": {
125+ "version": "5.0.2",
126+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
127+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
128+ "license": "MIT"
129+ },
130+ "node_modules/binary-extensions": {
131+ "version": "2.3.0",
132+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
133+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
134+ "license": "MIT",
135+ "engines": {
136+ "node": ">=8"
137+ },
138+ "funding": {
139+ "url": "https://github.com/sponsors/sindresorhus"
140+ }
141+ },
142+ "node_modules/braces": {
143+ "version": "3.0.3",
144+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
145+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
146+ "license": "MIT",
147+ "dependencies": {
148+ "fill-range": "^7.1.1"
149+ },
150+ "engines": {
151+ "node": ">=8"
152+ }
153+ },
154+ "node_modules/camelcase-css": {
155+ "version": "2.0.1",
156+ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
157+ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
158+ "license": "MIT",
159+ "engines": {
160+ "node": ">= 6"
161+ }
162+ },
163+ "node_modules/chokidar": {
164+ "version": "3.6.0",
165+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
166+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
167+ "license": "MIT",
168+ "dependencies": {
169+ "anymatch": "~3.1.2",
170+ "braces": "~3.0.2",
171+ "glob-parent": "~5.1.2",
172+ "is-binary-path": "~2.1.0",
173+ "is-glob": "~4.0.1",
174+ "normalize-path": "~3.0.0",
175+ "readdirp": "~3.6.0"
176+ },
177+ "engines": {
178+ "node": ">= 8.10.0"
179+ },
180+ "funding": {
181+ "url": "https://paulmillr.com/funding/"
182+ },
183+ "optionalDependencies": {
184+ "fsevents": "~2.3.2"
185+ }
186+ },
187+ "node_modules/chokidar/node_modules/glob-parent": {
188+ "version": "5.1.2",
189+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
190+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
191+ "license": "ISC",
192+ "dependencies": {
193+ "is-glob": "^4.0.1"
194+ },
195+ "engines": {
196+ "node": ">= 6"
197+ }
198+ },
199+ "node_modules/commander": {
200+ "version": "4.1.1",
201+ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
202+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
203+ "license": "MIT",
204+ "engines": {
205+ "node": ">= 6"
206+ }
207+ },
208+ "node_modules/cssesc": {
209+ "version": "3.0.0",
210+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
211+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
212+ "license": "MIT",
213+ "bin": {
214+ "cssesc": "bin/cssesc"
215+ },
216+ "engines": {
217+ "node": ">=4"
218+ }
219+ },
220+ "node_modules/didyoumean": {
221+ "version": "1.2.2",
222+ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
223+ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
224+ "license": "Apache-2.0"
225+ },
226+ "node_modules/dlv": {
227+ "version": "1.1.3",
228+ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
229+ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
230+ "license": "MIT"
231+ },
232+ "node_modules/es-errors": {
233+ "version": "1.3.0",
234+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
235+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
236+ "license": "MIT",
237+ "engines": {
238+ "node": ">= 0.4"
239+ }
240+ },
241+ "node_modules/fast-glob": {
242+ "version": "3.3.3",
243+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
244+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
245+ "license": "MIT",
246+ "dependencies": {
247+ "@nodelib/fs.stat": "^2.0.2",
248+ "@nodelib/fs.walk": "^1.2.3",
249+ "glob-parent": "^5.1.2",
250+ "merge2": "^1.3.0",
251+ "micromatch": "^4.0.8"
252+ },
253+ "engines": {
254+ "node": ">=8.6.0"
255+ }
256+ },
257+ "node_modules/fast-glob/node_modules/glob-parent": {
258+ "version": "5.1.2",
259+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
260+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
261+ "license": "ISC",
262+ "dependencies": {
263+ "is-glob": "^4.0.1"
264+ },
265+ "engines": {
266+ "node": ">= 6"
267+ }
268+ },
269+ "node_modules/fastq": {
270+ "version": "1.20.1",
271+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
272+ "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
273+ "license": "ISC",
274+ "dependencies": {
275+ "reusify": "^1.0.4"
276+ }
277+ },
278+ "node_modules/fdir": {
279+ "version": "6.5.0",
280+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
281+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
282+ "license": "MIT",
283+ "engines": {
284+ "node": ">=12.0.0"
285+ },
286+ "peerDependencies": {
287+ "picomatch": "^3 || ^4"
288+ },
289+ "peerDependenciesMeta": {
290+ "picomatch": {
291+ "optional": true
292+ }
293+ }
294+ },
295+ "node_modules/fill-range": {
296+ "version": "7.1.1",
297+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
298+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
299+ "license": "MIT",
300+ "dependencies": {
301+ "to-regex-range": "^5.0.1"
302+ },
303+ "engines": {
304+ "node": ">=8"
305+ }
306+ },
307+ "node_modules/fsevents": {
308+ "version": "2.3.3",
309+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
310+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
311+ "hasInstallScript": true,
312+ "license": "MIT",
313+ "optional": true,
314+ "os": [
315+ "darwin"
316+ ],
317+ "engines": {
318+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
319+ }
320+ },
321+ "node_modules/function-bind": {
322+ "version": "1.1.2",
323+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
324+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
325+ "license": "MIT",
326+ "funding": {
327+ "url": "https://github.com/sponsors/ljharb"
328+ }
329+ },
330+ "node_modules/glob-parent": {
331+ "version": "6.0.2",
332+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
333+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
334+ "license": "ISC",
335+ "dependencies": {
336+ "is-glob": "^4.0.3"
337+ },
338+ "engines": {
339+ "node": ">=10.13.0"
340+ }
341+ },
342+ "node_modules/hasown": {
343+ "version": "2.0.3",
344+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
345+ "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
346+ "license": "MIT",
347+ "dependencies": {
348+ "function-bind": "^1.1.2"
349+ },
350+ "engines": {
351+ "node": ">= 0.4"
352+ }
353+ },
354+ "node_modules/is-binary-path": {
355+ "version": "2.1.0",
356+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
357+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
358+ "license": "MIT",
359+ "dependencies": {
360+ "binary-extensions": "^2.0.0"
361+ },
362+ "engines": {
363+ "node": ">=8"
364+ }
365+ },
366+ "node_modules/is-core-module": {
367+ "version": "2.16.1",
368+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
369+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
370+ "license": "MIT",
371+ "dependencies": {
372+ "hasown": "^2.0.2"
373+ },
374+ "engines": {
375+ "node": ">= 0.4"
376+ },
377+ "funding": {
378+ "url": "https://github.com/sponsors/ljharb"
379+ }
380+ },
381+ "node_modules/is-extglob": {
382+ "version": "2.1.1",
383+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
384+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
385+ "license": "MIT",
386+ "engines": {
387+ "node": ">=0.10.0"
388+ }
389+ },
390+ "node_modules/is-glob": {
391+ "version": "4.0.3",
392+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
393+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
394+ "license": "MIT",
395+ "dependencies": {
396+ "is-extglob": "^2.1.1"
397+ },
398+ "engines": {
399+ "node": ">=0.10.0"
400+ }
401+ },
402+ "node_modules/is-number": {
403+ "version": "7.0.0",
404+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
405+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
406+ "license": "MIT",
407+ "engines": {
408+ "node": ">=0.12.0"
409+ }
410+ },
411+ "node_modules/jiti": {
412+ "version": "2.6.1",
413+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
414+ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
415+ "license": "MIT",
416+ "optional": true,
417+ "peer": true,
418+ "bin": {
419+ "jiti": "lib/jiti-cli.mjs"
420+ }
421+ },
422+ "node_modules/lilconfig": {
423+ "version": "3.1.3",
424+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
425+ "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
426+ "license": "MIT",
427+ "engines": {
428+ "node": ">=14"
429+ },
430+ "funding": {
431+ "url": "https://github.com/sponsors/antonk52"
432+ }
433+ },
434+ "node_modules/lines-and-columns": {
435+ "version": "1.2.4",
436+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
437+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
438+ "license": "MIT"
439+ },
440+ "node_modules/merge2": {
441+ "version": "1.4.1",
442+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
443+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
444+ "license": "MIT",
445+ "engines": {
446+ "node": ">= 8"
447+ }
448+ },
449+ "node_modules/micromatch": {
450+ "version": "4.0.8",
451+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
452+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
453+ "license": "MIT",
454+ "dependencies": {
455+ "braces": "^3.0.3",
456+ "picomatch": "^2.3.1"
457+ },
458+ "engines": {
459+ "node": ">=8.6"
460+ }
461+ },
462+ "node_modules/micromatch/node_modules/picomatch": {
463+ "version": "2.3.2",
464+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
465+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
466+ "license": "MIT",
467+ "engines": {
468+ "node": ">=8.6"
469+ },
470+ "funding": {
471+ "url": "https://github.com/sponsors/jonschlinkert"
472+ }
473+ },
474+ "node_modules/mz": {
475+ "version": "2.7.0",
476+ "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
477+ "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
478+ "license": "MIT",
479+ "dependencies": {
480+ "any-promise": "^1.0.0",
481+ "object-assign": "^4.0.1",
482+ "thenify-all": "^1.0.0"
483+ }
484+ },
485+ "node_modules/nanoid": {
486+ "version": "3.3.11",
487+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
488+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
489+ "funding": [
490+ {
491+ "type": "github",
492+ "url": "https://github.com/sponsors/ai"
493+ }
494+ ],
495+ "license": "MIT",
496+ "bin": {
497+ "nanoid": "bin/nanoid.cjs"
498+ },
499+ "engines": {
500+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
501+ }
502+ },
503+ "node_modules/normalize-path": {
504+ "version": "3.0.0",
505+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
506+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
507+ "license": "MIT",
508+ "engines": {
509+ "node": ">=0.10.0"
510+ }
511+ },
512+ "node_modules/object-assign": {
513+ "version": "4.1.1",
514+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
515+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
516+ "license": "MIT",
517+ "engines": {
518+ "node": ">=0.10.0"
519+ }
520+ },
521+ "node_modules/object-hash": {
522+ "version": "3.0.0",
523+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
524+ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
525+ "license": "MIT",
526+ "engines": {
527+ "node": ">= 6"
528+ }
529+ },
530+ "node_modules/path-parse": {
531+ "version": "1.0.7",
532+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
533+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
534+ "license": "MIT"
535+ },
536+ "node_modules/picocolors": {
537+ "version": "1.1.1",
538+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
539+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
540+ "license": "ISC"
541+ },
542+ "node_modules/picomatch": {
543+ "version": "4.0.4",
544+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
545+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
546+ "license": "MIT",
547+ "engines": {
548+ "node": ">=12"
549+ },
550+ "funding": {
551+ "url": "https://github.com/sponsors/jonschlinkert"
552+ }
553+ },
554+ "node_modules/pify": {
555+ "version": "2.3.0",
556+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
557+ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
558+ "license": "MIT",
559+ "engines": {
560+ "node": ">=0.10.0"
561+ }
562+ },
563+ "node_modules/pirates": {
564+ "version": "4.0.7",
565+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
566+ "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
567+ "license": "MIT",
568+ "engines": {
569+ "node": ">= 6"
570+ }
571+ },
572+ "node_modules/postcss": {
573+ "version": "8.5.10",
574+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz",
575+ "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==",
576+ "funding": [
577+ {
578+ "type": "opencollective",
579+ "url": "https://opencollective.com/postcss/"
580+ },
581+ {
582+ "type": "tidelift",
583+ "url": "https://tidelift.com/funding/github/npm/postcss"
584+ },
585+ {
586+ "type": "github",
587+ "url": "https://github.com/sponsors/ai"
588+ }
589+ ],
590+ "license": "MIT",
591+ "dependencies": {
592+ "nanoid": "^3.3.11",
593+ "picocolors": "^1.1.1",
594+ "source-map-js": "^1.2.1"
595+ },
596+ "engines": {
597+ "node": "^10 || ^12 || >=14"
598+ }
599+ },
600+ "node_modules/postcss-import": {
601+ "version": "15.1.0",
602+ "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
603+ "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
604+ "license": "MIT",
605+ "dependencies": {
606+ "postcss-value-parser": "^4.0.0",
607+ "read-cache": "^1.0.0",
608+ "resolve": "^1.1.7"
609+ },
610+ "engines": {
611+ "node": ">=14.0.0"
612+ },
613+ "peerDependencies": {
614+ "postcss": "^8.0.0"
615+ }
616+ },
617+ "node_modules/postcss-js": {
618+ "version": "4.1.0",
619+ "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz",
620+ "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==",
621+ "funding": [
622+ {
623+ "type": "opencollective",
624+ "url": "https://opencollective.com/postcss/"
625+ },
626+ {
627+ "type": "github",
628+ "url": "https://github.com/sponsors/ai"
629+ }
630+ ],
631+ "license": "MIT",
632+ "dependencies": {
633+ "camelcase-css": "^2.0.1"
634+ },
635+ "engines": {
636+ "node": "^12 || ^14 || >= 16"
637+ },
638+ "peerDependencies": {
639+ "postcss": "^8.4.21"
640+ }
641+ },
642+ "node_modules/postcss-load-config": {
643+ "version": "6.0.1",
644+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz",
645+ "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==",
646+ "funding": [
647+ {
648+ "type": "opencollective",
649+ "url": "https://opencollective.com/postcss/"
650+ },
651+ {
652+ "type": "github",
653+ "url": "https://github.com/sponsors/ai"
654+ }
655+ ],
656+ "license": "MIT",
657+ "dependencies": {
658+ "lilconfig": "^3.1.1"
659+ },
660+ "engines": {
661+ "node": ">= 18"
662+ },
663+ "peerDependencies": {
664+ "jiti": ">=1.21.0",
665+ "postcss": ">=8.0.9",
666+ "tsx": "^4.8.1",
667+ "yaml": "^2.4.2"
668+ },
669+ "peerDependenciesMeta": {
670+ "jiti": {
671+ "optional": true
672+ },
673+ "postcss": {
674+ "optional": true
675+ },
676+ "tsx": {
677+ "optional": true
678+ },
679+ "yaml": {
680+ "optional": true
681+ }
682+ }
683+ },
684+ "node_modules/postcss-nested": {
685+ "version": "6.2.0",
686+ "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
687+ "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
688+ "funding": [
689+ {
690+ "type": "opencollective",
691+ "url": "https://opencollective.com/postcss/"
692+ },
693+ {
694+ "type": "github",
695+ "url": "https://github.com/sponsors/ai"
696+ }
697+ ],
698+ "license": "MIT",
699+ "dependencies": {
700+ "postcss-selector-parser": "^6.1.1"
701+ },
702+ "engines": {
703+ "node": ">=12.0"
704+ },
705+ "peerDependencies": {
706+ "postcss": "^8.2.14"
707+ }
708+ },
709+ "node_modules/postcss-selector-parser": {
710+ "version": "6.1.2",
711+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
712+ "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
713+ "license": "MIT",
714+ "dependencies": {
715+ "cssesc": "^3.0.0",
716+ "util-deprecate": "^1.0.2"
717+ },
718+ "engines": {
719+ "node": ">=4"
720+ }
721+ },
722+ "node_modules/postcss-value-parser": {
723+ "version": "4.2.0",
724+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
725+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
726+ "license": "MIT"
727+ },
728+ "node_modules/queue-microtask": {
729+ "version": "1.2.3",
730+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
731+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
732+ "funding": [
733+ {
734+ "type": "github",
735+ "url": "https://github.com/sponsors/feross"
736+ },
737+ {
738+ "type": "patreon",
739+ "url": "https://www.patreon.com/feross"
740+ },
741+ {
742+ "type": "consulting",
743+ "url": "https://feross.org/support"
744+ }
745+ ],
746+ "license": "MIT"
747+ },
748+ "node_modules/read-cache": {
749+ "version": "1.0.0",
750+ "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
751+ "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
752+ "license": "MIT",
753+ "dependencies": {
754+ "pify": "^2.3.0"
755+ }
756+ },
757+ "node_modules/readdirp": {
758+ "version": "3.6.0",
759+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
760+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
761+ "license": "MIT",
762+ "dependencies": {
763+ "picomatch": "^2.2.1"
764+ },
765+ "engines": {
766+ "node": ">=8.10.0"
767+ }
768+ },
769+ "node_modules/readdirp/node_modules/picomatch": {
770+ "version": "2.3.2",
771+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
772+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
773+ "license": "MIT",
774+ "engines": {
775+ "node": ">=8.6"
776+ },
777+ "funding": {
778+ "url": "https://github.com/sponsors/jonschlinkert"
779+ }
780+ },
781+ "node_modules/resolve": {
782+ "version": "1.22.12",
783+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz",
784+ "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==",
785+ "license": "MIT",
786+ "dependencies": {
787+ "es-errors": "^1.3.0",
788+ "is-core-module": "^2.16.1",
789+ "path-parse": "^1.0.7",
790+ "supports-preserve-symlinks-flag": "^1.0.0"
791+ },
792+ "bin": {
793+ "resolve": "bin/resolve"
794+ },
795+ "engines": {
796+ "node": ">= 0.4"
797+ },
798+ "funding": {
799+ "url": "https://github.com/sponsors/ljharb"
800+ }
801+ },
802+ "node_modules/reusify": {
803+ "version": "1.1.0",
804+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
805+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
806+ "license": "MIT",
807+ "engines": {
808+ "iojs": ">=1.0.0",
809+ "node": ">=0.10.0"
810+ }
811+ },
812+ "node_modules/run-parallel": {
813+ "version": "1.2.0",
814+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
815+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
816+ "funding": [
817+ {
818+ "type": "github",
819+ "url": "https://github.com/sponsors/feross"
820+ },
821+ {
822+ "type": "patreon",
823+ "url": "https://www.patreon.com/feross"
824+ },
825+ {
826+ "type": "consulting",
827+ "url": "https://feross.org/support"
828+ }
829+ ],
830+ "license": "MIT",
831+ "dependencies": {
832+ "queue-microtask": "^1.2.2"
833+ }
834+ },
835+ "node_modules/source-map-js": {
836+ "version": "1.2.1",
837+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
838+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
839+ "license": "BSD-3-Clause",
840+ "engines": {
841+ "node": ">=0.10.0"
842+ }
843+ },
844+ "node_modules/sucrase": {
845+ "version": "3.35.1",
846+ "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz",
847+ "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==",
848+ "license": "MIT",
849+ "dependencies": {
850+ "@jridgewell/gen-mapping": "^0.3.2",
851+ "commander": "^4.0.0",
852+ "lines-and-columns": "^1.1.6",
853+ "mz": "^2.7.0",
854+ "pirates": "^4.0.1",
855+ "tinyglobby": "^0.2.11",
856+ "ts-interface-checker": "^0.1.9"
857+ },
858+ "bin": {
859+ "sucrase": "bin/sucrase",
860+ "sucrase-node": "bin/sucrase-node"
861+ },
862+ "engines": {
863+ "node": ">=16 || 14 >=14.17"
864+ }
865+ },
866+ "node_modules/supports-preserve-symlinks-flag": {
867+ "version": "1.0.0",
868+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
869+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
870+ "license": "MIT",
871+ "engines": {
872+ "node": ">= 0.4"
873+ },
874+ "funding": {
875+ "url": "https://github.com/sponsors/ljharb"
876+ }
877+ },
878+ "node_modules/tailwindcss": {
879+ "version": "3.4.19",
880+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
881+ "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==",
882+ "license": "MIT",
883+ "dependencies": {
884+ "@alloc/quick-lru": "^5.2.0",
885+ "arg": "^5.0.2",
886+ "chokidar": "^3.6.0",
887+ "didyoumean": "^1.2.2",
888+ "dlv": "^1.1.3",
889+ "fast-glob": "^3.3.2",
890+ "glob-parent": "^6.0.2",
891+ "is-glob": "^4.0.3",
892+ "jiti": "^1.21.7",
893+ "lilconfig": "^3.1.3",
894+ "micromatch": "^4.0.8",
895+ "normalize-path": "^3.0.0",
896+ "object-hash": "^3.0.0",
897+ "picocolors": "^1.1.1",
898+ "postcss": "^8.4.47",
899+ "postcss-import": "^15.1.0",
900+ "postcss-js": "^4.0.1",
901+ "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0",
902+ "postcss-nested": "^6.2.0",
903+ "postcss-selector-parser": "^6.1.2",
904+ "resolve": "^1.22.8",
905+ "sucrase": "^3.35.0"
906+ },
907+ "bin": {
908+ "tailwind": "lib/cli.js",
909+ "tailwindcss": "lib/cli.js"
910+ },
911+ "engines": {
912+ "node": ">=14.0.0"
913+ }
914+ },
915+ "node_modules/tailwindcss/node_modules/jiti": {
916+ "version": "1.21.7",
917+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
918+ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
919+ "license": "MIT",
920+ "bin": {
921+ "jiti": "bin/jiti.js"
922+ }
923+ },
924+ "node_modules/thenify": {
925+ "version": "3.3.1",
926+ "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
927+ "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
928+ "license": "MIT",
929+ "dependencies": {
930+ "any-promise": "^1.0.0"
931+ }
932+ },
933+ "node_modules/thenify-all": {
934+ "version": "1.6.0",
935+ "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
936+ "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
937+ "license": "MIT",
938+ "dependencies": {
939+ "thenify": ">= 3.1.0 < 4"
940+ },
941+ "engines": {
942+ "node": ">=0.8"
943+ }
944+ },
945+ "node_modules/tinyglobby": {
946+ "version": "0.2.16",
947+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
948+ "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
949+ "license": "MIT",
950+ "dependencies": {
951+ "fdir": "^6.5.0",
952+ "picomatch": "^4.0.4"
953+ },
954+ "engines": {
955+ "node": ">=12.0.0"
956+ },
957+ "funding": {
958+ "url": "https://github.com/sponsors/SuperchupuDev"
959+ }
960+ },
961+ "node_modules/to-regex-range": {
962+ "version": "5.0.1",
963+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
964+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
965+ "license": "MIT",
966+ "dependencies": {
967+ "is-number": "^7.0.0"
968+ },
969+ "engines": {
970+ "node": ">=8.0"
971+ }
972+ },
973+ "node_modules/ts-interface-checker": {
974+ "version": "0.1.13",
975+ "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
976+ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
977+ "license": "Apache-2.0"
978+ },
979+ "node_modules/util-deprecate": {
980+ "version": "1.0.2",
981+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
982+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
983+ "license": "MIT"
984+ }
985+ }
986+}
new file mode 100644
@@ -0,0 +1,986 @@
1+{
2+ "name": "glean",
3+ "lockfileVersion": 3,
4+ "requires": true,
5+ "packages": {
6+ "": {
7+ "dependencies": {
8+ "tailwindcss": "^3.4.19"
9+ }
10+ },
11+ "node_modules/@alloc/quick-lru": {
12+ "version": "5.2.0",
13+ "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
14+ "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
15+ "license": "MIT",
16+ "engines": {
17+ "node": ">=10"
18+ },
19+ "funding": {
20+ "url": "https://github.com/sponsors/sindresorhus"
21+ }
22+ },
23+ "node_modules/@jridgewell/gen-mapping": {
24+ "version": "0.3.13",
25+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
26+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
27+ "license": "MIT",
28+ "dependencies": {
29+ "@jridgewell/sourcemap-codec": "^1.5.0",
30+ "@jridgewell/trace-mapping": "^0.3.24"
31+ }
32+ },
33+ "node_modules/@jridgewell/resolve-uri": {
34+ "version": "3.1.2",
35+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
36+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
37+ "license": "MIT",
38+ "engines": {
39+ "node": ">=6.0.0"
40+ }
41+ },
42+ "node_modules/@jridgewell/sourcemap-codec": {
43+ "version": "1.5.5",
44+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
45+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
46+ "license": "MIT"
47+ },
48+ "node_modules/@jridgewell/trace-mapping": {
49+ "version": "0.3.31",
50+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
51+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
52+ "license": "MIT",
53+ "dependencies": {
54+ "@jridgewell/resolve-uri": "^3.1.0",
55+ "@jridgewell/sourcemap-codec": "^1.4.14"
56+ }
57+ },
58+ "node_modules/@nodelib/fs.scandir": {
59+ "version": "2.1.5",
60+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
61+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
62+ "license": "MIT",
63+ "dependencies": {
64+ "@nodelib/fs.stat": "2.0.5",
65+ "run-parallel": "^1.1.9"
66+ },
67+ "engines": {
68+ "node": ">= 8"
69+ }
70+ },
71+ "node_modules/@nodelib/fs.stat": {
72+ "version": "2.0.5",
73+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
74+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
75+ "license": "MIT",
76+ "engines": {
77+ "node": ">= 8"
78+ }
79+ },
80+ "node_modules/@nodelib/fs.walk": {
81+ "version": "1.2.8",
82+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
83+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
84+ "license": "MIT",
85+ "dependencies": {
86+ "@nodelib/fs.scandir": "2.1.5",
87+ "fastq": "^1.6.0"
88+ },
89+ "engines": {
90+ "node": ">= 8"
91+ }
92+ },
93+ "node_modules/any-promise": {
94+ "version": "1.3.0",
95+ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
96+ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
97+ "license": "MIT"
98+ },
99+ "node_modules/anymatch": {
100+ "version": "3.1.3",
101+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
102+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
103+ "license": "ISC",
104+ "dependencies": {
105+ "normalize-path": "^3.0.0",
106+ "picomatch": "^2.0.4"
107+ },
108+ "engines": {
109+ "node": ">= 8"
110+ }
111+ },
112+ "node_modules/anymatch/node_modules/picomatch": {
113+ "version": "2.3.2",
114+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
115+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
116+ "license": "MIT",
117+ "engines": {
118+ "node": ">=8.6"
119+ },
120+ "funding": {
121+ "url": "https://github.com/sponsors/jonschlinkert"
122+ }
123+ },
124+ "node_modules/arg": {
125+ "version": "5.0.2",
126+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
127+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
128+ "license": "MIT"
129+ },
130+ "node_modules/binary-extensions": {
131+ "version": "2.3.0",
132+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
133+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
134+ "license": "MIT",
135+ "engines": {
136+ "node": ">=8"
137+ },
138+ "funding": {
139+ "url": "https://github.com/sponsors/sindresorhus"
140+ }
141+ },
142+ "node_modules/braces": {
143+ "version": "3.0.3",
144+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
145+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
146+ "license": "MIT",
147+ "dependencies": {
148+ "fill-range": "^7.1.1"
149+ },
150+ "engines": {
151+ "node": ">=8"
152+ }
153+ },
154+ "node_modules/camelcase-css": {
155+ "version": "2.0.1",
156+ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
157+ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
158+ "license": "MIT",
159+ "engines": {
160+ "node": ">= 6"
161+ }
162+ },
163+ "node_modules/chokidar": {
164+ "version": "3.6.0",
165+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
166+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
167+ "license": "MIT",
168+ "dependencies": {
169+ "anymatch": "~3.1.2",
170+ "braces": "~3.0.2",
171+ "glob-parent": "~5.1.2",
172+ "is-binary-path": "~2.1.0",
173+ "is-glob": "~4.0.1",
174+ "normalize-path": "~3.0.0",
175+ "readdirp": "~3.6.0"
176+ },
177+ "engines": {
178+ "node": ">= 8.10.0"
179+ },
180+ "funding": {
181+ "url": "https://paulmillr.com/funding/"
182+ },
183+ "optionalDependencies": {
184+ "fsevents": "~2.3.2"
185+ }
186+ },
187+ "node_modules/chokidar/node_modules/glob-parent": {
188+ "version": "5.1.2",
189+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
190+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
191+ "license": "ISC",
192+ "dependencies": {
193+ "is-glob": "^4.0.1"
194+ },
195+ "engines": {
196+ "node": ">= 6"
197+ }
198+ },
199+ "node_modules/commander": {
200+ "version": "4.1.1",
201+ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
202+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
203+ "license": "MIT",
204+ "engines": {
205+ "node": ">= 6"
206+ }
207+ },
208+ "node_modules/cssesc": {
209+ "version": "3.0.0",
210+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
211+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
212+ "license": "MIT",
213+ "bin": {
214+ "cssesc": "bin/cssesc"
215+ },
216+ "engines": {
217+ "node": ">=4"
218+ }
219+ },
220+ "node_modules/didyoumean": {
221+ "version": "1.2.2",
222+ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
223+ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
224+ "license": "Apache-2.0"
225+ },
226+ "node_modules/dlv": {
227+ "version": "1.1.3",
228+ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
229+ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
230+ "license": "MIT"
231+ },
232+ "node_modules/es-errors": {
233+ "version": "1.3.0",
234+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
235+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
236+ "license": "MIT",
237+ "engines": {
238+ "node": ">= 0.4"
239+ }
240+ },
241+ "node_modules/fast-glob": {
242+ "version": "3.3.3",
243+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
244+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
245+ "license": "MIT",
246+ "dependencies": {
247+ "@nodelib/fs.stat": "^2.0.2",
248+ "@nodelib/fs.walk": "^1.2.3",
249+ "glob-parent": "^5.1.2",
250+ "merge2": "^1.3.0",
251+ "micromatch": "^4.0.8"
252+ },
253+ "engines": {
254+ "node": ">=8.6.0"
255+ }
256+ },
257+ "node_modules/fast-glob/node_modules/glob-parent": {
258+ "version": "5.1.2",
259+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
260+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
261+ "license": "ISC",
262+ "dependencies": {
263+ "is-glob": "^4.0.1"
264+ },
265+ "engines": {
266+ "node": ">= 6"
267+ }
268+ },
269+ "node_modules/fastq": {
270+ "version": "1.20.1",
271+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
272+ "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
273+ "license": "ISC",
274+ "dependencies": {
275+ "reusify": "^1.0.4"
276+ }
277+ },
278+ "node_modules/fdir": {
279+ "version": "6.5.0",
280+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
281+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
282+ "license": "MIT",
283+ "engines": {
284+ "node": ">=12.0.0"
285+ },
286+ "peerDependencies": {
287+ "picomatch": "^3 || ^4"
288+ },
289+ "peerDependenciesMeta": {
290+ "picomatch": {
291+ "optional": true
292+ }
293+ }
294+ },
295+ "node_modules/fill-range": {
296+ "version": "7.1.1",
297+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
298+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
299+ "license": "MIT",
300+ "dependencies": {
301+ "to-regex-range": "^5.0.1"
302+ },
303+ "engines": {
304+ "node": ">=8"
305+ }
306+ },
307+ "node_modules/fsevents": {
308+ "version": "2.3.3",
309+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
310+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
311+ "hasInstallScript": true,
312+ "license": "MIT",
313+ "optional": true,
314+ "os": [
315+ "darwin"
316+ ],
317+ "engines": {
318+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
319+ }
320+ },
321+ "node_modules/function-bind": {
322+ "version": "1.1.2",
323+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
324+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
325+ "license": "MIT",
326+ "funding": {
327+ "url": "https://github.com/sponsors/ljharb"
328+ }
329+ },
330+ "node_modules/glob-parent": {
331+ "version": "6.0.2",
332+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
333+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
334+ "license": "ISC",
335+ "dependencies": {
336+ "is-glob": "^4.0.3"
337+ },
338+ "engines": {
339+ "node": ">=10.13.0"
340+ }
341+ },
342+ "node_modules/hasown": {
343+ "version": "2.0.3",
344+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
345+ "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
346+ "license": "MIT",
347+ "dependencies": {
348+ "function-bind": "^1.1.2"
349+ },
350+ "engines": {
351+ "node": ">= 0.4"
352+ }
353+ },
354+ "node_modules/is-binary-path": {
355+ "version": "2.1.0",
356+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
357+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
358+ "license": "MIT",
359+ "dependencies": {
360+ "binary-extensions": "^2.0.0"
361+ },
362+ "engines": {
363+ "node": ">=8"
364+ }
365+ },
366+ "node_modules/is-core-module": {
367+ "version": "2.16.1",
368+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
369+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
370+ "license": "MIT",
371+ "dependencies": {
372+ "hasown": "^2.0.2"
373+ },
374+ "engines": {
375+ "node": ">= 0.4"
376+ },
377+ "funding": {
378+ "url": "https://github.com/sponsors/ljharb"
379+ }
380+ },
381+ "node_modules/is-extglob": {
382+ "version": "2.1.1",
383+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
384+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
385+ "license": "MIT",
386+ "engines": {
387+ "node": ">=0.10.0"
388+ }
389+ },
390+ "node_modules/is-glob": {
391+ "version": "4.0.3",
392+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
393+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
394+ "license": "MIT",
395+ "dependencies": {
396+ "is-extglob": "^2.1.1"
397+ },
398+ "engines": {
399+ "node": ">=0.10.0"
400+ }
401+ },
402+ "node_modules/is-number": {
403+ "version": "7.0.0",
404+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
405+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
406+ "license": "MIT",
407+ "engines": {
408+ "node": ">=0.12.0"
409+ }
410+ },
411+ "node_modules/jiti": {
412+ "version": "2.6.1",
413+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
414+ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
415+ "license": "MIT",
416+ "optional": true,
417+ "peer": true,
418+ "bin": {
419+ "jiti": "lib/jiti-cli.mjs"
420+ }
421+ },
422+ "node_modules/lilconfig": {
423+ "version": "3.1.3",
424+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
425+ "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
426+ "license": "MIT",
427+ "engines": {
428+ "node": ">=14"
429+ },
430+ "funding": {
431+ "url": "https://github.com/sponsors/antonk52"
432+ }
433+ },
434+ "node_modules/lines-and-columns": {
435+ "version": "1.2.4",
436+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
437+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
438+ "license": "MIT"
439+ },
440+ "node_modules/merge2": {
441+ "version": "1.4.1",
442+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
443+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
444+ "license": "MIT",
445+ "engines": {
446+ "node": ">= 8"
447+ }
448+ },
449+ "node_modules/micromatch": {
450+ "version": "4.0.8",
451+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
452+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
453+ "license": "MIT",
454+ "dependencies": {
455+ "braces": "^3.0.3",
456+ "picomatch": "^2.3.1"
457+ },
458+ "engines": {
459+ "node": ">=8.6"
460+ }
461+ },
462+ "node_modules/micromatch/node_modules/picomatch": {
463+ "version": "2.3.2",
464+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
465+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
466+ "license": "MIT",
467+ "engines": {
468+ "node": ">=8.6"
469+ },
470+ "funding": {
471+ "url": "https://github.com/sponsors/jonschlinkert"
472+ }
473+ },
474+ "node_modules/mz": {
475+ "version": "2.7.0",
476+ "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
477+ "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
478+ "license": "MIT",
479+ "dependencies": {
480+ "any-promise": "^1.0.0",
481+ "object-assign": "^4.0.1",
482+ "thenify-all": "^1.0.0"
483+ }
484+ },
485+ "node_modules/nanoid": {
486+ "version": "3.3.11",
487+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
488+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
489+ "funding": [
490+ {
491+ "type": "github",
492+ "url": "https://github.com/sponsors/ai"
493+ }
494+ ],
495+ "license": "MIT",
496+ "bin": {
497+ "nanoid": "bin/nanoid.cjs"
498+ },
499+ "engines": {
500+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
501+ }
502+ },
503+ "node_modules/normalize-path": {
504+ "version": "3.0.0",
505+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
506+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
507+ "license": "MIT",
508+ "engines": {
509+ "node": ">=0.10.0"
510+ }
511+ },
512+ "node_modules/object-assign": {
513+ "version": "4.1.1",
514+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
515+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
516+ "license": "MIT",
517+ "engines": {
518+ "node": ">=0.10.0"
519+ }
520+ },
521+ "node_modules/object-hash": {
522+ "version": "3.0.0",
523+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
524+ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
525+ "license": "MIT",
526+ "engines": {
527+ "node": ">= 6"
528+ }
529+ },
530+ "node_modules/path-parse": {
531+ "version": "1.0.7",
532+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
533+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
534+ "license": "MIT"
535+ },
536+ "node_modules/picocolors": {
537+ "version": "1.1.1",
538+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
539+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
540+ "license": "ISC"
541+ },
542+ "node_modules/picomatch": {
543+ "version": "4.0.4",
544+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
545+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
546+ "license": "MIT",
547+ "engines": {
548+ "node": ">=12"
549+ },
550+ "funding": {
551+ "url": "https://github.com/sponsors/jonschlinkert"
552+ }
553+ },
554+ "node_modules/pify": {
555+ "version": "2.3.0",
556+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
557+ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
558+ "license": "MIT",
559+ "engines": {
560+ "node": ">=0.10.0"
561+ }
562+ },
563+ "node_modules/pirates": {
564+ "version": "4.0.7",
565+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
566+ "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
567+ "license": "MIT",
568+ "engines": {
569+ "node": ">= 6"
570+ }
571+ },
572+ "node_modules/postcss": {
573+ "version": "8.5.10",
574+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz",
575+ "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==",
576+ "funding": [
577+ {
578+ "type": "opencollective",
579+ "url": "https://opencollective.com/postcss/"
580+ },
581+ {
582+ "type": "tidelift",
583+ "url": "https://tidelift.com/funding/github/npm/postcss"
584+ },
585+ {
586+ "type": "github",
587+ "url": "https://github.com/sponsors/ai"
588+ }
589+ ],
590+ "license": "MIT",
591+ "dependencies": {
592+ "nanoid": "^3.3.11",
593+ "picocolors": "^1.1.1",
594+ "source-map-js": "^1.2.1"
595+ },
596+ "engines": {
597+ "node": "^10 || ^12 || >=14"
598+ }
599+ },
600+ "node_modules/postcss-import": {
601+ "version": "15.1.0",
602+ "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
603+ "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
604+ "license": "MIT",
605+ "dependencies": {
606+ "postcss-value-parser": "^4.0.0",
607+ "read-cache": "^1.0.0",
608+ "resolve": "^1.1.7"
609+ },
610+ "engines": {
611+ "node": ">=14.0.0"
612+ },
613+ "peerDependencies": {
614+ "postcss": "^8.0.0"
615+ }
616+ },
617+ "node_modules/postcss-js": {
618+ "version": "4.1.0",
619+ "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz",
620+ "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==",
621+ "funding": [
622+ {
623+ "type": "opencollective",
624+ "url": "https://opencollective.com/postcss/"
625+ },
626+ {
627+ "type": "github",
628+ "url": "https://github.com/sponsors/ai"
629+ }
630+ ],
631+ "license": "MIT",
632+ "dependencies": {
633+ "camelcase-css": "^2.0.1"
634+ },
635+ "engines": {
636+ "node": "^12 || ^14 || >= 16"
637+ },
638+ "peerDependencies": {
639+ "postcss": "^8.4.21"
640+ }
641+ },
642+ "node_modules/postcss-load-config": {
643+ "version": "6.0.1",
644+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz",
645+ "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==",
646+ "funding": [
647+ {
648+ "type": "opencollective",
649+ "url": "https://opencollective.com/postcss/"
650+ },
651+ {
652+ "type": "github",
653+ "url": "https://github.com/sponsors/ai"
654+ }
655+ ],
656+ "license": "MIT",
657+ "dependencies": {
658+ "lilconfig": "^3.1.1"
659+ },
660+ "engines": {
661+ "node": ">= 18"
662+ },
663+ "peerDependencies": {
664+ "jiti": ">=1.21.0",
665+ "postcss": ">=8.0.9",
666+ "tsx": "^4.8.1",
667+ "yaml": "^2.4.2"
668+ },
669+ "peerDependenciesMeta": {
670+ "jiti": {
671+ "optional": true
672+ },
673+ "postcss": {
674+ "optional": true
675+ },
676+ "tsx": {
677+ "optional": true
678+ },
679+ "yaml": {
680+ "optional": true
681+ }
682+ }
683+ },
684+ "node_modules/postcss-nested": {
685+ "version": "6.2.0",
686+ "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
687+ "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
688+ "funding": [
689+ {
690+ "type": "opencollective",
691+ "url": "https://opencollective.com/postcss/"
692+ },
693+ {
694+ "type": "github",
695+ "url": "https://github.com/sponsors/ai"
696+ }
697+ ],
698+ "license": "MIT",
699+ "dependencies": {
700+ "postcss-selector-parser": "^6.1.1"
701+ },
702+ "engines": {
703+ "node": ">=12.0"
704+ },
705+ "peerDependencies": {
706+ "postcss": "^8.2.14"
707+ }
708+ },
709+ "node_modules/postcss-selector-parser": {
710+ "version": "6.1.2",
711+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
712+ "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
713+ "license": "MIT",
714+ "dependencies": {
715+ "cssesc": "^3.0.0",
716+ "util-deprecate": "^1.0.2"
717+ },
718+ "engines": {
719+ "node": ">=4"
720+ }
721+ },
722+ "node_modules/postcss-value-parser": {
723+ "version": "4.2.0",
724+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
725+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
726+ "license": "MIT"
727+ },
728+ "node_modules/queue-microtask": {
729+ "version": "1.2.3",
730+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
731+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
732+ "funding": [
733+ {
734+ "type": "github",
735+ "url": "https://github.com/sponsors/feross"
736+ },
737+ {
738+ "type": "patreon",
739+ "url": "https://www.patreon.com/feross"
740+ },
741+ {
742+ "type": "consulting",
743+ "url": "https://feross.org/support"
744+ }
745+ ],
746+ "license": "MIT"
747+ },
748+ "node_modules/read-cache": {
749+ "version": "1.0.0",
750+ "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
751+ "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
752+ "license": "MIT",
753+ "dependencies": {
754+ "pify": "^2.3.0"
755+ }
756+ },
757+ "node_modules/readdirp": {
758+ "version": "3.6.0",
759+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
760+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
761+ "license": "MIT",
762+ "dependencies": {
763+ "picomatch": "^2.2.1"
764+ },
765+ "engines": {
766+ "node": ">=8.10.0"
767+ }
768+ },
769+ "node_modules/readdirp/node_modules/picomatch": {
770+ "version": "2.3.2",
771+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
772+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
773+ "license": "MIT",
774+ "engines": {
775+ "node": ">=8.6"
776+ },
777+ "funding": {
778+ "url": "https://github.com/sponsors/jonschlinkert"
779+ }
780+ },
781+ "node_modules/resolve": {
782+ "version": "1.22.12",
783+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz",
784+ "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==",
785+ "license": "MIT",
786+ "dependencies": {
787+ "es-errors": "^1.3.0",
788+ "is-core-module": "^2.16.1",
789+ "path-parse": "^1.0.7",
790+ "supports-preserve-symlinks-flag": "^1.0.0"
791+ },
792+ "bin": {
793+ "resolve": "bin/resolve"
794+ },
795+ "engines": {
796+ "node": ">= 0.4"
797+ },
798+ "funding": {
799+ "url": "https://github.com/sponsors/ljharb"
800+ }
801+ },
802+ "node_modules/reusify": {
803+ "version": "1.1.0",
804+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
805+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
806+ "license": "MIT",
807+ "engines": {
808+ "iojs": ">=1.0.0",
809+ "node": ">=0.10.0"
810+ }
811+ },
812+ "node_modules/run-parallel": {
813+ "version": "1.2.0",
814+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
815+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
816+ "funding": [
817+ {
818+ "type": "github",
819+ "url": "https://github.com/sponsors/feross"
820+ },
821+ {
822+ "type": "patreon",
823+ "url": "https://www.patreon.com/feross"
824+ },
825+ {
826+ "type": "consulting",
827+ "url": "https://feross.org/support"
828+ }
829+ ],
830+ "license": "MIT",
831+ "dependencies": {
832+ "queue-microtask": "^1.2.2"
833+ }
834+ },
835+ "node_modules/source-map-js": {
836+ "version": "1.2.1",
837+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
838+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
839+ "license": "BSD-3-Clause",
840+ "engines": {
841+ "node": ">=0.10.0"
842+ }
843+ },
844+ "node_modules/sucrase": {
845+ "version": "3.35.1",
846+ "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz",
847+ "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==",
848+ "license": "MIT",
849+ "dependencies": {
850+ "@jridgewell/gen-mapping": "^0.3.2",
851+ "commander": "^4.0.0",
852+ "lines-and-columns": "^1.1.6",
853+ "mz": "^2.7.0",
854+ "pirates": "^4.0.1",
855+ "tinyglobby": "^0.2.11",
856+ "ts-interface-checker": "^0.1.9"
857+ },
858+ "bin": {
859+ "sucrase": "bin/sucrase",
860+ "sucrase-node": "bin/sucrase-node"
861+ },
862+ "engines": {
863+ "node": ">=16 || 14 >=14.17"
864+ }
865+ },
866+ "node_modules/supports-preserve-symlinks-flag": {
867+ "version": "1.0.0",
868+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
869+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
870+ "license": "MIT",
871+ "engines": {
872+ "node": ">= 0.4"
873+ },
874+ "funding": {
875+ "url": "https://github.com/sponsors/ljharb"
876+ }
877+ },
878+ "node_modules/tailwindcss": {
879+ "version": "3.4.19",
880+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
881+ "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==",
882+ "license": "MIT",
883+ "dependencies": {
884+ "@alloc/quick-lru": "^5.2.0",
885+ "arg": "^5.0.2",
886+ "chokidar": "^3.6.0",
887+ "didyoumean": "^1.2.2",
888+ "dlv": "^1.1.3",
889+ "fast-glob": "^3.3.2",
890+ "glob-parent": "^6.0.2",
891+ "is-glob": "^4.0.3",
892+ "jiti": "^1.21.7",
893+ "lilconfig": "^3.1.3",
894+ "micromatch": "^4.0.8",
895+ "normalize-path": "^3.0.0",
896+ "object-hash": "^3.0.0",
897+ "picocolors": "^1.1.1",
898+ "postcss": "^8.4.47",
899+ "postcss-import": "^15.1.0",
900+ "postcss-js": "^4.0.1",
901+ "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0",
902+ "postcss-nested": "^6.2.0",
903+ "postcss-selector-parser": "^6.1.2",
904+ "resolve": "^1.22.8",
905+ "sucrase": "^3.35.0"
906+ },
907+ "bin": {
908+ "tailwind": "lib/cli.js",
909+ "tailwindcss": "lib/cli.js"
910+ },
911+ "engines": {
912+ "node": ">=14.0.0"
913+ }
914+ },
915+ "node_modules/tailwindcss/node_modules/jiti": {
916+ "version": "1.21.7",
917+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
918+ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
919+ "license": "MIT",
920+ "bin": {
921+ "jiti": "bin/jiti.js"
922+ }
923+ },
924+ "node_modules/thenify": {
925+ "version": "3.3.1",
926+ "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
927+ "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
928+ "license": "MIT",
929+ "dependencies": {
930+ "any-promise": "^1.0.0"
931+ }
932+ },
933+ "node_modules/thenify-all": {
934+ "version": "1.6.0",
935+ "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
936+ "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
937+ "license": "MIT",
938+ "dependencies": {
939+ "thenify": ">= 3.1.0 < 4"
940+ },
941+ "engines": {
942+ "node": ">=0.8"
943+ }
944+ },
945+ "node_modules/tinyglobby": {
946+ "version": "0.2.16",
947+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
948+ "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
949+ "license": "MIT",
950+ "dependencies": {
951+ "fdir": "^6.5.0",
952+ "picomatch": "^4.0.4"
953+ },
954+ "engines": {
955+ "node": ">=12.0.0"
956+ },
957+ "funding": {
958+ "url": "https://github.com/sponsors/SuperchupuDev"
959+ }
960+ },
961+ "node_modules/to-regex-range": {
962+ "version": "5.0.1",
963+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
964+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
965+ "license": "MIT",
966+ "dependencies": {
967+ "is-number": "^7.0.0"
968+ },
969+ "engines": {
970+ "node": ">=8.0"
971+ }
972+ },
973+ "node_modules/ts-interface-checker": {
974+ "version": "0.1.13",
975+ "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
976+ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
977+ "license": "Apache-2.0"
978+ },
979+ "node_modules/util-deprecate": {
980+ "version": "1.0.2",
981+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
982+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
983+ "license": "MIT"
984+ }
985+ }
986+}
added package.json +5 -0
new file mode 100644
@@ -0,0 +1,5 @@
1+{
2+ "dependencies": {
3+ "tailwindcss": "^3.4.19"
4+ }
5+}
new file mode 100644
@@ -0,0 +1,5 @@
1+{
2+ "dependencies": {
3+ "tailwindcss": "^3.4.19"
4+ }
5+}
added static/favicon.svg +11 -0
new file mode 100644
@@ -0,0 +1,11 @@
1+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
2+ <rect width="32" height="32" rx="8" fill="#1E3932"/>
3+ <path d="M16 6 L16 22" stroke="#00754A" stroke-width="2.5" stroke-linecap="round"/>
4+ <path d="M16 10 Q10 8 8 12" stroke="#00754A" stroke-width="2" stroke-linecap="round" fill="none"/>
5+ <path d="M16 10 Q22 8 24 12" stroke="#00754A" stroke-width="2" stroke-linecap="round" fill="none"/>
6+ <path d="M16 14 Q9 12 7 17" stroke="#00754A" stroke-width="2" stroke-linecap="round" fill="none"/>
7+ <path d="M16 14 Q23 12 25 17" stroke="#00754A" stroke-width="2" stroke-linecap="round" fill="none"/>
8+ <path d="M16 18 Q11 16 9 21" stroke="#00754A" stroke-width="2" stroke-linecap="round" fill="none"/>
9+ <path d="M16 18 Q21 16 23 21" stroke="#00754A" stroke-width="2" stroke-linecap="round" fill="none"/>
10+ <circle cx="16" cy="25" r="2" fill="#00754A"/>
11+</svg>
new file mode 100644
@@ -0,0 +1,11 @@
1+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
2+ <rect width="32" height="32" rx="8" fill="#1E3932"/>
3+ <path d="M16 6 L16 22" stroke="#00754A" stroke-width="2.5" stroke-linecap="round"/>
4+ <path d="M16 10 Q10 8 8 12" stroke="#00754A" stroke-width="2" stroke-linecap="round" fill="none"/>
5+ <path d="M16 10 Q22 8 24 12" stroke="#00754A" stroke-width="2" stroke-linecap="round" fill="none"/>
6+ <path d="M16 14 Q9 12 7 17" stroke="#00754A" stroke-width="2" stroke-linecap="round" fill="none"/>
7+ <path d="M16 14 Q23 12 25 17" stroke="#00754A" stroke-width="2" stroke-linecap="round" fill="none"/>
8+ <path d="M16 18 Q11 16 9 21" stroke="#00754A" stroke-width="2" stroke-linecap="round" fill="none"/>
9+ <path d="M16 18 Q21 16 23 21" stroke="#00754A" stroke-width="2" stroke-linecap="round" fill="none"/>
10+ <circle cx="16" cy="25" r="2" fill="#00754A"/>
11+</svg>
modified static/input.css +87 -1
@@ -2,9 +2,95 @@
22 @tailwind components;
33 @tailwind utilities;
44
5-.line-clamp-2 {
5+@layer base {
6+ :root {
7+ --spot-bg: #0f1f1a;
8+ --spot-surface: #152b24;
9+ --spot-hover: #1a362e;
10+ --spot-hover-50: rgba(26,54,46,0.5);
11+ --spot-text: #ffffff;
12+ --spot-secondary: rgba(255,255,255,0.70);
13+ --spot-body: rgba(255,255,255,0.87);
14+ --spot-muted: rgba(255,255,255,0.25);
15+ --spot-divider: rgba(255,255,255,0.08);
16+ --spot-divider-30: rgba(255,255,255,0.12);
17+ --spot-outline: rgba(255,255,255,0.20);
18+ --spot-placeholder: rgba(255,255,255,0.30);
19+ --spot-active-bg: #ffffff;
20+ --spot-active-text: #1E3932;
21+ --spot-shadow: 0 0 0.5px rgba(0,0,0,0.14), 0 1px 1px rgba(0,0,0,0.24);
22+ --spot-shadow-heavy: 0 0 6px rgba(0,0,0,0.24), 0 8px 12px rgba(0,0,0,0.14);
23+ }
24+
25+ [data-theme="light"] {
26+ --spot-bg: #f2f0eb;
27+ --spot-surface: #ffffff;
28+ --spot-hover: #edebe9;
29+ --spot-hover-50: rgba(237,235,233,0.5);
30+ --spot-text: rgba(0,0,0,0.87);
31+ --spot-secondary: rgba(0,0,0,0.58);
32+ --spot-body: rgba(0,0,0,0.70);
33+ --spot-muted: rgba(0,0,0,0.25);
34+ --spot-divider: rgba(0,0,0,0.08);
35+ --spot-divider-30: rgba(0,0,0,0.12);
36+ --spot-outline: rgba(0,0,0,0.15);
37+ --spot-placeholder: rgba(0,0,0,0.30);
38+ --spot-active-bg: #1E3932;
39+ --spot-active-text: #ffffff;
40+ --spot-shadow: 0 0 0.5px rgba(0,0,0,0.14), 0 1px 1px rgba(0,0,0,0.24);
41+ --spot-shadow-heavy: 0 0 6px rgba(0,0,0,0.14), 0 8px 12px rgba(0,0,0,0.08);
42+ }
43+}
44+
45+@layer components {
46+ .article-body h1 { @apply text-2xl font-bold mt-8 mb-3 text-spot-text }
47+ .article-body h2 { @apply text-xl font-semibold mt-6 mb-2 text-spot-text }
48+ .article-body h3 { @apply text-lg font-semibold mt-5 mb-2 text-spot-text }
49+ .article-body h4 { @apply text-base font-semibold mt-4 mb-1 text-spot-text }
50+ .article-body p { @apply my-3 leading-7 text-spot-body }
51+ .article-body ul { @apply list-disc pl-6 my-3 text-spot-body }
52+ .article-body ol { @apply list-decimal pl-6 my-3 text-spot-body }
53+ .article-body li { @apply my-1 leading-7 }
54+ .article-body blockquote { @apply border-l-4 border-spot-divider pl-4 italic text-spot-secondary my-4 }
55+ .article-body pre { @apply bg-spot-bg text-spot-body rounded-lg p-4 overflow-x-auto my-4 text-sm leading-6 }
56+ .article-body code { @apply bg-spot-surface text-spot-body px-1.5 py-0.5 rounded text-sm font-mono }
57+ .article-body pre code { @apply bg-transparent text-spot-body p-0 }
58+ .article-body img { @apply rounded-lg max-w-full h-auto my-4 }
59+ .article-body figure { @apply my-4 }
60+ .article-body figcaption { @apply text-sm text-spot-secondary text-center mt-2 }
61+ .article-body a { @apply text-spot-green underline hover:brightness-110 }
62+ .article-body table { @apply w-full border-collapse my-4 }
63+ .article-body th { @apply border border-spot-divider px-3 py-2 bg-spot-surface font-semibold text-left text-spot-text }
64+ .article-body td { @apply border border-spot-divider px-3 py-2 text-spot-body }
65+ .article-body hr { @apply border-spot-divider my-6 }
66+ .article-body iframe, .article-body video { @apply rounded-lg my-4 max-w-full }
67+ .article-body iframe[src*="youtube.com"], .article-body iframe[src*="youtube-nocookie.com"], .article-body iframe[src*="vimeo.com"], .article-body iframe[src*="spotify.com"], .article-body iframe[src*="soundcloud.com"], .article-body iframe[src*="bandcamp.com"] { @apply w-full aspect-video }
68+ .article-body del { @apply line-through text-spot-secondary }
69+ .article-body mark { @apply bg-spot-orange/30 px-1 rounded }
70+}
71+
72+@layer utilities {
73+ .line-clamp-2 {
674 display: -webkit-box;
775 -webkit-line-clamp: 2;
876 -webkit-box-orient: vertical;
977 overflow: hidden;
78+ }
1079 }
80+
81+body {
82+ font-family: 'Inter', 'Helvetica Neue', helvetica, arial, sans-serif;
83+ letter-spacing: -0.01em;
84+}
85+
86+.sidebar-link {
87+ transition: color 0.15s;
88+}
89+.sidebar-link:hover {
90+ color: var(--spot-text);
91+}
92+
93+::-webkit-scrollbar { width: 8px; }
94+::-webkit-scrollbar-track { background: var(--spot-bg); }
95+::-webkit-scrollbar-thumb { background: var(--spot-muted); border-radius: 4px; }
96+::-webkit-scrollbar-thumb:hover { background: var(--spot-outline); }
@@ -2,9 +2,95 @@
2 @tailwind components;2 @tailwind components;
3 @tailwind utilities;3 @tailwind utilities;
4 4
5-.line-clamp-2 {5+@layer base {
6+ :root {
7+ --spot-bg: #0f1f1a;
8+ --spot-surface: #152b24;
9+ --spot-hover: #1a362e;
10+ --spot-hover-50: rgba(26,54,46,0.5);
11+ --spot-text: #ffffff;
12+ --spot-secondary: rgba(255,255,255,0.70);
13+ --spot-body: rgba(255,255,255,0.87);
14+ --spot-muted: rgba(255,255,255,0.25);
15+ --spot-divider: rgba(255,255,255,0.08);
16+ --spot-divider-30: rgba(255,255,255,0.12);
17+ --spot-outline: rgba(255,255,255,0.20);
18+ --spot-placeholder: rgba(255,255,255,0.30);
19+ --spot-active-bg: #ffffff;
20+ --spot-active-text: #1E3932;
21+ --spot-shadow: 0 0 0.5px rgba(0,0,0,0.14), 0 1px 1px rgba(0,0,0,0.24);
22+ --spot-shadow-heavy: 0 0 6px rgba(0,0,0,0.24), 0 8px 12px rgba(0,0,0,0.14);
23+ }
24+
25+ [data-theme="light"] {
26+ --spot-bg: #f2f0eb;
27+ --spot-surface: #ffffff;
28+ --spot-hover: #edebe9;
29+ --spot-hover-50: rgba(237,235,233,0.5);
30+ --spot-text: rgba(0,0,0,0.87);
31+ --spot-secondary: rgba(0,0,0,0.58);
32+ --spot-body: rgba(0,0,0,0.70);
33+ --spot-muted: rgba(0,0,0,0.25);
34+ --spot-divider: rgba(0,0,0,0.08);
35+ --spot-divider-30: rgba(0,0,0,0.12);
36+ --spot-outline: rgba(0,0,0,0.15);
37+ --spot-placeholder: rgba(0,0,0,0.30);
38+ --spot-active-bg: #1E3932;
39+ --spot-active-text: #ffffff;
40+ --spot-shadow: 0 0 0.5px rgba(0,0,0,0.14), 0 1px 1px rgba(0,0,0,0.24);
41+ --spot-shadow-heavy: 0 0 6px rgba(0,0,0,0.14), 0 8px 12px rgba(0,0,0,0.08);
42+ }
43+}
44+
45+@layer components {
46+ .article-body h1 { @apply text-2xl font-bold mt-8 mb-3 text-spot-text }
47+ .article-body h2 { @apply text-xl font-semibold mt-6 mb-2 text-spot-text }
48+ .article-body h3 { @apply text-lg font-semibold mt-5 mb-2 text-spot-text }
49+ .article-body h4 { @apply text-base font-semibold mt-4 mb-1 text-spot-text }
50+ .article-body p { @apply my-3 leading-7 text-spot-body }
51+ .article-body ul { @apply list-disc pl-6 my-3 text-spot-body }
52+ .article-body ol { @apply list-decimal pl-6 my-3 text-spot-body }
53+ .article-body li { @apply my-1 leading-7 }
54+ .article-body blockquote { @apply border-l-4 border-spot-divider pl-4 italic text-spot-secondary my-4 }
55+ .article-body pre { @apply bg-spot-bg text-spot-body rounded-lg p-4 overflow-x-auto my-4 text-sm leading-6 }
56+ .article-body code { @apply bg-spot-surface text-spot-body px-1.5 py-0.5 rounded text-sm font-mono }
57+ .article-body pre code { @apply bg-transparent text-spot-body p-0 }
58+ .article-body img { @apply rounded-lg max-w-full h-auto my-4 }
59+ .article-body figure { @apply my-4 }
60+ .article-body figcaption { @apply text-sm text-spot-secondary text-center mt-2 }
61+ .article-body a { @apply text-spot-green underline hover:brightness-110 }
62+ .article-body table { @apply w-full border-collapse my-4 }
63+ .article-body th { @apply border border-spot-divider px-3 py-2 bg-spot-surface font-semibold text-left text-spot-text }
64+ .article-body td { @apply border border-spot-divider px-3 py-2 text-spot-body }
65+ .article-body hr { @apply border-spot-divider my-6 }
66+ .article-body iframe, .article-body video { @apply rounded-lg my-4 max-w-full }
67+ .article-body iframe[src*="youtube.com"], .article-body iframe[src*="youtube-nocookie.com"], .article-body iframe[src*="vimeo.com"], .article-body iframe[src*="spotify.com"], .article-body iframe[src*="soundcloud.com"], .article-body iframe[src*="bandcamp.com"] { @apply w-full aspect-video }
68+ .article-body del { @apply line-through text-spot-secondary }
69+ .article-body mark { @apply bg-spot-orange/30 px-1 rounded }
70+}
71+
72+@layer utilities {
73+ .line-clamp-2 {
6 display: -webkit-box;74 display: -webkit-box;
7 -webkit-line-clamp: 2;75 -webkit-line-clamp: 2;
8 -webkit-box-orient: vertical;76 -webkit-box-orient: vertical;
9 overflow: hidden;77 overflow: hidden;
78+ }
10 }79 }
80+
81+body {
82+ font-family: 'Inter', 'Helvetica Neue', helvetica, arial, sans-serif;
83+ letter-spacing: -0.01em;
84+}
85+
86+.sidebar-link {
87+ transition: color 0.15s;
88+}
89+.sidebar-link:hover {
90+ color: var(--spot-text);
91+}
92+
93+::-webkit-scrollbar { width: 8px; }
94+::-webkit-scrollbar-track { background: var(--spot-bg); }
95+::-webkit-scrollbar-thumb { background: var(--spot-muted); border-radius: 4px; }
96+::-webkit-scrollbar-thumb:hover { background: var(--spot-outline); }
modified tailwind.config.js +6 -7
@@ -5,8 +5,11 @@ module.exports = {
55 extend: {
66 colors: {
77 spot: {
8- purple: '#a855f7',
9- 'purple-border': '#9333ea',
8+ green: '#00754A',
9+ 'green-dark': '#006241',
10+ 'green-house': '#1E3932',
11+ 'green-uplift': '#2b5148',
12+ 'green-light': '#d4e9e2',
1013 bg: 'var(--spot-bg)',
1114 surface: 'var(--spot-surface)',
1215 hover: 'var(--spot-hover)',
@@ -21,15 +24,11 @@ module.exports = {
2124 placeholder: 'var(--spot-placeholder)',
2225 'active-pill-bg': 'var(--spot-active-bg)',
2326 'active-pill-text': 'var(--spot-active-text)',
24- red: '#f3727f',
27+ red: '#c82014',
2528 orange: '#ffa42b',
2629 blue: '#539df5',
2730 }
2831 },
29- fontFamily: {
30- ui: ['SpotifyMixUI', 'CircularSp-Arab', 'CircularSp-Hebr', 'CircularSp-Cyrl', 'CircularSp-Grek', 'CircularSp-Deva', 'Helvetica Neue', 'helvetica', 'arial', 'Hiragino Sans', 'Hiragino Kaku Gothic ProN', 'Meiryo', 'MS Gothic', 'sans-serif'],
31- title: ['SpotifyMixUITitle', 'CircularSp-Arab', 'CircularSp-Hebr', 'CircularSp-Cyrl', 'CircularSp-Grek', 'CircularSp-Deva', 'Helvetica Neue', 'helvetica', 'arial', 'Hiragino Sans', 'Hiragino Kaku Gothic ProN', 'Meiryo', 'MS Gothic', 'sans-serif'],
32- },
3332 borderRadius: {
3433 pill: '9999px',
3534 'pill-lg': '500px',
@@ -5,8 +5,11 @@ module.exports = {
5 extend: {5 extend: {
6 colors: {6 colors: {
7 spot: {7 spot: {
8- purple: '#a855f7',8+ green: '#00754A',
9- 'purple-border': '#9333ea',9+ 'green-dark': '#006241',
10+ 'green-house': '#1E3932',
11+ 'green-uplift': '#2b5148',
12+ 'green-light': '#d4e9e2',
10 bg: 'var(--spot-bg)',13 bg: 'var(--spot-bg)',
11 surface: 'var(--spot-surface)',14 surface: 'var(--spot-surface)',
12 hover: 'var(--spot-hover)',15 hover: 'var(--spot-hover)',
@@ -21,15 +24,11 @@ module.exports = {
21 placeholder: 'var(--spot-placeholder)',24 placeholder: 'var(--spot-placeholder)',
22 'active-pill-bg': 'var(--spot-active-bg)',25 'active-pill-bg': 'var(--spot-active-bg)',
23 'active-pill-text': 'var(--spot-active-text)',26 'active-pill-text': 'var(--spot-active-text)',
24- red: '#f3727f',27+ red: '#c82014',
25 orange: '#ffa42b',28 orange: '#ffa42b',
26 blue: '#539df5',29 blue: '#539df5',
27 }30 }
28 },31 },
29- fontFamily: {
30- ui: ['SpotifyMixUI', 'CircularSp-Arab', 'CircularSp-Hebr', 'CircularSp-Cyrl', 'CircularSp-Grek', 'CircularSp-Deva', 'Helvetica Neue', 'helvetica', 'arial', 'Hiragino Sans', 'Hiragino Kaku Gothic ProN', 'Meiryo', 'MS Gothic', 'sans-serif'],
31- title: ['SpotifyMixUITitle', 'CircularSp-Arab', 'CircularSp-Hebr', 'CircularSp-Cyrl', 'CircularSp-Grek', 'CircularSp-Deva', 'Helvetica Neue', 'helvetica', 'arial', 'Hiragino Sans', 'Hiragino Kaku Gothic ProN', 'Meiryo', 'MS Gothic', 'sans-serif'],
32- },
33 borderRadius: {32 borderRadius: {
34 pill: '9999px',33 pill: '9999px',
35 'pill-lg': '500px',34 'pill-lg': '500px',