Project documentation

FixLab. A scroll-driven 3D repair shop website.

A complete, production-ready multi-page website for a mobile phone repair shop, built with pure HTML, CSS, and vanilla JavaScript. The centerpiece is a GSAP ScrollTrigger-driven 3D phone disassembly experience. No images. No glassmorphism. No text gradients. Every visual is hand-crafted in CSS.

Overview

FixLab is a nine-page website for a fictional mobile phone repair shop. It is designed to feel like an award-winning editorial experience - the kind you see on Apple product pages or Awwwards Site of the Year winners. The site is entirely static, with no build step, no framework, and no backend.

Design philosophy

  • No glassmorphism. No frosted glass. No backdrop-filter blurs. Solid backgrounds only.
  • No text gradients. Every headline uses a single solid color.
  • No generic AI look. Hand-crafted, editorial, technical.
  • No external media. Every visual is pure CSS or inline SVG. The phone illustration is built from six separate CSS layers that disassemble in 3D space.
  • No em dashes. Hyphens and commas only.

What you get

CSS-drawn phone

A fully detailed smartphone built from six separate layers: screen, battery, motherboard, camera, back glass, and frame.

3D explosion

Scroll-linked disassembly animation using GSAP ScrollTrigger with scrub: 1 for buttery smooth motion.

Camera tunnel

A fly-through transition that zooms through the scattered phone parts into the next section.

3D card reveals

Service cards rotate in from 90 degrees on the Y axis as they enter the viewport.

Horizontal scroll

The repair process timeline scrolls horizontally, driven by vertical scroll.

3D carousel

Testimonials arranged in a rotating 3D carousel that spins as the user scrolls.

Tech stack

The project uses a minimal, dependency-light stack. Everything loads via CDN. No build step required.

TechnologyVersionPurpose
HTML5Living standardSemantic markup for all 9 pages
CSS3Level 3 + custom propertiesSingle style.css with variables, 3D transforms, responsive design
Vanilla JavaScriptES6+Single script.js handling all interactivity
GSAP3.12.5Core animation engine
ScrollTrigger3.12.5Scroll-linked animations (the 3D experience)
Bootstrap5.3.2Responsive grid system only
FontAwesome6.5.1Icon set
Google Fonts-Space Grotesk (headings) + Inter (body)

Why no framework? The site is primarily a presentation layer. A framework would add bundle size and complexity without adding value. Vanilla JS plus GSAP gives full control over the scroll experience.

File structure

The project is flat. All files live in a single folder. No subfolders, no build tools.

fixlab/ ├── index.html Homepage with 3D scroll experience ├── services.html All repair services with details ├── pricing.html Transparent pricing by brand ├── about.html Shop story, team, certifications ├── contact.html Booking form and shop info ├── track-repair.html Repair status tracker (mockup) ├── gallery.html Before and after gallery ├── faq.html Categorized FAQ with search ├── 404.html Creative 404 page ├── style.css All styles (one file) ├── script.js All JavaScript (one file) └── documentation.html This file

One CSS file. One JS file. This is intentional. It keeps the project simple and avoids the overhead of a build step. If you need to split them for a larger project, the sections are clearly commented.

Installation

There is no installation. Clone or download the folder, then open index.html in a browser. That is it.

Option 1: Direct open

# Double-click index.html, or:
open index.html

Option 2: Local server (recommended)

Some browsers restrict certain features (like fonts and ES modules) when opened via file://. A local server avoids this.

# Python 3
python -m http.server 8000

# Node.js (npx)
npx serve

# PHP
php -S localhost:8000

Then visit http://localhost:8000.

Option 3: Deploy

Upload all files to any static host: Netlify, Vercel, GitHub Pages, Cloudflare Pages, or a plain Apache/Nginx server. No build step. No environment variables. No configuration.

Colors

The palette is light, warm, and technical. It avoids the cold sterility of pure white and the visual noise of gradients. Every color has a clear role.

Background
#FAFAF8 · --bg
Primary text / Dark
#1A1A2E · --ink / --dark
Accent (Teal)
#00B4D8 · --accent
Accent deep
#0096C7 · --accent-2
Amber (CTA)
#F77F00 · --amber
Card
#FFFFFF · --card
Border
#E8E6E1 · --border
Background alt
#F3F1EC · --bg-alt

Usage rules

  • --bg is the default page background. Used on most sections.
  • --bg-alt is used to create subtle section separation (stats, why-us, certs).
  • --dark is used for high-contrast sections (tunnel, process, CTA, footer).
  • --accent is the primary action color. Buttons, icons, links on hover.
  • --accent-2 is the darker accent, used for text highlights and the "accent" span class.
  • --amber is reserved for warnings, "most popular" badges, and the emergency hotline.

Typography

Two typefaces. That is it. They pair cleanly and cover every use case.

RoleFontWeights used
HeadingsSpace Grotesk500, 600, 700
BodyInter400, 500, 600, 700
Code / monoJetBrains Mono400, 500 (docs only)

Type scale

h1: clamp(2.4rem, 5vw, 4.2rem)   /* hero headlines */
h2: clamp(1.8rem, 3.5vw, 2.8rem) /* section titles */
h3: clamp(1.2rem, 2vw, 1.5rem)   /* card titles */
h4: 1.05rem                       /* small headings */
body: 1rem / 1.6 line-height
small: 0.85rem - 0.9rem

All headings use letter-spacing: -0.02em for a tighter, more editorial feel. Body text uses the default tracking.

Spacing & layout

Spacing is consistent and based on an 8px grid. Sections use generous vertical padding (100-140px) to create breathing room.

TokenValueUsed for
--radius-sm8pxInputs, small elements
--radius14pxCards, FAQ items, general
--radius-lg22pxLarge cards, sections
--radius-xl32pxIllustrations
Section padding100-140pxVertical rhythm between sections
Container max1240pxMain content width
Gutter24pxGrid gaps, padding

Responsive breakpoints

Desktop:  > 992px   /* full layout, 3D effects at full intensity */
Tablet:   768-992px /* stacked hero, reduced 3D */
Mobile:   < 768px   /* single column, 3D scaled to 50% */
Small:    < 480px   /* compact stats, stacked buttons */

Components

Every reusable UI element is defined once in style.css and used across all pages.

Buttons

Four button variants, all pill-shaped with smooth hover transitions.

ClassUse
.btn-accentPrimary CTA. Teal background, white text.
.btn-amberEmergency / warning. Amber background.
.btn-ghostSecondary. Transparent with border.
.btn-outlineTertiary. Dark border, fills on hover.

Cards

Three card styles, all with white backgrounds, subtle borders, and hover lift effects.

  • Service cards (.service-card) - 3D rotation reveal on scroll
  • Feature cards (.why-card, .guarantee-card) - simple lift on hover
  • Gallery cards (.gallery-card) - before/after comparison

Forms

Inputs use rounded corners, subtle borders, and a teal focus ring. Validation states use red borders and inline error messages.

Eyebrow

The small uppercase label above every section title. Uses .eyebrow class with Space Grotesk, 0.85rem, 0.12em letter-spacing, teal color.

The phone rig Core feature

The centerpiece of the site is a fully CSS-drawn smartphone that disassembles in 3D space as the user scrolls. It is built from six separate layers, each a .phone-layer div positioned absolutely inside a .phone-rig container with transform-style: preserve-3d.

Layer structure

Screen (OLED) z-index: 6 · topmost
Camera module z-index: 5
Battery z-index: 4
Motherboard z-index: 3
Back glass z-index: 2
Side frame z-index: 1 · bottom

HTML structure

<div class="phone-stage">
  <div class="phone-rig">
    <div class="phone-layer layer-frame">...</div>
    <div class="phone-layer layer-back">...</div>
    <div class="phone-layer layer-board">...</div>
    <div class="phone-layer layer-battery">...</div>
    <div class="phone-layer layer-camera">...</div>
    <div class="phone-layer layer-screen">...</div>
  </div>
  <div class="phone-labels">...</div>
</div>

