<VueCountdown
v-if="remaining"
:time="remaining"
- v-slot="data">
- <span
- v-for="part in ['days', 'hours', 'minutes', 'seconds']"
- :key="part">
- {{ data[part] }}{{ part[0].toLowerCase() }}
- <span
+ :transform="countdownTransform"
+ v-slot="data"
+ class="vs-countdown-wrapper">
+ <div
+ v-for="part in ['days', 'hours', 'minutes', 'seconds'].filter(part => part !== 'days' || data[part] !== '00')"
+ :key="part"
+ class="vs-countdown-item">
+ <div
+ class="vs-countdown-part">
+ <div class="vs-countdown-number">
+ {{ data[part] }}
+ </div>
+ <div class="vs-countdown-text">
+ {{ part }}
+ </div>
+ </div>
+ <div
v-if="part !== 'seconds'"
- class="px-1 text-xl font-bold">
+ class="vs-countdown-colon">
:
- </span>
- </span>
+ </div>
+ </div>
</VueCountdown>
</ClientOnly>
</template>
isVisible () {
return this.remaining > 0
}
+ },
+ methods: {
+ countdownTransform
}
}
</script>
-<style scoped>
-span {
- color: #ff5338;
+<style>
+.vs-countdown-wrapper {
+ align-items: center;
+ gap: 4px;
+ margin-right: 32px;
+ line-height: 1;
+ display: none;
+}
+
+.vs-countdown-item {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+.vs-countdown-wrapper .vs-countdown-part {
+ background: rgba(68, 249, 137, 0.5);
+ border-radius: 2px;
+ padding: 4px 0;
+ color: #44F989;
+ text-align: center;
+ width: 42px;
+}
+
+.vs-countdown-wrapper .vs-countdown-part .vs-countdown-number {
+ font-size: 28px;
+ font-weight: 500;
+ line-height: 28px;
+}
+
+.vs-countdown-wrapper .vs-countdown-part .vs-countdown-text {
+ font-size: 8px;
+ text-transform: uppercase;
+}
+
+.vs-countdown-colon {
+ color: #44F989;
font-weight: bold;
}
-</style>
+
+@media (min-width: 680px) {
+ .vs-countdown-wrapper {
+ display: flex;
+ }
+}
+</style>
\ No newline at end of file
--- /dev/null
+<template>
+ <ClientOnly>
+ <VueCountdown
+ v-if="remaining"
+ :time="remaining"
+ v-slot="data"
+ class="vs-countdown-mobile-wrapper">
+ <span
+ v-for="part in ['days', 'hours', 'minutes', 'seconds']"
+ :key="part">
+ {{ data[part] }}{{ part[0].toLowerCase() }}
+ <span
+ v-if="part !== 'seconds'">
+ :
+ </span>
+ </span>
+ </VueCountdown>
+ </ClientOnly>
+</template>
+
+<script>
+import VueCountdown from '@chenfengyuan/vue-countdown'
+
+const countdownTransform = (props) => {
+ Object.entries(props).forEach(([key, value]) => {
+ const digits = value < 10 ? `0${value}` : value
+ props[key] = digits
+ })
+ return props
+}
+
+export default {
+ components: {
+ VueCountdown
+ },
+ props: {
+ remaining: {
+ type: Number,
+ default: 0
+ }
+ },
+ computed: {
+ isVisible () {
+ return this.remaining > 0
+ }
+ }
+}
+</script>
+
+<style>
+.vs-countdown-mobile-wrapper {
+ display: block;
+ color: #40f98a;
+ text-align: center;
+ font-weight: bold;
+ font-size: 12px;
+}
+
+@media (min-width: 680px) {
+ .vs-countdown-mobile-wrapper {
+ display: none;
+ }
+}
+</style>
\ No newline at end of file
<a
v-if="isVisible"
id="vs"
- href="https://vuejsforge.com/?friend=vuerouter&utm_source=vuerouter&utm_medium=website&utm_campaign=affiliate&utm_content=top_banner"
+ href="https://vueschool.com/sales/vuejsforge?friend=vuerouter&utm_source=vuerouter&utm_medium=website&utm_campaign=affiliate&utm_content=top_banner"
target="_blank"
rel="noreferrer">
- <div class="vs-iso">
- <img src="/images/vueschool/vf-iso.svg" alt="Vue Forge Logo">
- </div>
- <div class="vs-logo">
- <img src="/images/vueschool/vf-logo.svg" alt="Vue Forge Logo">
- </div>
- <div class="vs-core">
- <div class="vs-slogan-wrapper">
- <div class="vs-slogan">
- Join the 2nd edition of the largest hands-on Vue.js Event
+ <div
+ class="vs-background-wrapper">
+ <div class="vs-core">
+ <div class="vs-backpack">
+ <img src="/images/vueschool/vs-backpack.png" alt="Backpack">
+ </div>
+ <div class="vs-slogan-wrapper">
+ <div class="vs-slogan">
+ Save 50% for a limited time
+ <span
+ v-if="isExtended">
+ · Extended!
+ </span>
+ </div>
+ <div class="vs-subline">
+ Vue.js Premium Video Courses
+ </div>
+ <BannerCountdownMobile
+ v-bind="{ remaining }" />
</div>
- <div class="vs-subline">
- Starts 30 Aug. Build your own e-commerce app!
+ <BannerCountdown
+ v-bind="{ remaining }" />
+ <div class="vs-button">
+ BUY NOW
</div>
</div>
- <div class="vs-button">
- JOIN FOR FREE
+ <div
+ id="vs-close"
+ class="vs-close"
+ @click.stop.prevent="close">
+ <img src="/images/vueschool/close.svg" alt="Close">
</div>
</div>
- <div
- id="vs-close"
- class="vs-close"
- @click.stop.prevent="close">
- <img src="/images/vueschool/close.svg" alt="Close">
- </div>
</a>
</template>
<script>
+import BannerCountdown from './BannerCountdown.vue'
+import BannerCountdownMobile from './BannerCountdownMobile.vue'
+
export default {
+ components: {
+ BannerCountdown,
+ BannerCountdownMobile
+ },
data () {
return {
+ isVisible: false,
+ isActive: null,
+ isExtended: null,
isVisible: false,
remaining: 0
}
},
mounted () {
const now = new Date()
- const end = new Date('2022-09-01T00:00:00+02:00')
- this.remaining = end - now
- this.isVisible = !localStorage.getItem('VF2') && this.remaining > 0
+ const extension = new Date('2022-09-01T00:00:00+02:00')
+ const end = new Date('2022-09-02T00:00:00+02:00')
+ this.isActive = now < end
+ this.isExtended = now > extension && now < end
+ this.remaining = (this.isExtended ? end : extension) - now
+ this.isVisible = !localStorage.getItem('VF_OFFER') && this.remaining > 0
if (this.isVisible) document.body.classList.add('has-top-banner')
},
methods: {
close () {
this.isVisible = false
document.body.classList.remove('has-top-banner')
- localStorage.setItem('VF2', 1)
+ localStorage.setItem('VF_OFFER', 1)
}
}
}
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap');
#vs {
- align-items: center;
background-color: #0A1124;
box-sizing: border-box;
color: #fff;
font-family: 'Roboto', -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
- justify-content: center;
position: fixed;
- padding: 0 10px;
left: 0;
right: 0;
top: 0;
z-index: 100;
height: 72px;
+ background: radial-gradient(circle at 98% 31%, #1f4491, #050a1e 56%);
+}
+
+.vs-background-wrapper {
+ align-items: center;
+ justify-content: center;
display: flex;
+ padding: 0 10px;
+ height: 100%;
+ width: 100%;
background-image: url(/images/vueschool/bg-mobile.png);
background-repeat: no-repeat;
background-size: cover;
color: #FFF;
}
-#vs .vs-iso {
- position: absolute;
- left: 10px;
- height: 28px;
+#vs .vs-core {
+ display: flex;
+ align-items: center;
+ width: 288px;
}
-#vs .vs-iso img {
- height: 28px;
+#vs .vs-core .vs-backpack {
+ height: 46px;
+ margin-right: 16px;
}
-#vs .vs-logo {
- position: absolute;
- display: none;
- left: 40px;
+#vs .vs-core .vs-backpack img {
+ height: 100%;
}
-#vs .vs-core {
- display: flex;
- align-items: center;
- width: 288px;
+#vs .vs-core .vs-slogan-wrapper {
+ margin-right: 12px;
}
#vs .vs-core .vs-slogan {
- color: #FFF;
+ color: #fdb92c;
font-weight: bold;
font-size: 12px;
- text-align: center;
}
-#vs .vs-core .vs-slogan-wrapper .vs-subline {
- color: #cfc1e3;
+#vs .vs-core .vs-subline {
+ color: #FFF;
text-align: center;
font-size: 10px;
margin-top: 4px;
}
@media (min-width: 680px) {
- #vs {
+ .vs-background-wrapper {
background-image: url(/images/vueschool/bg-tablet.png);
}
}
#vs .vs-core .vs-slogan-wrapper {
- margin-right: 26px;
+ margin-right: 32px;
}
#vs .vs-core .vs-slogan {
font-size: 16px;
}
- #vs .vs-core .vs-slogan-wrapper .vs-subline {
+ #vs .vs-core .vs-subline {
font-size: 16px;
+ text-align: left;
}
#vs .vs-core {
}
@media (min-width: 900px) {
- #vs {
+ .vs-background-wrapper {
background-image: url(/images/vueschool/bg-desktop.png);
}