├── README.md ├── assets ├── img │ ├── gallery-img-1.jpg │ ├── gallery-img-2.jpg │ ├── gallery-img-3.jpg │ ├── gallery-img-4.jpg │ ├── stacking-cards-img-1.jpg │ ├── stacking-cards-img-2.jpg │ ├── stacking-cards-img-3.jpg │ └── sticky-hero-img.jpg ├── script.js └── style.css ├── dark-theme.html └── index.html /README.md: -------------------------------------------------------------------------------- 1 | # The process of building a CSS Framework 2 | 3 | This template has been built using the [CodyHouse UI Library](https://codyhouse.co/ds/components). 4 | Read the [article on Codrops](https://tympanus.net/codrops/?p=56956) to learn how to build this page yourself in no time. 5 | 6 | [Demo](https://ambercreative.co/codrops/) 7 | [License](https://codyhouse.co/license#components) 8 | Images: [Unsplash](https://unsplash.com/) 9 | -------------------------------------------------------------------------------- /assets/img/gallery-img-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codyhouse/codyframe-template-for-codrops/0ef96155e039995896121d23439497f7aa53b13a/assets/img/gallery-img-1.jpg -------------------------------------------------------------------------------- /assets/img/gallery-img-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codyhouse/codyframe-template-for-codrops/0ef96155e039995896121d23439497f7aa53b13a/assets/img/gallery-img-2.jpg -------------------------------------------------------------------------------- /assets/img/gallery-img-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codyhouse/codyframe-template-for-codrops/0ef96155e039995896121d23439497f7aa53b13a/assets/img/gallery-img-3.jpg -------------------------------------------------------------------------------- /assets/img/gallery-img-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codyhouse/codyframe-template-for-codrops/0ef96155e039995896121d23439497f7aa53b13a/assets/img/gallery-img-4.jpg -------------------------------------------------------------------------------- /assets/img/stacking-cards-img-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codyhouse/codyframe-template-for-codrops/0ef96155e039995896121d23439497f7aa53b13a/assets/img/stacking-cards-img-1.jpg -------------------------------------------------------------------------------- /assets/img/stacking-cards-img-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codyhouse/codyframe-template-for-codrops/0ef96155e039995896121d23439497f7aa53b13a/assets/img/stacking-cards-img-2.jpg -------------------------------------------------------------------------------- /assets/img/stacking-cards-img-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codyhouse/codyframe-template-for-codrops/0ef96155e039995896121d23439497f7aa53b13a/assets/img/stacking-cards-img-3.jpg -------------------------------------------------------------------------------- /assets/img/sticky-hero-img.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codyhouse/codyframe-template-for-codrops/0ef96155e039995896121d23439497f7aa53b13a/assets/img/sticky-hero-img.jpg -------------------------------------------------------------------------------- /assets/script.js: -------------------------------------------------------------------------------- 1 | // File#: _1_main-header 2 | // Usage: codyhouse.co/license 3 | (function() { 4 | var mainHeader = document.getElementsByClassName('js-header'); 5 | if( mainHeader.length > 0 ) { 6 | var trigger = mainHeader[0].getElementsByClassName('js-header__trigger')[0], 7 | nav = mainHeader[0].getElementsByClassName('js-header__nav')[0]; 8 | 9 | // we'll use these to store the node that needs to receive focus when the mobile menu is closed 10 | var focusMenu = false; 11 | 12 | //detect click on nav trigger 13 | trigger.addEventListener("click", function(event) { 14 | event.preventDefault(); 15 | toggleNavigation(!Util.hasClass(nav, 'header__nav--is-visible')); 16 | }); 17 | 18 | // listen for key events 19 | window.addEventListener('keyup', function(event){ 20 | // listen for esc key 21 | if( (event.keyCode && event.keyCode == 27) || (event.key && event.key.toLowerCase() == 'escape' )) { 22 | // close navigation on mobile if open 23 | if(trigger.getAttribute('aria-expanded') == 'true' && isVisible(trigger)) { 24 | focusMenu = trigger; // move focus to menu trigger when menu is close 25 | trigger.click(); 26 | } 27 | } 28 | // listen for tab key 29 | if( (event.keyCode && event.keyCode == 9) || (event.key && event.key.toLowerCase() == 'tab' )) { 30 | // close navigation on mobile if open when nav loses focus 31 | if(trigger.getAttribute('aria-expanded') == 'true' && isVisible(trigger) && !document.activeElement.closest('.js-header')) trigger.click(); 32 | } 33 | }); 34 | 35 | // listen for resize 36 | var resizingId = false; 37 | window.addEventListener('resize', function() { 38 | clearTimeout(resizingId); 39 | resizingId = setTimeout(doneResizing, 500); 40 | }); 41 | 42 | function doneResizing() { 43 | if( !isVisible(trigger) && Util.hasClass(mainHeader[0], 'header--expanded')) toggleNavigation(false); 44 | }; 45 | } 46 | 47 | function isVisible(element) { 48 | return (element.offsetWidth || element.offsetHeight || element.getClientRects().length); 49 | }; 50 | 51 | function toggleNavigation(bool) { // toggle navigation visibility on small device 52 | Util.toggleClass(nav, 'header__nav--is-visible', bool); 53 | Util.toggleClass(mainHeader[0], 'header--expanded', bool); 54 | trigger.setAttribute('aria-expanded', bool); 55 | if(bool) { //opening menu -> move focus to first element inside nav 56 | nav.querySelectorAll('[href], input:not([disabled]), button:not([disabled])')[0].focus(); 57 | } else if(focusMenu) { 58 | focusMenu.focus(); 59 | focusMenu = false; 60 | } 61 | }; 62 | }()); 63 | 64 | // File#: _1_animated-headline 65 | // Usage: codyhouse.co/license 66 | (function() { 67 | var TextAnim = function(element) { 68 | this.element = element; 69 | this.wordsWrapper = this.element.getElementsByClassName(' js-text-anim__wrapper'); 70 | this.words = this.element.getElementsByClassName('js-text-anim__word'); 71 | this.selectedWord = 0; 72 | // interval between two animations 73 | this.loopInterval = parseFloat(getComputedStyle(this.element).getPropertyValue('--text-anim-pause'))*1000 || 1000; 74 | // duration of single animation (e.g., time for a single word to rotate) 75 | this.transitionDuration = parseFloat(getComputedStyle(this.element).getPropertyValue('--text-anim-duration'))*1000 || 1000; 76 | // keep animating after first loop was completed 77 | this.loop = (this.element.getAttribute('data-loop') && this.element.getAttribute('data-loop') == 'off') ? false : true; 78 | this.wordInClass = 'text-anim__word--in'; 79 | this.wordOutClass = 'text-anim__word--out'; 80 | // check for specific animations 81 | this.isClipAnim = Util.hasClass(this.element, 'text-anim--clip'); 82 | if(this.isClipAnim) { 83 | this.animBorderWidth = parseInt(getComputedStyle(this.element).getPropertyValue('--text-anim-border-width')) || 2; 84 | this.animPulseClass = 'text-anim__wrapper--pulse'; 85 | } 86 | initTextAnim(this); 87 | }; 88 | 89 | function initTextAnim(element) { 90 | // make sure there's a word with the wordInClass 91 | setSelectedWord(element); 92 | // if clip animation -> add pulse class 93 | if(element.isClipAnim) { 94 | Util.addClass(element.wordsWrapper[0], element.animPulseClass); 95 | } 96 | // init loop 97 | loopWords(element); 98 | }; 99 | 100 | function setSelectedWord(element) { 101 | var selectedWord = element.element.getElementsByClassName(element.wordInClass); 102 | if(selectedWord.length == 0) { 103 | Util.addClass(element.words[0], element.wordInClass); 104 | } else { 105 | element.selectedWord = Util.getIndexInArray(element.words, selectedWord[0]); 106 | } 107 | }; 108 | 109 | function loopWords(element) { 110 | // stop animation after first loop was completed 111 | if(!element.loop && element.selectedWord == element.words.length - 1) { 112 | return; 113 | } 114 | var newWordIndex = getNewWordIndex(element); 115 | setTimeout(function() { 116 | if(element.isClipAnim) { // clip animation only 117 | switchClipWords(element, newWordIndex); 118 | } else { 119 | switchWords(element, newWordIndex); 120 | } 121 | }, element.loopInterval); 122 | }; 123 | 124 | function switchWords(element, newWordIndex) { 125 | // switch words 126 | Util.removeClass(element.words[element.selectedWord], element.wordInClass); 127 | Util.addClass(element.words[element.selectedWord], element.wordOutClass); 128 | Util.addClass(element.words[newWordIndex], element.wordInClass); 129 | // reset loop 130 | resetLoop(element, newWordIndex); 131 | }; 132 | 133 | function resetLoop(element, newIndex) { 134 | setTimeout(function() { 135 | // set new selected word 136 | Util.removeClass(element.words[element.selectedWord], element.wordOutClass); 137 | element.selectedWord = newIndex; 138 | loopWords(element); // restart loop 139 | }, element.transitionDuration); 140 | }; 141 | 142 | function switchClipWords(element, newWordIndex) { 143 | // clip animation only 144 | var startWidth = element.words[element.selectedWord].offsetWidth, 145 | endWidth = element.words[newWordIndex].offsetWidth; 146 | 147 | // remove pulsing animation 148 | Util.removeClass(element.wordsWrapper[0], element.animPulseClass); 149 | // close word 150 | animateWidth(startWidth, element.animBorderWidth, element.wordsWrapper[0], element.transitionDuration, function() { 151 | // switch words 152 | Util.removeClass(element.words[element.selectedWord], element.wordInClass); 153 | Util.addClass(element.words[newWordIndex], element.wordInClass); 154 | element.selectedWord = newWordIndex; 155 | 156 | // open word 157 | animateWidth(element.animBorderWidth, endWidth, element.wordsWrapper[0], element.transitionDuration, function() { 158 | // add pulsing class 159 | Util.addClass(element.wordsWrapper[0], element.animPulseClass); 160 | loopWords(element); 161 | }); 162 | }); 163 | }; 164 | 165 | function getNewWordIndex(element) { 166 | // get index of new word to be shown 167 | var index = element.selectedWord + 1; 168 | if(index >= element.words.length) index = 0; 169 | return index; 170 | }; 171 | 172 | function animateWidth(start, to, element, duration, cb) { 173 | // animate width of a word for the clip animation 174 | var currentTime = null; 175 | 176 | var animateProperty = function(timestamp){ 177 | if (!currentTime) currentTime = timestamp; 178 | var progress = timestamp - currentTime; 179 | 180 | var val = Math.easeInOutQuart(progress, start, to - start, duration); 181 | element.style.width = val+"px"; 182 | if(progress < duration) { 183 | window.requestAnimationFrame(animateProperty); 184 | } else { 185 | cb(); 186 | } 187 | }; 188 | 189 | //set the width of the element before starting animation -> fix bug on Safari 190 | element.style.width = start+"px"; 191 | window.requestAnimationFrame(animateProperty); 192 | }; 193 | 194 | window.TextAnim = TextAnim; 195 | 196 | // init TextAnim objects 197 | var textAnim = document.getElementsByClassName('js-text-anim'), 198 | reducedMotion = Util.osHasReducedMotion(); 199 | if( textAnim ) { 200 | if(reducedMotion) return; 201 | for( var i = 0; i < textAnim.length; i++) { 202 | (function(i){ new TextAnim(textAnim[i]);})(i); 203 | } 204 | } 205 | }()); 206 | 207 | // File#: _1_sticky-hero 208 | // Usage: codyhouse.co/license 209 | (function() { 210 | var StickyBackground = function(element) { 211 | this.element = element; 212 | this.scrollingElement = this.element.getElementsByClassName('sticky-hero__content')[0]; 213 | this.nextElement = this.element.nextElementSibling; 214 | this.scrollingTreshold = 0; 215 | this.nextTreshold = 0; 216 | initStickyEffect(this); 217 | }; 218 | 219 | function initStickyEffect(element) { 220 | var observer = new IntersectionObserver(stickyCallback.bind(element), { threshold: [0, 0.1, 1] }); 221 | observer.observe(element.scrollingElement); 222 | if(element.nextElement) observer.observe(element.nextElement); 223 | }; 224 | 225 | function stickyCallback(entries, observer) { 226 | var threshold = entries[0].intersectionRatio.toFixed(1); 227 | (entries[0].target == this.scrollingElement) 228 | ? this.scrollingTreshold = threshold 229 | : this.nextTreshold = threshold; 230 | 231 | Util.toggleClass(this.element, 'sticky-hero--media-is-fixed', (this.nextTreshold > 0 || this.scrollingTreshold > 0)); 232 | }; 233 | 234 | 235 | var stickyBackground = document.getElementsByClassName('js-sticky-hero'), 236 | intersectionObserverSupported = ('IntersectionObserver' in window && 'IntersectionObserverEntry' in window && 'intersectionRatio' in window.IntersectionObserverEntry.prototype); 237 | if(stickyBackground.length > 0 && intersectionObserverSupported) { // if IntersectionObserver is not supported, animations won't be triggeres 238 | for(var i = 0; i < stickyBackground.length; i++) { 239 | (function(i){ // if animations are enabled -> init the StickyBackground object 240 | if( Util.hasClass(stickyBackground[i], 'sticky-hero--overlay-layer') || Util.hasClass(stickyBackground[i], 'sticky-hero--scale')) new StickyBackground(stickyBackground[i]); 241 | })(i); 242 | } 243 | } 244 | }()); 245 | 246 | // File#: _1_reveal-effects 247 | // Usage: codyhouse.co/license 248 | (function() { 249 | var fxElements = document.getElementsByClassName('reveal-fx'); 250 | var intersectionObserverSupported = ('IntersectionObserver' in window && 'IntersectionObserverEntry' in window && 'intersectionRatio' in window.IntersectionObserverEntry.prototype); 251 | if(fxElements.length > 0) { 252 | // deactivate effect if Reduced Motion is enabled 253 | if (Util.osHasReducedMotion() || !intersectionObserverSupported) { 254 | fxRemoveClasses(); 255 | return; 256 | } 257 | //on small devices, do not animate elements -> reveal all 258 | if( fxDisabled(fxElements[0]) ) { 259 | fxRevealAll(); 260 | return; 261 | } 262 | 263 | var fxRevealDelta = 120; // amount (in pixel) the element needs to enter the viewport to be revealed - if not custom value (data-reveal-fx-delta) 264 | 265 | var viewportHeight = window.innerHeight, 266 | fxChecking = false, 267 | fxRevealedItems = [], 268 | fxElementDelays = fxGetDelays(), //elements animation delay 269 | fxElementDeltas = fxGetDeltas(); // amount (in px) the element needs enter the viewport to be revealed (default value is fxRevealDelta) 270 | 271 | 272 | // add event listeners 273 | window.addEventListener('load', fxReveal); 274 | window.addEventListener('resize', fxResize); 275 | window.addEventListener('restartAll', fxRestart); 276 | 277 | // observe reveal elements 278 | var observer = []; 279 | initObserver(); 280 | 281 | function initObserver() { 282 | for(var i = 0; i < fxElements.length; i++) { 283 | observer[i] = new IntersectionObserver( 284 | function(entries, observer) { 285 | if(entries[0].isIntersecting) { 286 | fxRevealItemObserver(entries[0].target); 287 | observer.unobserve(entries[0].target); 288 | } 289 | }, 290 | {rootMargin: "0px 0px -"+fxElementDeltas[i]+"px 0px"} 291 | ); 292 | 293 | observer[i].observe(fxElements[i]); 294 | } 295 | }; 296 | 297 | function fxRevealAll() { // reveal all elements - small devices 298 | for(var i = 0; i < fxElements.length; i++) { 299 | Util.addClass(fxElements[i], 'reveal-fx--is-visible'); 300 | } 301 | }; 302 | 303 | function fxResize() { // on resize - check new window height and reveal visible elements 304 | if(fxChecking) return; 305 | fxChecking = true; 306 | (!window.requestAnimationFrame) ? setTimeout(function(){fxReset();}, 250) : window.requestAnimationFrame(fxReset); 307 | }; 308 | 309 | function fxReset() { 310 | viewportHeight = window.innerHeight; 311 | fxReveal(); 312 | }; 313 | 314 | function fxReveal() { // reveal visible elements 315 | for(var i = 0; i < fxElements.length; i++) {(function(i){ 316 | if(fxRevealedItems.indexOf(i) != -1 ) return; //element has already been revelead 317 | if(fxElementIsVisible(fxElements[i], i)) { 318 | fxRevealItem(i); 319 | fxRevealedItems.push(i); 320 | }})(i); 321 | } 322 | fxResetEvents(); 323 | fxChecking = false; 324 | }; 325 | 326 | function fxRevealItem(index) { 327 | if(fxElementDelays[index] && fxElementDelays[index] != 0) { 328 | // wait before revealing element if a delay was added 329 | setTimeout(function(){ 330 | Util.addClass(fxElements[index], 'reveal-fx--is-visible'); 331 | }, fxElementDelays[index]); 332 | } else { 333 | Util.addClass(fxElements[index], 'reveal-fx--is-visible'); 334 | } 335 | }; 336 | 337 | function fxRevealItemObserver(item) { 338 | var index = Util.getIndexInArray(fxElements, item); 339 | if(fxRevealedItems.indexOf(index) != -1 ) return; //element has already been revelead 340 | fxRevealItem(index); 341 | fxRevealedItems.push(index); 342 | fxResetEvents(); 343 | fxChecking = false; 344 | }; 345 | 346 | function fxGetDelays() { // get anmation delays 347 | var delays = []; 348 | for(var i = 0; i < fxElements.length; i++) { 349 | delays.push( fxElements[i].getAttribute('data-reveal-fx-delay') ? parseInt(fxElements[i].getAttribute('data-reveal-fx-delay')) : 0); 350 | } 351 | return delays; 352 | }; 353 | 354 | function fxGetDeltas() { // get reveal delta 355 | var deltas = []; 356 | for(var i = 0; i < fxElements.length; i++) { 357 | deltas.push( fxElements[i].getAttribute('data-reveal-fx-delta') ? parseInt(fxElements[i].getAttribute('data-reveal-fx-delta')) : fxRevealDelta); 358 | } 359 | return deltas; 360 | }; 361 | 362 | function fxDisabled(element) { // check if elements need to be animated - no animation on small devices 363 | return !(window.getComputedStyle(element, '::before').getPropertyValue('content').replace(/'|"/g, "") == 'reveal-fx'); 364 | }; 365 | 366 | function fxElementIsVisible(element, i) { // element is inside viewport 367 | return (fxGetElementPosition(element) <= viewportHeight - fxElementDeltas[i]); 368 | }; 369 | 370 | function fxGetElementPosition(element) { // get top position of element 371 | return element.getBoundingClientRect().top; 372 | }; 373 | 374 | function fxResetEvents() { 375 | if(fxElements.length > fxRevealedItems.length) return; 376 | // remove event listeners if all elements have been revealed 377 | window.removeEventListener('load', fxReveal); 378 | window.removeEventListener('resize', fxResize); 379 | }; 380 | 381 | function fxRemoveClasses() { 382 | // Reduced Motion on or Intersection Observer not supported 383 | while(fxElements[0]) { 384 | // remove all classes starting with 'reveal-fx--' 385 | var classes = fxElements[0].getAttribute('class').split(" ").filter(function(c) { 386 | return c.lastIndexOf('reveal-fx--', 0) !== 0; 387 | }); 388 | fxElements[0].setAttribute('class', classes.join(" ").trim()); 389 | Util.removeClass(fxElements[0], 'reveal-fx'); 390 | } 391 | }; 392 | 393 | function fxRestart() { 394 | // restart the reveal effect -> hide all elements and re-init the observer 395 | if (Util.osHasReducedMotion() || !intersectionObserverSupported || fxDisabled(fxElements[0])) { 396 | return; 397 | } 398 | // check if we need to add the event listensers back 399 | if(fxElements.length <= fxRevealedItems.length) { 400 | window.addEventListener('load', fxReveal); 401 | window.addEventListener('resize', fxResize); 402 | } 403 | // remove observer and reset the observer array 404 | for(var i = 0; i < observer.length; i++) { 405 | if(observer[i]) observer[i].disconnect(); 406 | } 407 | observer = []; 408 | // remove visible class 409 | for(var i = 0; i < fxElements.length; i++) { 410 | Util.removeClass(fxElements[i], 'reveal-fx--is-visible'); 411 | } 412 | // reset fxRevealedItems array 413 | fxRevealedItems = []; 414 | // restart observer 415 | initObserver(); 416 | }; 417 | } 418 | }()); 419 | 420 | // File#: _1_stacking-cards 421 | // Usage: codyhouse.co/license 422 | (function() { 423 | var StackCards = function(element) { 424 | this.element = element; 425 | this.items = this.element.getElementsByClassName('js-stack-cards__item'); 426 | this.scrollingFn = false; 427 | this.scrolling = false; 428 | initStackCardsEffect(this); 429 | initStackCardsResize(this); 430 | }; 431 | 432 | function initStackCardsEffect(element) { // use Intersection Observer to trigger animation 433 | setStackCards(element); // store cards CSS properties 434 | var observer = new IntersectionObserver(stackCardsCallback.bind(element), { threshold: [0, 1] }); 435 | observer.observe(element.element); 436 | }; 437 | 438 | function initStackCardsResize(element) { // detect resize to reset gallery 439 | element.element.addEventListener('resize-stack-cards', function(){ 440 | setStackCards(element); 441 | animateStackCards.bind(element); 442 | }); 443 | }; 444 | 445 | function stackCardsCallback(entries) { // Intersection Observer callback 446 | if(entries[0].isIntersecting) { 447 | if(this.scrollingFn) return; // listener for scroll event already added 448 | stackCardsInitEvent(this); 449 | } else { 450 | if(!this.scrollingFn) return; // listener for scroll event already removed 451 | window.removeEventListener('scroll', this.scrollingFn); 452 | this.scrollingFn = false; 453 | } 454 | }; 455 | 456 | function stackCardsInitEvent(element) { 457 | element.scrollingFn = stackCardsScrolling.bind(element); 458 | window.addEventListener('scroll', element.scrollingFn); 459 | }; 460 | 461 | function stackCardsScrolling() { 462 | if(this.scrolling) return; 463 | this.scrolling = true; 464 | window.requestAnimationFrame(animateStackCards.bind(this)); 465 | }; 466 | 467 | function setStackCards(element) { 468 | // store wrapper properties 469 | element.marginY = getComputedStyle(element.element).getPropertyValue('--stack-cards-gap'); 470 | getIntegerFromProperty(element); // convert element.marginY to integer (px value) 471 | element.elementHeight = element.element.offsetHeight; 472 | 473 | // store card properties 474 | var cardStyle = getComputedStyle(element.items[0]); 475 | element.cardTop = Math.floor(parseFloat(cardStyle.getPropertyValue('top'))); 476 | element.cardHeight = Math.floor(parseFloat(cardStyle.getPropertyValue('height'))); 477 | 478 | // store window property 479 | element.windowHeight = window.innerHeight; 480 | 481 | // reset margin + translate values 482 | if(isNaN(element.marginY)) { 483 | element.element.style.paddingBottom = '0px'; 484 | } else { 485 | element.element.style.paddingBottom = (element.marginY*(element.items.length - 1))+'px'; 486 | } 487 | 488 | for(var i = 0; i < element.items.length; i++) { 489 | if(isNaN(element.marginY)) { 490 | element.items[i].style.transform = 'none;'; 491 | } else { 492 | element.items[i].style.transform = 'translateY('+element.marginY*i+'px)'; 493 | } 494 | } 495 | }; 496 | 497 | function getIntegerFromProperty(element) { 498 | var node = document.createElement('div'); 499 | node.setAttribute('style', 'opacity:0; visbility: hidden;position: absolute; height:'+element.marginY); 500 | element.element.appendChild(node); 501 | element.marginY = parseInt(getComputedStyle(node).getPropertyValue('height')); 502 | element.element.removeChild(node); 503 | }; 504 | 505 | function animateStackCards() { 506 | if(isNaN(this.marginY)) { // --stack-cards-gap not defined - do not trigger the effect 507 | this.scrolling = false; 508 | return; 509 | } 510 | 511 | var top = this.element.getBoundingClientRect().top; 512 | 513 | if( this.cardTop - top + this.element.windowHeight - this.elementHeight - this.cardHeight + this.marginY + this.marginY*this.items.length > 0) { 514 | this.scrolling = false; 515 | return; 516 | } 517 | 518 | for(var i = 0; i < this.items.length; i++) { // use only scale 519 | var scrolling = this.cardTop - top - i*(this.cardHeight+this.marginY); 520 | if(scrolling > 0) { 521 | var scaling = i == this.items.length - 1 ? 1 : (this.cardHeight - scrolling*0.05)/this.cardHeight; 522 | this.items[i].style.transform = 'translateY('+this.marginY*i+'px) scale('+scaling+')'; 523 | } else { 524 | this.items[i].style.transform = 'translateY('+this.marginY*i+'px)'; 525 | } 526 | } 527 | 528 | this.scrolling = false; 529 | }; 530 | 531 | // initialize StackCards object 532 | var stackCards = document.getElementsByClassName('js-stack-cards'), 533 | intersectionObserverSupported = ('IntersectionObserver' in window && 'IntersectionObserverEntry' in window && 'intersectionRatio' in window.IntersectionObserverEntry.prototype), 534 | reducedMotion = Util.osHasReducedMotion(); 535 | 536 | if(stackCards.length > 0 && intersectionObserverSupported && !reducedMotion) { 537 | var stackCardsArray = []; 538 | for(var i = 0; i < stackCards.length; i++) { 539 | (function(i){ 540 | stackCardsArray.push(new StackCards(stackCards[i])); 541 | })(i); 542 | } 543 | 544 | var resizingId = false, 545 | customEvent = new CustomEvent('resize-stack-cards'); 546 | 547 | window.addEventListener('resize', function() { 548 | clearTimeout(resizingId); 549 | resizingId = setTimeout(doneResizing, 500); 550 | }); 551 | 552 | function doneResizing() { 553 | for( var i = 0; i < stackCardsArray.length; i++) { 554 | (function(i){stackCardsArray[i].element.dispatchEvent(customEvent)})(i); 555 | }; 556 | }; 557 | } 558 | }()); 559 | 560 | // File#: _1_theme-switch 561 | // Usage: codyhouse.co/license 562 | (function() { 563 | var themeSwitch = document.getElementsByClassName('js-radio-switch'); 564 | if(themeSwitch.length > 0) { 565 | themeSwitch[0].addEventListener('change', function(event){ 566 | var theme = event.target.value; 567 | theme == 'dark' ? document.body.setAttribute('data-theme', 'dark') : document.body.removeAttribute('data-theme'); 568 | }); 569 | } 570 | }()); -------------------------------------------------------------------------------- /dark-theme.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | The process of building a CSS Framework 10 | 11 | 12 | 13 |
14 |
15 | 20 | 21 | 25 | 26 | 59 |
60 |
61 | 62 | 63 |
64 |
65 |

66 | We designdevelopcreate digital experiences 67 |

68 | 69 |
70 |

This template has been built using the CodyHouse UI Library. Download the template on GitHub and read the article on Codrops to learn how to build this page yourself in no time.

71 | 72 | 80 |
81 |
82 |
83 | 84 | 85 |
86 | 87 | 88 |
89 |
90 |

One belongs to New York instantly, one belongs to it as much in five minutes as in five years.

91 | 92 | 93 |
94 |
95 |
96 | 97 | 98 |
99 |
100 |

Selected Works

101 | 102 |
103 | Image Preview 104 |
105 | 106 |
107 | Image Preview 108 |
109 | 110 |
111 | Image Preview 112 |
113 | 114 |
115 | Image Preview 116 |
117 |
118 |
119 | 120 | 121 |
122 |
123 |
124 |
125 |

Lorem, ipsum dolor sit amet consectetur adipisicing elit.

126 |
127 | 128 |
129 |
130 |

Lorem ipsum dolor sit amet consectetur, adipisicing elit. Sint tempore repellat earum. Natus accusantium dolores quas porro delectus corporis explicabo, adipisci eaque, tempore beatae blanditiis totam iste alias ipsum. Asperiores, temporibus quo.

131 | 132 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quis tempore officiis voluptatem et, sequi qui explicabo esse excepturi minus quisquam cupiditate cum recusandae ad dicta doloribus doloremque omnis inventore ipsa a veritatis laboriosam. Nostrum numquam nesciunt animi dolore. Sit earum vel illum obcaecati dolorem recusandae voluptates molestias iure repellendus blanditiis porro aspernatur tempora nam commodi ipsam ipsum minus nobis omnis pariatur sed dolor fugiat, nulla facilis! Ducimus quasi dolorum laudantium distinctio cum voluptatibus inventore nostrum, a qui deleniti odit dicta!

133 | 134 | 135 | Read more 136 | 137 | 138 |
139 |
140 |
141 |
142 |
143 | 144 | 145 |
146 |
147 | 160 |
161 |
162 | 163 | 164 |
165 |
166 |
167 |

Join our Newsletter

168 |

Get our monthly recap with the latest news.

169 |
170 | 171 |
172 |
173 | 174 |
175 | 176 |
177 | 178 |
179 |
180 | 181 | 182 | 183 |
184 | By subscribing you agree to our privacy policy. 185 |
186 |
187 |
188 | 189 | 190 | 274 | 275 | 276 | 277 | 278 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | The process of building a CSS Framework 10 | 11 | 12 | 13 |
14 |
15 | 20 | 21 | 25 | 26 | 59 |
60 |
61 | 62 | 63 |
64 |
65 |

66 | We designdevelopcreate digital experiences 67 |

68 | 69 |
70 |

This template has been built using the CodyHouse UI Library. Download the template on GitHub and read the article on Codrops to learn how to build this page yourself in no time.

71 | 72 | 80 |
81 |
82 |
83 | 84 | 85 |
86 | 87 | 88 |
89 |
90 |

One belongs to New York instantly, one belongs to it as much in five minutes as in five years.

91 | 92 | 93 |
94 |
95 |
96 | 97 | 98 |
99 |
100 |