Each layer contains

  • Screen: notch, status bar, 8 app icons (each a different gradient), home indicator
  • Camera: three lenses with radial gradients, a flash with warm glow
  • Battery: teal top stripe, "Li-Ion 5000" label, plus/minus terminals
  • Motherboard: green PCB with grid pattern, A17 chip, smaller ICs, gold traces, capacitors
  • Back glass: dark gradient with subtle "FixLab" watermark
  • Frame: dark titanium gradient, volume/power buttons, speaker grille

Explosion sequence GSAP

The explosion is a single GSAP timeline pinned to the hero section. As the user scrolls, the timeline scrubs forward, moving each layer to its exploded position.

Timeline phases

PhaseTimeWhat happens
1. Open up0 - 1Rig rotates to 15deg X, -25deg Y, scales to 1.05
2. Explode0.3 - 1.7Each layer moves to its exploded position (see below)
3. Labels0.9 - 1.5Six floating labels fade in, staggered
4. Tunnel1.6 - 2.6Rig scales to 3x, simulating camera fly-through
5. Fade2 - 2.5Layers blur and fade out as camera passes through

Layer destinations

// Screen
translateZ: 200px, rotateX: -15deg

// Battery
x: 300px, rotateY: 45deg

// Motherboard
x: -250px, rotateZ: 20deg

// Camera
y: -100px, translateZ: -150px

// Back glass
y: 200px, rotateX: 30deg

// Frame
scale: 0.9, opacity: 0.7

ScrollTrigger config

ScrollTrigger: {
  trigger: heroSection,
  start: 'top top',
  end: '+=2200',      // 2200px of scroll
  pin: true,
  scrub: 1,            // 1 second smooth scrub
  anticipatePin: 1     // prevents jitter on desktop
}

Why scrub: 1? The value 1 means the animation takes 1 second to catch up to the scroll position. This creates a buttery, slightly lagging feel that makes the motion feel physical rather than mechanical. Use scrub: true for instant sync, or higher numbers for more lag.

Mobile fallback

On screens narrower than 768px, all translateZ values are multiplied by 0.5 to reduce the 3D intensity and improve performance. The rig also uses gentler rotations (4deg X, -10deg Y instead of 8deg and -18deg).

Tunnel transition

After the phone explodes, the viewport appears to fly forward through the scattered parts into the next section. This is achieved in two ways:

  1. Rig scale-up: In the final phase of the explosion timeline, the rig scales from 1.05 to 3, creating the illusion of the camera rushing forward.
  2. Tunnel rings: Three concentric circles (.tunnel-ring) scale up and fade in as the user scrolls through the tunnel section, reinforcing the sense of forward motion.
// Tunnel rings animation
gsap.fromTo(ring,
  { scale: 0.3, opacity: 0 },
  {
    scale: 1.5 + i * 0.3,
    opacity: 0.6,
    scrollTrigger: {
      trigger: tunnel,
      start: 'top bottom',
      end: 'bottom top',
      scrub: 1
    }
  }
);

Reassembly (CTA section)

In the final section, a smaller version of the phone reassembles itself as the user scrolls into view. This mirrors the explosion and brings the experience full circle.

How it works

  1. On page load, each layer is set to its exploded position using gsap.set().
  2. A ScrollTrigger watches the CTA section.
  3. When triggered, a single gsap.to() animates all layers back to x: 0, y: 0, translateZ: 0, rotateX: 0, scale: 1, opacity: 1 with a 0.1s stagger.

Narrative arc: The site tells a story. The phone breaks apart (problem), the tunnel flies through the internals (expertise), the services reveal (solution), and the phone reassembles (resolution). Every animation serves the narrative.

All 9 pages

1. index.html Homepage

The full 3D scroll experience. Seven sections: Hero (explosion), Tunnel, Services (3D cards), Stats (parallax counters), Process (horizontal scroll), Testimonials (3D carousel), CTA (reassembly). Includes sticky navbar and full footer.

2. services.html

Detailed breakdown of all 10 repair services. Each service has an icon, description, price, time estimate, and "Book this service" button. Ends with a 5-question FAQ.

3. pricing.html

