Website: Add parallax image to homepage and landing pages. (#16035)
Closes: #14987 Changes: - Added a new component that displays a Fleet cloud city image with a parallax effect when scrolled. --------- Co-authored-by: Mike Thomas <78363703+mike-j-thomas@users.noreply.github.com>
BIN
website/assets/images/parallax-cloud-city/1-cloud-7050x600@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
website/assets/images/parallax-cloud-city/2-cloud-7050x600@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 77 KiB |
BIN
website/assets/images/parallax-cloud-city/4-island-7050x600@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 188 KiB |
BIN
website/assets/images/parallax-cloud-city/5-cloud-7050x600@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
website/assets/images/parallax-cloud-city/6-island-7050x600@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
website/assets/images/parallax-cloud-city/7-island-7050x600@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
website/assets/images/parallax-cloud-city/8-cloud-7050x600@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
website/assets/images/parallax-cloud-city/9-cloud-7050x600@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
website/assets/images/parallax-cloud-city/cloud-city-static-576x300@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 43 KiB |
126
website/assets/js/components/parallax-city.component.js
vendored
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
/**
|
||||
* <parallax-city>
|
||||
* -----------------------------------------------------------------------------
|
||||
* A button with a built-in loading spinner.
|
||||
*
|
||||
* @type {Component}
|
||||
*
|
||||
* @event click [emitted when clicked]
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
parasails.registerComponent('parallaxCity', {
|
||||
// ╔═╗╦═╗╔═╗╔═╗╔═╗
|
||||
// ╠═╝╠╦╝║ ║╠═╝╚═╗
|
||||
// ╩ ╩╚═╚═╝╩ ╚═╝
|
||||
props: ['isMobile'],
|
||||
|
||||
// ╦╔╗╔╦╔╦╗╦╔═╗╦ ╔═╗╔╦╗╔═╗╔╦╗╔═╗
|
||||
// ║║║║║ ║ ║╠═╣║ ╚═╗ ║ ╠═╣ ║ ║╣
|
||||
// ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝ ╚═╝ ╩ ╩ ╩ ╩ ╚═╝
|
||||
data: function (){
|
||||
return {
|
||||
parallaxCityElement: undefined,
|
||||
elementBottomPosition: undefined,
|
||||
elementHeight: undefined,
|
||||
distanceFromTopOfPage: undefined,
|
||||
distanceFromBottomOfPage: undefined,
|
||||
isAnimating: false,
|
||||
};
|
||||
},
|
||||
|
||||
// ╦ ╦╔╦╗╔╦╗╦
|
||||
// ╠═╣ ║ ║║║║
|
||||
// ╩ ╩ ╩ ╩ ╩╩═╝
|
||||
template: `
|
||||
<div>
|
||||
<div purpose="parallax-city-container">
|
||||
<div class="parallax-layer" purpose="background-cloud-3" scroll-amount=12></div>
|
||||
<div class="parallax-layer" purpose="background-cloud-2" scroll-amount=28></div>
|
||||
<div class="parallax-layer" purpose="small-island-2" scroll-amount=20></div>
|
||||
<div class="parallax-layer" purpose="small-island-1" scroll-amount=40></div>
|
||||
<div class="parallax-layer" purpose="background-cloud-1" scroll-amount=40></div>
|
||||
<div class="parallax-layer" purpose="large-island" scroll-amount=60></div>
|
||||
<div class="parallax-layer" purpose="foreground-cloud-2" scroll-amount=100></div>
|
||||
<div class="parallax-layer" purpose="foreground-cloud-1" scroll-amount=120></div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
|
||||
// ╦ ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╔═╗
|
||||
// ║ ║╠╣ ║╣ ║ ╚╦╝║ ║ ║╣
|
||||
// ╩═╝╩╚ ╚═╝╚═╝ ╩ ╚═╝╩═╝╚═╝
|
||||
beforeMount: function() {
|
||||
|
||||
},
|
||||
mounted: async function(){
|
||||
if(!this.isMobile){
|
||||
this.parallaxCityElement = document.querySelector('[purpose="parallax-city-container"]');
|
||||
this.elementHeight = this.parallaxCityElement.clientHeight;
|
||||
this.distanceFromTopOfPage = this.parallaxCityElement.offsetTop;
|
||||
this.distanceFromBottomOfPage = document.body.scrollHeight - this.distanceFromTopOfPage - (this.elementHeight * .5);
|
||||
this.elementBottomPosition = this.elementHeight + this.distanceFromTopOfPage;
|
||||
let parallaxCityElementPosition = this.parallaxCityElement.getBoundingClientRect();
|
||||
if(parallaxCityElementPosition.bottom > this.distanceFromTopOfPage) {
|
||||
this.handleParallaxScroll();
|
||||
}
|
||||
|
||||
this.parallaxCityElement.querySelectorAll('div.parallax-layer').forEach((layer)=>{
|
||||
let initialPosition = layer.getAttribute('scroll-amount');
|
||||
layer.style.bottom = `-${Number(initialPosition) + 4}px`;
|
||||
});
|
||||
|
||||
window.addEventListener('scroll', this.onScroll);
|
||||
window.addEventListener('resize', this.updateElementPositions);
|
||||
window.addEventListener('orientationchange', this.updateElementPositions);
|
||||
}
|
||||
},
|
||||
beforeDestroy: function() {
|
||||
if(!this.isMobile){
|
||||
window.removeEventListener('scroll', this.onScroll);
|
||||
window.removeEventListener('resize', this.updateElementPositions);
|
||||
window.removeEventListener('orientationchange', this.updateElementPositions);
|
||||
}
|
||||
},
|
||||
|
||||
// ╦╔╗╔╔╦╗╔═╗╦═╗╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗
|
||||
// ║║║║ ║ ║╣ ╠╦╝╠═╣║ ║ ║║ ║║║║╚═╗
|
||||
// ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝
|
||||
methods: {
|
||||
updateElementPositions: function() {
|
||||
this.elementHeight = this.parallaxCityElement.clientHeight;
|
||||
this.distanceFromTopOfPage = this.parallaxCityElement.offsetTop;
|
||||
this.elementBottomPosition = this.elementHeight + this.distanceFromTopOfPage;
|
||||
},
|
||||
onScroll: function() {
|
||||
if(!this.isAnimating){
|
||||
this.isAnimating = true;
|
||||
window.requestAnimationFrame(this.handleParallaxScroll);
|
||||
}
|
||||
return;
|
||||
},
|
||||
handleParallaxScroll: function() {
|
||||
let viewportBottom = window.scrollY + window.innerHeight;
|
||||
let percentageScrolled;
|
||||
if (this.parallaxCityElement.offsetTop < viewportBottom) {
|
||||
let visibleHeight = viewportBottom - Math.max(this.distanceFromTopOfPage, window.scrollY);
|
||||
percentageScrolled = visibleHeight / (this.distanceFromBottomOfPage + (this.elementHeight / 2 ));
|
||||
} else {
|
||||
percentageScrolled = 0;
|
||||
}
|
||||
if(percentageScrolled > 1){
|
||||
percentageScrolled = 1;
|
||||
}
|
||||
percentageScrolled = percentageScrolled.toFixed(4);
|
||||
if(percentageScrolled > .25){// When the element has been scrolled down 25%, start adjusting the position of layers.
|
||||
let adjustedPercentage = (percentageScrolled - .25) * 4/3;
|
||||
this.parallaxCityElement.querySelectorAll('div.parallax-layer').forEach((layer) => {
|
||||
let scrollAmount = layer.getAttribute('scroll-amount');
|
||||
let movement = adjustedPercentage * scrollAmount;
|
||||
layer.style.transform = 'translateY(-' + movement + 'px)';
|
||||
});
|
||||
}
|
||||
this.isAnimating = false;
|
||||
},
|
||||
}
|
||||
});
|
||||
111
website/assets/styles/components/parallax-city.component.less
vendored
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
|
||||
/**
|
||||
* <parallax-city>
|
||||
*
|
||||
* App-wide styles for our ajax buttons.
|
||||
*/
|
||||
|
||||
[parasails-component='parallax-city'] {
|
||||
background: linear-gradient(180deg, #E9F4F4 0%, #FFFFFF 650px);
|
||||
background-size: auto 100%;
|
||||
background-position: center bottom;
|
||||
background-repeat: no-repeat;
|
||||
max-height: 600px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
[purpose='parallax-city-container'] {
|
||||
height: 600px;
|
||||
position: relative;
|
||||
.parallax-layer {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: -1px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
will-change: transform;
|
||||
background-size: auto 100%;
|
||||
}
|
||||
[purpose='foreground-cloud-1'] {
|
||||
background-image: url('/images/parallax-cloud-city/1-cloud-7050x600@2x.png');
|
||||
|
||||
background-position: center bottom;
|
||||
background-repeat: no-repeat;
|
||||
z-index: 100;
|
||||
}
|
||||
[purpose='foreground-cloud-2'] {
|
||||
background-image: url('/images/parallax-cloud-city/2-cloud-7050x600@2x.png');
|
||||
background-position: center bottom;
|
||||
background-repeat: no-repeat;
|
||||
z-index: 99;
|
||||
}
|
||||
[purpose='large-island'] {
|
||||
background-image: url('/images/parallax-cloud-city/4-island-7050x600@2x.png');
|
||||
background-position: center bottom;
|
||||
background-repeat: no-repeat;
|
||||
z-index: 97;
|
||||
}
|
||||
[purpose='background-cloud-1'] {
|
||||
background-image: url('/images/parallax-cloud-city/5-cloud-7050x600@2x.png');
|
||||
background-position: center bottom;
|
||||
background-repeat: no-repeat;
|
||||
z-index: 96;
|
||||
}
|
||||
[purpose='small-island-1'] {
|
||||
background-image: url('/images/parallax-cloud-city/6-island-7050x600@2x.png');
|
||||
background-position: center bottom;
|
||||
background-repeat: no-repeat;
|
||||
z-index: 95;
|
||||
}
|
||||
[purpose='small-island-2'] {
|
||||
background-image: url('/images/parallax-cloud-city/7-island-7050x600@2x.png');
|
||||
background-position: center bottom;
|
||||
background-repeat: no-repeat;
|
||||
z-index: 95;
|
||||
}
|
||||
[purpose='background-cloud-2'] {
|
||||
background-image: url('/images/parallax-cloud-city/8-cloud-7050x600@2x.png');
|
||||
background-position: center bottom;
|
||||
background-repeat: no-repeat;
|
||||
z-index: 1;
|
||||
}
|
||||
[purpose='background-cloud-3'] {
|
||||
background-image: url('/images/parallax-cloud-city/9-cloud-7050x600@2x.png');
|
||||
background-position: center bottom;
|
||||
background-repeat: no-repeat;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
@media (max-width: 1399px) {
|
||||
[purpose='parallax-city-container'] {
|
||||
height: 500px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 991px) {
|
||||
[purpose='parallax-city-container'] {
|
||||
height: 400px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
[purpose='parallax-city-container'] {
|
||||
height: 300px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 575px) {
|
||||
[purpose='parallax-city-container'] {
|
||||
height: 300px;
|
||||
background-image: url('/images/parallax-cloud-city/cloud-city-static-576x300@2x.png');
|
||||
background-size: contain;
|
||||
background-position: center bottom;
|
||||
background-repeat: no-repeat;
|
||||
.parallax-layer {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (max-width: 375px) {
|
||||
[purpose='parallax-city-container'] {
|
||||
height: 200px;
|
||||
}
|
||||
}
|
||||
}
|
||||
1
website/assets/styles/importer.less
vendored
|
|
@ -25,6 +25,7 @@
|
|||
@import 'components/bar-chart.component.less';
|
||||
@import 'components/call-to-action.component.less';
|
||||
@import 'components/scrollable-tweets.component.less';
|
||||
@import 'components/parallax-city.component.less';
|
||||
|
||||
// Per-page styles
|
||||
@import 'pages/homepage.less';
|
||||
|
|
|
|||
1
website/views/layouts/layout-customer.ejs
vendored
|
|
@ -218,6 +218,7 @@
|
|||
<script src="/js/components/js-timestamp.component.js"></script>
|
||||
<script src="/js/components/modal.component.js"></script>
|
||||
<script src="/js/components/open-positions.component.js"></script>
|
||||
<script src="/js/components/parallax-city.component.js"></script>
|
||||
<script src="/js/components/rituals.component.js"></script>
|
||||
<script src="/js/components/scrollable-tweets.component.js"></script>
|
||||
<script src="/js/components/stripe-card-element.component.js"></script>
|
||||
|
|
|
|||
1
website/views/layouts/layout-sandbox.ejs
vendored
|
|
@ -323,6 +323,7 @@
|
|||
<script src="/js/components/js-timestamp.component.js"></script>
|
||||
<script src="/js/components/modal.component.js"></script>
|
||||
<script src="/js/components/open-positions.component.js"></script>
|
||||
<script src="/js/components/parallax-city.component.js"></script>
|
||||
<script src="/js/components/rituals.component.js"></script>
|
||||
<script src="/js/components/scrollable-tweets.component.js"></script>
|
||||
<script src="/js/components/stripe-card-element.component.js"></script>
|
||||
|
|
|
|||
1
website/views/layouts/layout.ejs
vendored
|
|
@ -410,6 +410,7 @@
|
|||
<script src="/js/components/js-timestamp.component.js"></script>
|
||||
<script src="/js/components/modal.component.js"></script>
|
||||
<script src="/js/components/open-positions.component.js"></script>
|
||||
<script src="/js/components/parallax-city.component.js"></script>
|
||||
<script src="/js/components/rituals.component.js"></script>
|
||||
<script src="/js/components/scrollable-tweets.component.js"></script>
|
||||
<script src="/js/components/stripe-card-element.component.js"></script>
|
||||
|
|
|
|||
6
website/views/pages/endpoint-ops.ejs
vendored
|
|
@ -192,11 +192,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<%/* Cloud city banner */%>
|
||||
<div class="d-flex flex-column" purpose="bottom-cloud-city-banner">
|
||||
<img alt="A glass city floating on top of fluffy white clouds" class="d-none d-lg-flex" src="/images/homepage-cloud-city-banner-lg-1600x375@2x.png">
|
||||
<img alt="A glass city floating on top of fluffy white clouds" class="d-none d-md-flex d-lg-none" src="/images/homepage-cloud-city-banner-md-990x375@2x.png">
|
||||
<img alt="A glass city floating on top of fluffy white clouds" class="d-flex d-md-none" src="/images/homepage-cloud-city-banner-sm-375x168@2x.png">
|
||||
</div>
|
||||
<parallax-city :is-mobile="bowser.mobile"></parallax-city>
|
||||
<modal purpose="video-modal" v-if="modal === 'austin-anderson'" @close="closeModal()">
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/G5Ry_vQPaYc?si=vv0AfRe30yssWWRM&rel=0" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
</modal>
|
||||
|
|
|
|||
49
website/views/pages/fleet-mdm.ejs
vendored
|
|
@ -120,10 +120,49 @@
|
|||
</div>
|
||||
</div>
|
||||
<%/* Cloud city banner */%>
|
||||
<div class="d-flex flex-column" purpose="bottom-cloud-city-banner">
|
||||
<img alt="A glass city floating on top of fluffy white clouds" class="d-none d-lg-flex" src="/images/homepage-cloud-city-banner-lg-1600x375@2x.png">
|
||||
<img alt="A glass city floating on top of fluffy white clouds" class="d-none d-md-flex d-lg-none" src="/images/homepage-cloud-city-banner-md-990x375@2x.png">
|
||||
<img alt="A glass city floating on top of fluffy white clouds" class="d-flex d-md-none" src="/images/homepage-cloud-city-banner-sm-375x168@2x.png">
|
||||
</div>
|
||||
<parallax-city :is-mobile="bowser.mobile"></parallax-city>
|
||||
<modal v-if="modal === 'mdm'" @close="closeModal()" data-backdrop="false" v-cloak purpose="modal">
|
||||
<div class="container p-0 d-flex flex-column justify-content-center align-items-center" purpose="mdm-modal">
|
||||
<div purpose="modal-form" v-if="!showSignupFormSuccess">
|
||||
<div class="modal-header pb-2">
|
||||
<h2 class="text-center mb-2">Is it any good?</h2>
|
||||
<p>Fill out the form below to see Dave bootstrapping a macOS device with MDM.</p>
|
||||
</div>
|
||||
<ajax-form action="deliverMdmDemoEmail" class="mdm-demo-video w-100" :syncing.sync="syncing" :cloud-error.sync="cloudError" :form-data="formData" :form-rules="formRules" :form-errors.sync="formErrors" @submitted="submittedForm()">
|
||||
<div class="form-group">
|
||||
<label for="emailAddress">Work email</label>
|
||||
<input class="form-control" id="emailAddress" :class="[formErrors.emailAddress ? 'is-invalid' : '']" v-model.trim="formData.emailAddress" @input="typeClearOneFormError('emailAddress')">
|
||||
<div class="invalid-feedback" v-if="formErrors.emailAddress" focus-first>Please enter a valid email address</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<p style="padding-bottom: 12px;" class="mb-0 font-weight-bold">How many hosts do you have?</p>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" id="more-than-1000" value="true" v-model="formData.hasOverOneThousandHosts" :class="[formErrors.hasOverOneThousandHosts ? 'is-invalid' : '']">
|
||||
<label class="form-check-label font-weight-normal" for="more-than-1000">
|
||||
More than 1,000
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" id="less-than-1000" value="false" v-model="formData.hasOverOneThousandHosts" :class="[formErrors.hasOverOneThousandHosts ? 'is-invalid' : '']">
|
||||
<label class="form-check-label font-weight-normal" for="less-than-1000">
|
||||
Less than 1,000
|
||||
</label>
|
||||
<div class="invalid-feedback" style="margin-left: -20px;" v-if="formErrors.hasOverOneThousandHosts" focus-first>Please choose one option</div>
|
||||
</div>
|
||||
</div>
|
||||
<cloud-error v-if="cloudError"></cloud-error>
|
||||
<div class="border-0 justify-content-center">
|
||||
<ajax-button purpose="submit-button" spinner="true" type="submit" :syncing="syncing" class="btn btn-sm btn-block btn-primary">Submit</ajax-button>
|
||||
</div>
|
||||
</ajax-form>
|
||||
<p class="mb-0 mt-4"><a href="/handbook/company/why-this-way#why-dont-we-sell-like-everyone-else" target="_blank">We will never spam you</a>.</p>
|
||||
</div>
|
||||
<div purpose="modal-form" class="text-center" v-else>
|
||||
<h2 class="mb-2">Thank you!</h2>
|
||||
<p class="mb-4">We just sent you an email in case you need to watch it again.</p>
|
||||
<a purpose="cta-button" class="btn btn-primary btn-block" href="https://play.goconsensus.com/ue326b4dd" target="_blank"><p class="mb-0">Watch Dave's MDM demo</p></a>
|
||||
</div>
|
||||
</div>
|
||||
</modal>
|
||||
</div>
|
||||
<%- /* Expose server-rendered data as window.SAILS_LOCALS :: */ exposeLocalsToBrowser() %>
|
||||
|
|
|
|||
7
website/views/pages/homepage.ejs
vendored
|
|
@ -229,12 +229,7 @@
|
|||
|
||||
</div>
|
||||
<%/* Cloud city banner */%>
|
||||
<div class="d-flex flex-column" purpose="bottom-cloud-city-banner">
|
||||
<img alt="A glass city floating on top of fluffy white clouds" class="d-none d-lg-flex" src="/images/homepage-cloud-city-banner-lg-1600x375@2x.png">
|
||||
<img alt="A glass city floating on top of fluffy white clouds" class="d-none d-md-flex d-lg-none" src="/images/homepage-cloud-city-banner-md-990x375@2x.png">
|
||||
<img alt="A glass city floating on top of fluffy white clouds" class="d-flex d-md-none" src="/images/homepage-cloud-city-banner-sm-375x168@2x.png">
|
||||
</div>
|
||||
|
||||
<parallax-city :is-mobile="bowser.mobile"></parallax-city>
|
||||
<modal purpose="video-modal" v-if="modal === 'austin-anderson'" @close="closeModal()">
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/G5Ry_vQPaYc?si=vv0AfRe30yssWWRM&rel=0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
</modal>
|
||||
|
|
|
|||
|
|
@ -151,11 +151,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<%/* Cloud city banner */%>
|
||||
<div class="d-flex flex-column" purpose="bottom-cloud-city-banner">
|
||||
<img alt="A glass city floating on top of fluffy white clouds" class="d-none d-lg-flex" src="/images/homepage-cloud-city-banner-lg-1600x375@2x.png">
|
||||
<img alt="A glass city floating on top of fluffy white clouds" class="d-none d-md-flex d-lg-none" src="/images/homepage-cloud-city-banner-md-990x375@2x.png">
|
||||
<img alt="A glass city floating on top of fluffy white clouds" class="d-flex d-md-none" src="/images/homepage-cloud-city-banner-sm-375x168@2x.png">
|
||||
</div>
|
||||
<parallax-city :is-mobile="bowser.mobile"></parallax-city>
|
||||
<modal purpose="video-modal" v-if="modal === 'austin-anderson'" @close="closeModal()">
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/G5Ry_vQPaYc?si=vv0AfRe30yssWWRM&rel=0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
</modal>
|
||||
|
|
|
|||