Skip to content

Claude/merge to main lsw dn#985

Open
executiveusa wants to merge 9 commits intoseanmorley15:mainfrom
executiveusa:claude/merge-to-main-lswDN
Open

Claude/merge to main lsw dn#985
executiveusa wants to merge 9 commits intoseanmorley15:mainfrom
executiveusa:claude/merge-to-main-lswDN

Conversation

@executiveusa
Copy link
Copy Markdown

No description provided.

executiveusa and others added 8 commits January 30, 2026 15:10
…locations

- Agua Azul waterfall (by Intricate Explorer)
- Monarch Butterfly Sanctuary (by kristhel kantún)
- Tepoztlán historic pyramid (by Abimelec Castillo)
- Xoxocotlán Market flowers (by Leonardo Iribe)
- Dinner at Los Manantiales (by Hybrid Storytellers)
- Butterfly Garden finale (by Jameson Quisenberry)

All images properly attributed with links to photographer profiles
- Install canvas-confetti and all frontend dependencies
- Copy itinerary JSON to static folder for web accessibility
- Build and verify all 6 password-protected surprise steps
- Confirm Google Maps integration for all locations
- Verify bilingual content (ES/EN) display correctly
- Test confetti animations on unlock
- Ensure all Unsplash images load properly
- Validate SVG blur placeholders for locked states

All features tested and working:
✅ Password protection system
✅ Progress tracking with localStorage
✅ Confetti celebrations on unlock
✅ Google Maps navigation links
✅ Responsive design for mobile/desktop
✅ Bilingual treasure hunt flow

Route: /surprise/yvette-cuernavaca

https://claude.ai/code/session_01V8xffavCQm3HHWRRTXxqAe
…stness

CRITICAL FIXES:
- Fix Svelte reactivity bug in progress tracking
  * Changed Set.add() mutation to new Set() assignment in SurpriseRevealFlow
  * Progress bar and completion message now update in real-time as steps unlock
  * Prevents stale UI state during user session

- Remove prop mutation anti-pattern in StepCard
  * Deleted direct assignment to isUnlocked prop
  * Parent component now has full control over unlock state
  * Follows Svelte best practices for one-way data flow

CODE QUALITY IMPROVEMENTS:
- Add graceful image error handling
  * Display gradient fallback with location icon when Unsplash photos fail
  * Prevents broken image icons from appearing
  * Better user experience during network issues

- Improve accessibility in PasswordUnlockModal
  * Add Escape key handler to close modal via keyboard
  * Add ARIA role and label for screen readers
  * Full keyboard navigation support

- Add localStorage error handling
  * Wrap all localStorage operations in try-catch blocks
  * Graceful degradation in private browsing mode
  * Console warnings for debugging without breaking app

- Remove unused code
  * Delete unused onMount import from StepCard
  * Delete unused imageLoaded variable
  * Remove unused password parameter from functions

TESTING:
✅ All 6 steps unlock correctly with progress updates
✅ Confetti animations trigger on unlock
✅ Completion message appears after unlocking all steps
✅ Progress persists across page reloads
✅ Image fallbacks work when photos fail to load
✅ Keyboard accessibility verified (Escape key closes modal)
✅ Build completes successfully with no errors

https://claude.ai/code/session_01V8xffavCQm3HHWRRTXxqAe
Copilot AI review requested due to automatic review settings February 1, 2026 14:15
@vercel
Copy link
Copy Markdown

vercel bot commented Feb 1, 2026

@claude is attempting to deploy a commit to the Sean Morley's Projects Team on Vercel.

A member of the Team first needs to authorize it.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a special “Yvette Cuernavaca” surprise itinerary experience on the frontend, including a password-gated multi-step reveal flow with progress persistence, visual assets, and deployment configuration for Vercel. It also adds confetti effects and design tokens, and does some minor cleanup/formatting and dependency updates.

Changes:

  • Added a dedicated /surprise/yvette-cuernavaca route that renders a new SurpriseRevealFlow experience backed by a static JSON itinerary and new SVG background assets.
  • Implemented surprise-step UI components (StepCard, PasswordUnlockModal, language block, progress persistence hook, design tokens/rules) and wired them to the itinerary data and localStorage-based progress tracking.
  • Added Vercel configuration for building the frontend sub-app, updated frontend dependencies (including canvas-confetti and typings), and performed minor formatting/whitespace adjustments.

Reviewed changes

Copilot reviewed 14 out of 47 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
vercel.json New Vercel config to build the SvelteKit frontend (cd frontend && npm run build) and point Vercel to the frontend/.vercel/output directory.
package-lock.json New root-level npm lockfile (no matching root package.json, as the main app lives under frontend/).
frontend/static/surprise/yvette/step1.svg Static SVG background for surprise step 1 (“Las Huertas – Step 1”).
frontend/static/surprise/yvette/step1-blur.svg Blurred SVG variant for locked state of step 1.
frontend/static/surprise/yvette/step2.svg Static SVG background for surprise step 2 (“Barranca – Step 2”).
frontend/static/surprise/yvette/step2-blur.svg Blurred SVG variant for locked state of step 2.
frontend/static/surprise/yvette/step3.svg Static SVG background for surprise step 3 (“Catedral – Step 3”).
frontend/static/surprise/yvette/step3-blur.svg Blurred SVG variant for locked state of step 3.
frontend/static/surprise/yvette/step4.svg Static SVG background for surprise step 4 (“Jardín Borda – Step 4”).
frontend/static/surprise/yvette/step4-blur.svg Blurred SVG variant for locked state of step 4.
frontend/static/surprise/yvette/step5.svg Static SVG background for surprise step 5 (“Los Manantiales – Step 5”).
frontend/static/surprise/yvette/step5-blur.svg Blurred SVG variant for locked state of step 5.
frontend/static/surprise/yvette/step6.svg Static SVG background for surprise step 6 (“Mariposario – Step 6”).
frontend/static/surprise/yvette/step6-blur.svg Blurred SVG variant for locked state of step 6.
frontend/static/surprise/itinerary/yvette-cuernavaca.json Public static JSON defining the six-step bilingual itinerary used by the surprise flow via fetch('/surprise/itinerary/...').
frontend/src/lib/surprise/useSurpriseProgress.ts New helper that persists unlocked step numbers in localStorage and exposes get/save/reset APIs for surprise progress.
frontend/src/lib/surprise/types.ts TypeScript Step interface describing the itinerary item shape (titles, clues, descriptions, media, password, etc.).
frontend/src/lib/surprise/itinerary/yvette-cuernavaca.json Library copy of the same itinerary JSON data, parallel to the static version.
frontend/src/lib/surprise/design/tokens.ts Design tokens for the surprise flow (colors, gradients, spacing, shadows, animation timing).
frontend/src/lib/surprise/design/rules.md Internal design & implementation guidelines for the surprise card layout, typography, states, and accessibility.
frontend/src/lib/surprise/SurpriseRevealFlow.svelte Main surprise-flow container: loads itinerary JSON on mount, restores progress from useSurpriseProgress, renders StepCard grid and completion message, and applies page-level styling.
frontend/src/lib/surprise/StepCard.svelte Card component for each surprise step, handling locked/unlocked rendering, image loading, confetti trigger on unlock, and “Navigate” link to Google Maps.
frontend/src/lib/surprise/PasswordUnlockModal.svelte Modal that prompts for a step-specific password, tracks attempts, and emits unlocked/close events to the parent.
frontend/src/lib/surprise/LanguageBlock.svelte Small helper component for consistent Spanish/English text blocks with custom font stacks and sizing.
frontend/src/routes/surprise/yvette-cuernavaca/+page.svelte New route file that sets <head> metadata and renders the SurpriseRevealFlow component.
frontend/src/routes/transportations/[id]/+page.svelte Minor reactive statement formatting change for computing showLocalTripTime (no behavioral change).
frontend/src/routes/collections/+page.server.ts Whitespace cleanup inside the actions export (no functional change).
frontend/src/locales/en.json Re-indented and reformatted English locale file without changing keys or messages.
frontend/package.json Added canvas-confetti and @types/canvas-confetti to dependencies/devDependencies for the new confetti effect.
frontend/package-lock.json Updated lockfile to include canvas-confetti, @types/canvas-confetti, yaml, and an adjusted postcss-load-config version compatible with the existing dependency ranges.
Files not reviewed (1)
  • frontend/package-lock.json: Language not supported

Comment on lines +1 to +43
<script lang="ts">
import confetti from 'canvas-confetti';
import { fly, fade } from 'svelte/transition';
import PasswordUnlockModal from './PasswordUnlockModal.svelte';

export let step: number;
export let title_es: string;
export let title_en: string;
export let clue_es: string;
export let clue_en: string;
export let description_es: string;
export let description_en: string;
export let locationName: string;
export let googleMapsUrl: string;
export let photo: string;
export let photoBlur: string;
export let password: string;
export let isUnlocked: boolean = false;
export let onUnlock: () => void = () => {};

let showPasswordModal = false;
let imageError = false;

function handleUnlockClick() {
showPasswordModal = true;
}

function handlePasswordUnlocked() {
showPasswordModal = false;
onUnlock();

// Trigger confetti
if (typeof window !== 'undefined') {
confetti({
particleCount: 150,
spread: 70,
origin: { x: 0.5, y: 0.5 },
colors: ['#9333ea', '#ec4899', '#8b5cf6', '#f472b6'],
gravity: 0.8,
ticks: 80
});
}
}
Copy link

Copilot AI Feb 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

canvas-confetti is imported at the top level of this Svelte component, which means it will also be loaded in the SSR context; in this codebase, other browser-only libraries (for example emoji-picker-element in frontend/src/lib/components/CategoryDropdown.svelte:85-92 and CategoryModal.svelte:20-25) are loaded via dynamic import inside onMount to avoid server-side failures. To stay consistent and avoid potential SSR/runtime errors if canvas-confetti touches window or document at module initialization, consider switching to a dynamic import('canvas-confetti') inside the unlock handler or an onMount block and caching the loaded function.

Copilot uses AI. Check for mistakes.
Comment on lines +41 to +53
role="button"
tabindex="-1"
aria-label="Close modal"
>
<div
class="bg-white rounded-2xl p-8 max-w-sm w-full mx-4 shadow-2xl"
on:click|stopPropagation
transition:fly={{ y: 20 }}
>
<div class="text-center">
<div class="text-5xl mb-4">🔐</div>
<h2 class="text-xl font-bold mb-2 text-gray-800">Unlock Step {stepNumber}</h2>
<p class="text-sm text-gray-600 mb-6">Enter the password to reveal this surprise</p>
Copy link

Copilot AI Feb 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The overlay div for the password modal is given role="button" and aria-label="Close modal", but it visually represents a modal backdrop rather than an interactive button, which is confusing for assistive technologies and does not expose a proper dialog role. For better accessibility, the backdrop should be a neutral container (no button role/label), and the inner content should be exposed as a role="dialog" (or use the native <dialog> element) with aria-modal="true" and an accessible title, while keeping Escape and click-outside behavior on the backdrop or window.

