CodyHouse » Templates https://codyhouse.co A free library of HTML/CSS/Javascript resources to boost your web projects and learn new tricks. Tue, 04 Apr 2017 16:36:48 +0000 en-US hourly 1 http://wordpress.org/?v=4.2.12 Image Mask Effect https://codyhouse.co/gem/image-mask-effect/ https://codyhouse.co/gem/image-mask-effect/#comments Wed, 15 Feb 2017 12:13:59 +0000 https://codyhouse.co/?post_type=gem&p=19101 image-mask-effect An immersive transition effect powered by image masks and CSS transforms. We’ve been publishing in our library some nice mask effects powered by SVG properties. This time we took advantage of the PNG transparencies to zoom through the mask layer, into a project background image. If you want to change the color of the .pgn masks, you can easily do it in Photoshop (or any other graphic tool) by applying a color overlay to the whole image layer. If you plan to create your own masks, please note that this effect works only if there’s an empty space in the very center of the mask. Inspiration: Offsite Homepage Animation by Hrvoje Grubisic.

Creating the structure

The HTML structure is composed of a list of <section>s wrapped in a .cd-image-mask-effect element. Each <section> contains a div.featured-image (project image), a div.mask (image mask) and a div.cd-project-info for the project content.
<section class="project-1 cd-project-mask">
	<h1>Project Name</h1>
	<div class="featured-image"></div>
	<div class="mask">
		<img src="img/mask-01.png" alt="mask">
		<span class="mask-border mask-border-top"></span>
		<span class="mask-border mask-border-bottom"></span>
		<span class="mask-border mask-border-left"></span>
		<span class="mask-border mask-border-right"></span>
	</div>

	<a href="#0" class="project-trigger">Explore Project</a>

	<a href="#0" class="cd-scroll cd-img-replace">Scroll down</a>

	<div class="cd-project-info" data-url="project-1">
		<!-- content loaded using js -->
	</div>

	<a href="#0" class="project-close cd-img-replace">Close Project</a>
</section> <!-- .cd-project-mask -->

<section class="project-2 cd-project-mask">
	<!-- content here -->
</section>

<!-- other sections here -->
The project content is not included in the HTML but is loaded using JavaScript.

Adding style

Each .cd-project-mask has a height of 100vh (viewport height) and a width of 100%; the project image is set as background-image of the .featured-image element, while the mask image is wrapped inside the .mask element. Four .mask-border elements have been used to create a frame around the image mask to make sure the project featured image is not visible outside the mask (we used <span> elements rather than pseudo elements because their behaviour was buggy on Safari 9).
.cd-project-mask {
  position: relative;
  height: 100vh;
  width: 100%;
  overflow: hidden;
}

.cd-project-mask .featured-image {
    /* project intro image */
    position: absolute;
    left: 50%;
    top: 50%;
    bottom: auto;
    right: auto;
    transform: translateX(-50%) translateY(-50%);
  	height: 100%;
  	width: 100%;
  	background: url(../img/img-01.jpg) no-repeat center center;
  	background-size: cover;
}

.cd-project-mask .mask {
  position: absolute;
  left: 50%;
  top: 50%;
  bottom: auto;
  right: auto;
  transform: translateX(-50%) translateY(-50%);
  width: 300px;
  height: 300px;
}

.cd-project-mask .mask .mask-border {
  /* this is used to create a frame around the mask */
  position: absolute;
}

.cd-project-mask .mask .mask-border-top,
.cd-project-mask .mask .mask-border-bottom {
    /* this is used to create a frame around the mask */
    height: calc(50vh - 150px + 10px);
    width: 100vw;
    left: 50%;
    right: auto;
    transform: translateX(-50%);
}

.cd-project-mask .mask .mask-border-top {
    bottom: calc(100% - 10px);
}

.cd-project-mask .mask .mask-border-bottom {
    top: calc(100% - 10px);
}

.cd-project-mask .mask .mask-border-left,
.cd-project-mask .mask .mask-border-right {
  	/* this is used to create a frame around the mask */
	height: 100vh;
	width: calc(50vw - 150px + 10px);
	top: 50%;
	bottom: auto;
    transform: translateY(-50%);
}

.cd-project-mask .mask .mask-border-left {
  left: calc(100% - 10px);
}

.cd-project-mask .mask .mask-border-right {
  right: calc(100% - 10px);
}
When the user selects a project, the class .project-view (added to the wrapper .cd-image-mask-effect) is used to hide all the other projects. The .mask element is then scaled up to reveal the project featured image and the project content is loaded (more in the Events handling section).
.project-view .cd-project-mask:not(.project-selected) {
   /* the project-view class is added to the .cd-image-mask-effect element when a project is selected - hide all not selected projects */
   position: absolute;
   top: 0;
   left: 0;
   opacity: 0;
   visibility: hidden;
}

Events handling

To implement this image mask effect, we created a ProjectMask object and used the initProject method to attach the proper event handlers.
function ProjectMask( element ) {
	this.element = element;
	this.projectTrigger = this.element.find('.project-trigger');
	this.projectClose = this.element.find('.project-close'); 
	this.projectTitle = this.element.find('h1');
	this.projectMask = this.element.find('.mask');
	//...
	this.initProject();
}

var revealingProjects = $('.cd-project-mask');
var objProjectMasks = [];

if( revealingProjects.length > 0 ) {
	revealingProjects.each(function(){
		//create ProjectMask objects
		objProjectMasks.push(new ProjectMask($(this)));
	});
}
When the user selects a project, the revealProject method is used to scale up the mask image while the uploadContent method takes care of loading the project content (using the load() function) and adding the new page to the window.history (using the pushState() method).
ProjectMask.prototype.initProject = function() {
	var self = this;

	//open the project
	this.projectTrigger.on('click', function(event){
		event.preventDefault();
		if( !self.animating ) {
			self.animating = true;
			//upload project content
			self.uploadContent();
			//show project content and scale up mask
			self.revealProject();
		}
	});

	//...
};

ProjectMask.prototype.revealProject = function() {
	var self = this;
	//get mask scale value
	self.updateMaskScale();
	//scale up mask and animate project title
	self.projectTitle.attr('style', 'opacity: 0;');
	self.projectMask.css('transform', 'translateX(-50%) translateY(-50%) scale('+self.maskScaleValue+')').one(transitionEnd, function(){
		self.element.addClass('center-title');
		self.projectTitle.attr('style', '');
		self.animating = false;
	});

	//hide the other sections
	self.element.addClass('project-selected content-visible').parent('.cd-image-mask-effect').addClass('project-view');
}

ProjectMask.prototype.uploadContent = function(){
	var self = this;
	//if content has not been loaded -> load it
	if( self.projectContent.find('.content-wrapper').length == 0 ) self.projectContent.load(self.projectContentUrl+'.html .cd-project-info > *');
	
	if( self.projectContentUrl+'.html'!=window.location ){
        //add the new page to the window.history
        window.history.pushState({path: self.projectContentUrl+'.html'},'',self.projectContentUrl+'.html');
    }
}
 ]]>
