HTML and CSS course ยท Module 6: CSS Animations

Main Project - Animated 3D Portfolio Gallery

15 min read ยท
In this lesson5

Egyptian burial chambers told their stories picture by picture, and a visitor with a torch discovered them one after another. Your portfolio can work the same way: a cube rotates in space, and the project cards enter the stage one by one. In this project, we'll create a spectacular 3D gallery using only HTML and CSS. The gallery will include advanced @keyframes animations, 3D transformations, hover effects, and automatic slideshows. The project demonstrates all CSS animation techniques from this module without using JavaScript.

Project description

Our 3D gallery will include:

  • 3D Carousel - a rotating cube with images
  • Animated project cards - with hover effects and transformations
  • Automatic slideshow - using only CSS
  • Particle effects - animated background and decorations
  • Animated navigation menu - with transition effects
  • Loading screen - with advanced animations

1. The 3D scene

Everything starts with the scene. perspective given to a parent sets how far from the screen the viewer stands and gives depth to its children, while transform-style: preserve-3d lets the faces of the cube live in one shared space:

body {
  perspective: 1000px;
}

.cube-container {
  transform-style: preserve-3d;
}

.front { transform: translateZ(150px); }
.back { transform: rotateY(180deg) translateZ(150px); }

Each face is rotated and pushed out by half the width of the cube, which is 150 pixels. perspective works on the children of an element, and the cube is a grandchild of body, so in this code it rotates without perspective foreshortening. Add perspective: 1000px to .hero and compare the depth.

2. Keyframes for the rotation

The rotation is described by a @keyframes rule, from zero to a full 360 degrees around both axes at once:

@keyframes rotateCube {
  0% { transform: rotateX(0) rotateY(0); }
  100% { transform: rotateX(360deg) rotateY(360deg); }
}

rotateY() rotates an element around the vertical axis, like a carousel, and rotateX() around the horizontal one, like a water wheel on the Nile.

3. The animation in motion

The rotation is started by the animation declaration on the cube container, with the duration stored in a CSS variable:

.cube-container {
  animation: rotateCube var(--animation-duration) linear infinite;
}

The --animation-duration variable is set to 20s in :root, linear keeps a constant speed, and infinite spins the cube forever.

4. Hover effects on the cards

The project cards rise smoothly under the cursor, and when the page loads they have an entrance animation of their own:

.project-card {
  transition: transform 0.6s ease;
  animation: cardAppear 0.8s ease-out backwards;
}

.project-card:hover {
  transform: translateY(-10px) rotateX(5deg);
}

The backwards mode is the key. An animation with forwards would hold the card in its last frame and keep overriding its transform, so the hover effect would not work. With backwards, the animation hands the card back to its normal styles when it ends, and transition can lift it.

5. The entrance sequence

Finally, animation-delay makes the cards appear one after another instead of all at once:

.project-card:nth-child(1) { animation-delay: 0.1s; }
.project-card:nth-child(2) { animation-delay: 0.2s; }
.project-card:nth-child(3) { animation-delay: 0.3s; }

Each card starts 0.1 seconds after the previous one. Thanks to backwards, it waits for its turn invisible and rotated, instead of flashing before the start.

Bonus: a slider without JavaScript

The automatic slider works thanks to keyframes that repeat the same value. 0%, 20% { transform: translateX(0); } keeps a slide still for the first 20% of the cycle, then it moves by its full width and waits again. With a fifteen-second cycle, each slide stops for 3 seconds.

Find two flaws

The full code hides two traps. The first one sits in the skills section: :nth-child counts all siblings, and the first child of the section is the h2 heading. The .skill-item:nth-child(1) rule therefore matches nothing, the delays and widths shift by one, and the last bar gets the full width. :nth-of-type() fixes it, because it counts only elements of the same type. The second trap is timing: the loading screen disappears after five seconds, while the cards finish their entrance after a second and a half, so the whole sequence plays out behind the curtain.

