Project Cards Template

Project Cards Template

A portfolio template with expandable projects and a full-page navigation inspired by Primer app.

Nucleo icons

Sponsored by Nucleo, a free application to collect, customize and export all your icons as icon font and SVG symbols. Made by the CodyHouse folks!

All the resources available on CodyHouse are released under the BSD-3-Clause license. You can support our project with a Paypal donation 🙌

Today’s resource is inspired by the Primer app, which makes a great use of cards and motion throughout its design. We applied similar effects to a portfolio template, with expandable items and a bold, full-page navigation.

Images: Unsplash

Creating the structure

The HTML structure is composed by 3 main elements: a .cd-nav-trigger for the menu icon, a nav.cd-primary-nav for the main navigation, and a .cd-projects-container wrapping the unordered list of projects.
Each project contains a div.cd-title with the project title and a div.cd-project-info with project description. The project image is set as background-image of the .cd-title::before pseudo-element.

<header>
   <a href="#0" class="cd-logo"><img src="img/cd-logo.svg"></a>
	
   <button class="cd-nav-trigger">Menu<span aria-hidden="true" class="cd-icon"></span></button>
</header>

<nav class="cd-primary-nav">
   <ul>
      <li class="cd-label">Navigation</li>
      <li><a href="#0">The team</a></li>
      <!-- other navigation items here -->
   </ul>
</nav> <!-- .cd-primary-nav -->

<div class="cd-projects-container">
   <ul>
      <li class="single-project">
         <div class="cd-title">
            <h2>Project 1</h2>
         </div> <!-- .cd-title -->

         <div class="cd-project-info">
            <button class="cd-scroll">Scroll down</button>
				
            <div class="content-wrapper">
               <p>
                  Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quisquam molestias suscipit mollitia vitae ea non ex, dignissimos aperiam minus magni totam sint culpa vel voluptate ipsa sunt repellendus. Ab, magni!
               </p>

               <!-- additional project info here -->
            </div>
         </div> <!-- .cd-project-info -->
      </li>

      <!-- other projects here -->
   </ul>
</div> <!-- .cd-projects-container -->

Adding style

The div.cd-projects-container has a height of 100% and a relative position. The single projects are in absolute position, have a height of 100% and are placed one on top of the others in the top left corner of their wrapper .cd-projects-container.
The second and third projects are then translated along the Y axis of, respectively, one-third and two-thirds of the .cd-projects-container height. This way, only one-third of the viewport height is visible for each project.

.cd-projects-container {
  height: 100%;
  position: relative;
  overflow: hidden;
}
.cd-projects-container .single-project {
  position: absolute;
  top: 0px;
  left: 0px;
  height: 100%;
  width: 100%;
  transition: transform 0.4s;
}
.cd-projects-container .single-project:nth-of-type(2) {
  transform: translateY(33.3333333333%);
}
.cd-projects-container .single-project:nth-of-type(3) {
  transform: translateY(66.6666666667%);
}

Here's a quick animation that explains the cards positioning:

projects-template-explained

We then set a height of 33.33% to the .cd-title (one-third of the viewport height ), and a height: 300% to the .cd-title::before pseudo-element (equal to the viewport height).

.cd-title {
  height: 33.3333333333%;
}
.cd-title::before {
  /* background image */
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  height: 300%;
  width: 100%;
  background-position: center center;
  background-repeat: no-repeat;
  background-size: cover;
}
.single-project:nth-of-type(1) .cd-title::before {
  background-image: url(../img/img-1.jpg);
}

When a project is selected, we use the .selected class to assign a translateY(0) to the selected project, while we translate its project siblings to the bottom (translateY(100%)) so that the whole project image is revealed.

.cd-projects-container .single-project.selected {
  /* selected project */
  transform: translateY(0);
}
.cd-projects-container .single-project.selected ~ li {
  /* hide siblings projects */
  transform: translateY(100%);
}

As for the .cd-project-info, it has a height of 100%, an overflow: auto (to be able to scroll it) and is placed in the top-left corner of its .single-project parent. Its ::before pseudo-element is then used to push the div.content-wrapper below the project image.

.cd-project-info {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  overflow: auto;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.4s, visibility 0.4s;
}
.cd-project-info::before {
  /* use to push the .content-wrapper below the intro project image */
  content: '';
  display: block;
  height: 100%;
  width: 100%;
  pointer-events: none;
}
.cd-project-info .content-wrapper {
  position: relative;
  z-index: 2;
  padding: 2em 0 3em;
  background-color: #FFFFFF;
}
.selected .cd-project-info {
  opacity: 1;
  visibility: visible;
  transition: opacity 0s, visibility 0s;
}

As for the full-page navigation, the .cd-primary-nav is placed below the .cd-projects-container; when the user clicks the .cd-nav-trigger, the single projects are translated to the bottom to reveal the navigation.

.cd-primary-nav {
  position: absolute;
  top: 0;
  left: 0;
  /* height = (100% - 9%) - 9% is the space taken by the projects when the navigation is open */
  height: 91%;
  width: 100%;
  overflow: auto;
  opacity: 0;
}
.cd-primary-nav ul {
  transform: translateY(50px);
  transition: transform 0.4s;
}
.cd-primary-nav.nav-open {
  opacity: 1;
}
.cd-primary-nav.nav-open ul {
  transform: translateY(0);
}

.cd-projects-container.nav-open .single-project {
  box-shadow: 0 0 30px rgba(0, 0, 0, 0.5);
  transform: translateY(91%);
}
.cd-projects-container.nav-open .single-project:nth-of-type(2) {
  transform: translateY(94%);
}
.cd-projects-container.nav-open .single-project:nth-of-type(3) {
  transform: translateY(97%);
}

Events handling

We used jQuery to listen to the click events on the .cd-nav-trigger and .single-project and to add/remove classes accordingly.

Join our newsletter

Get our monthly recap with the latest CodyHouse news