Transparent pricing organized by brand (iPhone, Samsung, Pixel, OnePlus, Other). Tab switcher reveals the relevant table. Each row shows service, price range, time, and a "Book" button. Includes three guarantee cards (price match, no fix no fee, 12-month warranty).

4. about.html

Shop story with CSS-drawn workshop illustration. "Why FixLab" section with six feature cards. Team section with four CSS avatar circles. Certifications grid. Company timeline from 2016 to 2026.

5. contact.html

Two-column layout. Left: full booking form with validation. Right: shop info, CSS-drawn map placeholder with animated pin, social links. Emergency hotline banner in amber.

6. track-repair.html

Ticket number input. On submit (try FL-12345), a status timeline animates in showing five stages: Received, Diagnosing, Repairing, Quality Check, Ready. Includes animated progress bar and ETA.

7. gallery.html

Before/after comparison cards. Each card shows a CSS-drawn phone in "broken" and "fixed" states. Filter buttons let users view by repair type (screen, battery, water, back, camera). Each card includes a customer testimonial.

8. faq.html

Searchable FAQ with six categories (General, Screen, Battery, Water Damage, Warranty, Pricing). Accordion uses GSAP for smooth height animation. Search filters questions in real time.

9. 404.html

Creative error page with CSS-drawn broken phone illustration (with flying shards). Large "404" in teal. Search bar and two CTAs. Animates in on load.

Shared elements

Every page shares the same navbar and footer for consistency.

Navbar

  • Fixed to top, 76px tall
  • Transparent on load, gains shadow and border on scroll (via .scrolled class)
  • CSS-drawn logo: phone icon + wrench dot + "FixLab" text
  • Eight nav links with active state
  • "Book Repair" CTA button
  • Mobile: hamburger menu that opens a slide-in drawer from the right

Footer

  • Dark background (navy), four-column layout
  • Column 1: Logo, about text, social icons
  • Column 2: Quick links
  • Column 3: Services
  • Column 4: Contact info (address, phone, email, hours)
  • Bottom bar: copyright with dynamic year, privacy/terms links, back-to-top button

CSS architecture

The single style.css file is organized into 26 clearly commented sections. No preprocessor, no modules, no build step.

Section breakdown

#SectionWhat it covers
1VariablesAll colors, fonts, radii, shadows, easing
2Reset & baseBox-sizing, typography, scrollbar, selection
3Layout helpersContainer, section heads, eyebrows
4ButtonsAll four button variants
5NavbarSticky nav, logo, mobile drawer
6HeroHero layout, copy, scroll cue
7Phone rigAll six phone layers and labels
8TunnelTunnel section and rings
9ServicesService cards grid
10StatsParallax stats section
11ProcessHorizontal scroll track
12Testimonials3D carousel
13CTABooking form and reassembly phone
14Page heroSubpage hero styles
15Services detailDetailed service cards
16FAQAccordion and search
17PricingTables and brand tabs
18AboutStory, why-us, team, certs, timeline
19ContactForm, map, info
20Track repairTracker form and timeline
21GalleryFilter and before/after cards
22404Error page
23FooterSite footer
24GSAP helpersReveal classes
25ResponsiveBreakpoints at 992, 768, 480
26PrintPrint-friendly styles

