v0.1 public beta early access on GitHub
Home / Screens / health-quest-complete
Screen template

Quest Complete Screen

Celebration beat: confetti drift, trophy glass disc, payout pills, milestone track filling toward next level

pro Expo Go ✓ iosandroid gestures: tap haptics: medium · selection
$ npx lumioui add health-quest-complete
import { QuestCompleteScreen } from "@/screens/health/quest-complete";
Registry dependencies3
Native packages installed6
@lumioui/ui@latest@lumioui/core@latestreact-native-safe-area-context@*react-native-reanimated@*react-native-svg@*nativewind@*
health/quest-complete.tsxsource · yours to edit
import * as React from "react";
import { View } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { Button, Glass, Text } from "@lumioui/ui";
import { useHaptic } from "@lumioui/core";
import { Drift, HeroGradient, Mount, Press, SERIF, StickyBottomBar } from "../shared";

const CONFETTI = [
  { emoji: "🎉", cls: "-left-2 top-16", size: "text-[48px]", rotate: -14 },
  { emoji: "✨", cls: "right-6 top-10", size: "text-[40px]", rotate: 10 },
  { emoji: "💎", cls: "-right-3 top-44", size: "text-[52px]", rotate: 8 },
  { emoji: "⚡", cls: "left-4 top-72", size: "text-[44px]", rotate: -6 },
  { emoji: "🏅", cls: "right-14 top-[340px]", size: "text-[36px]", rotate: 14 },
  { emoji: "🎊", cls: "left-10 bottom-56", size: "text-[50px]", rotate: 0 },
  { emoji: "🪙", cls: "right-4 bottom-48", size: "text-[42px]", rotate: -12 },
] as const;

const XP_NOW = 135;
const XP_MAX = 150;
const MILESTONES = [30, 60, 90, 120, 150];

/** Quest complete — the celebration beat after a day's quest resolves:
 *  confetti drift over the sky world, trophy disc, payout pills, and the
 *  milestone track showing the XP this win added. Sibling to habit-quest. */
export function QuestCompleteScreen({
  onClaim,
  onShare,
}: {
  onClaim?: () => void;
  onShare?: () => void;
}) {
  const haptics = useHaptic();

  return (
    <View className="flex-1 bg-[#A8D9F2]">
      <HeroGradient from="#A8D9F2" to="#EAF6FD" />
      {CONFETTI.map((o, i) => (
        <Drift key={o.emoji} emoji={o.emoji} cls={o.cls} size={o.size} rotate={o.rotate} index={i} />
      ))}

      <SafeAreaView className="flex-1" edges={["top", "bottom"]}>
        <View className="flex-1 items-center justify-center gap-6 px-8">
          <Mount className="items-center gap-6">
            {/* Trophy disc — frosted glass over the confetti. */}
            <View className="h-28 w-28 items-center justify-center overflow-hidden rounded-full">
              <Glass effect="regular" sheen={0.6} className="absolute inset-0 rounded-full" pointerEvents="none" />
              <View className="absolute inset-2 rounded-full border-2 border-white/50" pointerEvents="none" />
              <Text className="text-[52px]">🏆</Text>
            </View>
            <View className="items-center">
              <Text className="text-center text-[34px] font-bold leading-tight tracking-tight text-[#0B3B5C]">
                Quest{" "}
                <Text
                  style={{ fontFamily: SERIF, fontStyle: "italic" }}
                  className="text-[34px] font-bold text-[#0B3B5C]"
                >
                  complete
                </Text>
              </Text>
              <Text className="mt-2 text-center text-[15px] leading-6 text-[#0B3B5C]/60">
                Morning run logged — you're on a{"\n"}4-day streak, James.
              </Text>
            </View>

            {/* Payout pills. */}
            <View className="flex-row gap-2">
              {[
                { emoji: "⚡", label: "+40 XP" },
                { emoji: "💎", label: "+12" },
                { emoji: "🔥", label: "Day 4" },
              ].map((p) => (
                <View key={p.label} className="overflow-hidden rounded-full">
                  <Glass effect="regular" className="absolute inset-0 rounded-full" pointerEvents="none" />
                  <View className="flex-row items-center gap-1.5 px-3.5 py-2">
                    <Text>{p.emoji}</Text>
                    <Text className="text-[13px] font-bold text-[#0B3B5C]">{p.label}</Text>
                  </View>
                </View>
              ))}
            </View>
          </Mount>

          {/* Milestone track — the XP just earned filling toward Level 5. */}
          <Mount delay={140} className="w-full">
            <View className="overflow-hidden rounded-3xl">
              <Glass effect="regular" tintColor="#D8F1DC" className="absolute inset-0 rounded-3xl" pointerEvents="none" />
              <View className="gap-3 p-4">
                <View className="flex-row items-center justify-between">
                  <Text className="text-[13px] font-bold text-[#14532D]">👑 Level 5 — almost</Text>
                  <Text className="text-[12px] font-semibold text-[#14532D]/60">⚡ {XP_NOW}/{XP_MAX} XP</Text>
                </View>
                <View className="relative h-8 justify-center">
                  <View className="h-1.5 rounded-full bg-[#15803D]/15" />
                  <View
                    className="absolute h-1.5 rounded-full bg-[#22C55E]"
                    style={{ width: `${(XP_NOW / XP_MAX) * 96}%` }}
                  />
                  <View className="absolute inset-x-0 flex-row justify-between">
                    {MILESTONES.map((m) => (
                      <View
                        key={m}
                        className="h-8 w-8 items-center justify-center rounded-full"
                        style={{
                          backgroundColor: m <= XP_NOW ? "#22C55E" : "#DCF5E3",
                        }}
                      >
                        <Text className="text-[11px]">{m <= XP_NOW ? "✓" : "🏆"}</Text>
                      </View>
                    ))}
                  </View>
                </View>
                <Text className="text-[12px] text-[#14532D]/60">
                  15 XP to the 🏆 milestone — one more quest does it.
                </Text>
              </View>
            </View>
          </Mount>
        </View>

        <StickyBottomBar className="px-6">
          <Button
            size="lg"
            className="w-full bg-[#0B3B5C]"
            textClassName="text-white"
            onPress={() => {
              haptics.medium();
              onClaim?.();
            }}
            accessibilityLabel="Claim rewards"
          >
            Claim rewards
          </Button>
          <Press
            onPress={() => {
              haptics.selection();
              onShare?.();
            }}
            accessibilityRole="button"
            accessibilityLabel="Share result"
            className="mt-1 items-center py-2"
          >
            <Text className="text-[14px] font-semibold text-[#0B3B5C]/70">Share streak</Text>
          </Press>
        </StickyBottomBar>
      </SafeAreaView>
    </View>
  );
}
QuestCompleteScreen.displayName = "QuestCompleteScreen";
agent prompt — paste into Claude Code, Cursor, Devin…
Recreate the "Quest Complete Screen" screen in React Native using LumioUI.

Goal: Celebration beat: confetti drift, trophy glass disc, payout pills, milestone track filling toward next level Match the reference implementation's layout, behavior, and motion exactly — this is a production screen, not a mockup.

## Setup

Fastest path — install the screen plus its component deps:

```bash
npx lumioui add health-quest-complete
```

Or pull just the components: `npx lumioui add button glass text`

Native packages:
- @lumioui/ui@latest
- @lumioui/core@latest
- react-native-safe-area-context@*
- react-native-reanimated@*
- react-native-svg@*
- nativewind@*

Stack assumptions: Expo SDK 54 / RN 0.81, Reanimated 4, NativeWind v4, TypeScript strict, Expo Go compatible.

## Components — how, where, when

### `button` — Tactile pill button with press spring, ink wash and async loading.
- `<Button>` ×1

### `glass` — iOS 26 liquid-glass surface — real glass via expo-glass-effect (Expo Go) or @callstack/liquid-glass (dev builds), SVG refraction + specular rim on web, frosted fallback elsewhere; GlassContainer melts adjacent panes into one blob
- `<Glass>` ×3

### `text` — Themed type scale from caption to display.
- `<Text>` ×11

## Conventions (non-negotiable)

- Styling via NativeWind `className` with semantic tokens only: `bg-background`, `text-foreground`, `bg-card`, `text-card-foreground`, `bg-secondary`, `bg-muted`, `text-muted-foreground`, `bg-accent`, `bg-destructive`, `border-border`. Hard-coded colors only inside media overlays (`text-white`, `bg-black/N`).
- Motion via the token presets from `@lumioui/core` (`spring.press`, `spring.surface`, `spring.glide`, `timing.fast`, `timing.slow`, …) — no bespoke spring configs or durations.
- Gate transform motion behind `useReducedMotion()`; keep opacity/color feedback, drop travel and overshoot.
- Haptics via `useHaptic()`: `light` on press, `selection` on toggle/select, `warning` on invalid input. Never import expo-haptics directly.
- Press feedback via `usePressAnimation()` (~0.97 scale), not `activeOpacity` tricks.
- a11y: `accessibilityRole` + `accessibilityLabel` on every control, touch targets ≥44pt.
- Entrance choreography: staggered `Mount` wrappers (delay 0/60/120…) so regions land in sequence instead of popping in at once.
- Wrap the screen in `SafeAreaView`; keep dark mode working via the semantic tokens.

## Deliverable

One file: `screens/health/quest-complete.tsx` exporting QuestCompleteScreen. Preserve the props interface — the host app wires the callbacks (navigation, submit handlers) through them.

## Reference implementation

The LumioUI source for this screen — reproduce it faithfully:

```tsx
import * as React from "react";
import { View } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { Button, Glass, Text } from "@lumioui/ui";
import { useHaptic } from "@lumioui/core";
import { Drift, HeroGradient, Mount, Press, SERIF, StickyBottomBar } from "../shared";

const CONFETTI = [
  { emoji: "🎉", cls: "-left-2 top-16", size: "text-[48px]", rotate: -14 },
  { emoji: "✨", cls: "right-6 top-10", size: "text-[40px]", rotate: 10 },
  { emoji: "💎", cls: "-right-3 top-44", size: "text-[52px]", rotate: 8 },
  { emoji: "⚡", cls: "left-4 top-72", size: "text-[44px]", rotate: -6 },
  { emoji: "🏅", cls: "right-14 top-[340px]", size: "text-[36px]", rotate: 14 },
  { emoji: "🎊", cls: "left-10 bottom-56", size: "text-[50px]", rotate: 0 },
  { emoji: "🪙", cls: "right-4 bottom-48", size: "text-[42px]", rotate: -12 },
] as const;

const XP_NOW = 135;
const XP_MAX = 150;
const MILESTONES = [30, 60, 90, 120, 150];

/** Quest complete — the celebration beat after a day's quest resolves:
 *  confetti drift over the sky world, trophy disc, payout pills, and the
 *  milestone track showing the XP this win added. Sibling to habit-quest. */
export function QuestCompleteScreen({
  onClaim,
  onShare,
}: {
  onClaim?: () => void;
  onShare?: () => void;
}) {
  const haptics = useHaptic();

  return (
    <View className="flex-1 bg-[#A8D9F2]">
      <HeroGradient from="#A8D9F2" to="#EAF6FD" />
      {CONFETTI.map((o, i) => (
        <Drift key={o.emoji} emoji={o.emoji} cls={o.cls} size={o.size} rotate={o.rotate} index={i} />
      ))}

      <SafeAreaView className="flex-1" edges={["top", "bottom"]}>
        <View className="flex-1 items-center justify-center gap-6 px-8">
          <Mount className="items-center gap-6">
            {/* Trophy disc — frosted glass over the confetti. */}
            <View className="h-28 w-28 items-center justify-center overflow-hidden rounded-full">
              <Glass effect="regular" sheen={0.6} className="absolute inset-0 rounded-full" pointerEvents="none" />
              <View className="absolute inset-2 rounded-full border-2 border-white/50" pointerEvents="none" />
              <Text className="text-[52px]">🏆</Text>
            </View>
            <View className="items-center">
              <Text className="text-center text-[34px] font-bold leading-tight tracking-tight text-[#0B3B5C]">
                Quest{" "}
                <Text
                  style={{ fontFamily: SERIF, fontStyle: "italic" }}
                  className="text-[34px] font-bold text-[#0B3B5C]"
                >
                  complete
                </Text>
              </Text>
              <Text className="mt-2 text-center text-[15px] leading-6 text-[#0B3B5C]/60">
                Morning run logged — you're on a{"\n"}4-day streak, James.
              </Text>
            </View>

            {/* Payout pills. */}
            <View className="flex-row gap-2">
              {[
                { emoji: "⚡", label: "+40 XP" },
                { emoji: "💎", label: "+12" },
                { emoji: "🔥", label: "Day 4" },
              ].map((p) => (
                <View key={p.label} className="overflow-hidden rounded-full">
                  <Glass effect="regular" className="absolute inset-0 rounded-full" pointerEvents="none" />
                  <View className="flex-row items-center gap-1.5 px-3.5 py-2">
                    <Text>{p.emoji}</Text>
                    <Text className="text-[13px] font-bold text-[#0B3B5C]">{p.label}</Text>
                  </View>
                </View>
              ))}
            </View>
          </Mount>

          {/* Milestone track — the XP just earned filling toward Level 5. */}
          <Mount delay={140} className="w-full">
            <View className="overflow-hidden rounded-3xl">
              <Glass effect="regular" tintColor="#D8F1DC" className="absolute inset-0 rounded-3xl" pointerEvents="none" />
              <View className="gap-3 p-4">
                <View className="flex-row items-center justify-between">
                  <Text className="text-[13px] font-bold text-[#14532D]">👑 Level 5 — almost</Text>
                  <Text className="text-[12px] font-semibold text-[#14532D]/60">⚡ {XP_NOW}/{XP_MAX} XP</Text>
                </View>
                <View className="relative h-8 justify-center">
                  <View className="h-1.5 rounded-full bg-[#15803D]/15" />
                  <View
                    className="absolute h-1.5 rounded-full bg-[#22C55E]"
                    style={{ width: `${(XP_NOW / XP_MAX) * 96}%` }}
                  />
                  <View className="absolute inset-x-0 flex-row justify-between">
                    {MILESTONES.map((m) => (
                      <View
                        key={m}
                        className="h-8 w-8 items-center justify-center rounded-full"
                        style={{
                          backgroundColor: m <= XP_NOW ? "#22C55E" : "#DCF5E3",
                        }}
                      >
                        <Text className="text-[11px]">{m <= XP_NOW ? "✓" : "🏆"}</Text>
                      </View>
                    ))}
                  </View>
                </View>
                <Text className="text-[12px] text-[#14532D]/60">
                  15 XP to the 🏆 milestone — one more quest does it.
                </Text>
              </View>
            </View>
          </Mount>
        </View>

        <StickyBottomBar className="px-6">
          <Button
            size="lg"
            className="w-full bg-[#0B3B5C]"
            textClassName="text-white"
            onPress={() => {
              haptics.medium();
              onClaim?.();
            }}
            accessibilityLabel="Claim rewards"
          >
            Claim rewards
          </Button>
          <Press
            onPress={() => {
              haptics.selection();
              onShare?.();
            }}
            accessibilityRole="button"
            accessibilityLabel="Share result"
            className="mt-1 items-center py-2"
          >
            <Text className="text-[14px] font-semibold text-[#0B3B5C]/70">Share streak</Text>
          </Press>
        </StickyBottomBar>
      </SafeAreaView>
    </View>
  );
}
QuestCompleteScreen.displayName = "QuestCompleteScreen";
```

Screen: https://lumioui.dev/screens/health-quest-complete
iPhone 17 · iOS
9:41
Live preview — the real screen via Expo Web · open full ↗