Suggested change
role="button"
tabindex="-1"
aria-label="Close modal"
>
<div
class="bg-white rounded-2xl p-8 max-w-sm w-full mx-4 shadow-2xl"
on:click|stopPropagation
transition:fly={{ y: 20 }}
>
<div class="text-center">
<div class="text-5xl mb-4">🔐</div>
<h2 class="text-xl font-bold mb-2 text-gray-800">Unlock Step {stepNumber}</h2>
<p class="text-sm text-gray-600 mb-6">Enter the password to reveal this surprise</p>
>
<div
class="bg-white rounded-2xl p-8 max-w-sm w-full mx-4 shadow-2xl"
on:click|stopPropagation
transition:fly={{ y: 20 }}
role="dialog"
aria-modal="true"
aria-labelledby="password-unlock-title"
aria-describedby="password-unlock-description"
>
<div class="text-center">
<div class="text-5xl mb-4">🔐</div>
<h2
id="password-unlock-title"
class="text-xl font-bold mb-2 text-gray-800"
>
Unlock Step {stepNumber}
</h2>
<p
id="password-unlock-description"
class="text-sm text-gray-600 mb-6"
>
Enter the password to reveal this surprise
</p>

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +92
[
{
"step": 1,
"day": 1,
"title_es": "Tu primera sorpresa: un oasis termal escondido…",
"title_en": "Your first surprise: a hidden thermal oasis…",
"clue_es": "Hoy comenzamos con calma, agua tibia y naturaleza. La primera magia de nuestro viaje.",
"clue_en": "We begin with warmth, water, and nature. The first magic of our journey.",
"locationName": "Las Huertas Thermal Springs",
"googleMapsUrl": "https://maps.google.com/?q=Las+Huertas+Morelos+Mexico",
"photo": "https://images.unsplash.com/photo-1656352451218-202ab9bd09a6?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w4NjYwMTR8MHwxfHNlYXJjaHwxfHx3YXRlcmZhbGwlMjBBZ3VhJTIwQXp1bCUyME1leGljb3xlbnwwfDB8fHwxNzY5ODQ5MTg5fDA&ixlib=rb-4.1.0&q=80&w=1080",
"photoBlur": "/surprise/yvette/step1-blur.svg",
"password": "AGUA2026",
"description_es": "Sumérgate en aguas termales naturales rodeadas de vegetación exuberante.",
"description_en": "Immerse yourself in natural thermal waters surrounded by lush vegetation."
},
{
"step": 2,
"day": 1,
"title_es": "Un paseo secreto entre barrancas verdes…",
"title_en": "A hidden ravine walk inside the city…",
"clue_es": "Entre los árboles y el sonido del agua, descubriremos paz.",
"clue_en": "Among the trees and the sound of water, we'll discover peace.",
"locationName": "Barranca de Amanalco",
"googleMapsUrl": "https://maps.google.com/?q=Barranca+de+Amanalco+Cuernavaca",
"photo": "https://images.unsplash.com/photo-1710196913619-75570747478a?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w4NjYwMTR8MHwxfHNlYXJjaHwxfHxtb25hcmNoJTIwYnV0dGVyZmx5JTIwZm9yZXN0JTIwTWV4aWNvfGVufDB8MHx8fDE3Njk4NDkxNzB8MA&ixlib=rb-4.1.0&q=80&w=1080",
"photoBlur": "/surprise/yvette/step2-blur.svg",
"password": "BOSQUE2026",
"description_es": "Un refugio natural de serenidad a pocos minutos del centro.",
"description_en": "A natural sanctuary of serenity within minutes of the city center."
},
{
"step": 3,
"day": 1,
"title_es": "Un patio histórico en silencio dorado…",
"title_en": "A peaceful historic courtyard in golden light…",
"clue_es": "Las piedras cuentan historias de siglos pasados.",
"clue_en": "The stones tell stories of centuries past.",
"locationName": "Catedral de Cuernavaca",
"googleMapsUrl": "https://maps.google.com/?q=Catedral+de+Cuernavaca+Mexico",
"photo": "https://images.unsplash.com/photo-1581388231958-4e1978771a61?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w4NjYwMTR8MHwxfHNlYXJjaHwxfHxUZXBvenRsYW4lMjBNZXhpY28lMjBtb3VudGFpbnMlMjBweXJhbWlkfGVufDB8MHx8fDE3Njk4NDkxNzF8MA&ixlib=rb-4.1.0&q=80&w=1080",
"photoBlur": "/surprise/yvette/step3-blur.svg",
"password": "HISTORIA2026",
"description_es": "El claustro histórico de la Catedral Metropolitana, un tesoro arquitectónico.",
"description_en": "The historic cloister of the Metropolitan Cathedral, an architectural treasure."
},
{
"step": 4,
"day": 1,
"title_es": "Jardines románticos escondidos en el corazón…",
"title_en": "Romantic gardens in the heart of the city…",
"clue_es": "Fuentes, flores, y un paraíso verde te esperan.",
"clue_en": "Fountains, flowers, and a green paradise await.",
"locationName": "Jardín Borda",
"googleMapsUrl": "https://maps.google.com/?q=Jardin+Borda+Cuernavaca",
"photo": "https://images.unsplash.com/photo-1762592958299-f41204b8b358?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w4NjYwMTR8MHwxfHNlYXJjaHwxfHxNZXhpY2FuJTIwbWFya2V0JTIwZmxvd2VycyUyMGNvbG9yZnVsJTIwdmVuZG9yfGVufDB8MHx8fDE3Njk4NDkxNzF8MA&ixlib=rb-4.1.0&q=80&w=1080",
"photoBlur": "/surprise/yvette/step4-blur.svg",
"password": "FLORES2026",
"description_es": "Un jardín histórico de ensueño con fuentes y senderos románticos.",
"description_en": "A dreamlike historic garden with fountains and romantic pathways."
},
{
"step": 5,
"day": 1,
"title_es": "Cena sorpresa bajo una obra maestra arquitectónica…",
"title_en": "Dinner beneath an architectural masterpiece…",
"clue_es": "La arquitectura de Félix Candela te rodeará mientras disfrutamos juntos.",
"clue_en": "Félix Candela's architecture will surround us as we enjoy together.",
"locationName": "Los Manantiales",
"googleMapsUrl": "https://maps.google.com/?q=Los+Manantiales+Cuernavaca",
"photo": "https://images.unsplash.com/photo-1666025959951-da3036b03b29?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w4NjYwMTR8MHwxfHNlYXJjaHwxfHxNZXhpY2FuJTIwcmVzdGF1cmFudCUyMGRpbm5lciUyMGVsZWdhbnR8ZW58MHwwfHx8MTc2OTg0OTE3Mnww&ixlib=rb-4.1.0&q=80&w=1080",
"photoBlur": "/surprise/yvette/step5-blur.svg",
"password": "CENA2026",
"description_es": "Un restaurante icónico bajo la estructura wavit de Candela.",
"description_en": "An iconic restaurant beneath Candela's wave-like structure."
},
{
"step": 6,
"day": 2,
"title_es": "Gran revelación: mariposas y jardines infinitos…",
"title_en": "Grand reveal: butterflies and endless gardens…",
"clue_es": "Mariposas de colores y un cielo infinito nos esperan. ¡El gran final!",
"clue_en": "Colorful butterflies and an infinite sky await us. The grand finale!",
"locationName": "Jardines de México — Mariposario",
"googleMapsUrl": "https://maps.google.com/?q=Jardines+de+Mexico+Mariposario+Tequesquitengo",
"photo": "https://images.unsplash.com/photo-1592510476943-59d922f151cf?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w4NjYwMTR8MHwxfHNlYXJjaHwxfHxidXR0ZXJmbHklMjBnYXJkZW4lMjBuYXR1cmUlMjBjb2xvcmZ1bCUyMG1hZ2ljYWx8ZW58MHwwfHx8MTc2OTg0OTE3Mnww&ixlib=rb-4.1.0&q=80&w=1080",
"photoBlur": "/surprise/yvette/step6-blur.svg",
"password": "MARIPOSA2026",
"description_es": "Un santuario de mariposas en un jardín botánico espectacular.",
"description_en": "A butterfly sanctuary in a spectacular botanical garden."
}
]
Copy link

Copilot AI Feb 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This itinerary JSON duplicates the data in frontend/static/surprise/itinerary/yvette-cuernavaca.json, but the runtime code in SurpriseRevealFlow.svelte only fetches the static version. Maintaining two copies of the same itinerary risks them getting out of sync; consider keeping a single source of truth (either import this JSON and use it for both code and static output, or remove the unused copy) to simplify future edits.

Copilot uses AI. Check for mistakes.
Remove password protection and make the treasure hunt fully accessible via shareable link.
All 6 locations now display content immediately with beautiful animations and confetti
triggered on scroll-into-view. Progress tracking persists across visits for UX continuity.

Changes:
- Remove all password/authentication requirements from surprise feature
- Auto-reveal all step content immediately (no unlock modals)
- Add view-based confetti animations when cards scroll into viewport
- Track "viewed" steps instead of "unlocked" (localStorage for UX only)
- Add URL personalization (?for=name or ?invite=token) for custom greeting
- Replace unlock button with direct "Open in Maps" CTA
- Update progress bar from "Unlocked" to "Discovered" terminology
- Remove PasswordUnlockModal dependency from StepCard component

The experience is now completely client-side and works as a beautiful
standalone invitation that anyone can access and enjoy.

https://claude.ai/code/session_01V8xffavCQm3HHWRRTXxqAe
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants