重要前提
安装AI Skills的关键前提是:必须科学上网,且开启TUN模式,这一点至关重要,直接决定安装能否顺利完成,在此郑重提醒三遍:科学上网,科学上网,科学上网。查看完整安装教程 →
grove-ui-design by autumnsgrove/groveengine
npx skills add https://github.com/autumnsgrove/groveengine --skill grove-ui-design在以下情况下激活此技能:
Grove 是一个地方。它以自然为主题,温暖而诱人——就像一个提供优质文档的午夜茶馆。
温暖、内省、酷儿、毫不掩饰地构建有意义的事物。
以午夜茶馆的温暖和优质文档的清晰度进行写作。
每个设计选择都应让人感觉:
Grove 有一个术语系统,可根据用户的 Grove 模式 设置自动在 Grove 主题术语和标准术语之间切换。在 UI 中始终使用 GroveTerm 组件,而不是硬编码 Grove 术语。
默认情况下,新访客的 Grove 模式为关闭状态。他们看到的是熟悉的术语:例如“帖子”而非“绽放”、“仪表板”而非“树冠”、“支持”而非“门廊”。当用户通过页脚切换选择加入时,他们将看到完整的自然主题词汇表,并带有交互式定义。
组件套件:
import {(GroveTerm, GroveSwap, GroveText, GroveSwapText, GroveIntro)} from '@autumnsgrove/lattice/ui';
import groveTermManifest from '$lib/data/grove-term-manifest.json';
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
| 组件 | 用例 | 行为 |
|---|---|---|
GroveTerm | 带有弹出定义的交互式术语 | 开启时显示彩色下划线,点击弹出定义。关闭时显示标准术语。 |
GroveSwap | 静默文本替换 | 响应式替换文本。无下划线,无交互。 |
GroveText | 解析数据字符串中的 [[term]] 语法 | 将 `[[bloom |
GroveSwapText | 静默解析 [[term]] 语法 | 相同的解析,静默替换(无弹出窗口)。 |
GroveIntro | “我们称之为 X”页面横幅 | 页面标题下方的标准化介绍。 |
用法:
<!-- Interactive term with popup -->
<GroveTerm term="bloom" manifest={groveTermManifest} />
<!-- Custom display text -->
<GroveTerm term="wanderer" manifest={groveTermManifest}>wanderers</GroveTerm>
<!-- Silent swap (no popup, no underline) -->
<GroveSwap term="arbor" manifest={groveTermManifest} />
<!-- Parse [[term]] syntax in data strings (ideal for FAQ items, pricing, etc.) -->
<GroveText
content="Your [[bloom|posts]] live in your [[garden|blog]]."
manifest={groveTermManifest}
/>
<!-- Page intro banner: "we call it the Meadow" -->
<GroveIntro term="meadow" manifest={groveTermManifest} />
关键规则:
/porch、/garden),无论显示模式如何。[[term]] 语法(常见问题解答项目、定价细则、功能列表)。@autumnsgrove/lattice/ui/stores 的 groveModeStore。切换开关位于页脚。完整身份语言文档请参阅 docs/grove-user-identity.md,组件源代码请参阅 libs/engine/src/lib/ui/components/ui/groveterm/。
玻璃效果在揭示背景装饰暗示的同时创造了可读性。
Background (gradients, vines, nature)
↓
Decorative Elements (trees, clouds, particles)
↓
Glass Surface (translucent + blur)
↓
Content (text, cards, UI)
import {(Glass, GlassCard, GlassButton, GlassOverlay)} from '@lattice/ui/ui';
<!-- Container with glass effect -->
<Glass variant="tint" class="p-6 rounded-xl">
<p>Readable text over busy backgrounds</p>
</Glass>
<!-- Card with glass styling -->
<GlassCard title="Settings" variant="default" hoverable>Content here</GlassCard>
<!-- Glass button -->
<GlassButton variant="accent">Subscribe</GlassButton>
| 变体 | 用例 | 浅色模式 | 深色模式 |
|---|---|---|---|
surface | 页眉、导航栏 | 95% 白色 | 95% 石板色 |
tint | 背景上的文本 | 60% 白色 | 50% 石板色 |
card | 内容卡片 | 80% 白色 | 70% 石板色 |
accent | 提示、高亮 | 30% 强调色 | 20% 强调色 |
overlay | 模态背景 | 50% 黑色 | 60% 黑色 |
muted | 微妙的背景 | 40% 白色 | 30% 石板色 |
<!-- Apply directly to any element -->
<div class="glass rounded-xl p-4">Basic glass</div>
<div class="glass-tint p-6">Text container</div>
<div class="glass-accent p-4">Highlighted section</div>
<nav class="glass-surface sticky top-0">Navbar</nav>
<nav
class="sticky top-[73px] z-30 bg-white/80 dark:bg-slate-900/80 backdrop-blur-sm border-b border-divider"
>
<!-- Navigation content -->
</nav>
Grove 使用四个季节,每个季节都有独特的颜色、天气效果和氛围。
import {season} from '$lib/stores/season'; const isSpring = $derived($season === 'spring'); const isAutumn
= $derived($season === 'autumn'); const isWinter = $derived($season === 'winter'); // Summer is the default
(no flag needed)
从以下位置导入:@autumnsgrove/lattice/ui/nature 或 $lib/components/nature/palette
import { greens, bark, earth, natural } from "@autumnsgrove/lattice/ui/nature";
// Greens - organized dark-to-light for atmospheric depth
greens.darkForest; // #0d4a1c - Background trees
greens.deepGreen; // #166534 - Mid-distance
greens.grove; // #16a34a - Grove brand primary
greens.meadow; // #22c55e - Standard foliage
greens.spring; // #4ade80 - Bright accent
greens.mint; // #86efac - Light accent
greens.pale; // #bbf7d0 - Foreground highlights
// Bark - warm wood tones
bark.darkBark; // #3d2817 - Oak, older trees
bark.bark; // #5d4037 - Standard trunk
bark.warmBark; // #6B4423 - Pine, cedar
bark.lightBark; // #8b6914 - Young trees
// Earth - ground elements
(earth.soil, earth.mud, earth.clay, earth.sand, earth.stone, earth.pebble, earth.slate);
// Natural - cream and off-whites
(natural.cream, natural.aspenBark, natural.bone, natural.mushroom, natural.birchWhite);
import {
springFoliage,
springSky,
wildflowers,
cherryBlossoms,
cherryBlossomsPeak,
} from "@autumnsgrove/lattice/ui/nature";
// Spring Foliage - yellow-green new growth
springFoliage.sprout; // #65a30d - Distant new growth
springFoliage.newLeaf; // #84cc16 - Classic spring lime
springFoliage.freshGreen; // #a3e635 - Bright foreground
springFoliage.budding; // #bef264 - Pale new leaf
springFoliage.tender; // #d9f99d - Very pale
// Spring Sky
springSky.clear; // #7dd3fc - Clear morning
springSky.soft; // #bae6fd - Pale sky
// Wildflowers - unified meadow flower colors
wildflowers.buttercup; // #facc15 - Yellow
wildflowers.daffodil; // #fde047 - Pale yellow
wildflowers.crocus; // #a78bfa - Purple crocus
wildflowers.violet; // #8b5cf6 - Wild violets
wildflowers.purple; // #a855f7 - Lupine, thistle
wildflowers.lavender; // #c4b5fd - Distant masses
wildflowers.tulipPink; // #f9a8d4 - Pink tulips
wildflowers.tulipRed; // #fb7185 - Red tulips
wildflowers.white; // #fefefe - Daisies, trillium
// Cherry Blossoms - summer standard
cherryBlossoms.deep; // #db2777 - Dense centers
cherryBlossoms.standard; // #ec4899 - Standard blossom
cherryBlossoms.light; // #f472b6 - Light petals
cherryBlossoms.pale; // #f9a8d4 - Pale blossoms
cherryBlossoms.falling; // #fbcfe8 - Falling petals
// Cherry Blossoms Peak - vibrant spring (one shade brighter!)
cherryBlossomsPeak.deep; // #ec4899
cherryBlossomsPeak.standard; // #f472b6
cherryBlossomsPeak.light; // #f9a8d4
cherryBlossomsPeak.pale; // #fbcfe8
cherryBlossomsPeak.falling; // #fce7f3
flowers 命名空间将所有花卉颜色整合到一个有组织的结构中:
import { flowers } from "@autumnsgrove/lattice/ui/nature";
// Meadow wildflowers (yellows, purples, pinks, whites)
flowers.wildflower.buttercup; // #facc15 - Yellow
flowers.wildflower.daffodil; // #fde047 - Pale yellow
flowers.wildflower.crocus; // #a78bfa - Purple crocus
flowers.wildflower.violet; // #8b5cf6 - Wild violets
flowers.wildflower.purple; // #a855f7 - Lupine, thistle
flowers.wildflower.lavender; // #c4b5fd - Distant masses
flowers.wildflower.tulipPink; // #f9a8d4 - Pink tulips
flowers.wildflower.tulipRed; // #fb7185 - Red tulips
flowers.wildflower.white; // #fefefe - Daisies, trillium
// Cherry blossoms - standard summer
flowers.cherry.deep; // #db2777
flowers.cherry.standard; // #ec4899
flowers.cherry.light; // #f472b6
flowers.cherry.pale; // #f9a8d4
flowers.cherry.falling; // #fbcfe8
// Cherry blossoms at peak bloom - vibrant spring
flowers.cherryPeak.deep; // #ec4899
flowers.cherryPeak.standard; // #f472b6
flowers.cherryPeak.light; // #f9a8d4
flowers.cherryPeak.pale; // #fbcfe8
flowers.cherryPeak.falling; // #fce7f3
使用 flowers.wildflower 而不是 accents.flower —— 后者已弃用。
import { autumn, autumnReds, winter } from "@autumnsgrove/lattice/ui/nature";
// Autumn - warm fall foliage (dark-to-light for depth)
autumn.rust; // #9a3412 - Deep background
autumn.ember; // #c2410c - Oak-like
autumn.pumpkin; // #ea580c - Maple mid-tones
autumn.amber; // #d97706 - Classic fall
autumn.gold; // #eab308 - Aspen/birch
autumn.honey; // #facc15 - Bright foreground
autumn.straw; // #fde047 - Pale dying leaves
// Autumn Reds - cherry/maple fall foliage
autumnReds.crimson; // #be123c - Deep maple
autumnReds.scarlet; // #e11d48 - Bright cherry
autumnReds.rose; // #f43f5e - Light autumn
autumnReds.coral; // #fb7185 - Pale accent
// Winter - frost, snow, ice + frosted evergreens
(winter.snow, winter.frost, winter.ice, winter.glacier);
(winter.frostedPine, winter.winterGreen, winter.coldSpruce);
(winter.winterSky, winter.twilight, winter.overcast);
(winter.bareBranch, winter.frostedBark, winter.coldWood);
(winter.hillDeep, winter.hillMid, winter.hillNear, winter.hillFront);
import { accents, wildflowers } from "@autumnsgrove/lattice/ui/nature";
// Mushrooms - fairy tale pops of color
(accents.mushroom.redCap, accents.mushroom.orangeCap, accents.mushroom.brownCap);
(accents.mushroom.spots, accents.mushroom.gill);
// Firefly - bioluminescence
(accents.firefly.glow, accents.firefly.warmGlow, accents.firefly.body);
// Berry - rich saturated
(accents.berry.ripe, accents.berry.elderberry, accents.berry.red);
// Water - cool blue spectrum
(accents.water.surface, accents.water.deep, accents.water.shallow, accents.water.lily);
// Sky - time of day
(accents.sky.dayLight, accents.sky.dayMid, accents.sky.sunset, accents.sky.night, accents.sky.star);
// Birds - species-specific colors
(accents.bird.cardinalRed, accents.bird.cardinalMask, accents.bird.cardinalBeak);
(accents.bird.chickadeeCap, accents.bird.chickadeeBody, accents.bird.chickadeeBelly);
(accents.bird.robinBody, accents.bird.robinBreast, accents.bird.robinBeak);
(accents.bird.bluebirdBody, accents.bird.bluebirdWing, accents.bird.bluebirdBreast);
// NOTE: accents.flower is deprecated - use flowers.wildflower instead
import {
getSeasonalGreens,
getCherryColors,
isTreeBare,
pickRandom,
pickFrom,
} from "@autumnsgrove/lattice/ui/nature";
// Get foliage colors mapped to season
const foliage = getSeasonalGreens(season);
// spring → springFoliage colors
// summer → greens
// autumn → autumn palette
// winter → frosted evergreen colors
// Get cherry tree colors by season
const cherryColors = getCherryColors(season);
// spring → cherryBlossomsPeak (vibrant!)
// summer → cherryBlossoms (standard)
// autumn → autumnReds
// winter → null (bare tree)
// Check if deciduous tree is bare
if (isTreeBare("cherry", "winter")) {
/* no foliage */
}
// Random color selection for natural variation
const randomGreen = pickRandom(greens);
const specificGreen = pickFrom(greens, ["grove", "meadow"]);
// These work but will be removed in v1.0:
import { spring, pinks, springBlossoms } from "@autumnsgrove/lattice/ui/nature";
// spring → use springFoliage, wildflowers, springSky instead
// pinks → use cherryBlossoms instead
// springBlossoms → use cherryBlossomsPeak instead
// accents.flower → use flowers.wildflower instead
| 季节 | 主要颜色 | 氛围 |
|---|---|---|
| 春季 | springFoliage, cherryBlossomsPeak, wildflowers | 新生、希望 |
| 夏季 | greens, cherryBlossoms | 生长、温暖 |
| 秋季 | autumn, autumnReds | 收获、反思 |
| 冬季 | winter(霜、雪、霜冻的松树) | 休息、宁静 |
<!-- Winter: Snowfall -->
{#if isWinter}
<SnowfallLayer count={40} zIndex={5} opacity={{ min: 0.4, max: 0.8 }} spawnDelay={8} />
{/if}
<!-- Spring: Cherry blossom petals -->
{#if isSpring}
<FallingPetalsLayer count={80} zIndex={100} opacity={{ min: 0.5, max: 0.9 }} />
{/if}
<!-- Autumn: Falling leaves (tied to trees) -->
{#if isAutumn}
<FallingLeavesLayer
trees={forestTrees}
season={$season}
minLeavesPerTree={2}
maxLeavesPerTree={4}
/>
{/if}
<main class="min-h-screen transition-colors duration-1000
{isWinter ? 'bg-gradient-to-b from-slate-200 via-slate-100 to-slate-50 dark:from-slate-900 dark:via-slate-800 dark:to-slate-700' : ''}
{isAutumn ? 'bg-gradient-to-b from-orange-100 via-amber-50 to-yellow-50 dark:from-slate-900 dark:via-amber-950 dark:to-orange-950' : ''}
{isSpring ? 'bg-gradient-to-b from-pink-50 via-sky-50 to-lime-50 dark:from-slate-900 dark:via-pink-950 dark:to-lime-950' : ''}
{/* Summer default */} 'bg-gradient-to-b from-sky-100 via-sky-50 to-emerald-50 dark:from-slate-900 dark:via-slate-800 dark:to-emerald-950'
">
森林应该让人感觉充满生机,并且每次访问都有所不同。
interface GeneratedTree {
id: number;
x: number; // percentage from left (5-93% to avoid edges)
size: number; // base width in pixels
aspectRatio: number; // height = size * aspectRatio (1.0-1.5 range)
treeType: TreeType; // 'logo' | 'pine' | 'cherry' | 'aspen' | 'birch'
opacity: number; // 0.5-0.9 for depth
zIndex: number; // larger trees = higher z-index
}
// Aspect ratio creates natural height variation
const TREE_ASPECT_RATIO_RANGE = { min: 1.0, max: 1.5 };
function generateSectionTrees(count: number): GeneratedTree[] {
const trees: GeneratedTree[] = [];
const usedPositions: number[] = [];
for (let i = 0; i < count; i++) {
// Find non-overlapping position
let x: number;
let attempts = 0;
do {
x = 5 + Math.random() * 88;
attempts++;
} while (usedPositions.some((pos) => Math.abs(pos - x) < 8) && attempts < 20);
usedPositions.push(x);
const size = 80 + Math.random() * 80;
const aspectRatio = 1.0 + Math.random() * 0.5;
const opacity = 0.5 + Math.random() * 0.4;
const zIndex = size > 130 ? 3 : size > 100 ? 2 : 1;
trees.push({
id: i,
x,
size,
aspectRatio,
treeType: pickRandom(treeTypes),
opacity,
zIndex,
});
}
return trees.sort((a, b) => a.x - b.x);
}
{#each forestTrees as tree (tree.id)}
<div
class="absolute"
style="
left: {tree.x}%;
bottom: 0;
width: {tree.size}px;
height: {tree.size * tree.aspectRatio}px;
opacity: {tree.opacity};
z-index: {tree.zIndex};
transform: translateX(-50%);
"
>
{#if tree.treeType === "logo"}
<Logo class="w-full h-full" season={$season} animate />
{:else if tree.treeType === "pine"}
<TreePine class="w-full h-full" season={$season} animate />
{:else if tree.treeType === "cherry"}
<TreeCherry class="w-full h-full" season={$season} animate />
{:else if tree.treeType === "aspen"}
<TreeAspen class="w-full h-full" season={$season} animate />
{:else if tree.treeType === "birch"}
<TreeBirch class="w-full h-full" season={$season} animate />
{/if}
</div>
{/each}
function calculateDensity(): number {
const width = window.innerWidth;
if (width < 768) return 1; // Mobile: base count
if (width < 1024) return 1.3; // Tablet
if (width < 1440) return 1.8; // Desktop
if (width < 2560) return 2.5; // Large desktop
return 3.5; // Ultrawide
}
Grove 拥有一个广泛的装饰性组件库。通过以下方式探索:
# Trees
ls landing/src/lib/components/trees/
ls landing/src/lib/components/nature/trees/
# Weather (seasonal particles)
ls landing/src/lib/components/nature/weather/
# Sky (clouds, stars, moon)
ls landing/src/lib/components/nature/sky/
# Botanical (leaves, petals, vines)
ls landing/src/lib/components/nature/botanical/
# Ground (flowers, grass, mushrooms)
ls landing/src/lib/components/nature/ground/
# Structural (lattice, lanterns, paths)
ls landing/src/lib/components/nature/structural/
# Birds (cardinals, robins, bluebirds)
ls landing/src/lib/components/nature/creatures/
| 组件 | 用途 | 示例属性 |
|---|---|---|
Logo | Grove 树,季节性 | season, animate, breathing |
TreePine | 常青树,秋季保持绿色 | season, animate |
TreeCherry | 春季开花,冬季光秃 | season, animate |
TreeAspen / TreeBirch | 落叶树,季节性颜色 | season, animate |
Cloud | 装饰性天空元素 | variant, animate, speed, direction |
SnowfallLayer | 冬季粒子 | count, opacity, spawnDelay |
FallingPetalsLayer | 春季樱花花瓣 | count, opacity, fallDuration |
FallingLeavesLayer | 秋季落叶(与树木关联) | trees, season |
Cardinal / Chickadee | 冬季鸟类 | facing |
Robin / Bluebird | 春季鸟类 | facing |
Vine | 装饰性藤蔓/常春藤 | 多种 |
Lantern | 温暖发光点 | 多种 |
<!-- Winter birds -->
{#if isWinter}
<Cardinal facing="right" style="..." />
<Chickadee facing="left" style="..." />
{/if}
<!-- Spring birds -->
{#if isSpring}
<Robin facing="right" style="..." />
<Bluebird facing="left" style="..." />
{/if}
适用于梦幻、遥远未来、神秘的内容。存在于明日边缘的茶馆。
import { midnightBloom } from "$lib/components/nature/palette";
// Available colors:
midnightBloom.deepPlum; // #581c87 - Night sky depth
midnightBloom.purple; // #7c3aed - Soft purple glow
midnightBloom.violet; // #8b5cf6 - Lighter accent
midnightBloom.amber; // #f59e0b - Lantern warmth
midnightBloom.warmCream; // #fef3c7 - Tea steam, page glow
midnightBloom.softGold; // #fcd34d - Fairy lights
<section class="bg-gradient-to-b from-orange-950/50 via-purple-950 to-slate-950">
<!-- Stars -->
<StarCluster count={12} class="absolute top-12 left-[10%]" />
<!-- Moon -->
<Moon phase="waning-crescent" class="absolute top-20 right-[15%] w-16 h-16 opacity-60" />
<!-- Fireflies -->
<Firefly count={8} class="absolute inset-0" />
<!-- Content with purple glass -->
<blockquote class="bg-purple-900/30 backdrop-blur-sm border border-purple-700/30 rounded-lg p-6">
<p class="text-purple-200 italic">Dreamy quote here...</p>
</blockquote>
</section>
绝不使用表情符号。始终使用 Lucide 图标。
import {(MapPin, Check, Leaf, Trees, Mail)} from '@lucide/svelte';
<!-- Good -->
<MapPin class="w-4 h-4" />
<Check class="w-5 h-5 text-green-500" />
<!-- Bad - NEVER do this -->
<!-- ❌ 🌱 📧 ✅ -->
在整个项目中一致地使用这些图标:
| 概念 | 图标 | 备注 |
|---|---|---|
| 导航 | ||
| 主页 | Home | |
| 关于 | Info | |
| 愿景 | Telescope | 展望未来 |
| 路线图 | Map | 旅程/方向 |
| 定价 | HandCoins | 金钱/货币 |
| 知识 | BookOpen | 学习/文档 |
| 森林 | Trees | 社区博客 |
| 博客 | PenLine | 写作 |
| 功能 | ||
| 电子邮件 | Mail | |
| 存储 | HardDrive | |
| 主题 | Palette | 自定义 |
| 身份验证 | ShieldCheck | 安全 |
| 云 | Cloud | 远程/无服务器 |
| 搜索 | SearchCode | 代码/高级搜索 |
| 存档 | Archive | 备份 |
| 上传 | Upload | |
| 视频 | Video | |
| 评论 | MessagesSquare | 用户讨论 |
| GitHub | Github | 指向 GitHub 的外部链接 |
| 状态 | ||
| 成功 | Check | 已完成/有效 |
| 错误 | X | 失败/关闭 |
| 加载中 | Loader2 | 配合 animate-spin 使用 |
| 内容 | ||
| 帖子 | FileText | 博客帖子 |
| 标签 | Tag | 分类 |
| 成长 | Sprout | Grove 品牌,新的开始 |
| 心 | Heart | 喜爱,关怀 |
| 外部 | ExternalLink | 在新标签页中打开 |
| 位置 | MapPin | 当前位置 |
| 阶段 | ||
| 即将推出 | Seedling | 正在成长的事物 |
| 优化 | Gem | 打磨,质量 |
| 梦想 | Sparkles | 神秘(谨慎使用!) |
| 夜晚 | Star | 午夜主题 |
| 操作 | ||
| 入门指南 | Compass | 指导 |
| 最新动态 | Megaphone | 公告 |
| 后续步骤 | Lightbulb | 想法 |
在每个使用图标的组件/页面顶部创建一致的图标映射:
// landing/src/lib/utils/icons.ts - Centralized icon registry
import {
Mail,
HardDrive,
Palette,
ShieldCheck,
Cloud,
SearchCode,
Archive,
Upload,
MessagesSquare,
Github,
Check,
X,
Loader2,
FileText,
Tag,
Sprout,
Heart,
ExternalLink,
MapPin, // ... etc
} from "@lucide/svelte";
export const featureIcons = {
mail: Mail,
harddrive: HardDrive,
palette: Palette,
shieldcheck: ShieldCheck,
cloud: Cloud,
searchcode: SearchCode,
// ... all mapped icons
} as const;
export const stateIcons = {
success: Check,
error: X,
loading: Loader2,
} as const;
然后在组件中使用:
<script lang="ts">
import { featureIcons } from "$lib/utils/icons";
</script>
{#each features as feature}
<svelte:component this={featureIcons[feature.icon]} class="w-5 h-5" />
{/each}
优点:
landing/src/lib/utils/icons.ts<!-- Inline with text -->
<span class="inline-flex items-center gap-1.5">
<Leaf class="w-4 h-4" /> Feature name
</span>
<!-- Button icon -->
<button class="p-2">
<Menu class="w-5 h-5" />
</button>
<!-- Large decorative -->
<Gem class="w-8 h-8 text-amber-400" />
理念: “树林不需要被画出来。它只需要被安排。”
对于创建自定义徽标、插图或装饰元素,应组合现有的 Lucide 图标,而不是从头开始绘制自定义 SVG。这确保了与图标系统的视觉一致性。
Lucide 图标使用 24×24 的 viewBox 和 2px 描边。直接从源代码提取路径:
# Find icon paths in Lucide source
curl -s https://raw.githubusercontent.com/lucide-icons/lucide/main/icons/tree-pine.svg
# Look for the <path d="..." /> elements
用于 Grove 组合的关键 Lucide 图标路径:
// TreePine - conifer silhouette
const treePine = {
canopy:
"m17 14 3 3.3a1 1 0 0 1-.7 1.7H4.7a1 1 0 0 1-.7-1.7L7 14h-.3a1 1 0 0 1-.7-1.7L9 9h-.2A1 1 0 0 1 8 7.3L12 3l4 4.3a1 1 0 0 1-.8 1.7H15l3 3.3a1 1 0 0 1-.7 1.7H17Z",
trunk: "M12 22v-3",
};
// TreeDeciduous - deciduous/round tree
const treeDeciduous = {
canopy:
"M8 19a4 4 0 0 1-2.24-7.32A3.5 3.5 0 0 1 9 6.03V6a3 3 0 1 1 6 0v.04a3.5 3.5 0 0 1 3.24 5.65A4 4 0 0 1 16 19Z
Activate this skill when:
Grove is a place. It's nature-themed, warm, and inviting—like a midnight tea shop with good documentation.
Warm, introspective, queer, unapologetically building something meaningful.
Write with the warmth of a midnight tea shop and the clarity of good documentation.
Every design choice should feel:
Grove has a terminology system that automatically switches between Grove-themed terms and standard terms based on the user's Grove Mode setting. Always use GroveTerm components instead of hardcoding Grove terminology in UI.
By default, Grove Mode is OFF for new visitors. They see familiar terms: "Posts" instead of "Blooms", "Dashboard" instead of "Arbor", "Support" instead of "Porch". When users opt in via the footer toggle, they see the full nature-themed vocabulary with interactive definitions.
The Component Suite:
import {(GroveTerm, GroveSwap, GroveText, GroveSwapText, GroveIntro)} from '@autumnsgrove/lattice/ui';
import groveTermManifest from '$lib/data/grove-term-manifest.json';
| Component | Use Case | Behavior |
|---|---|---|
GroveTerm | Interactive terms with popup definitions | Colored underline when ON, click for popup. Shows standard term when OFF. |
GroveSwap | Silent text replacement | Reactively swaps text. No underline, no interaction. |
GroveText | Parse [[term]] syntax in data strings | Renders `[[bloom |
GroveSwapText | Parse [[term]] syntax silently |
Usage:
<!-- Interactive term with popup -->
<GroveTerm term="bloom" manifest={groveTermManifest} />
<!-- Custom display text -->
<GroveTerm term="wanderer" manifest={groveTermManifest}>wanderers</GroveTerm>
<!-- Silent swap (no popup, no underline) -->
<GroveSwap term="arbor" manifest={groveTermManifest} />
<!-- Parse [[term]] syntax in data strings (ideal for FAQ items, pricing, etc.) -->
<GroveText
content="Your [[bloom|posts]] live in your [[garden|blog]]."
manifest={groveTermManifest}
/>
<!-- Page intro banner: "we call it the Meadow" -->
<GroveIntro term="meadow" manifest={groveTermManifest} />
Key Rules:
/porch, /garden) regardless of display mode.[[term]] syntax for data-driven content (FAQ items, pricing fineprint, feature lists).groveModeStore from @autumnsgrove/lattice/ui/stores. Toggle lives in the footer.See docs/grove-user-identity.md for the full identity language documentation and libs/engine/src/lib/ui/components/ui/groveterm/ for component source.
Glass effects create readability while revealing hints of background decoration.
Background (gradients, vines, nature)
↓
Decorative Elements (trees, clouds, particles)
↓
Glass Surface (translucent + blur)
↓
Content (text, cards, UI)
import {(Glass, GlassCard, GlassButton, GlassOverlay)} from '@lattice/ui/ui';
<!-- Container with glass effect -->
<Glass variant="tint" class="p-6 rounded-xl">
<p>Readable text over busy backgrounds</p>
</Glass>
<!-- Card with glass styling -->
<GlassCard title="Settings" variant="default" hoverable>Content here</GlassCard>
<!-- Glass button -->
<GlassButton variant="accent">Subscribe</GlassButton>
| Variant | Use Case | Light Mode | Dark Mode |
|---|---|---|---|
surface | Headers, navbars | 95% white | 95% slate |
tint | Text over backgrounds | 60% white | 50% slate |
card | Content cards | 80% white | 70% slate |
accent | Callouts, highlights | 30% accent | 20% accent |
<!-- Apply directly to any element -->
<div class="glass rounded-xl p-4">Basic glass</div>
<div class="glass-tint p-6">Text container</div>
<div class="glass-accent p-4">Highlighted section</div>
<nav class="glass-surface sticky top-0">Navbar</nav>
<nav
class="sticky top-[73px] z-30 bg-white/80 dark:bg-slate-900/80 backdrop-blur-sm border-b border-divider"
>
<!-- Navigation content -->
</nav>
Grove uses four seasons, each with distinct colors, weather effects, and moods.
import {season} from '$lib/stores/season'; const isSpring = $derived($season === 'spring'); const isAutumn
= $derived($season === 'autumn'); const isWinter = $derived($season === 'winter'); // Summer is the default
(no flag needed)
Import from: @autumnsgrove/lattice/ui/nature or $lib/components/nature/palette
import { greens, bark, earth, natural } from "@autumnsgrove/lattice/ui/nature";
// Greens - organized dark-to-light for atmospheric depth
greens.darkForest; // #0d4a1c - Background trees
greens.deepGreen; // #166534 - Mid-distance
greens.grove; // #16a34a - Grove brand primary
greens.meadow; // #22c55e - Standard foliage
greens.spring; // #4ade80 - Bright accent
greens.mint; // #86efac - Light accent
greens.pale; // #bbf7d0 - Foreground highlights
// Bark - warm wood tones
bark.darkBark; // #3d2817 - Oak, older trees
bark.bark; // #5d4037 - Standard trunk
bark.warmBark; // #6B4423 - Pine, cedar
bark.lightBark; // #8b6914 - Young trees
// Earth - ground elements
(earth.soil, earth.mud, earth.clay, earth.sand, earth.stone, earth.pebble, earth.slate);
// Natural - cream and off-whites
(natural.cream, natural.aspenBark, natural.bone, natural.mushroom, natural.birchWhite);
import {
springFoliage,
springSky,
wildflowers,
cherryBlossoms,
cherryBlossomsPeak,
} from "@autumnsgrove/lattice/ui/nature";
// Spring Foliage - yellow-green new growth
springFoliage.sprout; // #65a30d - Distant new growth
springFoliage.newLeaf; // #84cc16 - Classic spring lime
springFoliage.freshGreen; // #a3e635 - Bright foreground
springFoliage.budding; // #bef264 - Pale new leaf
springFoliage.tender; // #d9f99d - Very pale
// Spring Sky
springSky.clear; // #7dd3fc - Clear morning
springSky.soft; // #bae6fd - Pale sky
// Wildflowers - unified meadow flower colors
wildflowers.buttercup; // #facc15 - Yellow
wildflowers.daffodil; // #fde047 - Pale yellow
wildflowers.crocus; // #a78bfa - Purple crocus
wildflowers.violet; // #8b5cf6 - Wild violets
wildflowers.purple; // #a855f7 - Lupine, thistle
wildflowers.lavender; // #c4b5fd - Distant masses
wildflowers.tulipPink; // #f9a8d4 - Pink tulips
wildflowers.tulipRed; // #fb7185 - Red tulips
wildflowers.white; // #fefefe - Daisies, trillium
// Cherry Blossoms - summer standard
cherryBlossoms.deep; // #db2777 - Dense centers
cherryBlossoms.standard; // #ec4899 - Standard blossom
cherryBlossoms.light; // #f472b6 - Light petals
cherryBlossoms.pale; // #f9a8d4 - Pale blossoms
cherryBlossoms.falling; // #fbcfe8 - Falling petals
// Cherry Blossoms Peak - vibrant spring (one shade brighter!)
cherryBlossomsPeak.deep; // #ec4899
cherryBlossomsPeak.standard; // #f472b6
cherryBlossomsPeak.light; // #f9a8d4
cherryBlossomsPeak.pale; // #fbcfe8
cherryBlossomsPeak.falling; // #fce7f3
The flowers namespace consolidates all flower colors into one organized structure:
import { flowers } from "@autumnsgrove/lattice/ui/nature";
// Meadow wildflowers (yellows, purples, pinks, whites)
flowers.wildflower.buttercup; // #facc15 - Yellow
flowers.wildflower.daffodil; // #fde047 - Pale yellow
flowers.wildflower.crocus; // #a78bfa - Purple crocus
flowers.wildflower.violet; // #8b5cf6 - Wild violets
flowers.wildflower.purple; // #a855f7 - Lupine, thistle
flowers.wildflower.lavender; // #c4b5fd - Distant masses
flowers.wildflower.tulipPink; // #f9a8d4 - Pink tulips
flowers.wildflower.tulipRed; // #fb7185 - Red tulips
flowers.wildflower.white; // #fefefe - Daisies, trillium
// Cherry blossoms - standard summer
flowers.cherry.deep; // #db2777
flowers.cherry.standard; // #ec4899
flowers.cherry.light; // #f472b6
flowers.cherry.pale; // #f9a8d4
flowers.cherry.falling; // #fbcfe8
// Cherry blossoms at peak bloom - vibrant spring
flowers.cherryPeak.deep; // #ec4899
flowers.cherryPeak.standard; // #f472b6
flowers.cherryPeak.light; // #f9a8d4
flowers.cherryPeak.pale; // #fbcfe8
flowers.cherryPeak.falling; // #fce7f3
Useflowers.wildflower instead of accents.flower — the accents version is deprecated.
import { autumn, autumnReds, winter } from "@autumnsgrove/lattice/ui/nature";
// Autumn - warm fall foliage (dark-to-light for depth)
autumn.rust; // #9a3412 - Deep background
autumn.ember; // #c2410c - Oak-like
autumn.pumpkin; // #ea580c - Maple mid-tones
autumn.amber; // #d97706 - Classic fall
autumn.gold; // #eab308 - Aspen/birch
autumn.honey; // #facc15 - Bright foreground
autumn.straw; // #fde047 - Pale dying leaves
// Autumn Reds - cherry/maple fall foliage
autumnReds.crimson; // #be123c - Deep maple
autumnReds.scarlet; // #e11d48 - Bright cherry
autumnReds.rose; // #f43f5e - Light autumn
autumnReds.coral; // #fb7185 - Pale accent
// Winter - frost, snow, ice + frosted evergreens
(winter.snow, winter.frost, winter.ice, winter.glacier);
(winter.frostedPine, winter.winterGreen, winter.coldSpruce);
(winter.winterSky, winter.twilight, winter.overcast);
(winter.bareBranch, winter.frostedBark, winter.coldWood);
(winter.hillDeep, winter.hillMid, winter.hillNear, winter.hillFront);
import { accents, wildflowers } from "@autumnsgrove/lattice/ui/nature";
// Mushrooms - fairy tale pops of color
(accents.mushroom.redCap, accents.mushroom.orangeCap, accents.mushroom.brownCap);
(accents.mushroom.spots, accents.mushroom.gill);
// Firefly - bioluminescence
(accents.firefly.glow, accents.firefly.warmGlow, accents.firefly.body);
// Berry - rich saturated
(accents.berry.ripe, accents.berry.elderberry, accents.berry.red);
// Water - cool blue spectrum
(accents.water.surface, accents.water.deep, accents.water.shallow, accents.water.lily);
// Sky - time of day
(accents.sky.dayLight, accents.sky.dayMid, accents.sky.sunset, accents.sky.night, accents.sky.star);
// Birds - species-specific colors
(accents.bird.cardinalRed, accents.bird.cardinalMask, accents.bird.cardinalBeak);
(accents.bird.chickadeeCap, accents.bird.chickadeeBody, accents.bird.chickadeeBelly);
(accents.bird.robinBody, accents.bird.robinBreast, accents.bird.robinBeak);
(accents.bird.bluebirdBody, accents.bird.bluebirdWing, accents.bird.bluebirdBreast);
// NOTE: accents.flower is deprecated - use flowers.wildflower instead
import {
getSeasonalGreens,
getCherryColors,
isTreeBare,
pickRandom,
pickFrom,
} from "@autumnsgrove/lattice/ui/nature";
// Get foliage colors mapped to season
const foliage = getSeasonalGreens(season);
// spring → springFoliage colors
// summer → greens
// autumn → autumn palette
// winter → frosted evergreen colors
// Get cherry tree colors by season
const cherryColors = getCherryColors(season);
// spring → cherryBlossomsPeak (vibrant!)
// summer → cherryBlossoms (standard)
// autumn → autumnReds
// winter → null (bare tree)
// Check if deciduous tree is bare
if (isTreeBare("cherry", "winter")) {
/* no foliage */
}
// Random color selection for natural variation
const randomGreen = pickRandom(greens);
const specificGreen = pickFrom(greens, ["grove", "meadow"]);
// These work but will be removed in v1.0:
import { spring, pinks, springBlossoms } from "@autumnsgrove/lattice/ui/nature";
// spring → use springFoliage, wildflowers, springSky instead
// pinks → use cherryBlossoms instead
// springBlossoms → use cherryBlossomsPeak instead
// accents.flower → use flowers.wildflower instead
| Season | Primary Colors | Mood |
|---|---|---|
| Spring | springFoliage, cherryBlossomsPeak, wildflowers | Renewal, hope |
| Summer | greens, cherryBlossoms | Growth, warmth |
| Autumn | autumn, autumnReds |
<!-- Winter: Snowfall -->
{#if isWinter}
<SnowfallLayer count={40} zIndex={5} opacity={{ min: 0.4, max: 0.8 }} spawnDelay={8} />
{/if}
<!-- Spring: Cherry blossom petals -->
{#if isSpring}
<FallingPetalsLayer count={80} zIndex={100} opacity={{ min: 0.5, max: 0.9 }} />
{/if}
<!-- Autumn: Falling leaves (tied to trees) -->
{#if isAutumn}
<FallingLeavesLayer
trees={forestTrees}
season={$season}
minLeavesPerTree={2}
maxLeavesPerTree={4}
/>
{/if}
<main class="min-h-screen transition-colors duration-1000
{isWinter ? 'bg-gradient-to-b from-slate-200 via-slate-100 to-slate-50 dark:from-slate-900 dark:via-slate-800 dark:to-slate-700' : ''}
{isAutumn ? 'bg-gradient-to-b from-orange-100 via-amber-50 to-yellow-50 dark:from-slate-900 dark:via-amber-950 dark:to-orange-950' : ''}
{isSpring ? 'bg-gradient-to-b from-pink-50 via-sky-50 to-lime-50 dark:from-slate-900 dark:via-pink-950 dark:to-lime-950' : ''}
{/* Summer default */} 'bg-gradient-to-b from-sky-100 via-sky-50 to-emerald-50 dark:from-slate-900 dark:via-slate-800 dark:to-emerald-950'
">
The forest should feel alive and different every visit.
interface GeneratedTree {
id: number;
x: number; // percentage from left (5-93% to avoid edges)
size: number; // base width in pixels
aspectRatio: number; // height = size * aspectRatio (1.0-1.5 range)
treeType: TreeType; // 'logo' | 'pine' | 'cherry' | 'aspen' | 'birch'
opacity: number; // 0.5-0.9 for depth
zIndex: number; // larger trees = higher z-index
}
// Aspect ratio creates natural height variation
const TREE_ASPECT_RATIO_RANGE = { min: 1.0, max: 1.5 };
function generateSectionTrees(count: number): GeneratedTree[] {
const trees: GeneratedTree[] = [];
const usedPositions: number[] = [];
for (let i = 0; i < count; i++) {
// Find non-overlapping position
let x: number;
let attempts = 0;
do {
x = 5 + Math.random() * 88;
attempts++;
} while (usedPositions.some((pos) => Math.abs(pos - x) < 8) && attempts < 20);
usedPositions.push(x);
const size = 80 + Math.random() * 80;
const aspectRatio = 1.0 + Math.random() * 0.5;
const opacity = 0.5 + Math.random() * 0.4;
const zIndex = size > 130 ? 3 : size > 100 ? 2 : 1;
trees.push({
id: i,
x,
size,
aspectRatio,
treeType: pickRandom(treeTypes),
opacity,
zIndex,
});
}
return trees.sort((a, b) => a.x - b.x);
}
{#each forestTrees as tree (tree.id)}
<div
class="absolute"
style="
left: {tree.x}%;
bottom: 0;
width: {tree.size}px;
height: {tree.size * tree.aspectRatio}px;
opacity: {tree.opacity};
z-index: {tree.zIndex};
transform: translateX(-50%);
"
>
{#if tree.treeType === "logo"}
<Logo class="w-full h-full" season={$season} animate />
{:else if tree.treeType === "pine"}
<TreePine class="w-full h-full" season={$season} animate />
{:else if tree.treeType === "cherry"}
<TreeCherry class="w-full h-full" season={$season} animate />
{:else if tree.treeType === "aspen"}
<TreeAspen class="w-full h-full" season={$season} animate />
{:else if tree.treeType === "birch"}
<TreeBirch class="w-full h-full" season={$season} animate />
{/if}
</div>
{/each}
function calculateDensity(): number {
const width = window.innerWidth;
if (width < 768) return 1; // Mobile: base count
if (width < 1024) return 1.3; // Tablet
if (width < 1440) return 1.8; // Desktop
if (width < 2560) return 2.5; // Large desktop
return 3.5; // Ultrawide
}
Grove has an extensive library of decorative components. Explore with:
# Trees
ls landing/src/lib/components/trees/
ls landing/src/lib/components/nature/trees/
# Weather (seasonal particles)
ls landing/src/lib/components/nature/weather/
# Sky (clouds, stars, moon)
ls landing/src/lib/components/nature/sky/
# Botanical (leaves, petals, vines)
ls landing/src/lib/components/nature/botanical/
# Ground (flowers, grass, mushrooms)
ls landing/src/lib/components/nature/ground/
# Structural (lattice, lanterns, paths)
ls landing/src/lib/components/nature/structural/
# Birds (cardinals, robins, bluebirds)
ls landing/src/lib/components/nature/creatures/
| Component | Use | Example Props |
|---|---|---|
Logo | Grove tree, seasonal | season, animate, breathing |
TreePine | Evergreen, stays green in autumn | season, animate |
TreeCherry |
<!-- Winter birds -->
{#if isWinter}
<Cardinal facing="right" style="..." />
<Chickadee facing="left" style="..." />
{/if}
<!-- Spring birds -->
{#if isSpring}
<Robin facing="right" style="..." />
<Bluebird facing="left" style="..." />
{/if}
For dreamy , far-future , mystical content. The tea shop that exists at the edge of tomorrow.
import { midnightBloom } from "$lib/components/nature/palette";
// Available colors:
midnightBloom.deepPlum; // #581c87 - Night sky depth
midnightBloom.purple; // #7c3aed - Soft purple glow
midnightBloom.violet; // #8b5cf6 - Lighter accent
midnightBloom.amber; // #f59e0b - Lantern warmth
midnightBloom.warmCream; // #fef3c7 - Tea steam, page glow
midnightBloom.softGold; // #fcd34d - Fairy lights
<section class="bg-gradient-to-b from-orange-950/50 via-purple-950 to-slate-950">
<!-- Stars -->
<StarCluster count={12} class="absolute top-12 left-[10%]" />
<!-- Moon -->
<Moon phase="waning-crescent" class="absolute top-20 right-[15%] w-16 h-16 opacity-60" />
<!-- Fireflies -->
<Firefly count={8} class="absolute inset-0" />
<!-- Content with purple glass -->
<blockquote class="bg-purple-900/30 backdrop-blur-sm border border-purple-700/30 rounded-lg p-6">
<p class="text-purple-200 italic">Dreamy quote here...</p>
</blockquote>
</section>
NEVER use emojis. ALWAYS use Lucide icons.
import {(MapPin, Check, Leaf, Trees, Mail)} from '@lucide/svelte';
<!-- Good -->
<MapPin class="w-4 h-4" />
<Check class="w-5 h-5 text-green-500" />
<!-- Bad - NEVER do this -->
<!-- ❌ 🌱 📧 ✅ -->
Use these icons consistently across the project:
| Concept | Icon | Notes |
|---|---|---|
| Navigation | ||
| Home | Home | |
| About | Info | |
| Vision | Telescope | Looking forward |
| Roadmap | Map | Journey/direction |
| Pricing | HandCoins |
Create a consistent icon map at the top of each component/page that uses icons:
// landing/src/lib/utils/icons.ts - Centralized icon registry
import {
Mail,
HardDrive,
Palette,
ShieldCheck,
Cloud,
SearchCode,
Archive,
Upload,
MessagesSquare,
Github,
Check,
X,
Loader2,
FileText,
Tag,
Sprout,
Heart,
ExternalLink,
MapPin, // ... etc
} from "@lucide/svelte";
export const featureIcons = {
mail: Mail,
harddrive: HardDrive,
palette: Palette,
shieldcheck: ShieldCheck,
cloud: Cloud,
searchcode: SearchCode,
// ... all mapped icons
} as const;
export const stateIcons = {
success: Check,
error: X,
loading: Loader2,
} as const;
Then use in components:
<script lang="ts">
import { featureIcons } from "$lib/utils/icons";
</script>
{#each features as feature}
<svelte:component this={featureIcons[feature.icon]} class="w-5 h-5" />
{/each}
Benefits:
landing/src/lib/utils/icons.ts for all icon sets<!-- Inline with text -->
<span class="inline-flex items-center gap-1.5">
<Leaf class="w-4 h-4" /> Feature name
</span>
<!-- Button icon -->
<button class="p-2">
<Menu class="w-5 h-5" />
</button>
<!-- Large decorative -->
<Gem class="w-8 h-8 text-amber-400" />
Philosophy: "The grove doesn't need to be drawn. It just needs to be arranged."
For creating custom logos, illustrations, or decorative elements, compose existing Lucide icons rather than drawing custom SVG from scratch. This ensures visual consistency with the icon system.
Lucide icons use a 24×24 viewBox with 2px strokes. Extract paths directly from source:
# Find icon paths in Lucide source
curl -s https://raw.githubusercontent.com/lucide-icons/lucide/main/icons/tree-pine.svg
# Look for the <path d="..." /> elements
Key Lucide icon paths for Grove compositions:
// TreePine - conifer silhouette
const treePine = {
canopy:
"m17 14 3 3.3a1 1 0 0 1-.7 1.7H4.7a1 1 0 0 1-.7-1.7L7 14h-.3a1 1 0 0 1-.7-1.7L9 9h-.2A1 1 0 0 1 8 7.3L12 3l4 4.3a1 1 0 0 1-.8 1.7H15l3 3.3a1 1 0 0 1-.7 1.7H17Z",
trunk: "M12 22v-3",
};
// TreeDeciduous - deciduous/round tree
const treeDeciduous = {
canopy:
"M8 19a4 4 0 0 1-2.24-7.32A3.5 3.5 0 0 1 9 6.03V6a3 3 0 1 1 6 0v.04a3.5 3.5 0 0 1 3.24 5.65A4 4 0 0 1 16 19Z",
trunk: "M12 19v3",
};
// Moon - crescent moon
const moon =
"M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401";
// Flame - campfire/hearth
const flame =
"M12 3q1 4 4 6.5t3 5.5a1 1 0 0 1-14 0 5 5 0 0 1 1-3 1 1 0 0 0 5 0c0-2-1.5-3-1.5-5q0-2 2.5-4";
Use <g transform="..."> to position, scale, and rotate icons:
<svg viewBox="0 0 48 32" fill="none">
<!-- Left tree (larger, foreground) -->
<g
transform="translate(2, 4) scale(0.85)"
stroke={color}
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d={treePine.canopy} />
<path d={treePine.trunk} />
</g>
<!-- Right tree (smaller, background, tilted) -->
<g
transform="translate(20, 8) scale(0.65) rotate(-5, 12, 12)"
stroke={color}
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
opacity="0.7"
>
<path d={treePine.canopy} />
<path d={treePine.trunk} />
</g>
<!-- Add simple custom elements sparingly -->
<circle cx="30" cy="10" r="1.5" fill={glowColor} opacity="0.8" />
<!-- firefly -->
<path d="M2 28h44" stroke={color} stroke-width="1.5" opacity="0.3" />
<!-- ground -->
</svg>
| Transform | Effect | Example |
|---|---|---|
translate(x, y) | Move origin | translate(20, 8) moves icon right 20, down 8 |
scale(s) | Uniform size | scale(0.65) makes icon 65% size |
rotate(deg, cx, cy) | Rotation around point | rotate(-5, 12, 12) tilts 5° around center |
| Combined | Chain transforms |
See /landing/src/lib/components/logo-concepts/ for real implementations:
| Logo | Composition |
|---|---|
LogoFireflyForest | TreePine + TreeDeciduous + glowing circles |
LogoGatheringHearth | Two trees angled toward center Flame |
LogoStarlightPines | Two TreePines + Moon + star circles |
LogoShelter | Two TreePines forming archway + Moon |
LogoWinterGrove | TreePines + snow line accents |
Desktop navigation items that don't fit should go to a mobile sheet menu:
<!-- Mobile menu button (visible md:hidden) -->
<button onclick={() => (mobileMenuOpen = true)} class="md:hidden p-2">
<Menu class="w-5 h-5" />
</button>
<!-- Sheet menu -->
<MobileMenu bind:open={mobileMenuOpen} onClose={() => (mobileMenuOpen = false)} />
| Element | Mobile Treatment |
|---|---|
| Trees | Reduce count, simplify (density multiplier = 1) |
| Particles | Reduce count (40→20 snowflakes) |
| Clouds | Hide some, keep 2-3 |
| Complex animations | Reduce or disable |
| Touch targets | Minimum 44x44px |
<!-- Reduce particle counts on mobile -->
<SnowfallLayer count={isLargeScreen ? 100 : 40} ... />
<!-- Skip complex effects for reduced-motion -->
{#if !prefersReducedMotion}
<FallingLeavesLayer ... />
{/if}
| Pattern | Good For |
|---|---|
| Glassmorphism | Text over backgrounds, navbars, cards, modals |
| Randomized forests | Story pages, about pages, visual sections |
| Seasonal themes | Roadmaps, timelines, emotional storytelling |
| Midnight Bloom | Future features, dreams, mystical content |
| Weather particles | Hero sections, transitions between seasons |
| Birds | Adding life to forest scenes, seasonal indicators |
| Pattern | Avoid When |
|---|---|
| Heavy decoration | Data-dense pages, admin interfaces, forms |
| Particle effects | Performance-critical pages, accessibility concerns |
| Seasonal colors | Brand-critical contexts needing consistent colors |
| Multiple glass layers | Can cause blur performance issues |
| Randomization | Content that needs to match between sessions |
| Complex forests | Mobile-first pages, simple informational content |
Study these for implementation patterns:
/forest — Full randomized forest with all seasons/roadmap — Seasonal sections, progressive decoration, midnight bloom/vision — Narrative page with glass calloutsGrove uses dynamic OG images for social media previews (Discord, Twitter, iMessage, etc.).
OG images are generated by a separate Cloudflare Worker at og.grove.place due to WASM bundling limitations with SvelteKit + Cloudflare Pages.
grove.place/api/og?title=X
↓ 302 redirect
og.grove.place/?title=X
↓ workers-og
PNG image (1200×630)
GET https://og.grove.place/?title=X&subtitle=Y&accent=HEX
| Param | Default | Description |
|---|---|---|
title | "Grove" | Main title (max 100 chars) |
subtitle | "A place to Be." | Subtitle (max 200 chars) |
accent | "16a34a" | Hex color without # (forest green) |
Use the SEO component which handles OG meta tags:
<script>
import SEO from "$lib/components/SEO.svelte";
</script>
<SEO
title="Page Title"
description="Page description for search engines"
ogImage="/api/og?title=Page%20Title&subtitle=Custom%20subtitle"
/>
services/og-worker/ — Standalone Worker (uses workers-og)landing/src/routes/api/og/+server.ts — Proxy to og.grove.placelanding/src/lib/components/SEO.svelte — Meta tag managementWhen writing text for Grove UI (tooltips, buttons, onboarding, error messages), invoke the grove-documentation skill first. The voice should match the visuals.
Typical flow:
grove-documentation for any user-facing textBefore shipping any Grove page, look at it. CI passing is not the same as looking correct. Use Glimpse to capture the rendered page and review it yourself:
# Capture the page with Grove theme injection
uv run --project tools/glimpse glimpse capture http://localhost:5173/[page] \
--season autumn --theme dark --logs
# Verify all season × theme combos
uv run --project tools/glimpse glimpse matrix http://localhost:5173/[page]
# Interactive browse — click around, verify navigation and flows
uv run --project tools/glimpse glimpse browse http://localhost:5173/[page] \
--do "click links, scroll down, interact with elements" --screenshot-each --logs
The iterate loop: Capture → review → fix → capture again. Repeat until the page matches the Grove aesthetic. Don't ship what you haven't seen.
Before shipping a Grove page:
matrix)--logs outputprefers-reduced-motion?grove-documentation)?[[term]] syntax with GroveText?Weekly Installs
53
Repository
GitHub Stars
2
First Seen
Feb 5, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode53
codex53
gemini-cli52
codebuddy51
claude-code51
github-copilot51
React视图过渡API使用指南:实现原生浏览器动画与状态管理
9,100 周安装
| Same parsing, silent swaps (no popups). |
GroveIntro | "We call it X" page banners | Standardized intro below page titles. |
overlay| Modal backdrops |
| 50% black |
| 60% black |
muted | Subtle backgrounds | 40% white | 30% slate |
| Harvest, reflection |
| Winter | winter (frost, snow, frosted pines) | Rest, stillness |
| Blossoms in spring, bare in winter |
season, animate |
TreeAspen / TreeBirch | Deciduous, seasonal colors | season, animate |
Cloud | Decorative sky element | variant, animate, speed, direction |
SnowfallLayer | Winter particles | count, opacity, spawnDelay |
FallingPetalsLayer | Spring cherry blossoms | count, opacity, fallDuration |
FallingLeavesLayer | Autumn leaves (tied to trees) | trees, season |
Cardinal / Chickadee | Winter birds | facing |
Robin / Bluebird | Spring birds | facing |
Vine | Decorative ivy/vines | varies |
Lantern | Warm glow points | varies |
| Money/currency |
| Knowledge | BookOpen | Learning/docs |
| Forest | Trees | Community blogs |
| Blog | PenLine | Writing |
| Features |
Mail |
| Storage | HardDrive |
| Theming | Palette | Customization |
| Authentication | ShieldCheck | Security |
| Cloud | Cloud | Remote/serverless |
| Search | SearchCode | Code/advanced search |
| Archives | Archive | Backups |
| Upload | Upload |
| Video | Video |
| Comments | MessagesSquare | User discussions |
| GitHub | Github | External links to GitHub |
| States |
| Success | Check | Completed/valid |
| Error | X | Failed/close |
| Loading | Loader2 | With animate-spin |
| Content |
| Posts | FileText | Blog posts |
| Tags | Tag | Categorization |
| Growth | Sprout | Grove brand, new beginnings |
| Heart | Heart | Love, care |
| External | ExternalLink | Opens new tab |
| Location | MapPin | Current position |
| Phases |
| Coming Soon | Seedling | Something growing |
| Refinement | Gem | Polish, quality |
| The Dream | Sparkles | Mystical (use sparingly!) |
| Night | Star | Midnight themes |
| Actions |
| Getting Started | Compass | Guidance |
| What's New | Megaphone | Announcements |
| Next Steps | Lightbulb | Ideas |
translate(20, 8) scale(0.65) rotate(-5, 12, 12) |