https://codyhouse.co/gem/image-mask-effect/feed/ 11
Immersive Video Template https://codyhouse.co/gem/immersive-video-template/ https://codyhouse.co/gem/immersive-video-template/#comments Tue, 20 Dec 2016 11:53:39 +0000 https://codyhouse.co/?post_type=gem&p=18351 Immersive Video Template A full-screen video presentation, that is resized and animated to become the content of a mobile device. Lately, we’ve come across a couple of websites using full-screen videos as a way to “dramatically” introduce a product or a feature of an app. We tried to create something similar, in the form of a simple application template. With the help of some CSS tricks (mostly CSS transforms and animations), we’ve created a video introduction that turns into content for a mobile device. Some of the tricky parts were about picking the right assets (we’ve created this video that introduces some features of a photo editing app) and handling the loading animation. We’re aware that showing a loader right away is not a great experience. Therefore, before using this template you should consider 1) whether the video would be a great selling point or not, and 2) whether you expect many returning visitors, or the goal is just to let the user download the app and (almost) never come back. If the answers to these questions are 1) YES and 2) No, then this template will come in handy for you ;) Video footage: pexels.com Inspiration: Landing Page by Cuberto Icons: Nucleo

Creating the structure

The HTML structure is composed of two main elements: a div.product-intro (product title, action button, ..) and a div.product-preview for the image/video preview of the product.
<div class="cd-immersive-video">
	<div class="intro-wrapper">
		<div class="product-intro">
			<div>
				<h1 class="item item-1">Immersive Video Template</h1>
				
				<p class="item item-2">
					<!-- product description here -->
				</p>

				<a href="#0" class="cd-btn item item-3">Download &amp; Article</a>
			</div>
		</div> <!-- .product-intro -->

		<div class="product-preview no-transition">
			<div class="device-frame">
				<img src="assets/img/mobile-frame.svg" alt="Device Frame">
			</div>

			<div class="product-image">
				<img src="assets/img/video-fallback.jpg" alt="Product Preview">
			</div>

			<div class="product-video" data-video="assets/video/video.mp4">
				<div class="video-wrapper">
					<!-- video will be insereted here using js -->
				</div>
			</div>
		</div> <!-- .product-preview -->
	</div> 

	<div class="cd-loader"></div>
</div> <!-- .cd-immersive-video -->
The video is not directly inserted in the HTML but it's loaded using JavaScript.

Adding style

On small devices (viewport width less than 800px), the css is pretty straightforward: both the div.product-intro and the div.product-preview are initially hidden ( opacity: 0 ) and then revealed using the cd-item-move-up animation.
@keyframes cd-item-move-up {
  	0% {
    	opacity: 0;
    	transform: translateY(50px);
  	}
  	100% {
   		opacity: 1;
    	transform: translateY(0);
  	}
}
The product preview video is not loaded on these devices while an image is used as a preview of the product (div.product-image > img). On bigger devices, the product intro and preview are initially hidden; once the preview video has been loaded (more in the Events handling section), the .video-is-loaded class is used to reveal the content and trigger the video animation.
@media only screen and (min-width: 800px) {
  .cd-immersive-video .intro-wrapper {
    /* while loading the video - hide the content */
    visibility: hidden;
    opacity: 0;
    transition: opacity 1.5s, visibility 1.5s;
  }
  .cd-immersive-video.video-is-loaded .intro-wrapper {
    /* video has been loaded - reveal content */
    visibility: visible;
    opacity: 1;
  }
}
For the video animation: the div.product-video is initially scaled-up (using JavaScript) to cover the entire viewport. Once the video has been played for around 1s, the div.product-video is scaled down and translated to the right. The div.product-intro is then animated using the .animate-content class: each item of the intro (h1, p..) is revealed using the cd-item-slide-in animation with a different animation-delay.
@media only screen and (min-width: 800px) {
  .cd-immersive-video .product-intro .item {
    opacity: 0;
  }
  .cd-immersive-video .product-intro.animate-content .item {
    animation: cd-item-slide-in .4s both;
  }
  .cd-immersive-video .product-intro.animate-content .item-1 {
    animation-delay: .1s;
  }
  .cd-immersive-video .product-intro.animate-content .item-2 {
    animation-delay: .2s;
  }
  .cd-immersive-video .product-intro.animate-content .item-3 {
    animation-delay: .3s;
  }
}

Events handling

On big devices, we use jQuery to load the product preview video and insert it into the HTML structure. We used the canplaythrough event to detect if the video is ready to be played; we also added a check to the readyState of the video  (if the video is cached the canplaythrough event may not be detected).
if( productVideo.is(':visible') ) { //productVideo = $('.product-video');
	//the video src is the data-video of productVideo
	var	video = $('<video><source src="'+productVideo.data('video')+'" type="video/mp4" />Sorry, your browser does not support HTML5 video.</video>');
	
	//check if the canplaythrough event occurs - video is ready to be played
	video.on('canplaythrough', function() {
		video.off('canplaythrough').trigger('readyToPlay');
	});
	// if video is in cache 'canplaythrough' won't be triggered 
	if (video.get(0).readyState > 3) {
		video.off('canplaythrough').trigger('readyToPlay');
	}
}
Once the video is ready, we insert it into the HTML, hide the loader and then trigger the video animation.
video.on('readyToPlay', function(){
	//video id ready to play
	video.appendTo(productVideo.find('.video-wrapper'));

	//wait for the end of an animation iteraction and reveal the video
	loader.one(animationIteration, function() {
        loader.addClass('no-animation').off(animationIteration);
		//makes sure the transition is applied (when using the scale-down class)
        //https://css-tricks.com/restart-css-animation/
        void loader.get(0).offsetWidth; 
        loader.addClass('scale-down');
        loader.on(transitionEnd, function(){
        	loader.off(transitionEnd);
        	immersiveVideoWrapper.trigger('startAnimation'); //animate div.product-video
        });
    });
});
]]>
https://codyhouse.co/gem/immersive-video-template/feed/ 4
Schedule Template https://codyhouse.co/gem/schedule-template/ https://codyhouse.co/gem/schedule-template/#comments Wed, 16 Nov 2016 12:05:33 +0000 https://codyhouse.co/?post_type=gem&p=17901 Schedule Template A simple template that lets you display events on a timeline, as well as organize them in groups (week days, conference rooms etc…) We’ve come across this web component many times: when we check the schedule of a conference, or the timetable of the classes of our gym. From a web designer perspective, it is handy to have a simple, responsive template to use if you ever need to create a schedule table. So we built one!

Creating the structure

The HTML structure is composed of three different elements: a div.timeline for the events timeline(09:00, 09:30, ..), a div.events wrapping the events list and a div.event-modal for the modal window used to provide more details about the selected event.
<div class="cd-schedule">
	<div class="timeline">
		<ul>
			<li><span>09:00</span></li>
			<li><span>09:30</span></li>
			<!-- additional elements here -->
		</ul>
	</div>

	<div class="events">
		<ul>
			<li class="events-group">
				<div class="top-info"><span>Monday</span></div>

				<ul>
					<li class="single-event" data-start="09:30" data-end="10:30" data-content="event-abs-circuit" data-event="event-1">
						<a href="#0">
							<em class="event-name">Abs Circuit</em>
						</a>
					</li>

					<!-- other events here -->
				</ul>
			</li>

			<li class="events-group">
				<div class="top-info"><span>Tuesday</span></div>

				<ul>
					<!-- events here -->
				</ul>
			</li>

			<!-- additional li.events-group here -->
		</ul>
	</div>

	<div class="event-modal">
		<header class="header">
			<div class="content">
				<span class="event-date"></span>
				<h3 class="event-name"></h3>
			</div>

			<div class="header-bg"></div>
		</header>

		<div class="body">
			<div class="event-info"></div>
			<div class="body-bg"></div>
		</div>

		<a href="#0" class="close">Close</a>
	</div>
</div>

Adding style

On small devices (window width less than 800px), all the events inside an .events-group are lined up horizontally: we set a display: flex to the .events-group > ul element and an overflow-x: scroll to make the events scrollable.
.cd-schedule .events .events-group > ul {
  position: relative;
  padding: 0 5%;
  /* force its children to stay on one line */
  display: flex;
  overflow-x: scroll;
  -webkit-overflow-scrolling: touch;
}

.cd-schedule .events .single-event {
  /* force them to stay on one line */
  flex-shrink: 0;
  float: left;
  height: 150px;
  width: 70%;
  max-width: 300px;
}
As for the .event-modal, it is has a fixed position and it is moved to the right outside the viewport. When the user selects an event, the .modal-is-open class is used to translate the .event-modal back into the viewport.
.cd-schedule .event-modal {
  position: fixed;
  z-index: 3;
  top: 0;
  right: 0;
  height: 100%;
  width: 100%;
  visibility: hidden;
  transform: translateX(100%);
  transition: transform .4s, visibility .4s;
}

.cd-schedule.modal-is-open .event-modal {
  /* .modal-is-open class is added as soon as an event is selected */
  transform: translateX(0);
  visibility: visible;
}
On bigger devices, all the events are in absolute position and placed inside a timetable: the top position and the height of each event are evaluated using the data-start and data-end attributes of the event itself and set using JavaScript (more in the Events handling section).
@media only screen and (min-width: 800px) {
  .cd-schedule .events {
    float: left;
    width: 100%;
  }
  .cd-schedule .events .events-group {
    width: 20%;
    float: left;
  }
  .cd-schedule .events .single-event {
    position: absolute;
    z-index: 3;
    /* top position and height will be set using js */
    width: calc(100% + 2px);
    left: -1px;
  }
}
As for the .event-modal, the opening/closing animation is created using jQuery combined with CSS Transitions and Transformations (more in the Events handling section).

Events handling

To implement this event schedule, we created a SchedulePlan object and used the scheduleReset() and initEvents() functions to init the schedule and attach event handlers to the proper elements.
function SchedulePlan( element ) {
  this.element = element;
  this.timeline = this.element.find('.timeline');
  //...
  
  this.eventsWrapper = this.element.find('.events');
  this.eventsGroup = this.eventsWrapper.find('.events-group');
  this.singleEvents = this.eventsGroup.find('.single-event');
  //..

  this.scheduleReset();
  this.initEvents();
}
On big devices, the scheduleReset() method takes care of placing the events inside the timetable and set their height. To evaluate the height, for example, we calculate the duration of the event (data-end minus data-start), divide it by the 'eventUnit' (in our case it's 30 minutes) and then multiply it by the height of 'timeline unit' (in our case, 50px).
var self = this;
this.singleEvents.each(function(){
  //place each event in the grid -> need to set top position and height
  var start = getScheduleTimestamp($(this).attr('data-start')), //getScheduleTimestamp converts hh:mm to timestamp
    duration = getScheduleTimestamp($(this).attr('data-end')) - start;

  var eventTop = self.eventUnitHeight*(start - self.timelineStart)/self.timelineUnitDuration,
    eventHeight = self.eventUnitHeight*duration/self.timelineUnitDuration;
  
  $(this).css({
    top: (eventTop -1) +'px',
    height: (eventHeight+1)+'px'
  });
});
When the user selects an event, the jQuery load() function is used to load the content of the event just selected (its data-content is used to determine the file content to be loaded). In addition to that, on big devices, the .event-modal is animated to show the event content. First, the .event-modal is placed on top of the selected event and its height and width are changed to be equal to the ones of the selected event; then the .header-bg and .body-bg elements are scaled up to create the morphing animation; at the end of this animation, the modal content is revealed.
SchedulePlan.prototype.openModal = function(event) {
	var self = this;
	var mq = self.mq();
	this.animating = true;

	//update event name and time
	this.modalHeader.find('.event-name').text(event.find('.event-name').text());
	this.modalHeader.find('.event-date').text(event.find('.event-date').text());
	this.modal.attr('data-event', event.parent().attr('data-event'));

	//update event content
	this.modalBody.find('.event-info').load(event.parent().attr('data-content')+'.html .event-info > *', function(data){
		//once the event content has been loaded
		self.element.addClass('content-loaded');
	});

	this.element.addClass('modal-is-open');

	if( mq == 'mobile' ) {
		self.modal.one(transitionEnd, function(){
			self.modal.off(transitionEnd);
			self.animating = false;
		});
	} else {
		//change modal height/width and translate it
		self.modal.css({
			top: eventTop+'px', //this is the selected event top position
			left: eventLeft+'px', //this is the selected event left position
			height: modalHeight+'px', //this is the modal final height
			width: modalWidth+'px', //this is the modal final width
		});
		transformElement(self.modal, 'translateY('+modalTranslateY+'px) translateX('+modalTranslateX+'px)');

		//set modalHeader width
		self.modalHeader.css({
			width: eventWidth+'px',  //this is the selected event width
		});
		//set modalBody left margin
		self.modalBody.css({
			marginLeft: eventWidth+'px',
		});

		//change modalBodyBg height/width and scale it
		self.modalBodyBg.css({
			height: eventHeight+'px',
			width: '1px',
		});
		transformElement(self.modalBodyBg, 'scaleY('+HeaderBgScaleY+') scaleX('+BodyBgScaleX+')');

		//change modal modalHeaderBg height/width and scale it
		self.modalHeaderBg.css({
			height: eventHeight+'px',
			width: eventWidth+'px',
		});
		transformElement(self.modalHeaderBg, 'scaleY('+HeaderBgScaleY+')');
		
		self.modalHeaderBg.one(transitionEnd, function(){
			//wait for the  end of the modalHeaderBg transformation and show the modal content
			self.modalHeaderBg.off(transitionEnd);
			self.animating = false;
			self.element.addClass('animation-completed');
		});
	}
};
One note: we implemented a simple load() function to upload the new html content, but you may wanna replace it with a $.ajax call in order to handle errors, beforeSend request etc. according to your project.]]>
https://codyhouse.co/gem/schedule-template/feed/ 30
3D Portfolio Template https://codyhouse.co/gem/3d-portfolio-template/ https://codyhouse.co/gem/3d-portfolio-template/#comments Tue, 20 Sep 2016 11:24:04 +0000 https://codyhouse.co/?post_type=gem&p=16675 3D Portfolio Template A portfolio template, with a filter that triggers the rotation of 3D sections. CSS 3D Transforms can be used in plenty of creative ways, particularly if combined with CSS Transitions! Today’s nugget is a good example of how to use CSS to create a parallelepiped, whose faces are different projects. A filter on top of the page triggers the 3D rotations that reveal new projects. Images: Unsplash

