Skip to content

Commit

Permalink
feat: Teaser video (#491)
Browse files Browse the repository at this point in the history
* feat: Teaser video

* styles: handle video end

* fix: do not show the CookieBanner in the teaser page

* fix: update poster

* shorten Teaser video

* move content to JSON file

* style: smoothen transitions

* aspect ratio on mobile

* webm video file

* styles: mobile

* style: CTA button fades in

* style: mobile viewport

* fix: responsive video

* fix: remove unnecesary code

* fix: replace mobile video

* feat: loading state

* styles: remove unnecessary code
  • Loading branch information
DiogoSoaress authored Nov 14, 2024
1 parent 44d2435 commit 69ff680
Show file tree
Hide file tree
Showing 15 changed files with 286 additions and 3 deletions.
5 changes: 5 additions & 0 deletions public/images/Teaser/loading.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/Teaser/poster.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions public/images/Teaser/telegram.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/videos/Teaser/Teaser_mobile.mp4
Binary file not shown.
Binary file added public/videos/Teaser/Teaser_mobile.webm
Binary file not shown.
Binary file added public/videos/Teaser/Teaser_with_logo.mp4
Binary file not shown.
Binary file added public/videos/Teaser/Teaser_with_logo.webm
Binary file not shown.
74 changes: 74 additions & 0 deletions src/components/Teaser/Video/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { useEffect, useRef, useState, type ReactElement } from 'react'
import { ButtonBase } from '@mui/material'
import { ALPHA_TELEGRAM_LINK } from '@/config/constants'
import LoadingLogo from '@/public/images/Teaser/loading.svg'
import css from './styles.module.css'

const Video = (): ReactElement => {
const [ready, setReady] = useState(false)
const [showButton, setShowButton] = useState(false)

const videoRef = useRef<HTMLVideoElement>(null)

const handleVideoEnd = () => {
setShowButton(true)
}

useEffect(() => {
const poll = setInterval(() => {
if (!videoRef.current) return
if (videoRef.current.readyState >= videoRef.current.HAVE_FUTURE_DATA) {
setReady(true)
clearInterval(poll)
}
}, 100)

return () => clearInterval(poll)
}, [])

return (
<div className={css.container}>
{/* Loading state */}
{!ready && <LoadingLogo className={css.loadingIndicator} />}

<div className={`${css.videoWrapper} ${showButton ? css.hidden : ''}`}>
{/* Video desktop */}
<video
autoPlay
muted
playsInline
ref={videoRef}
onEnded={handleVideoEnd}
className={`${css.video} ${css.desktopVideo} ${ready ? css.ready : ''}`}
>
<source src="/videos/Teaser/Teaser_with_logo.mp4" type="video/mp4" />
<source src="/videos/Teaser/Teaser_with_logo.webm" type="video/webm" />
</video>

{/* Video mobile */}
<video
autoPlay
muted
playsInline
ref={videoRef}
style={{ opacity: 0 }}
onEnded={handleVideoEnd}
className={`${css.video} ${css.mobileVideo} ${ready ? css.ready : ''}`}
>
<source src="/videos/Teaser/Teaser_mobile.mp4" type="video/mp4" />
<source src="/videos/Teaser/Teaser_mobile.webm" type="video/webm" />
</video>
</div>

{ready ? (
<div className={`${css.imageWrapper} ${showButton ? css.visible : ''}`}>
<ButtonBase target="_blank" rel="noreferrer" href={ALPHA_TELEGRAM_LINK} className={css.button}>
<img src="/images/Teaser/telegram.svg" alt="Telegram" className={css.image} />
</ButtonBase>
</div>
) : null}
</div>
)
}

export default Video
152 changes: 152 additions & 0 deletions src/components/Teaser/Video/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
.container {
display: flex;
align-items: center;
justify-content: center;
position: relative;
width: 100%;
height: 100vh;
}

.videoWrapper,
.imageWrapper {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
transition: opacity 0.5s ease-in-out, visibility 0.5s ease-in-out;
}

.videoWrapper {
opacity: 1;
visibility: visible;
}

.videoWrapper.hidden {
opacity: 0;
visibility: hidden;
}

.imageWrapper {
background-image: url('/images/Teaser/poster.png');
background-repeat: no-repeat;
overflow: hidden;
background-size: cover;
background-position: center;
position: relative;

opacity: 0;
visibility: hidden;
}

.imageWrapper.visible {
opacity: 1;
visibility: visible;
}

.button {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 16px;
cursor: pointer;
z-index: 1;
width: 150%;

display: flex;
flex-direction: column;
}

.image {
opacity: 0;
transition: opacity 2s ease-in-out;
}

.imageWrapper.visible .image {
opacity: 1;
}

.video {
box-sizing: border-box;
display: block;
width: 100%;

height: 100vh;
object-fit: cover;
}

.video.ready {
transition: opacity 0.3s ease;
opacity: 1 !important;
}

.mobileVideo {
display: block;
}

.desktopVideo {
display: none;
}

.loadingIndicator {
filter: drop-shadow(0 0 3px #12ff80);
animation: pulse 2s ease-in-out infinite;
}

@keyframes pulse {
0% {
transform: scale(1);
opacity: 1;
}
25% {
transform: scale(1.1);
opacity: 0.7;
}
50% {
transform: scale(1);
opacity: 1;
}
}

@media (min-width: 600px) {
.container,
.imageWrapper {
aspect-ratio: 16 / 9;
margin: 0 auto;
}

.imageWrapper {
background-size: contain;
}

.button {
width: 100%;
}

.image {
max-width: 40%;
}

.video {
position: absolute;
top: 0;
left: 0;
object-fit: contain;
}

.mobileVideo {
display: none;
}

.desktopVideo {
display: block;
}
}

@media (min-width: 1440px) {
.container,
.imageWrapper {
max-width: 1440px;
}
}
5 changes: 5 additions & 0 deletions src/components/Teaser/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { ReactElement } from 'react'
import PageContent from '@/components/common/PageContent'
import teaserContent from '@/content/teaser.json'

export const Teaser = (): ReactElement => <PageContent content={teaserContent} path="teaser.json" />
1 change: 1 addition & 0 deletions src/config/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export const AppRoutes = {
trademark: '/trademark',
token: '/token',
terms: '/terms',
teaser: '/teaser',
privacy: '/privacy',
press: '/press',
pass: '/pass',
Expand Down
10 changes: 10 additions & 0 deletions src/content/teaser.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[
{
"pageTitle": "Ethereum Smart Accounts",
"description": "Own the Internet. Ethereum Smart Accounts to safeguard your digital assets and build the future of ownership.",
"component": "common/MetaTags"
},
{
"component": "Teaser/Video"
}
]
7 changes: 5 additions & 2 deletions src/contexts/CookieBannerContext.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { createContext, useCallback, useContext, useEffect, useState } from 'react'
import type { ReactNode } from 'react'
import { localItem } from '@/services/Storage/local'
import { usePathname } from 'next/navigation'
import { AppRoutes } from '@/config/routes'

const ANALYTICS_PREFERENCE_KEY = 'analyticsPreference'
const analyticsPreference = localItem<boolean>(ANALYTICS_PREFERENCE_KEY)
Expand All @@ -20,6 +22,7 @@ const CookieBannerContext = createContext<{
})

export const CookieBannerContextProvider = ({ children }: { children: ReactNode }) => {
const pathname = usePathname()
const [isBannerOpen, setIsBannerOpen] = useState(false)
const [isAnalyticsEnabled, setIsAnalyticsEnabled] = useState(false)

Expand All @@ -36,12 +39,12 @@ export const CookieBannerContextProvider = ({ children }: { children: ReactNode
const preference = analyticsPreference.get()

// Open cookie banner if no preference is set
if (preference == null) {
if (preference == null && pathname !== AppRoutes.teaser) {
openBanner()
} else {
setIsAnalyticsEnabled(Boolean(preference))
}
}, [openBanner])
}, [openBanner, pathname])

const storeIsAnalyticsEnabled = useCallback((preference: boolean) => {
analyticsPreference.set(preference)
Expand Down
2 changes: 1 addition & 1 deletion src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ const App = ({

<SearchParamsContextProvider>{getLayout(<Component {...pageProps} />)}</SearchParamsContextProvider>

<CookieBanner />
{Component.name !== 'TeaserPage' && <CookieBanner />}
</CookieBannerContextProvider>
</CssVarsProvider>
</CacheProvider>
Expand Down
13 changes: 13 additions & 0 deletions src/pages/teaser.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { ReactElement } from 'react'
import type { NextPageWithLayout } from '@/pages/_app'
import { Teaser } from '@/components/Teaser'

const TeaserPage: NextPageWithLayout = () => {
return <Teaser />
}

TeaserPage.getLayout = function getLayout(page: ReactElement) {
return <>{page}</>
}

export default TeaserPage

0 comments on commit 69ff680

Please sign in to comment.