Complete project code

The styles sit in the style tag, so all you need is to save the whole file as index.html and open it in a browser:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Animated 3D Portfolio Gallery</title>
    <style>
        /* Reset and variables */
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        :root {
            --primary-color: #6c5ce7;
            --secondary-color: #a29bfe;
            --accent-color: #fd79a8;
            --dark-color: #2d3436;
            --light-color: #dfe6e9;
            --animation-duration: 20s;
        }

        body {
            font-family: 'Arial', sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            overflow-x: hidden;
            perspective: 1000px;
        }

        /* Animated background */
        @keyframes gradientShift {
            0%, 100% { background-position: 0% 50%; }
            50% { background-position: 100% 50%; }
        }

        body {
            background-size: 200% 200%;
            animation: gradientShift 15s ease infinite;
        }

        /* Loading Screen */
        .loading-screen {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: #2d3436;
            display: flex;
            justify-content: center;
            align-items: center;
            z-index: 9999;
            animation: hideLoading 3s ease-out forwards;
            animation-delay: 2s;
        }

        @keyframes hideLoading {
            to {
                opacity: 0;
                pointer-events: none;
            }
        }

        .loader {
            width: 100px;
            height: 100px;
            position: relative;
        }

        .loader-circle {
            position: absolute;
            width: 100%;
            height: 100%;
            border: 4px solid transparent;
            border-top-color: var(--primary-color);
            border-radius: 50%;
            animation: rotate 1s linear infinite;
        }

        .loader-circle:nth-child(2) {
            width: 80%;
            height: 80%;
            top: 10%;
            left: 10%;
            border-top-color: var(--secondary-color);
            animation-duration: 0.8s;
            animation-direction: reverse;
        }

        .loader-circle:nth-child(3) {
            width: 60%;
            height: 60%;
            top: 20%;
            left: 20%;
            border-top-color: var(--accent-color);
            animation-duration: 0.6s;
        }

        @keyframes rotate {
            to { transform: rotate(360deg); }
        }

        /* Navigation */
        .navbar {
            position: fixed;
            top: 0;
            width: 100%;
            background: rgba(45, 52, 54, 0.9);
            backdrop-filter: blur(10px);
            padding: 1rem 2rem;
            z-index: 1000;
            animation: slideDown 0.8s ease-out;
        }

        @keyframes slideDown {
            from {
                transform: translateY(-100%);
                opacity: 0;
            }
            to {
                transform: translateY(0);
                opacity: 1;
            }
        }

        .nav-container {
            max-width: 1200px;
            margin: 0 auto;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        .logo {
            font-size: 2rem;
            font-weight: bold;
            color: white;
            animation: logoGlow 3s ease-in-out infinite;
        }

        @keyframes logoGlow {
            0%, 100% {
                text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
            }
            50% {
                text-shadow: 0 0 20px rgba(255, 255, 255, 0.8),
                             0 0 30px var(--accent-color);
            }
        }

        .nav-links {
            display: flex;
            gap: 2rem;
            list-style: none;
        }

        .nav-link {
            color: white;
            text-decoration: none;
            position: relative;
            transition: color 0.3s ease;
        }

        .nav-link::after {
            content: '';
            position: absolute;
            bottom: -5px;
            left: 0;
            width: 0;
            height: 2px;
            background: var(--accent-color);
            transition: width 0.3s ease;
        }

        .nav-link:hover::after {
            width: 100%;
        }

        /* Hero Section with 3D Cube */
        .hero {
            height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            position: relative;
            margin-top: -60px;
        }

        .cube-container {
            width: 300px;
            height: 300px;
            position: relative;
            transform-style: preserve-3d;
            animation: rotateCube var(--animation-duration) linear infinite;
        }

        @keyframes rotateCube {
            0% { transform: rotateX(0) rotateY(0); }
            100% { transform: rotateX(360deg) rotateY(360deg); }
        }

        .cube-face {
            position: absolute;
            width: 300px;
            height: 300px;
            background: rgba(255, 255, 255, 0.1);
            backdrop-filter: blur(10px);
            border: 2px solid rgba(255, 255, 255, 0.2);
            display: flex;
            justify-content: center;
            align-items: center;
            font-size: 3rem;
            color: white;
        }

        .cube-face::before {
            content: '';
            position: absolute;
            inset: 0;
            background: linear-gradient(135deg,
                transparent 0%,
                var(--primary-color) 50%,
                transparent 100%);
            opacity: 0;
            animation: shimmer 3s ease-in-out infinite;
        }

        @keyframes shimmer {
            0%, 100% { opacity: 0; }
            50% { opacity: 0.5; }
        }

        .front { transform: translateZ(150px); }
        .back { transform: rotateY(180deg) translateZ(150px); }
        .left { transform: rotateY(-90deg) translateZ(150px); }
        .right { transform: rotateY(90deg) translateZ(150px); }
        .top { transform: rotateX(90deg) translateZ(150px); }
        .bottom { transform: rotateX(-90deg) translateZ(150px); }

        /* Animated particles */
        .particles {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            pointer-events: none;
            overflow: hidden;
        }

        .particle {
            position: absolute;
            width: 4px;
            height: 4px;
            background: white;
            border-radius: 50%;
            animation: float linear infinite;
        }

        @keyframes float {
            from {
                transform: translateY(100vh) rotate(0deg);
                opacity: 0;
            }
            10% {
                opacity: 1;
            }
            90% {
                opacity: 1;
            }
            to {
                transform: translateY(-100vh) rotate(360deg);
                opacity: 0;
            }
        }

        .particle:nth-child(1) { left: 10%; animation-duration: 15s; animation-delay: 0s; }
        .particle:nth-child(2) { left: 20%; animation-duration: 20s; animation-delay: 2s; }
        .particle:nth-child(3) { left: 30%; animation-duration: 18s; animation-delay: 4s; }
        .particle:nth-child(4) { left: 40%; animation-duration: 22s; animation-delay: 1s; }
        .particle:nth-child(5) { left: 50%; animation-duration: 16s; animation-delay: 3s; }
        .particle:nth-child(6) { left: 60%; animation-duration: 19s; animation-delay: 5s; }
        .particle:nth-child(7) { left: 70%; animation-duration: 21s; animation-delay: 2s; }
        .particle:nth-child(8) { left: 80%; animation-duration: 17s; animation-delay: 4s; }
        .particle:nth-child(9) { left: 90%; animation-duration: 23s; animation-delay: 1s; }
        .particle:nth-child(10) { left: 95%; animation-duration: 14s; animation-delay: 6s; }

        /* Project gallery */
        .gallery {
            padding: 100px 2rem;
            max-width: 1200px;
            margin: 0 auto;
        }

        .gallery-title {
            text-align: center;
            font-size: 3rem;
            color: white;
            margin-bottom: 3rem;
            animation: fadeInUp 1s ease-out;
        }

        @keyframes fadeInUp {
            from {
                opacity: 0;
                transform: translateY(30px);
            }
            to {
                opacity: 1;
                transform: translateY(0);
            }
        }

        .projects-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
            gap: 2rem;
        }

        .project-card {
            background: rgba(255, 255, 255, 0.1);
            backdrop-filter: blur(10px);
            border-radius: 15px;
            overflow: hidden;
            position: relative;
            height: 400px;
            transform-style: preserve-3d;
            transition: transform 0.6s ease;
            animation: cardAppear 0.8s ease-out backwards;
        }

        .project-card:nth-child(1) { animation-delay: 0.1s; }
        .project-card:nth-child(2) { animation-delay: 0.2s; }
        .project-card:nth-child(3) { animation-delay: 0.3s; }
        .project-card:nth-child(4) { animation-delay: 0.4s; }
        .project-card:nth-child(5) { animation-delay: 0.5s; }
        .project-card:nth-child(6) { animation-delay: 0.6s; }

        @keyframes cardAppear {
            from {
                opacity: 0;
                transform: rotateY(90deg) scale(0.8);
            }
            to {
                opacity: 1;
                transform: rotateY(0) scale(1);
            }
        }

        .project-card:hover {
            transform: translateY(-10px) rotateX(5deg);
            box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
        }

        .project-image {
            width: 100%;
            height: 60%;
            background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
            position: relative;
            overflow: hidden;
        }

        .project-image::before {
            content: 'โ–ฃ';
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            font-size: 4rem;
            opacity: 0.5;
        }

        .project-wave {
            position: absolute;
            bottom: -50%;
            left: -50%;
            width: 200%;
            height: 200%;
            background: rgba(255, 255, 255, 0.1);
            border-radius: 40%;
            animation: wave 10s linear infinite;
        }

        @keyframes wave {
            from { transform: rotate(0deg); }
            to { transform: rotate(360deg); }
        }

        .project-content {
            padding: 1.5rem;
            color: white;
        }

        .project-title {
            font-size: 1.5rem;
            margin-bottom: 0.5rem;
        }

        .project-description {
            opacity: 0.8;
            line-height: 1.6;
        }

        /* Automatic slider */
        .slider {
            width: 100%;
            height: 500px;
            margin: 100px auto;
            overflow: hidden;
            position: relative;
            background: rgba(255, 255, 255, 0.05);
            border-radius: 20px;
        }

        .slider-wrapper {
            display: flex;
            animation: slide 15s ease-in-out infinite;
        }

        @keyframes slide {
            0%, 20% { transform: translateX(0); }
            25%, 45% { transform: translateX(-100%); }
            50%, 70% { transform: translateX(-200%); }
            75%, 95% { transform: translateX(-300%); }
            100% { transform: translateX(0); }
        }

        .slide {
            min-width: 100%;
            height: 500px;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 3rem;
            color: white;
            position: relative;
        }

        .slide:nth-child(1) { background: linear-gradient(135deg, #667eea, #764ba2); }
        .slide:nth-child(2) { background: linear-gradient(135deg, #f093fb, #f5576c); }
        .slide:nth-child(3) { background: linear-gradient(135deg, #4facfe, #00f2fe); }
        .slide:nth-child(4) { background: linear-gradient(135deg, #fa709a, #fee140); }

        .slide-content {
            text-align: center;
            animation: slideContent 15s ease-in-out infinite;
        }

        @keyframes slideContent {
            0%, 20% { opacity: 1; transform: scale(1); }
            22%, 25% { opacity: 0; transform: scale(0.8); }
            25%, 45% { opacity: 1; transform: scale(1); }
            47%, 50% { opacity: 0; transform: scale(0.8); }
            50%, 70% { opacity: 1; transform: scale(1); }
            72%, 75% { opacity: 0; transform: scale(0.8); }
            75%, 95% { opacity: 1; transform: scale(1); }
            97%, 100% { opacity: 0; transform: scale(0.8); }
        }

        /* Slide indicators */
        .slider-indicators {
            position: absolute;
            bottom: 20px;
            left: 50%;
            transform: translateX(-50%);
            display: flex;
            gap: 10px;
        }

        .indicator {
            width: 50px;
            height: 4px;
            background: rgba(255, 255, 255, 0.3);
            position: relative;
            overflow: hidden;
        }

        .indicator::before {
            content: '';
            position: absolute;
            top: 0;
            left: -100%;
            width: 100%;
            height: 100%;
            background: white;
            animation: indicatorFill 15s ease-in-out infinite;
        }

        .indicator:nth-child(1)::before { animation-delay: 0s; }
        .indicator:nth-child(2)::before { animation-delay: 3.75s; }
        .indicator:nth-child(3)::before { animation-delay: 7.5s; }
        .indicator:nth-child(4)::before { animation-delay: 11.25s; }

        @keyframes indicatorFill {
            0% { left: -100%; }
            25% { left: 0; }
            26%, 100% { left: 100%; }
        }

        /* Skills section with animations */
        .skills {
            padding: 100px 2rem;
            max-width: 1200px;
            margin: 0 auto;
        }

        .skills-title {
            text-align: center;
            font-size: 3rem;
            color: white;
            margin-bottom: 3rem;
        }

        .skill-item {
            margin-bottom: 2rem;
            opacity: 0;
            animation: skillAppear 0.8s ease-out forwards;
        }

        .skill-item:nth-child(1) { animation-delay: 0.2s; }
        .skill-item:nth-child(2) { animation-delay: 0.4s; }
        .skill-item:nth-child(3) { animation-delay: 0.6s; }
        .skill-item:nth-child(4) { animation-delay: 0.8s; }

        @keyframes skillAppear {
            from {
                opacity: 0;
                transform: translateX(-50px);
            }
            to {
                opacity: 1;
                transform: translateX(0);
            }
        }

        .skill-name {
            color: white;
            margin-bottom: 0.5rem;
        }

        .skill-bar {
            width: 100%;
            height: 20px;
            background: rgba(255, 255, 255, 0.1);
            border-radius: 10px;
            overflow: hidden;
        }

        .skill-progress {
            height: 100%;
            background: linear-gradient(90deg, var(--primary-color), var(--accent-color));
            border-radius: 10px;
            animation: fillBar 2s ease-out forwards;
            animation-delay: 1s;
            transform-origin: left;
            transform: scaleX(0);
        }

        @keyframes fillBar {
            to { transform: scaleX(1); }
        }

        .skill-item:nth-child(1) .skill-progress { width: 90%; }
        .skill-item:nth-child(2) .skill-progress { width: 85%; }
        .skill-item:nth-child(3) .skill-progress { width: 95%; }
        .skill-item:nth-child(4) .skill-progress { width: 80%; }

        /* Footer with animation */
        .footer {
            padding: 50px 2rem;
            text-align: center;
            color: white;
            position: relative;
            overflow: hidden;
        }

        .footer::before {
            content: '';
            position: absolute;
            top: 0;
            left: -100%;
            width: 100%;
            height: 2px;
            background: linear-gradient(90deg,
                transparent,
                var(--accent-color),
                transparent);
            animation: footerLine 3s linear infinite;
        }

        @keyframes footerLine {
            to { left: 100%; }
        }

        /* Responsiveness */
        @media (max-width: 768px) {
            .cube-container {
                width: 200px;
                height: 200px;
            }

            .cube-face {
                width: 200px;
                height: 200px;
                font-size: 2rem;
            }

            .front { transform: translateZ(100px); }
            .back { transform: rotateY(180deg) translateZ(100px); }
            .left { transform: rotateY(-90deg) translateZ(100px); }
            .right { transform: rotateY(90deg) translateZ(100px); }
            .top { transform: rotateX(90deg) translateZ(100px); }
            .bottom { transform: rotateX(-90deg) translateZ(100px); }

            .gallery-title,
            .skills-title {
                font-size: 2rem;
            }

            .nav-links {
                display: none;
            }
        }
    </style>
</head>
<body>
    <!-- Loading Screen -->
    <div class="loading-screen">
        <div class="loader">
            <div class="loader-circle"></div>
            <div class="loader-circle"></div>
            <div class="loader-circle"></div>
        </div>
    </div>

    <!-- Animated background particles -->
    <div class="particles">
        <div class="particle"></div>
        <div class="particle"></div>
        <div class="particle"></div>
        <div class="particle"></div>
        <div class="particle"></div>
        <div class="particle"></div>
        <div class="particle"></div>
        <div class="particle"></div>
        <div class="particle"></div>
        <div class="particle"></div>
    </div>

    <!-- Navigation -->
    <nav class="navbar">
        <div class="nav-container">
            <div class="logo">Portfolio 3D</div>
            <ul class="nav-links">
                <li><a href="#home" class="nav-link">Home</a></li>
                <li><a href="#projects" class="nav-link">Projects</a></li>
                <li><a href="#skills" class="nav-link">Skills</a></li>
                <li><a href="#contact" class="nav-link">Contact</a></li>
            </ul>
        </div>
    </nav>

    <!-- Hero Section with 3D Cube -->
    <section class="hero" id="home">
        <div class="cube-container">
            <div class="cube-face front">Front</div>
            <div class="cube-face back">Back</div>
            <div class="cube-face left">Left</div>
            <div class="cube-face right">Right</div>
            <div class="cube-face top">Top</div>
            <div class="cube-face bottom">Bottom</div>
        </div>
    </section>

    <!-- Automatic Slider -->
    <div class="slider">
        <div class="slider-wrapper">
            <div class="slide">
                <div class="slide-content">
                    <h2>Project 1</h2>
                    <p>Corporate website</p>
                </div>
            </div>
            <div class="slide">
                <div class="slide-content">
                    <h2>Project 2</h2>
                    <p>Mobile application</p>
                </div>
            </div>
            <div class="slide">
                <div class="slide-content">
                    <h2>Project 3</h2>
                    <p>E-commerce</p>
                </div>
            </div>
            <div class="slide">
                <div class="slide-content">
                    <h2>Project 4</h2>
                    <p>Analytics dashboard</p>
                </div>
            </div>
        </div>
        <div class="slider-indicators">
            <div class="indicator"></div>
            <div class="indicator"></div>
            <div class="indicator"></div>
            <div class="indicator"></div>
        </div>
    </div>

    <!-- Project gallery -->
    <section class="gallery" id="projects">
        <h2 class="gallery-title">My Projects</h2>
        <div class="projects-grid">
            <div class="project-card">
                <div class="project-image">
                    <div class="project-wave"></div>
                </div>
                <div class="project-content">
                    <h3 class="project-title">Project Alpha</h3>
                    <p class="project-description">A modern web application using the latest technologies.</p>
                </div>
            </div>
            <div class="project-card">
                <div class="project-image">
                    <div class="project-wave"></div>
                </div>
                <div class="project-content">
                    <h3 class="project-title">Project Beta</h3>
                    <p class="project-description">A responsive online store with an advanced payment system.</p>
                </div>
            </div>
            <div class="project-card">
                <div class="project-image">
                    <div class="project-wave"></div>
                </div>
                <div class="project-content">
                    <h3 class="project-title">Project Gamma</h3>
                    <p class="project-description">A content management system for large enterprises.</p>
                </div>
            </div>
            <div class="project-card">
                <div class="project-image">
                    <div class="project-wave"></div>
                </div>
                <div class="project-content">
                    <h3 class="project-title">Project Delta</h3>
                    <p class="project-description">An interactive educational platform with gamification.</p>
                </div>
            </div>
            <div class="project-card">
                <div class="project-image">
                    <div class="project-wave"></div>
                </div>
                <div class="project-content">
                    <h3 class="project-title">Project Epsilon</h3>
                    <p class="project-description">A mobile application for personal finance management.</p>
                </div>
            </div>
            <div class="project-card">
                <div class="project-image">
                    <div class="project-wave"></div>
                </div>
                <div class="project-content">
                    <h3 class="project-title">Project Zeta</h3>
                    <p class="project-description">A social portal for photography enthusiasts.</p>
                </div>
            </div>
        </div>
    </section>

    <!-- Skills section -->
    <section class="skills" id="skills">
        <h2 class="skills-title">Skills</h2>
        <div class="skill-item">
            <div class="skill-name">HTML/CSS</div>
            <div class="skill-bar">
                <div class="skill-progress"></div>
            </div>
        </div>
        <div class="skill-item">
            <div class="skill-name">JavaScript</div>
            <div class="skill-bar">
                <div class="skill-progress"></div>
            </div>
        </div>
        <div class="skill-item">
            <div class="skill-name">CSS Animations</div>
            <div class="skill-bar">
                <div class="skill-progress"></div>
            </div>
        </div>
        <div class="skill-item">
            <div class="skill-name">Responsive Design</div>
            <div class="skill-bar">
                <div class="skill-progress"></div>
            </div>
        </div>
    </section>

    <!-- Footer -->
    <footer class="footer">
        <p>&copy; 2024 Portfolio 3D. All rights reserved.</p>
    </footer>
</body>
</html>

Summary

This project demonstrates advanced CSS animation techniques without using JavaScript:

  1. @keyframes - 17 different animations
  2. animation-timing-function - various timing functions (linear, ease, ease-in-out)
  3. animation-delay - sequential animations with precise delays
  4. animation-direction - normal and reversed animation directions
  5. animation-iteration-count - single and infinite animations
  6. animation-fill-mode - forwards and backwards to preserve states
  7. Transform 3D - a full 3D cube in a preserve-3d space
  8. Automatic slider - using only CSS
  9. Animated progress indicators - synchronization of multiple animations
  10. Particle effects - animated elements placed so that they look random

The project is fully responsive and uses the latest CSS techniques to create an impressive portfolio presentation without a single line of JavaScript. My advice: add the prefers-reduced-motion rule from the previous location, because some viewers turn off motion in their system.

In the editor below you will practise these techniques on a smaller scene with the lid of a sarcophagus. The .tomb parent already has perspective: 500px and the lid has transform-origin: top center, so all you need is to uncomment the openLid animation and its @keyframes.

Code for this lesson: style.css
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: 'Segoe UI', sans-serif;
  background: linear-gradient(180deg, #1a0a2e, #3d2914);
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  color: white;
}

.scene {
  text-align: center;
  padding: 20px;
}

h2 {
  margin-bottom: 30px;
  color: #f0c040;
}

.tomb {
  display: flex;
  justify-content: center;
  perspective: 500px;
}

.sarcophagus {
  position: relative;
  width: 200px;
  height: 300px;
}

.base {
  width: 200px;
  height: 280px;
  background: linear-gradient(180deg, #d4af37, #b8941e);
  border-radius: 10px 10px 5px 5px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 40px;
  border: 3px solid #8d6e2e;
  position: absolute;
  bottom: 0;
}

.lid {
  width: 210px;
  height: 50px;
  background: linear-gradient(180deg, #c9a84c, #a88932);
  border-radius: 8px 8px 0 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 18px;
  font-weight: bold;
  border: 3px solid #8d6e2e;
  position: absolute;
  top: 0;
  left: -5px;
  transform-origin: top center;
  /* Add the animation shorthand here */
  /* animation: openLid 3s ease-in-out 1s 1 normal forwards; */
}

/* Define @keyframes openLid */
/* @keyframes openLid { */
/*   0% { transform: rotateX(0deg); } */
/*   100% { transform: rotateX(-120deg); } */
/* } */

.hint {
  margin-top: 30px;
  color: #aaa;
  font-style: italic;
}

Remember: a 3D gallery is a burial chamber in which perspective places the viewer, keyframes tell the story, and delays reveal it picture by picture.

Docs entries used in this lesson

Spotted a mistake in this lesson?

Check yourself

Answer the questions from this lesson. Pick an answer to see right away whether it is correct.

  1. 1. Which CSS property on the parent creates a 3D scene with depth for its child elements?

  2. 2. Which transform function rotates a 3D carousel around the vertical axis?

These are 2 of 3 questions for this lesson. Solve the rest in the game.

Hands-on tasks in the game

  • Vertical ordering

    Order the steps for building the animated 3D gallery from foundation to final effect:

Useful articles