Naming conventions

  • BEM-lite: .block-element (e.g., .svc-icon, .tcard-quote)
  • Modifiers: .is-active, .is-open, .has-error
  • JS hooks: IDs for JS targets (#phoneRig, #carousel), classes for styling
  • Utility classes: .accent, .eyebrow, .lead

JavaScript architecture

The single script.js file is organized into 24 numbered sections. No modules, no classes, no framework. Plain functions and event listeners.

Section breakdown

#SectionWhat it does
1Register pluginsgsap.registerPlugin(ScrollTrigger)
2Utilities$, $$, isMobile helpers
3Dynamic yearFooter copyright year
4Navbar scrollAdds .scrolled class after 20px
5Mobile menuToggle drawer and hamburger
6Back to topSmooth scroll to top
7Phone explosionThe main 3D timeline (pinned)
8Tunnel ringsScale/fade rings on scroll
9Service cards3D rotation reveal
10StatsParallax depth + counter animation
11ProcessHorizontal scroll pin
12Testimonials3D carousel rotation
13CTA reassemblyPhone reassembles on scroll
14FAQ accordionGSAP smooth height
15Pricing tabsBrand switcher
16Gallery filterCategory filter
17Contact formValidation and mock submit
18Track repairMock tracker with progress bar
19Upload zoneFile input visual feedback
20Subpage revealsGeneric scroll animations
21Smooth scrollAnchor links
22RefreshScrollTrigger refresh on load/resize
23404 animationBroken phone entrance
24Reduced motionRespects prefers-reduced-motion

GSAP configuration

Every scroll-linked animation uses the same core pattern. Understanding this pattern is the key to modifying the site.

Standard ScrollTrigger pattern

gsap.to(element, {
  /* animation properties */
  scrollTrigger: {
    trigger: '.section',    /* what element triggers it */
    start: 'top 80%',        /* when to start */
    end: 'bottom 20%',       /* when to end */
    scrub: 1,                /* smooth scroll sync */
    pin: false,              /* pin the trigger? */
    markers: false           /* debug markers (turn on to debug) */
  }
});

Start/end syntax

ValueMeaning
'top top'Trigger's top hits viewport's top
'top 80%'Trigger's top hits 80% down the viewport
'center center'Trigger's center hits viewport's center
'+=2000'2000px after the start position

Pin configuration

Used on the hero and process sections. Pins the element in place while the animation plays.

scrollTrigger: {
  trigger: heroSection,
  start: 'top top',
  end: '+=2200',
  pin: true,
  scrub: 1,
  anticipatePin: 1  // critical for smooth pinning
}

Always use anticipatePin: 1 on desktop. Without it, the pinned element can jitter by 1px when the pin engages. On mobile, set it to 0 to avoid a different set of issues.

Debugging

To see exactly when each animation starts and ends, set markers: true on any ScrollTrigger. This draws colored lines on the page showing the start and end positions. Remember to turn it off before shipping.

Customization guide

The site is designed to be easy to rebrand. Here are the most common changes.

Change the shop name

Search for FixLab across all HTML files and replace with your name. The logo text is in the navbar and footer. The logo mark (the CSS-drawn phone + wrench) can be replaced with your own SVG.

Change the colors

All colors are CSS variables at the top of style.css. Change them once, and the whole site updates.

:root {
  --bg: #FAFAF8;        /* page background */
  --ink: #1A1A2E;       /* primary text */
  --accent: #00B4D8;    /* primary accent */
  --accent-2: #0096C7;  /* deeper accent */
  --amber: #F77F00;     /* warning / CTA */
}

Change contact details

Search for these HTML comments in the HTML files:

  • <!-- UPDATE SHOP DESCRIPTION -->
  • <!-- UPDATE ADDRESS -->
  • <!-- UPDATE PHONE -->
  • <!-- UPDATE EMAIL -->
  • <!-- UPDATE EMERGENCY PHONE -->

Change the phone illustration

The phone is built from six layers. Each layer is a .phone-layer div. You can:

  • Change colors by editing the CSS for each layer
  • Change the size by editing width and height on .phone-layer
  • Change the exploded positions by editing the GSAP timeline in script.js section 7
  • Replace the entire phone with your own SVG by replacing the contents of .phone-rig

Add a new service

  1. Add a new <article class="service-card card-3d"> in index.html section 3
  2. Add a new <article class="svc-detail"> in services.html
  3. Add a new row to the relevant pricing tables in pricing.html
  4. The scroll animations will apply automatically (they target all .card-3d elements)

Change the scroll distance

The hero explosion uses end: '+=2200'. This means the animation plays over 2200px of scroll. Increase this number for a slower, more dramatic explosion. Decrease it for a faster one.

Performance

The site is designed to be fast. No images, no videos, no heavy libraries. The total payload is under 200KB (gzipped).

Optimizations applied

  • will-change applied only to animated elements (not globally)
  • transform-style: preserve-3d only on the rig, not on every element
  • Mobile fallback: 3D translateZ values halved on mobile
  • Font preconnect: <link rel="preconnect"> for Google Fonts
  • CDN loading: GSAP, Bootstrap, FontAwesome all load from fast CDNs
  • No images: Zero image requests. Every visual is CSS.
  • Passive scroll listeners: { passive: true } on scroll handlers

Lighthouse targets

MetricTarget
Performance90+
Accessibility95+
Best Practices100
SEO100

If animations feel janky on low-end devices, reduce the number of layers, simplify the motherboard details, or disable the tunnel transition. The site is designed to degrade gracefully.

Accessibility

The site follows WCAG 2.1 AA guidelines where possible.

Implemented

  • Semantic HTML: <nav>, <main>, <section>, <article>, <aside>, <footer>
  • ARIA labels: on icon-only buttons, social links, logo
  • ARIA expanded: on FAQ accordion and mobile menu
  • Keyboard navigation: all interactive elements are focusable
  • Focus styles: visible focus rings on inputs and buttons
  • Color contrast: all text meets 4.5:1 minimum ratio
  • Reduced motion: prefers-reduced-motion slows the timeline and disables scrub
  • Form labels: every input has a visible label
  • Error messages: inline, associated with inputs

Known limitations

  • The 3D phone is decorative. Screen readers will not describe it (it has no alt text, as it is CSS, not an image). Consider adding a visually-hidden description if the phone conveys meaning.
  • The horizontal scroll section may be confusing for keyboard-only users. Consider adding a skip link or alternative navigation.

Browser support

The site works in all modern browsers. IE is not supported.

BrowserVersionStatus
Chrome90+Full support
Firefox90+Full support
Safari15+Full support
Edge90+Full support
iOS Safari15+Full support (reduced 3D)
Samsung Internet15+Full support
IE 11-Not supported

Required features

  • CSS custom properties (variables)
  • CSS 3D transforms (perspective, preserve-3d, translateZ)
  • CSS Grid and Flexbox
  • CSS clamp()
  • JavaScript ES6+ (arrow functions, template literals, const/let)
  • GSAP 3 (requires modern browser)

Deployment

The site is fully static. Deploy it anywhere.

Netlify

# Drag and drop the folder into netlify.com/drop
# Or connect a Git repo for auto-deploys

Vercel

npm i -g vercel
vercel

GitHub Pages

# Push to a repo, enable Pages in settings
git init
git add .
git commit -m "Initial commit"
git branch -M main
git remote add origin <your-repo-url>
git push -u origin main

Cloudflare Pages

Connect your Git repo in the Cloudflare dashboard. No build command needed.

Any static host

Upload all files via FTP, S3, or any other method. No server-side code required.

Troubleshooting

The phone does not animate

  • Check that GSAP and ScrollTrigger are loading (open DevTools console, type gsap)
  • Check that the script.js file is loaded after the HTML
  • Check the browser console for errors
  • Make sure you are not opening the file via file:// (use a local server)

Animations feel janky

  • Add will-change: transform to the animated elements (already done)
  • Reduce the number of 3D layers
  • On mobile, the 3D values are already halved. If still janky, disable the tunnel transition
  • Check that no other JS is fighting with GSAP

The pin feels sticky or jitters

  • Add anticipatePin: 1 to the ScrollTrigger config
  • Make sure the pinned element has a solid background (no transparency)
  • Check that no other element has position: fixed inside the pinned section

Fonts do not load

  • Check the Google Fonts link in the HTML head
  • Make sure you have internet access (fonts load from CDN)
  • If using a local server, check CORS headers

Mobile menu does not open

  • Check that the viewport is under 992px (the menu is hidden on desktop)
  • Check that script.js is loaded
  • Check the browser console for errors

Form validation does not work

  • The form is a mockup. It validates client-side but does not submit to a backend
  • To connect a real backend, replace the contactForm.addEventListener('submit') handler with a fetch() call

Still stuck? Open the browser console (F12) and look for red errors. 90% of issues are caused by a missing script, a typo in a class name, or a file not loading.