Creating the structure

The HTML structure is composed of two main elements: a nav.cd-3d-portfolio-navigation for the top projects navigation and a div.projects wrapping the portfolio projects. Inside the div.projects, three unordered lists (ul.row) are used to create the three rotating parallelepipeds.
<div class="cd-3d-portfolio">
	<nav class="cd-3d-portfolio-navigation">
		<div class="cd-wrapper">
			<h1>3D Portfolio Template</h1>
				
			<ul>
				<li><a href="#0" class="selected">Filter 1</a></li>
				<li><a href="#0">Filter 2</a></li>
				<li><a href="#0">Filter 3</a></li>
			</ul>
		</div>
	</nav> <!-- .cd-3d-portfolio-navigation -->
	
	<div class="projects">
		<ul class="row">
			<li class="front-face selected project-1">
				<div class="project-wrapper">
					<div class="project-image">
						<div class="project-title">
							<h2>Project 1</h2>
						</div>
					</div> <!-- .project-image -->

					<div class="project-content">
						<!-- project content here -->
					</div> <!-- .project-content -->

					<a href="#0" class="close-project">Close</a>
				</div> <!-- .project-wrapper -->
			</li>

			<li class="right-face project-2">
				<div class="project-wrapper">
					<div class="project-image">
						<div class="project-title">
							<h2>Project 2</h2>
						</div>
					</div> <!-- .project-image -->

					<div class="project-content">
						<!-- project content here -->
					</div> <!-- .project-content -->

					<a href="#0" class="close-project">Close</a>
				</div> <!-- .project-wrapper -->
			</li>

			<li class="right-face project-3">
				<div class="project-wrapper">
					<div class="project-image">
						<div class="project-title">
							<h2>Project 3</h2>
						</div>
					</div> <!-- .project-image -->

					<div class="project-content">
						<!-- project content here -->
					</div> <!-- .project-content -->

					<a href="#0" class="close-project">Close</a>
				</div> <!-- .project-wrapper -->
			</li>
		</ul> <!-- .row -->
	
		<ul class="row">
			<!-- projects here -->
		</ul> <!-- .row -->
	
		<ul class="row">
			<!-- projects here -->
		</ul> <!-- .row -->
	</div><!-- .projects -->
</div>

Adding style

Each ul.row has a height equal to one-fourth of the viewport height and is translated along the Z-axis of half the viewport width. This way, we move the rotation center of the element away from the user of a quantity equal to half the element width. Its list items (portfolio projects) are then used to create the different faces of the parallelepiped and are translated/rotated according to the face they are on. For example, the front-face just needs to be translated back along the Z-axis, while the right face needs to be rotated along the Y-axis and translated back. Here's a simple animation explaining this concept (created using Adobe After Effects): parallelepiped-animation
.cd-3d-portfolio .projects .row {
  height: 25vh;
  position: relative;
  z-index: 1;
  /* position its children in a 3d space */
  transform-style: preserve-3d;
  transform: translateZ(-50vw);
  transition: transform 0.6s cubic-bezier(0.5, 0, 0.1, 1);
}

.cd-3d-portfolio .projects .row > li {
  /* this is the single project */
  position: absolute;
  z-index: 1;
  height: 100%;
  width: 100%;
  overflow: hidden;
}
.cd-3d-portfolio .projects .row > li.front-face {
  transform: translateZ(50vw);
}
.cd-3d-portfolio .projects .row > li.right-face {
  transform: rotateY(90deg) translateZ(50vw);
}
.cd-3d-portfolio .projects .row > li.left-face {
  transform: rotateY(-90deg) translateZ(50vw);
}
.cd-3d-portfolio .projects .row > li.back-face {
  transform: rotateY(180deg) translateZ(50vw);
}
When the user selects one of the filters in the top navigation, each ul.row is rotated to reveal the selected face (more in the Event handling section). As for the single projects, the project preview image is set as background-image of the .project-image::before element; it has an absolute position and a height of 240% the project height (which means, 60% of the viewport height); this way only a portion of the preview image is visible due to the overflow property of its .row > li ancestor. The project content is wrapped inside the .project-content element which is placed right below the project preview image. When a project is open, the overflow property of the .row > li element is changed to reveal its content.
.cd-3d-portfolio .projects .project-image {
  position: relative;
  width: 100%;
  height: 25%;
  transition: transform 0.6s;
}
.cd-3d-portfolio .projects .project-image::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  z-index: 1;
  height: 240%;
  background-position: center center;
  background-repeat: no-repeat;
  background-size: cover;
}

.cd-3d-portfolio .projects .project-content {
  position: absolute;
  /* place the content right below the project image */
  top: 60%;
  width: 100%;
  background: white;
}
For the 3D rotation to work, the transform-style property of the .row elements is set to preserve-3d so that its children are placed in a 3D space. If the browser does not support this property, we replace the 3D effect with a fade-in/fade-out effect (we use Modernizr to check browser support).
.no-preserve3d .cd-3d-portfolio .projects .row {
  	/* fallback for browsers that don't support the preser3d property */
    transform: translateZ(0);
}
.no-preserve3d .cd-3d-portfolio .projects .row > li {
  	opacity: 0;
  	transform: translateX(0);
}
.no-preserve3d .cd-3d-portfolio .projects .row > li.front-face, 
.no-preserve3d .cd-3d-portfolio .projects .row > li.right-face, 
.no-preserve3d .cd-3d-portfolio .projects .row > li.left-face, 
.no-preserve3d .cd-3d-portfolio .projects .row > li.back-face {
    transform: translateX(0);
}
.no-preserve3d .cd-3d-portfolio .projects .row > li.selected {
  opacity: 1;
}

Events handling