Selected Works

101 | 102 |
103 | Image Preview 104 |
105 | 106 |
107 | Image Preview 108 |
109 | 110 |
111 | Image Preview 112 |
113 | 114 |
115 | Image Preview 116 |
117 |
118 |
119 | 120 | 121 |
122 |
123 |
124 |
125 |

Lorem, ipsum dolor sit amet consectetur adipisicing elit.

126 |
127 | 128 |
129 |
130 |

Lorem ipsum dolor sit amet consectetur, adipisicing elit. Sint tempore repellat earum. Natus accusantium dolores quas porro delectus corporis explicabo, adipisci eaque, tempore beatae blanditiis totam iste alias ipsum. Asperiores, temporibus quo.

131 | 132 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quis tempore officiis voluptatem et, sequi qui explicabo esse excepturi minus quisquam cupiditate cum recusandae ad dicta doloribus doloremque omnis inventore ipsa a veritatis laboriosam. Nostrum numquam nesciunt animi dolore. Sit earum vel illum obcaecati dolorem recusandae voluptates molestias iure repellendus blanditiis porro aspernatur tempora nam commodi ipsam ipsum minus nobis omnis pariatur sed dolor fugiat, nulla facilis! Ducimus quasi dolorum laudantium distinctio cum voluptatibus inventore nostrum, a qui deleniti odit dicta!

133 | 134 | 135 | Read more 136 | 137 | 138 |
139 |
140 |
141 |
142 |
143 | 144 | 145 |
146 |
147 | 160 |
161 |
162 | 163 | 164 |
165 |
166 |
167 |

Join our Newsletter

168 |

Get our monthly recap with the latest news.

169 |
170 | 171 |
172 |
173 | 174 |
175 | 176 |
177 | 178 |
179 |
180 | 181 | 182 | 183 |
184 | By subscribing you agree to our privacy policy. 185 |
186 |
187 |
188 | 189 | 190 | 274 | 275 | 276 | 277 | 278 | --------------------------------------------------------------------------------