An elegant shisha lounge interior with plush seating and ornate hookahs
Concept visualization: an elegant shisha lounge interior with plush seating and ornate hookahs. Generated illustration, not a photo of the actual business.
2023 Web Development GitHub ↗

Overview

Faiaz Shisha is a multi-page website for a shisha lounge that leads with atmosphere. The design uses a deep dark background palette with gold accent colours, layered CSS gradients, and scroll-triggered fade-in animations to create the immersive, ambient feel the business wanted online. The site spans five pages: a hero home page establishing the mood, a flavour menu categorised into fruit, mint, and mixed blends, a photo gallery with a masonry-inspired CSS layout, a booking information page with a validated reservation form, and a contact/location page. JavaScript-powered tabs on the menu page let customers browse by category without leaving the page. CSS IntersectionObserver-triggered animations reveal content as users scroll, reinforcing the atmospheric design direction throughout the experience.

Architecture

graph TD subgraph Pages["Pages"] A["index.html\nHero · Atmosphere · CTA"] B["menu.html\nFlavour Cards + Category Tabs"] C["gallery.html\nMasonry-style Photo Grid"] D["booking.html\nReservation Form + Validation"] E["contact.html\nLocation · Hours · Map"] end subgraph CSS["CSS Modules"] F["style.css\nDark Theme · Custom Properties · Layout"] G["animations.css\nKeyframes · Fade-in · Gradient Pulse"] end subgraph JS["JavaScript"] H["main.js\nMenu Tabs · IntersectionObserver\nBooking Validation · Mobile Menu"] end A --> F B --> F C --> F D --> F E --> F F --> G A --> H B --> H D --> H style A fill:#1a1a2e,stroke:#00d4ff,color:#e0e0e0 style B fill:#1a1a2e,stroke:#00d4ff,color:#e0e0e0 style C fill:#1a1a2e,stroke:#00d4ff,color:#e0e0e0 style D fill:#1a1a2e,stroke:#00d4ff,color:#e0e0e0 style E fill:#1a1a2e,stroke:#00d4ff,color:#e0e0e0 style F fill:#181818,stroke:#1e1e1e,color:#888 style G fill:#181818,stroke:#1e1e1e,color:#888 style H fill:#181818,stroke:#1e1e1e,color:#888

Tech Stack

  • HTML5, Semantic page structure; aria-selected attributes on tab buttons for accessibility; aria-live on the booking confirmation message
  • CSS3, Custom properties for the gold/dark colour scheme; @keyframes for balloon float, fade-in cascade, and gradient pulse animations; CSS columns for masonry gallery layout
  • CSS Animations, Scroll-triggered via IntersectionObserver; elements start opacity: 0; transform: translateY(20px) and transition to visible on entry
  • Vanilla JavaScript, Tab-based menu filtering, IntersectionObserver animation trigger, booking form validation (date range, time slot, party size limits), mobile menu toggle
  • Responsive Design, Mobile-first base with breakpoints at 480 px, 768 px, and 1024 px; dark theme maintained consistently at all sizes

Build Process

1
Design Direction: Dark Ambient Theme

Established the visual language with deep charcoal backgrounds (#0d0d0d, #1a1a1a), gold accent colours (#c9a84c, #e8c96e), and off-white body text. All values were encoded as CSS custom properties so the entire theme can be adjusted from one declaration block.

:root {
  --color-bg:       #0d0d0d;
  --color-surface:  #1a1a1a;
  --color-gold:     #c9a84c;
  --color-gold-lt:  #e8c96e;
  --color-text:     #e8e0d5;
  --color-muted:    #888;
  --font-display:   'Playfair Display', serif;
  --font-body:      'Inter', sans-serif;
}
2
Hero Section with Layered CSS Gradients

The hero uses a background image overlaid with a multi-stop linear-gradient from near-opaque dark at the bottom to semi-transparent at the top, creating depth without a separate overlay element. A subtle CSS @keyframes animation pulses the gradient opacity to give the hero a breathing, atmospheric feel on desktop.

.hero {
  background-image:
    linear-gradient(to top, rgba(13,13,13,0.95) 0%, rgba(13,13,13,0.4) 60%, transparent 100%),
    url('images/hero-bg.jpg');
  background-size: cover;
  background-position: center;
  min-height: 100svh;
  display: flex;
  align-items: flex-end;
}
3
Menu Page: Flavour Cards with JavaScript Tabs

Each flavour card has a data-category attribute (fruit, mint, mixed). Tab buttons use aria-selected for accessibility. The JavaScript filter works identically to the product filter pattern, reading data-category on each card and setting visibility based on the active tab, ensuring the DOM is the single source of truth.

4
Gallery with CSS Columns Masonry Layout

The gallery uses the CSS columns property to achieve a masonry-style layout without JavaScript. Images flow into column tracks naturally, with a break-inside: avoid on each figure to prevent images splitting across column boundaries.

.gallery-masonry {
  columns: 1;
  gap: 1rem;
}

@media (min-width: 480px) { .gallery-masonry { columns: 2; } }
@media (min-width: 768px) { .gallery-masonry { columns: 3; } }

.gallery-masonry figure {
  break-inside: avoid;
  margin-bottom: 1rem;
}
5
Booking Form with JavaScript Validation

The booking form validates: date (must be future date using Date.now() comparison), time slot (valid hours 17:00 to 01:00), and party size (1 to 20). Invalid states display inline error messages adjacent to the affected field. On valid submission a confirmation message receives focus for screen-reader accessibility.

function validateBooking(form) {
  const date = new Date(form.date.value);
  const hour = parseInt(form.time.value.split(':')[0]);
  const size = parseInt(form.partySize.value);
  const validHours = (hour >= 17) || (hour < 2);

  if (date <= new Date()) return showError(form.date, 'Date must be in the future');
  if (!validHours) return showError(form.time, 'Bookings available 17:00 to 01:00');
  if (size < 1 || size > 20) return showError(form.partySize, 'Party size: 1 to 20');
  return true;
}
6
Scroll-Triggered Animations with IntersectionObserver

Elements that should animate on scroll are given a .reveal class. A single IntersectionObserver watches all .reveal elements and adds .is-visible when they enter the viewport. CSS handles the transition entirely, .reveal starts at opacity: 0; transform: translateY(20px) and .reveal.is-visible transitions to opacity: 1; transform: none. Cascade delays are set with inline --delay custom properties.

// JS - set up observer
const observer = new IntersectionObserver((entries) => {
  entries.forEach(e => { if (e.isIntersecting) { e.target.classList.add('is-visible'); observer.unobserve(e.target); } });
}, { threshold: 0.15 });
document.querySelectorAll('.reveal').forEach(el => observer.observe(el));

/* CSS */
.reveal { opacity: 0; transform: translateY(20px); transition: opacity 0.5s ease var(--delay, 0s), transform 0.5s ease var(--delay, 0s); }
.reveal.is-visible { opacity: 1; transform: none; }

Visitor Experience Flow

flowchart LR A["Visitor Arrives\nHero Page"] --> B["Atmospheric Hero\nCSS Gradient + Animation"] B --> C{"Explore Further"} C -->|"View flavours"| D["menu.html\nFlavour Cards + Tabs"] C -->|"See the space"| E["gallery.html\nMasonry Photo Grid"] C -->|"Make a booking"| F["booking.html\nReservation Form"] D --> F E --> F F --> G["JS Validates\nDate · Time · Party Size"] G -->|"Invalid"| H["Inline Error\nMessages Shown"] G -->|"Valid"| I["Confirmation\nMessage Shown + aria-live"] H --> F style A fill:#1a1a2e,stroke:#00d4ff,color:#e0e0e0 style B fill:#181818,stroke:#1e1e1e,color:#888 style C fill:#1a1a2e,stroke:#00d4ff,color:#e0e0e0 style D fill:#181818,stroke:#1e1e1e,color:#888 style E fill:#181818,stroke:#1e1e1e,color:#888 style F fill:#1a1a2e,stroke:#00d4ff,color:#e0e0e0 style G fill:#181818,stroke:#1e1e1e,color:#888 style H fill:#181818,stroke:#1e1e1e,color:#888 style I fill:#1a1a2e,stroke:#00ff88,color:#e0e0e0

Challenges & Solutions

IntersectionObserver Browser Support. Older browsers, primarily Internet Explorer 11 and early Edge, do not support IntersectionObserver. A feature-detection check (if ('IntersectionObserver' in window)) was used: if the API is available, the observer is set up; if not, all .reveal elements have .is-visible added immediately so they render fully visible without animation. This polyfill-free graceful degradation covers the vast majority of real-world users without adding any external dependency.

Booking Form Time Slot Restrictions. The HTML time input alone cannot restrict allowed hours to a business-hours window. Pure HTML min/max attributes on a time input only handle the simple case of a contiguous window and cannot handle the 17:00 to 01:00 window that wraps midnight. Custom JavaScript validation was required, parsing the hour from the input value and checking it against the allowed range with a logical OR for before-midnight and after-midnight hours.

What I Learned

  • Layered CSS gradients over background images are more maintainable than a separate overlay element and give finer control over the blend
  • The CSS columns property is a quick, JavaScript-free way to achieve masonry-style gallery layouts; break-inside: avoid is essential to prevent image splitting
  • IntersectionObserver is the performant, standards-based approach for scroll-triggered animations, polling with scroll event listeners causes layout thrashing
  • Using a single --delay CSS custom property on each element for staggered cascade animations avoids duplicating transition rules and keeps the cascade flexible
  • Time-range validation that wraps midnight cannot be handled with HTML input attributes alone, custom JavaScript logic is necessary
  • Dark-theme design systems benefit significantly from CSS custom properties; a palette change requires editing only the :root block

How I Built It

Phase 1: Structure and dark-theme design system. I started with the multi-page skeleton and a dark theme built on CSS custom properties in :root, so the entire palette can be adjusted in one place. Layered CSS gradients sit over background imagery for depth without extra overlay elements.

Phase 2: Flavour menu and gallery. The flavour menu and gallery pages came next. The gallery uses the CSS columns property for a masonry-style layout with break-inside: avoid so images never split across columns, entirely without JavaScript.

Phase 3: Booking form with business-hours logic. The booking form validates against the lounge's 17:00 to 01:00 hours. Because that window wraps midnight, plain HTML min/max attributes cannot express it, so custom JavaScript parses the hour and checks the allowed range with a logical OR covering both sides of midnight.

Phase 4: Scroll-triggered reveals. Message sections cascade into view through IntersectionObserver, with a per-element --delay CSS custom property driving the stagger. A feature-detection guard falls back to fully visible content on older browsers.

Phase 5: Responsive pass. Media queries reflow the layout for phones and tablets, and the design was checked at common breakpoints before shipping.

What You Need

CategoryItemNotes
ToolA modern web browserEverything is static HTML, CSS, and vanilla JavaScript
SoftwareCode editorMulti-page static site; no build step
SoftwareImage editorFor resizing and optimising menu and gallery photos
SkillHTML5 and semantic markupMulti-page structure with consistent navigation
SkillCSS custom properties and keyframesDark theme tokens, gradients, scroll reveal transitions
SkillJavaScript form validationCustom time-window logic for the booking hours
SkillIntersectionObserverPerformant scroll-triggered animations with graceful fallback
AccountStatic host (e.g. GitHub Pages)Where the live site is published

Challenges and Lessons Learned

Time windows that wrap midnight. HTML input attributes assume contiguous ranges, but 17:00 to 01:00 crosses the day boundary. Writing custom validation logic was unavoidable, and it taught me to read form constraints as UX promises: whatever the business hours are, the form must enforce exactly those, not an approximation.

Graceful degradation for IntersectionObserver. Rather than shipping a polyfill for IE11-era browsers, the feature-detection fallback keeps content visible everywhere. The lesson: animation is enhancement, and enhancement should fail open.

Dark-theme maintainability. Centralising the palette in :root custom properties meant late-stage colour tweaks were one-line edits instead of search-and-replace across the stylesheet. Design tokens earn their keep on the first redesign, not the first build.

What Is Next

  • Table reservation system with real availability, replacing the enquiry-style booking form
  • Menu management via a small CMS so the lounge staff can update flavours without editing code
  • Multilingual support for the menu pages
  • Event and promotions section with date-based publishing
  • Performance budget: responsive image sizes and lazy loading for the gallery
HTML5 CSS3 Animations JavaScript Dark Theme Responsive