To implement this 3D portfolio, we created a Portfolio3D object and used the bindEvents function to attach event handlers to the proper elements.
function Portfolio3D( element ) {
	//define a Portfolio3D object
	this.element = element;
	this.navigation = this.element.children('.cd-3d-portfolio-navigation');
	this.rowsWrapper = this.element.children('.projects');
	this.rows = this.rowsWrapper.children('.row');
	this.visibleFace = 'front';
	this.visibleRowIndex = 0;
	this.rotationValue = 0;
	//animating variables
	this.animating = false;
	this.scrolling = false;
	// bind portfolio events
	this.bindEvents();
}

if( $('.cd-3d-portfolio').length > 0 ) {
	var portfolios3D = [];
	$('.cd-3d-portfolio').each(function(){
		//create a Portfolio3D object for each .cd-3d-portfolio
		portfolios3D.push(new Portfolio3D($(this)));
	});
}
The visibleFace property is used to store the parallelepiped face visible at the moment (if the ul.row has not been rotated, the visible face is the front face while, if it has been rotated of 90deg, the visible face is the left one and so on). When the user selects a filter in the top navigation, the showNewContent() method is used to move the selected faces in the right position and to rotate the ul.row elements.
Portfolio3D.prototype.bindEvents = function() {
	var self = this;

	this.navigation.on('click', 'a:not(.selected)', function(event){
		//update visible projects when clicking on the filter
		event.preventDefault();
		if( !self.animating ) {
			self.animating = true;
			var index = $(this).parent('li').index();
			
			//show new projects
			self.showNewContent(index);

			//update filter selected element
			//..
		}
	});

	//...
};
According to whether the selected filter precedes or follows the one already selected, the ul.row are rotated clockwise ('rightToLeft') or anticlockwise ('leftToRight'). The getRotationPrameters method uses this direction value plus the visibleFace property value to determine the new rotation value of the ul.row; additionally, it takes care of determining which face is going to be visible to give it the proper classes. For example, if the face visible at the moment is the front face and we need to rotate the parallelepiped 'rightToLeft', then the new visible face will be the right one.
Portfolio3D.prototype.showNewContent = function(index) {
	var self = this,
		direction = ( index > self.visibleRowIndex ) ? 'rightToLeft' : 'leftToRight',
		rotationParams = this.getRotationPrameters( direction ),
		newVisibleFace = rotationParams[0],
		rotationY = rotationParams[1],
		translateZ = $(window).width()/2;
	
	//rotate the parallelepiped
	this.setTransform(rotationY, translateZ);
	
	//update .row > li classes
	//...

	//update Portfolio3D properties
	//..
};
]]>
https://codyhouse.co/gem/3d-portfolio-template/feed/ 10
Product Builder https://codyhouse.co/gem/product-builder/ https://codyhouse.co/gem/product-builder/#comments Wed, 31 Aug 2016 12:31:00 +0000 https://codyhouse.co/?post_type=gem&p=16220 Product Builder A customizable and responsive product builder for your online store. Product builders are useful shopping tools, that allow potential customers to “build” an ideal version of a product by combining different options. At the end of this process the user is generally given an action to perform: save the build, share it or buy the product directly. Creating such a web component from scratch is no easy work. Therefore we decided to create an easy-to-customize template that can help you realize your own product builder! Photo credits for the demo: BMW.

Creating the structure

The HTML structure is composed of three main elements: a header.main-header for the builder top navigation, a div.cd-builder-steps for the builder steps and a footer.cd-builder-footer for the builder bottom navigation and the overview of the selected product.
<div class="cd-product-builder">
	<header class="main-header">
		<h1>Product Builder</h1>
		
		<nav class="cd-builder-main-nav disabled">
			<ul>
				<li class="active"><a href="#models">Models</a></li>
				<li><a href="#colors">Colors</a></li>
				<li><a href="#accessories">Accessories</a></li>
				<li><a href="#summary">Summary</a></li>
			</ul>
		</nav>
	</header>

	<div class="cd-builder-steps">
		<ul>
			<li data-selection="models" class="active builder-step">
				<section class="cd-step-content">
					<header>
						<h1>Select Model</h1>
						<span class="steps-indicator">Step <b>1</b> of 4</span>
					</header>

					<a href="#0" class="cd-nugget-info">Article &amp; Downaload</a>

					<ul class="models-list options-list cd-col-2">
						<!-- models here -->
					</ul>
				</section>
			</li>
			<!-- additional content will be inserted using ajax -->
		</ul>
	</div>

	<footer class="cd-builder-footer disabled step-1">
		<div class="selected-product">
			<img src="img/product01_col01.jpg" alt="Product preview">

			<div class="tot-price">
				<span>Total</span>
				<span class="total">$<b>0</b></span>
			</div>
		</div>
		
		<nav class="cd-builder-secondary-nav">
			<ul>
				<li class="next nav-item">
					<ul>
						<li class="visible"><a href="#0">Colors</a></li>
						<li><a href="#0">Accessories</a></li>
						<li><a href="#0">Summary</a></li>
						<li class="buy"><a href="#0">Buy Now</a></li>
					</ul>
				</li>
				<li class="prev nav-item">
					<ul>
						<li class="visible"><a href="#0">Models</a></li>
						<li><a href="#0">Models</a></li>
						<li><a href="#0">Colors</a></li>
						<li><a href="#0">Accessories</a></li>
					</ul>
				</li>
			</ul>
		</nav>

		<span class="alert">Please, select a model first!</span>
	</footer>
</div>
As for the builder steps, only the first one can be found in the index.html file. The other steps depend on the model the user selects and are loaded using Ajax (more in the Events handling section).

Adding style

The CSS used for this resource is quite simple, you can download the demo file to take a look at the entire code. Just one note about the style of the main elements of the builder: the div.cd-builder-steps is used to wrap the different builder steps; its list items are in absolute position, have a height and width of 100%, are one on top of the others and are hidden by default; the class .active is then used to reveal the active step.
.cd-builder-steps > ul {
  height: 100%;
  overflow: hidden;
}
.cd-builder-steps .builder-step {
  position: absolute;
  z-index: 1;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  overflow: auto;
  visibility: hidden;
  transition: visibility .5s;
}
.cd-builder-steps .builder-step.active {
  position: relative;
  z-index: 2;
  visibility: visible;
}

Events handling

To implement the product builder, we created a ProductBuilder object and used the bindEvents function to attach event handlers to the proper elements.
function ProductBuilder( element ) {
	this.element = element;
	this.stepsWrapper = this.element.children('.cd-builder-steps');
	//...
	this.bindEvents();
}


