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.
| Technology | Version | Purpose |
|---|---|---|
| HTML5 | Living standard | Semantic markup for all 9 pages |
| CSS3 | Level 3 + custom properties | Single style.css with variables, 3D transforms, responsive design |
| Vanilla JavaScript | ES6+ | Single script.js handling all interactivity |
| GSAP | 3.12.5 | Core animation engine |
| ScrollTrigger | 3.12.5 | Scroll-linked animations (the 3D experience) |
| Bootstrap | 5.3.2 | Responsive grid system only |
| FontAwesome | 6.5.1 | Icon 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.
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.
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.
| Role | Font | Weights used |
|---|---|---|
| Headings | Space Grotesk | 500, 600, 700 |
| Body | Inter | 400, 500, 600, 700 |
| Code / mono | JetBrains Mono | 400, 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.
| Token | Value | Used for |
|---|---|---|
--radius-sm | 8px | Inputs, small elements |
--radius | 14px | Cards, FAQ items, general |
--radius-lg | 22px | Large cards, sections |
--radius-xl | 32px | Illustrations |
| Section padding | 100-140px | Vertical rhythm between sections |
| Container max | 1240px | Main content width |
| Gutter | 24px | Grid 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.
| Class | Use |
|---|---|
.btn-accent | Primary CTA. Teal background, white text. |
.btn-amber | Emergency / warning. Amber background. |
.btn-ghost | Secondary. Transparent with border. |
.btn-outline | Tertiary. 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
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
| Phase | Time | What happens |
|---|---|---|
| 1. Open up | 0 - 1 | Rig rotates to 15deg X, -25deg Y, scales to 1.05 |
| 2. Explode | 0.3 - 1.7 | Each layer moves to its exploded position (see below) |
| 3. Labels | 0.9 - 1.5 | Six floating labels fade in, staggered |
| 4. Tunnel | 1.6 - 2.6 | Rig scales to 3x, simulating camera fly-through |
| 5. Fade | 2 - 2.5 | Layers 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:
- 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.
- 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
- On page load, each layer is set to its exploded position using
gsap.set(). - A ScrollTrigger watches the CTA section.
- When triggered, a single
gsap.to()animates all layers back tox: 0, y: 0, translateZ: 0, rotateX: 0, scale: 1, opacity: 1with 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.
CSS architecture
The single style.css file is organized into 26 clearly commented sections. No preprocessor, no modules, no build step.
Section breakdown
| # | Section | What it covers |
|---|---|---|
| 1 | Variables | All colors, fonts, radii, shadows, easing |
| 2 | Reset & base | Box-sizing, typography, scrollbar, selection |
| 3 | Layout helpers | Container, section heads, eyebrows |
| 4 | Buttons | All four button variants |
| 5 | Navbar | Sticky nav, logo, mobile drawer |
| 6 | Hero | Hero layout, copy, scroll cue |
| 7 | Phone rig | All six phone layers and labels |
| 8 | Tunnel | Tunnel section and rings |
| 9 | Services | Service cards grid |
| 10 | Stats | Parallax stats section |
| 11 | Process | Horizontal scroll track |
| 12 | Testimonials | 3D carousel |
| 13 | CTA | Booking form and reassembly phone |
| 14 | Page hero | Subpage hero styles |
| 15 | Services detail | Detailed service cards |
| 16 | FAQ | Accordion and search |
| 17 | Pricing | Tables and brand tabs |
| 18 | About | Story, why-us, team, certs, timeline |
| 19 | Contact | Form, map, info |
| 20 | Track repair | Tracker form and timeline |
| 21 | Gallery | Filter and before/after cards |
| 22 | 404 | Error page |
| 23 | Footer | Site footer |
| 24 | GSAP helpers | Reveal classes |
| 25 | Responsive | Breakpoints at 992, 768, 480 |
| 26 | Print-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
| # | Section | What it does |
|---|---|---|
| 1 | Register plugins | gsap.registerPlugin(ScrollTrigger) |
| 2 | Utilities | $, $$, isMobile helpers |
| 3 | Dynamic year | Footer copyright year |
| 4 | Navbar scroll | Adds .scrolled class after 20px |
| 5 | Mobile menu | Toggle drawer and hamburger |
| 6 | Back to top | Smooth scroll to top |
| 7 | Phone explosion | The main 3D timeline (pinned) |
| 8 | Tunnel rings | Scale/fade rings on scroll |
| 9 | Service cards | 3D rotation reveal |
| 10 | Stats | Parallax depth + counter animation |
| 11 | Process | Horizontal scroll pin |
| 12 | Testimonials | 3D carousel rotation |
| 13 | CTA reassembly | Phone reassembles on scroll |
| 14 | FAQ accordion | GSAP smooth height |
| 15 | Pricing tabs | Brand switcher |
| 16 | Gallery filter | Category filter |
| 17 | Contact form | Validation and mock submit |
| 18 | Track repair | Mock tracker with progress bar |
| 19 | Upload zone | File input visual feedback |
| 20 | Subpage reveals | Generic scroll animations |
| 21 | Smooth scroll | Anchor links |
| 22 | Refresh | ScrollTrigger refresh on load/resize |
| 23 | 404 animation | Broken phone entrance |
| 24 | Reduced motion | Respects 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
| Value | Meaning |
|---|---|
'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
widthandheighton.phone-layer - Change the exploded positions by editing the GSAP timeline in
script.jssection 7 - Replace the entire phone with your own SVG by replacing the contents of
.phone-rig
Add a new service
- Add a new
<article class="service-card card-3d">inindex.htmlsection 3 - Add a new
<article class="svc-detail">inservices.html - Add a new row to the relevant pricing tables in
pricing.html - The scroll animations will apply automatically (they target all
.card-3delements)
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-changeapplied only to animated elements (not globally)transform-style: preserve-3donly 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
| Metric | Target |
|---|---|
| Performance | 90+ |
| Accessibility | 95+ |
| Best Practices | 100 |
| SEO | 100 |
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-motionslows 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.
| Browser | Version | Status |
|---|---|---|
| Chrome | 90+ | Full support |
| Firefox | 90+ | Full support |
| Safari | 15+ | Full support |
| Edge | 90+ | Full support |
| iOS Safari | 15+ | Full support (reduced 3D) |
| Samsung Internet | 15+ | 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: transformto 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: 1to the ScrollTrigger config - Make sure the pinned element has a solid background (no transparency)
- Check that no other element has
position: fixedinside 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.jsis 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 afetch()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.