# Villa Mare Montenegro — DirectBalkan Demo Site — Design Spec

Date: 2026-08-13
Status: Approved

## Purpose

Production-ready, interactive demo website at `demo.directbalkan.com` showcasing a fictional
direct-booking accommodation site ("Villa Mare Montenegro", Budva) to convince accommodation
owners to buy a DirectBalkan website, and to serve as the reusable template/architecture for
future real client sites.

Full functional/content requirements are defined by the user's original brief (14 sections,
covering page structure, i18n, config schema, Make.com webhook contract, demo-mode rules,
accessibility, performance/SEO, and acceptance criteria). This spec captures the architecture
and design decisions made on top of that brief; it does not restate content already fixed by
the brief (copy requirements, config field names, FAQ questions, etc. — see conversation/brief).

## Architecture

Static site, no build step, no framework.

```
/directbalkan-demo/          (deployed as demo.directbalkan.com root)
  demo.html
  css/
    style.css
  js/
    config.js      # siteConfig — all client-swappable data
    i18n.js         # translations dict + setLanguage()
    calendar.js     # availability calendar widget
    gallery.js      # grid + lightbox
    form.js         # booking request form + webhook simulate/send
    app.js          # wiring: theme toggle, nav, reveal-on-scroll, WhatsApp link, sticky CTA
  assets/
    logo.svg
    images/         # README notes on where real photos go; demo uses inline SVG placeholders
  README.md
```

`js/*.js` are ES modules loaded via `<script type="module" src="js/app.js">`, which imports
the others. No bundler — files are served as-is, which keeps local testing to "open demo.html
in a browser" or a static file server.

Rationale for splitting into files (vs. single HTML file): the brief's explicit template goal
(section 2/12) means a future client swap should touch `config.js` and copy/content, not wade
through a monolithic file. Section boundaries: `config.js` has zero logic (data only), `i18n.js`
has zero DOM-structure knowledge (only text swapping), `calendar.js`/`gallery.js`/`form.js` each
own one interactive widget end-to-end (render + events + public init function), `app.js` is the
only file that touches cross-cutting concerns (theme, nav, global scroll behavior).

## Design tokens

Defined as CSS custom properties on `:root`, redefined under `[data-theme="dark"]`.

- **Color**: primary accent `#C1633A` (terracotta), secondary accent `#5C6B3F` (olive — also
  used for the calendar "available" state so it reads as a natural material color, not a
  generic UI-green), warm off-white background `#FAF6F1`, deep charcoal text `#2A2420`. Dark
  mode inverts to a deep warm charcoal background with the same two accents, lightened for
  contrast.
- **Typography**: fluid `clamp()` scale, 5 steps from small print to hero display. Serif-leaning
  display stack for headings (warm/hospitality feel), clean sans stack for body. Both are
  system-safe font stacks — no webfont network requests, keeping first paint fast and the demo
  free of external dependencies per the brief's performance/SEO section.
- **Spacing**: 8-step scale, `clamp()`-driven for section padding/gaps so rhythm compresses
  gracefully on mobile without hard breakpoint jumps.
- **Radii/shadow**: modest radii (6–10px), single-layer soft shadows only — deliberately avoids
  the "bubbly SaaS" look the brief prohibits.
- **Breakpoints**: mobile-first; component styles written unprefixed (mobile), with `min-width`
  media queries layering in tablet/desktop refinements. Primary breakpoints ~640px, ~1024px.

## Placeholder imagery

No real photos exist yet. Every image slot (hero, gallery ×8, location map) renders an inline
SVG/CSS-gradient "art" placeholder in a terracotta/olive duotone with a subtle topographic-line
motif (suggests coastline/hills abstractly, avoids a literal broken-image or gray-box look).
Each placeholder carries a small overlay caption (e.g. "Replace: villa exterior at sunset") and
a `data-replace-with` attribute describing the intended real photo, so swapping in production
images later is a content edit, not a layout change. This satisfies the brief's rule against
using unverified external image URLs while still meeting the "must feel premium, no empty
sections" requirement.

## Key interactive components

- **i18n.js** — translation dictionary keyed by `en`/`de`/`nl`; `setLanguage(lang)` walks
  `[data-i18n]` (text), `[data-i18n-placeholder]`, `[data-i18n-aria-label]` nodes and swaps
  content; current language held in a module-level variable only (no `localStorage`/
  `sessionStorage`, per brief). Language switcher updates `aria-pressed`/active state and needs
  no page reload.
- **calendar.js** — renders a month grid from `siteConfig.availability` (fixed demo data, dates
  keyed `YYYY-MM-DD` → `available`/`unavailable`); click-to-select arrival then departure with
  range highlighting between them; month navigation; all visible strings routed through i18n;
  visible "This is example availability for the demo" notice.
- **gallery.js** — responsive image grid; lightbox with prev/next, close button, Escape and
  arrow-key support, focus trapped while open and restored to the triggering thumbnail on close.
- **form.js** — validates required fields inline with specific per-field error messages; builds
  the exact Make.com payload shape from the brief (section 7); if `siteConfig.booking.makeWebhookUrl`
  is empty, simulates success and logs to console that it's a simulation; if set, POSTs JSON and
  handles success/network-failure/loading/duplicate-submit states (submit button disabled while
  in flight).
- **app.js** — theme toggle (light default, manual dark toggle, no OS-preference auto-detect
  needed since the brief only asks for a manual toggle), mobile sticky CTA bar (Check
  availability / Ask on WhatsApp, non-blocking), section reveal-on-scroll respecting
  `prefers-reduced-motion`, WhatsApp deep-link builder (prefills property + selected dates +
  guest count from current form/calendar state).

## Data flow / state

All demo-editable data lives in `siteConfig` (`config.js`): property info, contact info,
branding colors, supported languages, Make webhook URL + demo-message flag, availability map.
Widgets read from `siteConfig` at init and never mutate it — user selections (language, chosen
dates, guest count, theme) live in local module state / DOM, not in the config object, so the
config file stays a pure "content to edit" surface for future client swaps.

## Testing / QA approach

No automated test framework — consistent with the brief's "no unnecessary libraries" rule and
the fully static nature of the deliverable. Verification is a manual pass against the brief's
section 14 acceptance criteria: desktop + 375px mobile, all nav/gallery/lightbox/calendar/
form/WhatsApp/FAQ/dark-mode interactions, keyboard navigation, `prefers-reduced-motion`, no
horizontal overflow, no console errors, demo content clearly marked as demo, and confirmation
that a new accommodation can be created by editing `config.js` + content only.

## Out of scope (explicitly, per brief section 12 "optional modules")

Online payment, real iCal sync, multi-unit support, price calculation, real Google Maps
embed, additional languages beyond en/de/nl, SEO landing pages. Architecture should not block
adding these later, but none are built now.