if( $('.cd-product-builder').length > 0 ) {
	$('.cd-product-builder').each(function(){
		//create a productBuilder object for each .cd-product-builder
		new ProductBuilder($(this));
	});
}
When the user selects one of the models, an Ajax call takes care of loading the new content for the model selected; a data-model attribute is added to each list item inside the ul.models-list and is equal to the name of the HTML file the new content has to be retrieved from (modelType in the code below).
$.ajax({
    type       : "GET",
    dataType   : "html",
    url        : modelType+".html",
    beforeSend : function(){
    	self.loaded = false;
    },
    success    : function(data){
    	self.models.after(data);
    	self.loaded = true;
    	//...
    },
    error     : function(jqXHR, textStatus, errorThrown) {
       //...
    }
});
Each list item has also a data-price, which is the price of the model selected. Data-price attributes are also added to the other options inside the builder (colors, accessories) and are used to update the final price of the product. As for the possible product customizations, we used the .options-list class to target lists of options among which the user can choose (in our example models and accessories). When the user selects an option, the updateListOptions() function takes care of updating the product price (and builder content, if a model has been selected).
//detect click on one element in an options list (e.g, models, accessories)
this.optionsLists.on('click', '.js-option', function(event){
	self.updateListOptions($(this));
});
The class .js-option is added to each list item inside the ul.options-list and is used to detect the click event. If the options are exclusive (like with models since you can select just one), a second class (.js-radio) is added. The .cd-product-customizer class is instead used for customizations like Colors, where the user can select an option and see a change in the product. When one of these options is selected, the customizeModel() function takes care of updating the product preview and the product price (if necessary).
//detect clicks on customizer controls (e.g., colors ...)
this.stepsWrapper.on('click', '.cd-product-customizer a', function(event){
	event.preventDefault();
	self.customizeModel($(this));
})
]]>
https://codyhouse.co/gem/product-builder/feed/ 27
Sliding Panels Template https://codyhouse.co/gem/sliding-panels-template/ https://codyhouse.co/gem/sliding-panels-template/#comments Wed, 17 Feb 2016 11:00:40 +0000 https://codyhouse.co/?post_type=gem&p=8532 sliding-panels-template A simple portfolio template, with project preview images that slide out to reveal the selected project. We’re not new to experimenting with portfolio templates. This time we’ve been playing around the idea of moving blocks of content as a way to transition from the main/gallery page to the project page. All panels move along the y-axis (x-axis on smaller devices), and the movement is triggered by whether the user wants to learn more about a project, or wants to access the navigation. Images: unsplash.com

Creating the structure

The HTML structure is composed of two unordered lists, the ul.cd-projects-previews for the project preview images and the ul.cd-projects for the project details, and a nav.cd-primary-nav wrapping the main navigation.
<div class="cd-projects-container">
	<ul class="cd-projects-previews">
		<li>
			<a href="#0">
				<div class="cd-project-title">
					<h2>Project 1</h2>
					<p>Brief description of the project here</p>
				</div>
			</a>
		</li>

		<li>
			<!-- project preview here -->
		</li>
		
		<!-- other project previews here -->
	</ul> <!-- .cd-projects-previews -->

	<ul class="cd-projects">
		<li>
			<div class="preview-image">
				<div class="cd-project-title">
					<h2>Project 1</h2>
					<p>Brief description of the project here</p>
				</div> 
			</div>

			<div class="cd-project-info">
				<!-- project description here -->
			</div> <!-- .cd-project-info -->
		</li>

		<!-- projects here -->
	</ul> <!-- .cd-projects -->

	<button class="scroll cd-text-replace">Scroll</button>
</div> <!-- .cd-project-container -->

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

Adding style

On small devices, each project preview <li> has width equal to the viewport width and height equal to one-fourth of the viewport height (4 projects in our demo). The project preview image is set as background-image of its <a> child element; it has height equal to the viewport height and is translated to the top to cover the entire viewport.
.cd-projects-previews li {
  height: 25%;
  width: 100%;
  overflow: hidden;
  transition: transform 0.5s;
}
.cd-projects-previews a {
  display: block;
  height: 100vh;
  width: 100%;
  opacity: 0;
  transition: opacity 0.5s;
  transform: translateY(0%);
}
.cd-projects-previews li:nth-of-type(2) a {
  transform: translateY(-25%);
}
.cd-projects-previews li:nth-of-type(3) a {
  transform: translateY(-50%);
}
.cd-projects-previews li:nth-of-type(4) a {
  transform: translateY(-75%);
}
As for the project details (.cd-projects > li), each list item has an absolute position, a width and height equal to the viewport width and height respectively and is hidden by default.
.cd-projects > li {
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  width: 100%;
  opacity: 0;
  transition: opacity 0.3s;
}
When a user selects a project, the .selected class is added to the corresponding .cd-projects > li, while the .slide-out class is added to the project previews (in a random order and with a delay to create the slide-out effect).
.cd-projects-previews li.slide-out {
  transform: translateX(-100%);
}

.cd-projects > li.selected {
  z-index: 1;
  opacity: 1;
  transition: opacity 0s;
}
On bigger devices (viewport width bigger than 1024px), the project previews height is set to 100% and its width to one-fourth of the viewport width while the <a> child is translated to the left to cover the entire viewport.
@media only screen and (min-width: 1024px) {
  .cd-projects-previews li {
    display: inline-block;
    height: 100%;
    width: 25%;
    float: left;
  }
  .cd-projects-previews li.slide-out {
    transform: translateY(-100%);
  }
  .cd-projects-previews a {
    /* width equal to window width */
    width: 400%;
  }
  .cd-projects-previews li:nth-of-type(2) a {
    transform: translateX(-25%);
  }
  .cd-projects-previews li:nth-of-type(3) a {
    transform: translateX(-50%);
  }
  .cd-projects-previews li:nth-of-type(4) a {
    transform: translateX(-75%);
  }
}
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 .slide-out class is added to the project previews to reveal the navigation.
.cd-primary-nav {
  position: absolute;
  z-index: 1;
  top: 0;
  left: 0;
  height: 100%;
  width: 100%;
  overflow: auto;
  text-align: center;
  opacity: 0;
  transition: opacity 0.6s;
}
.cd-primary-nav.nav-visible {
  opacity: 1;
}
About the projects number: if you need to create more than 4 projects, you then need to update width/height of the project previews (and translate value of its <a> children). If you use SASS, though, you can update the $items variable inside partials > _variables.scss.

Events handling

