Skip to content

Instantly share code, notes, and snippets.

@danestves
Created February 23, 2026 20:01
Show Gist options
  • Select an option

  • Save danestves/893b54f523210d1cca741a7a81b2cfdb to your computer and use it in GitHub Desktop.

Select an option

Save danestves/893b54f523210d1cca741a7a81b2cfdb to your computer and use it in GitHub Desktop.
Vela — Minimalist Garden Strategy: The Pressed Botanical Collection

🌿 Vela — Minimalist Garden Strategy

"The Pressed Botanical Collection"

One document. One vision. One day to ship.


1. The Vision

We're killing the over-engineered Skia/Rive/Lottie garden. In its place: The Pressed Botanical Collection — a premium, minimalist scrapbook where your garden grows through layered botanical illustrations, not complex animation.

The feeling: Opening a leather-bound herbarium. Each page reveals a new pressed flower. Quiet. Elegant. Tactile.

Why this wins:

  • Zero exotic dependencies (no Skia, no Rive, no Lottie)
  • Ships in one day, not one sprint
  • Emotional resonance > technical spectacle
  • Works identically on iOS and Android with zero platform-specific code

2. Core Mechanics

2a. 5-Stage Growth via Layered PNGs

The garden is a stack of 5 absolutely-positioned images. Each layer fades/scales in when the user reaches the corresponding level.

Level 1: Seed        →  [bare soil]
Level 2: Sprout      →  + [sprout overlay]
Level 3: Growing     →  + [small plant overlay]
Level 4: Blooming    →  + [flowers overlay]
Level 5: Flourishing →  + [full bloom + butterflies]

All 5 PNGs share the same dimensions — they simply stack. The visual result is a garden that "fills in" organically. Placeholder rectangles work for day-one dev; real botanical art swaps in later.

2b. GP Calculation — Category Count

No formulas. No weights. No balancing spreadsheets.

Action GP
Daily check-in +10
Complete a session/lesson +25
7-day streak bonus +50
First-time achievement +30

Level thresholds (linear):

Level Name GP Required
1 Seed 0
2 Sprout 50
3 Growing 150
4 Blooming 350
5 Flourishing 700

A single pure function computes level from total GP. No state management library. No backend call.

2c. Rewards — Botanical Stamps

Each completed cycle (reaching level 5) awards a botanical stamp — a collectible card in a grid gallery. Stamps are per-cycle-phase (menstrual, follicular, ovulation, luteal), giving 4 unique illustrations per set.

  • Unlocked: Full-color botanical PNG with phase-accent border on cream/paper background.
  • Locked: Grayscale silhouette + 🔒.
  • Tap interaction: Card flips (rotateY) to reveal a wellness tip.

This is the long-term retention loop: the garden resets each cycle, but stamps are permanent.


3. Technical Implementation — The "Zero-Friction" Plan

3a. expo-image Cross-Fades

import { Image } from 'expo-image';

<Image
  source={layerSource}
  contentFit="contain"
  transition={300}              // built-in crossfade
  cachePolicy="memory-disk"     // aggressive caching
  recyclingKey={`layer-${i}`}
  style={StyleSheet.absoluteFill}
/>

Why expo-image: shared memory cache, built-in transitions, blurhash placeholders, better perf on large PNGs than RN Image.

3b. Reanimated Simple Transforms (Scale + Fade)

import Animated, {
  useSharedValue, useAnimatedStyle,
  withTiming, withSpring
} from 'react-native-reanimated';

function GardenLayer({ source, visible }: { source: any; visible: boolean }) {
  const opacity = useSharedValue(0);
  const scale = useSharedValue(0.8);

  useEffect(() => {
    if (visible) {
      opacity.value = withTiming(1, { duration: 600 });
      scale.value = withSpring(1, { damping: 15, stiffness: 150 });
    }
  }, [visible]);

  const style = useAnimatedStyle(() => ({
    opacity: opacity.value,
    transform: [{ scale: scale.value }],
  }));

  return (
    <Animated.View style={[StyleSheet.absoluteFill, style]}>
      <Image source={source} style={StyleSheet.absoluteFill} contentFit="contain" />
    </Animated.View>
  );
}

That's the entire animation system. Two shared values. Spring easing for organic feel.

3c. Minimal Drizzle Schema

// drizzle/schema/garden.ts
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';

export const unlockedItems = sqliteTable('unlocked_items', {
  id:           text('id').primaryKey(),          // uuid
  userId:       text('user_id').notNull(),
  itemKey:      text('item_key').notNull(),       // e.g. "stamp_follicular_lavender"
  itemType:     text('item_type').notNull(),      // 'stamp' | 'badge'
  unlockedAt:   integer('unlocked_at', { mode: 'timestamp' }),
  metadata:     text('metadata'),                 // JSON blob for extensibility
});

One table. That's the entire persistence layer for collectibles. GP total lives in AsyncStorage as a single integer — no table needed.

// GP persistence — AsyncStorage, nothing more
async function addGP(points: number): Promise<GPState> {
  const raw = await AsyncStorage.getItem('gp_total');
  const newTotal = (raw ? parseInt(raw, 10) : 0) + points;
  await AsyncStorage.setItem('gp_total', String(newTotal));
  return computeGPState(newTotal);
}

3d. Premium Feel — Zero Extra Code

Pattern Implementation
Haptics Haptics.impactAsync(Medium) on unlock, Light on tap
Spring physics withSpring({ damping: 15, stiffness: 150 }) everywhere
Staggered entry delay: index * 100 on stamp grid items
Muted palette Sage greens, dusty pinks, warm cream
Typography Serif for names, sans-serif for body
Whitespace ≥ 20px padding between all cards

4. One-Day MVP Roadmap

Morning — Build the Engine (3h)

Block Task Output
0:00–0:30 Create 5 placeholder PNGs (colored rectangles) assets/garden-*.png
0:30–1:30 Build GardenScene — layered image stack component Working visual
1:30–2:30 Add Reanimated fade/scale per layer Animated transitions
2:30–3:00 Build GardenScreen with level label + progress bar Navigable screen

Afternoon — Wire the Logic (3h)

Block Task Output
3:00–3:45 Implement computeGPState() pure function + level thresholds GP engine
3:45–4:30 Wire GP to AsyncStorage (addGP / read on mount) Persistence
4:30–5:30 Level-up celebration: haptic + scale bounce + emoji burst Delight
5:30–6:00 Polish: progress bar animation, nav entry point, debug +GP button Shippable MVP

Deliverable

A working screen where:

  • The garden visually grows as GP accumulates (5 layers)
  • GP persists across sessions
  • Level-ups trigger haptic + animation celebration
  • A debug button allows manual GP injection for testing

What's NOT in Day 1

  • Real botanical art (placeholders are fine)
  • Stamp collection gallery (day 2)
  • Backend sync (AsyncStorage is enough)
  • Sound effects (nice-to-have, not MVP)

Architecture Summary

Decision Choice Rationale
Rendering Layered PNGs + absolute positioning Visual depth, trivial code
Animation react-native-reanimated spring/timing Already in Expo, 60fps native thread
Images expo-image Cache, transitions, perf
GP Engine Pure function + AsyncStorage Zero dependencies
Collectibles Drizzle unlocked_items table One table, extensible
Levels Fixed 5-tier linear thresholds No balancing math
Killed Skia, Rive, Lottie, Zustand, Redux, backend API 🎯

Built for Vela. Ship today, enchant tomorrow.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment