DEV Community

Cover image for Building DateSpark: A Date Planning App for Couples
Grace Olabode
Grace Olabode

Posted on • Edited on

Building DateSpark: A Date Planning App for Couples

Live Demo: DateSpark
GitHub Repository: DateSpark Repo

The Problem Statement

We’ve all experienced that moment: staring blankly at each other at 7 PM, trapped in the cycle of "I don't know, what do you want to do?" By the time you come up with something, everything is closed.

This is the problem DateSpark addresses. DateSpark is a date planning web app designed to reduce the stress of decision-making. You answer three simple questions: your budget, your vibe (activity preference), and how much time you have. The app then gives you a curated date idea. No more endless scrolling.

DateSpark's Landing Page

DateSpark targets couples looking for meaningful experiences without the hassle. It’s essentially a relationship saver in a simple interface.

The Architecture: Keeping It Vanilla

While it might be tempting to use a large framework for every project, we built DateSpark with vanilla JavaScript, semantic HTML, and a highly customized CSS setup.

For our data, we avoided a heavy backend and used a local JSON file (dateIdeas.json) as a read-only database. We relied on the browser's localStorage to save user-specific data. Here’s how it works behind the scenes.

1. The Generate Flow: The "No Dead Ends" Algorithm
The Generate page is the core of the app. When a user submits their three preferences, our JavaScript gets to work.

  • Data Mapping: It takes the form inputs and maps them to our JSON data aliases (for example, it maps a "low" budget to "Free" and "₦").

  • The Fallback Protocol: What happens if someone requests a 5 hour, free, luxury spa experience? Instead of showing an annoying "No results found" message, we created a "relaxation" fallback. The logic ignores filters one by one (first duration, then activity, then budget) until it finds suitable ideas. You always receive a date suggestion.

  • Saving State: See an idea you like? Clicking "Save to My Plans" packages the current idea with a timestamp and sends it right to localStorage.

2. My Plans: A LocalStorage Dashboard
This page serves as the user’s personal dashboard for saved dates.

  • State Management: On loading, the JavaScript reads from the datespark.savedPlans.v1 array in local storage and separates the UI into "Upcoming" and "Completed" views based on a simple true/false condition.

  • Instant UI Updates: We added a dynamic search bar that filters the grid in real-time by checking the input against titles, tags, and descriptions. Clicking "Mark Completed" updates the object, overwrites local storage, and refreshes the display instantly. It’s quick and has no network delays.

3. Browse Flow: Deep Linking for Sharing
Sometimes you just want to casually look for date ideas. The Browse page displays our entire JSON library in a clean grid.

  • Smart Filtering: Category buttons filter the array immediately without needing to reload the page.

  • Deep Linking: We didn’t just create modals; we made shareable ones. When you click on an idea, the JavaScript updates the URL parameter (?idea=ID). This means if you discover the perfect "Starlit Rooftop Picnic," you can copy the link and text it to your partner, and it will open directly to that specific modal.

4. Home Page: Drag-to-Scroll and Simulated Delays
First impressions count, so the Home page needed to feel engaging.

  • The Carousel: Instead of a basic CSS slider, we built a custom JavaScript carousel for the "Curated Experiences" section that supports drag-to-scroll, button clicks, and arrow key navigation for better usability.

  • Form Validation: The newsletter form checks submissions, runs a strict Regex check on the email, and simulates a processing delay before switching the success/error DOM states.

DateSpark's User Flow

The Design System: Using CSS Custom Properties

To maintain consistent styling across five different pages, the entire design system relies on CSS variables.

- Emotional Design

:root {
  /* --- Brand Colors --- */
  --color-brand-50:  #FFF1F2;
  --color-brand-100: #FFE4E6;
  --color-brand-500: #F43F5E;
  --color-brand-600: #E11D48; /* Primary Brand Color */
  --color-brand-700: #BE123C;
  --color-brand-900: #881337;

  /* --- Neutral Colors --- */
  --color-bg-cream:  #FDF2F0; /* Global Background */
  --color-bg-white:  #FFFFFF;
  --color-text-main: #1F2937;
  --color-text-muted:#4B5563;
  --color-border:    #E5E7EB;

  /* --- Extended Colors --- */
  --color-burgundy-900: #7E0C2A;
  --color-rose-25: #FFF7F7;
  --color-rose-30: #FFF5F3;
  --color-rose-50: #FFF5F5;
}

Enter fullscreen mode Exit fullscreen mode

DateSpark's Color Palette

We steered clear of cliché "Valentine's Day Hot Pink." Instead, we chose a mature color scheme that ranges from rose to deep burgundy, complemented by a warm cream background (#FDF2F0).

  • The Spacing & Shadow System Nothing is hardcoded. Every gap and padding uses an 8-point scale (for example, --space-4 for 16px). Shadows are named according to their component instead of their technical values:

--shadow-primary-btn for our call-to-action buttons.
--shadow-feature-card for key elements.

  • CSS Grid to the Rescue We relied heavily on CSS Grid to handle common layout challenges. For example, centering a navbar with different widths for the logo and the auth buttons can be tricky with Flexbox. Using CSS Grid with 1fr auto 1fr solves this perfectly:
.navbar {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  grid-template-areas: "logo  links  auth";
  align-items: center;
}
Enter fullscreen mode Exit fullscreen mode

The two 1fr columns share the space equally, keeping the links centered relative to the viewport, no matter what happens on the sides.

  • Typography
/* --- Typography --- */
--font-serif: 'Playfair Display', serif;
--font-sans:  'Inter', sans-serif;

--text-size-h1: 3rem;    /* 48px */
--text-size-h2: 2rem;    /* 32px */
--text-size-h3: 1.25rem; /* 20px */
--text-size-p:  1rem;    /* 16px */

/* Additional fluid sizes */
--text-size-xs:  0.75rem;
--text-size-sm:  0.95rem;
--text-size-2xs: 0.7rem;
Enter fullscreen mode Exit fullscreen mode

DateSpark's Typography

Playfair Display handles all headings and the logo. It has an elegant and editorial quality. Inter handles body text, navigation, and UI labels because it is clean and very readable at small sizes on screens.

Semantic HTML & Accessibility

Semantic HTML was a priority. Every saved plan is wrapped in an tag because it is a self-contained piece of content. We linked headings to their parent containers using aria-labelledby so screen readers properly announce card titles. Here is a plan card from the My Plans page:

<article aria-labelledby="plan-1-title" class="plan-card card">
  <figure class="plan-media card-media">
    <img
      src="https://images.unsplash.com/photo-1604942030135-73e2f02503b3?q=80&w=751"
      alt="Starlit rooftop picnic setting"
      width="600"
      height="360"
    />
  </figure>

  <h2 id="plan-1-title" class="plan-title">Starlit Rooftop Picnic</h2>

  <p aria-label="Plan tags" class="plan-tags tag-list">
    <span>Romantic</span>
    <span>NN</span>
    <span>3 hrs</span>
  </p>

  <p>
    A curated evening with artisanal snacks, plush blankets, and a
    telescope for star-gazing.
  </p>

  <div aria-label="Plan actions" class="plan-actions">
    <a href="/plans/starlit-rooftop-picnic" class="plan-link">View Details</a>
    <button type="button" class="plan-done">Completed</button>
  </div>
</article>
Enter fullscreen mode Exit fullscreen mode

Each card is an because it is a self contained piece of content. The heading is linked to the article using aria-labelledby so screen readers announce the card title when a user navigates to it.

DateSpark's My Plans Page

Other accessibility decisions across the project:

  • Skip Links: A hidden <a class="skip-link" href="#main-content"> lets keyboard users jump straight past the navigation.

  • Semantic Outlines: Strict heading hierarchy (one <h1> per page, down to <h3> for cards) for both SEO and screen readers.

  • Screen-Reader Only Text: We used a .sr-only utility class for labels that are meaningful to assistive tech but shouldn't clutter the visual UI.

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0,0,0,0);
  white-space: nowrap;
  border: 0;
}
Enter fullscreen mode Exit fullscreen mode

The Most Challenging Component: CSS Only Hamburger Menu
The mobile navigation works with absolutely zero JavaScript. We used the "hidden checkbox hack." A hidden <input type="checkbox"> acts as the state, and a styled acts as the button.

<!-- input and label MUST come before the nav groups -->
<input type="checkbox" id="nav-check" class="nav-check" aria-hidden="true">
<label for="nav-check" class="nav-toggle" aria-label="Toggle navigation">
  <span></span>
  <span></span>
  <span></span>
</label>

<div class="nav-links">...</div>
<div class="nav-auth-group">...</div>
Enter fullscreen mode Exit fullscreen mode

Using the CSS sibling selector (~), checking the box reveals the menu and animates the three hamburger bars into an "X":

.nav-check:checked ~ .nav-links {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-4);
  padding: var(--space-4) 0 var(--space-2);
  border-top: var(--border-1) solid var(--color-border);
  margin-top: var(--space-3);
}

.nav-check:checked ~ .nav-toggle span:nth-child(1) {
  transform: translateY(7px) rotate(45deg);
}
.nav-check:checked ~ .nav-toggle span:nth-child(2) {
  opacity: 0;
}
.nav-check:checked ~ .nav-toggle span:nth-child(3) {
  transform: translateY(-7px) rotate(-45deg);
}
Enter fullscreen mode Exit fullscreen mode

Reflection: Lessons and Version 2

What was hardest: Keeping CSS class names consistent across multiple HTML files before the styles began to break. We quickly learned to treat class names as unchangeable contracts before applying any styling.

What surprised me technically: position: sticky completely gives up if a parent container has position: relative. It creates a scroll boundary that breaks the sticky behavior. It isn't obvious until you spend an hour battling it.

What I would do differently in version two: While localStorage and a static JSON file are very fast, they are tied to the user’s current device. The next step for DateSpark is to replace our local storage setup with a real database. This way, users can create accounts, log in from anywhere, and keep their date plans in sync permanently.

Contributors

This project was a wonderful team effort! A big thanks to my amazing coding partner, Chijioke.

  • Check out their GitHub here: Chijex5
  • Read about their side of the DateSpark journey here.

Top comments (0)