We used jQuery to detect click events on project previews and .cd-nav-trigger element. When a user selects a project/opens the main navigation, the slideToggleProjects() function takes care of sliding-in/out the projects, while the makeUniqueRandom() function is used to extract random numbers (between 1 and 4) for the projects exit order.
function slideToggleProjects(projectsPreviewWrapper, projectIndex, index, bool) {
  var randomProjectIndex = makeUniqueRandom();
  
  if( index < numRandoms - 1 ) {
    projectsPreviewWrapper.eq(randomProjectIndex).toggleClass('slide-out', bool);
    setTimeout( function(){
      //animate next preview project
      slideToggleProjects(projectsPreviewWrapper, projectIndex, index + 1, bool);
    }, 150);
  } else {
    //this is the last project preview to be animated 
    projectsPreviewWrapper.eq(randomProjectIndex).toggleClass('slide-out', bool).one('webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend', function(){
      // ...
      animating = false;
    });
  }
}
]]>
https://codyhouse.co/gem/sliding-panels-template/feed/ 51
Reading Progress Indicator https://codyhouse.co/gem/reading-progress-indicator/ https://codyhouse.co/gem/reading-progress-indicator/#comments Mon, 21 Dec 2015 12:15:00 +0000 https://codyhouse.co/?post_type=gem&p=6512 reading-progress-indicator A widget containing a list of suggested articles, with a reading progress indicator powered by SVG, CSS and jQuery. Today’s resource was inspired by a widget found on The Daily Beast: a list of related articles, enriched by a filling effect to indicate the reading progress. We created something similar, although we used SVG to animate the stroke property of a circle element. Note that the url changes according to the article in focus, in case the user wants to share a specific article as opposed to the whole page. Since such a widget is not a fundamental element of the page, but more of a subtle enrichment, we decided to hide it on smaller devices.

Creating the structure

The HTML structure is composed by <article> elements for the article contents, and an <aside> element wrapping the list of suggested articles.
<div class="cd-articles">
	<article>
		<header>
			<img src="img/img-1.png" alt="article image">
			<h1>20 Star Wars Secrets Revealed: From Leia’s ‘Cocaine Nail’ to the Ronald Reagan Connection</h1>
		</header>
		
		<p>
			Lorem ipsum dolor sit amet, consectetur adipisicing elit. Perferendis maxime id, sunt, eum sed blanditiis aliquid! Minus assumenda tempore perspiciatis, numquam est aliquam, quis molestias enim consequuntur suscipit similique cumque ut natus facilis laboriosam quidem, nesciunt quasi doloribus tenetur. Quas doloremque suscipit, molestias odit, et quasi? Quas hic numquam, vitae?
		</p>
		<!-- additional content here -->
	</article>

	<article>
		<!-- article content here -->
	</article>

	<!-- additional articles here -->

	<aside class="cd-read-more">
		<ul>
			<li>
				<a href="index.html">
					<em>20 Star Wars Secrets Revealed</em>
					<b>by J. Morrison</b>
					<svg x="0px" y="0px" width="36px" height="36px" viewBox="0 0 36 36"><circle fill="none" stroke="#2a76e8" stroke-width="2" cx="18" cy="18" r="16" stroke-dasharray="100 100" stroke-dashoffset="100" transform="rotate(-90 18 18)"></circle></svg>
				</a>
			</li>

			<!-- additional links to articles -->
		</ul>
	</aside> <!-- .cd-read-more -->
</div> <!-- .cd-articles -->

Adding style

The <aside> element is visible only on big devices (viewport width bigger than 1100px): it has an absolute position and is placed in the top-right corner of the .cd-articles element; the class .fixed is then used to change its position to fixed so that it's always accessible while the user scrolls through the articles.
@media only screen and (min-width: 1100px) {
  .cd-articles {
    position: relative;
    width: 970px;
    padding-right: 320px;
  }
}

.cd-read-more {
  /* hide on mobile */
  display: none;
}
@media only screen and (min-width: 1100px) {
  .cd-read-more {
    display: block;
    width: 290px;
    position: absolute;
    top: 3em;
    right: 0;
  }
  .cd-read-more.fixed {
    position: fixed;
    right: calc(50% - 485px);
  }
}
To create the progress effect, we used the two svg attributes stroke-dasharray and stroke-dashoffset. Imagining the circle as a dashed line, the stroke-dasharray lets you specify dashes and gaps length, while the stroke-dashoffset lets you change where the dasharray starts. We initially set stroke-dasharray="100 100" and stroke-dashoffset="100" (where 100 is the svg circle circumference). This way, the dash and gap are both equal to the circle circumference, and since the stroke-dashoffset is equal to the circle circumference too, only the gap (transparent) is visible. To create the progress effect, we change the stroke-dashoffset from 100 to 0 (more in the Events Handling section).

Events handling

On big devices (viewport width bigger than 1100px) we bind the updateArticle() and updateSidebarPosition() functions to the window scroll events: the first one checks which article the user is reading and updates the corresponding svg stroke-dashoffset attribute to show the progress, while the second function updates the sidebar position attribute (using the .fixed class ) according to the window scroll top value. Finally, the changeUrl() function is used to update the page url according to the article being read.
function updateArticle() {
	var scrollTop = $(window).scrollTop();

	articles.each(function(){ //articles = $('.cd-articles').children('article');
		var article = $(this),
			articleSidebarLink = articleSidebarLinks.eq(article.index()).children('a'); //articleSidebarLinks = $('.cd-read-more').find('li')

		if( articleTop > scrollTop) { //articleTop = $(this).offset().top
			articleSidebarLink.removeClass('read reading');
		} else if( scrollTop >= articleTop && articleTop + articleHeight > scrollTop) { //articleHeight = $(this).outerHeight()
			var dashoffsetValue = svgCircleLength*( 1 - (scrollTop - articleTop)/articleHeight); //svgCircleLength = 100
			articleSidebarLink.addClass('reading').removeClass('read').find('circle').attr({ 'stroke-dashoffset': dashoffsetValue });
			changeUrl(articleSidebarLink.attr('href'));
		} else {
			articleSidebarLink.removeClass('reading').addClass('read');
		}
	});
}

function updateSidebarPosition() {
	var scrollTop = $(window).scrollTop();

	if( scrollTop < articlesWrapperTop) { //$('.cd-articles').offset().top
		aside.removeClass('fixed').attr('style', ''); //aside = $('.cd-read-more')
	} else if( scrollTop >= articlesWrapperTop && scrollTop < articlesWrapperTop + articlesWrapperHeight - windowHeight) { // articlesWrapperHeight = $('.cd-articles').outerHeight()
		aside.addClass('fixed').attr('style', '');
	} else {
		if( aside.hasClass('fixed') ) aside.removeClass('fixed').css('top', articlesWrapperHeight + articlePaddingTop - windowHeight + 'px');//articlePaddingTop = Number($('.cd-articles').children('article').eq(1).css('padding-top').replace('px', ''))
	}
}
]]>
https://codyhouse.co/gem/reading-progress-indicator/feed/ 25
Presentation Slideshow https://codyhouse.co/gem/presentation-slideshow/ https://codyhouse.co/gem/presentation-slideshow/#comments Wed, 09 Dec 2015 12:18:41 +0000 http://codyhouse.co/?post_type=gem&p=5934 presentation-slideshow A simple presentation template in CSS and jQuery. Although presentations are usually created using native apps, we decided to challenge ourselves and design a presentation template for the browser. It wasn’t an easy task, for the way you interact with a presentation slideshow is different compared to how you scroll through a website: each unit/slide must be isolated, so that its content can be assimilated before switching to the next one. How do you navigate through a presentation? The easiest way is to use keyboard arrow keys (for devices that have them). However we had to take into account that all other interactions (click, scroll…) had to work as well when building something for the web. Let us know what you think in the comments section ;)

Creating the structure

The HTML structure is composed by two main elements: a <nav> for the slideshow navigation, and an ordered list for the slideshow items. We used a nested ordered list for items with multiple sub slides.
<div class="cd-slideshow-wrapper">
	<nav class="cd-slideshow-nav">
		<button class="cd-nav-trigger">
			Open Navigation
			<span aria-hidden="true"></span>
		</button>
		
		<div class="cd-nav-items">
			<ol>
				<li><a href="#slide-1">Slide 1</a></li>
				<li>
					<a href="#slide-2">Slide 2</a>
					<ol class="sub-nav">
						<li><a href="#slide-2">Slide 2 - Sub 1</a></li>
						<!-- other sub-slide links here -->
					</ol>
				</li>
				<li><a href="#slide-3">Slide 3</a></li>
				<!-- other slide links here -->
			</ol>
		</div> <!-- .cd-nav-items -->
	</nav> <!-- .cd-slideshow-nav -->
	
	<ol class="cd-slideshow">
		<li class="visible" id="slide-1">
			<div class="cd-slider-content">
				<div class="content-wrapper">
					<h2>Presentation Slideshow</h2>
					<p>A simple presentation template in CSS &amp; jQuery.</p>
				</div>
			</div>
		</li>

		<li id="slide-2">
			<ol class="sub-slides">
				<li>
					<div class="cd-slider-content">
						<div class="content-wrapper">
							<h2>Slider #2</h2>
						</div>
					</div>
				</li>
				<!-- sub-slides content here -->
				
			</ol> <!-- .sub-slides -->
		</li>

		<!-- additional slides here -->
	</ol> <!-- .cd-slideshow -->
</div> <!-- .cd-slideshow-wrapper -->

Adding style

On small devices (viewport width smaller than 1100px), we organized all slides as a simple list. For items with sub slides, though, we implemented a slider with drag/swipe interaction to navigate it.
.cd-slideshow .sub-slides {
  width: 100%;
  transition: transform 0.3s;
}
.cd-slideshow > li, 
.cd-slideshow .sub-slides > li {
  position: relative;
  z-index: 1;
  height: 100vh;
  width: 100vw;
}
.cd-slideshow .sub-slides > li {
  float: left;
}
The width of the ordered list items with sub slides is set using JavaScript. When a user navigates from a sub slide to the previous/next one, we translate the .sub-slides element (nested ordered list) along the X axis (more in the Events handling section). On bigger devices, we set the .cd-slideshow-wrapper height to 100vh and add an overflow hidden, so that only the slide in the viewport is visible. We then set height, width and margin of the .cd-slider-content to center it in the viewport. The .visible class is added to the visible slide: it is used to hide the .cd-slider-content::after pseudo-element (used to change the slide background color when the slide is out of focus) and show the slide content.
@media only screen and (min-width: 1100px) {
  .cd-slideshow-wrapper {
    height: 100vh;
    overflow: hidden;
  }

  .cd-slideshow {
    transition: transform 0.6s;
  }
  .cd-slideshow > li, .cd-slideshow .sub-slides > li {
    height: auto;
    width: auto;
  }

  .cd-slider-content {
    height: 84vh;
    width: 90vw;
    margin: 2vh 5vw;
    border-radius: 10px;
    cursor: pointer;
  }
  .visible .sub-visible .cd-slider-content, 
  .visible > .cd-slider-content {
    /* visible slide */
    cursor: auto;
  }
  .cd-slideshow > li:first-of-type .cd-slider-content {
    margin-top: 8vh;
  }
  .sub-slides > li:first-of-type .cd-slider-content {
    margin-left: 5vw;
  }
  .sub-slides > li .cd-slider-content {
    margin-left: 1.25vw;
    margin-right: 1.25vw;
  }
  .cd-slider-content .content-wrapper {
    height: 100%;
    /* hide the slide content if the slide is not selected/visible */
    opacity: 0;
    box-shadow: 0 6px 40px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.15);
    border-radius: inherit;
    transition: opacity 0.6s;
  }
  .cd-slider-content::after {
    /* this is used to change the slide background color when the slide is out of focus */
    content: '';
    position: absolute;
    z-index: 3;
    top: 0;
    left: 0;
    height: 100%;
    width: 100%;
    border-radius: inherit;
    background-color: #3a3a3a;
    box-shadow: 0 4px 30px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.1);
    opacity: 1;
    visibility: visible;
    transition: opacity 0.6s, visibility 0.6s;
  }
  .visible .cd-slider-content .content-wrapper {
    opacity: 1;
  }
  .visible .cd-slider-content::after {
    opacity: 0;
    visibility: hidden;
  }
}
When a user navigates from a slide to the next/previous one, we translate the .cd-slideshow element along the Y axis (more in the Events handling section).

Events handling

The presentation can be navigated in different ways: using the keyboard; using the slideshow main navigation; clicking on slides out of focus or scrolling through the slides. Two main functions have been used to implement the slideshow navigation: the updateSlide to navigate from a slide to the next/previous one, and the updateSubSlide to navigate from a sub slide to the next/previous one. For the updateSubSlide function, for example, we used:
function updateSubSlide(listItem, string, subSlide) {
	var translate,
		marginSlide = Number(listItem.find('.cd-slider-content').eq(0).css('margin-right').replace('px', ''))*6,
		windowWidth = window.innerWidth;

	windowWidth = ( mq == 'desktop' ) ? windowWidth - marginSlide : windowWidth;

	if( listItem.children('.sub-slides').length > 0 ) {
		var subSlidesWrapper = listItem.children('.sub-slides'),
			visibleSubSlide = subSlidesWrapper.children('.sub-visible');
		
		if( string == 'nav' ) {
			/* we have choosen a new slide from the navigation */
			var	newSubSlide = subSlide;
		} else {
			var	newSubSlide = (string == 'next') ? visibleSubSlide.next() : visibleSubSlide.prev();
		}
		var newSubSlidePosition = newSubSlide.index();
		translate = parseInt(- newSubSlidePosition*windowWidth);
		setTransformValue(subSlidesWrapper.get(0), 'translateX', translate + 'px');
		visibleSubSlide.removeClass('sub-visible');
		newSubSlide.addClass('sub-visible');
	}
}

function setTransformValue(element, property, value) {
	element.style["-webkit-transform"] = property+"("+value+")";
	element.style["-moz-transform"] = property+"("+value+")";
	element.style["-ms-transform"] = property+"("+value+")";
	element.style["-o-transform"] = property+"("+value+")";
	element.style["transform"] = property+"("+value+")";
	// ...
}
]]>
https://codyhouse.co/gem/presentation-slideshow/feed/ 20