mirror of
https://github.com/ThaUnknown/miru.git
synced 2026-04-20 19:12:09 +00:00
feat(web): showcase video
fix: showcase cards clipping and gradients fix: touch scroll position
This commit is contained in:
parent
8b77da7400
commit
af3f311232
7 changed files with 140 additions and 29 deletions
|
|
@ -12,7 +12,7 @@
|
|||
<SmallCard {media} preview={i === 1} />
|
||||
{/each}
|
||||
</div>
|
||||
<div class='d-inline-block gallery' style:--top={(-1100 + ($scrollPosition - 800)) / 5 + 'px'}>
|
||||
<div class='d-inline-block gallery' style:--top={(-1600 + ($scrollPosition - 800)) / 5 + 'px'}>
|
||||
{#each cards.slice(4) as media}
|
||||
<SmallCard {media} />
|
||||
{/each}
|
||||
|
|
@ -20,11 +20,6 @@
|
|||
</div>
|
||||
|
||||
<style>
|
||||
@media (pointer: none), (pointer: coarse){
|
||||
.gallery {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
}
|
||||
.gallery {
|
||||
margin-top: var(--top);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
<script>
|
||||
export let stargazers = []
|
||||
/**
|
||||
* @type {Promise<any>}
|
||||
*/
|
||||
export let stargazers
|
||||
|
||||
function * chunks (arr, n) {
|
||||
for (let i = 0; i < arr.length; i += n) {
|
||||
|
|
@ -32,7 +35,7 @@
|
|||
|
||||
<style>
|
||||
.overlay-gradient {
|
||||
background: linear-gradient(90deg, rgba(15,17,19,1) 0%, rgba(15,17,19,0) 25%, rgba(15,17,19,0) 75%, rgba(15,17,19,1) 100%);
|
||||
background: linear-gradient(90deg, rgba(16,17,19,1) 0%, rgba(16,17,19,0) 25%, rgba(16,17,19,0) 75%, rgba(16,17,19,1) 100%);
|
||||
pointer-events: none;
|
||||
}
|
||||
.stargazers {
|
||||
|
|
|
|||
42
web/src/lib/components/VideoModal.svelte
Normal file
42
web/src/lib/components/VideoModal.svelte
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<script>
|
||||
import { getContext } from 'svelte'
|
||||
import { scale } from 'svelte/transition'
|
||||
import { quartOut } from 'svelte/easing'
|
||||
|
||||
const show = getContext('video-modal')
|
||||
|
||||
/**
|
||||
* @type {HTMLDivElement}
|
||||
*/
|
||||
let modal
|
||||
function close () {
|
||||
$show = false
|
||||
}
|
||||
function checkClose ({ keyCode }) {
|
||||
if (keyCode === 27) close()
|
||||
}
|
||||
$: $show && modal?.focus()
|
||||
</script>
|
||||
|
||||
{#if $show}
|
||||
<div class='modal z-40' class:show={$show}>
|
||||
<div class='modal-dialog' on:pointerup|self={close} on:keydown={checkClose} tabindex='-1' role='button' bind:this={modal} transition:scale={{ duration: 500, opacity: 0.5, start: 0.9, easing: quartOut }}>
|
||||
<div class='modal-content w-three-quarter h-full bg-transparent d-flex justify-content-center flex-column'>
|
||||
<button class='close pointer z-30 top-20 right-0 position-absolute' type='button' on:click={close}> × </button>
|
||||
<!-- eslint-disable-next-line svelte/valid-compile -->
|
||||
<video src='/showcase.mp4' controls class='border rounded overflow-hidden' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.close {
|
||||
top: 4rem !important;
|
||||
left: unset !important;
|
||||
right: 4rem !important;
|
||||
}
|
||||
.modal {
|
||||
top: 0 !important
|
||||
}
|
||||
</style>
|
||||
60
web/src/lib/components/VideoShowcase.svelte
Normal file
60
web/src/lib/components/VideoShowcase.svelte
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
<script>
|
||||
import { getContext } from 'svelte'
|
||||
|
||||
const show = getContext('video-modal')
|
||||
|
||||
/** @type {HTMLDivElement} */
|
||||
let container
|
||||
let top = 0
|
||||
let left = 0
|
||||
/** @param {MouseEvent} param0 */
|
||||
function followMouse ({ clientX, clientY }) {
|
||||
if (!clientX || !clientY) return
|
||||
const containerRect = container.getBoundingClientRect()
|
||||
left = clientX - containerRect.left
|
||||
top = clientY - containerRect.top
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class='position-relative play-container' on:mousemove={followMouse} bind:this={container} role='none'>
|
||||
<button class='btn rounded-circle btn-square btn-lg d-flex align-items-center justify-content-center position-absolute z-100 w-50 h-50' style:--left={left + 'px'} style:--top={top + 'px'} on:click={() => { show.value = true }}>
|
||||
<span class='material-symbols-outlined filled text-white'>play_arrow</span>
|
||||
</button>
|
||||
<img src='app.webp' alt='app' class='mw-full px-20 w-full app-image' />
|
||||
<div class='overlay-gradient position-absolute top-0 left-0 w-full h-full' />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.app-image {
|
||||
aspect-ratio: 2/1;
|
||||
object-fit: contain;
|
||||
}
|
||||
.overlay-gradient {
|
||||
background: linear-gradient(0deg, #101113 15.27%, rgba(16, 17, 19, 0.92) 41.28%, rgba(16, 17, 19, 0.25) 74.32%);
|
||||
pointer-events: none;
|
||||
}
|
||||
.play-container:hover > .btn {
|
||||
transition: opacity .2s ease-in-out, width .2s ease-in-out, height .2s ease-in-out, font-size .2s ease-in-out;
|
||||
font-size: 30px !important;
|
||||
left: clamp(0px, var(--left), 100%);
|
||||
top: clamp(0px, var(--top), 100%);
|
||||
width: 6rem !important;
|
||||
height: 6rem !important;
|
||||
}
|
||||
.material-symbols-outlined {
|
||||
transition: font-size .2s ease-in-out;
|
||||
}
|
||||
.play-container:hover .material-symbols-outlined {
|
||||
font-size: 30px !important;
|
||||
}
|
||||
.btn {
|
||||
transition: all .2s ease-in-out, width .2s ease-in-out, height .2s ease-in-out;
|
||||
box-shadow: 0 0 1rem #ffffff83;
|
||||
backdrop-filter: blur(8px);
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
background: none;
|
||||
offset-position: center;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -5,6 +5,9 @@
|
|||
import { throttle } from '@/modules/util.js'
|
||||
import { setContext } from 'svelte'
|
||||
import { writable } from 'simple-store-svelte'
|
||||
import VideoModal from '$lib/components/VideoModal.svelte'
|
||||
|
||||
setContext('video-modal', writable(false))
|
||||
|
||||
const scrollPosition = writable(0)
|
||||
setContext('scroll-position', scrollPosition)
|
||||
|
|
@ -37,9 +40,20 @@
|
|||
return deltaTime / 14
|
||||
}
|
||||
|
||||
t.addEventListener('pointerup', () => { pos = scrollTop = scrollPosition.value = t.scrollTop })
|
||||
function updateScrollPosition () {
|
||||
const pos = t.scrollTop
|
||||
scrollPosition.value = pos
|
||||
return pos
|
||||
}
|
||||
|
||||
t.addEventListener('scrollend', throttle(() => { scrollTop = scrollPosition.value = t.scrollTop }, 1000))
|
||||
// as lightweight as possible scroll position tracking for mobile touch
|
||||
t.addEventListener('touchmove', updateScrollPosition)
|
||||
t.addEventListener('touchstart', () => t.removeEventListener('scroll', updateScrollPosition, { passive: true }))
|
||||
t.addEventListener('touchend', () => t.addEventListener('scroll', updateScrollPosition, { passive: true }))
|
||||
|
||||
t.addEventListener('pointerup', () => { pos = scrollTop = updateScrollPosition() })
|
||||
|
||||
t.addEventListener('scrollend', throttle(() => { scrollTop = updateScrollPosition() }, 1000))
|
||||
|
||||
function update () {
|
||||
const delta = pos - scrollTop === smooth * 2 ? 0 : ((pos - scrollTop) / smooth) * getDeltaTime()
|
||||
|
|
@ -56,6 +70,7 @@
|
|||
<Loader />
|
||||
<div class='page-wrapper with-transitions position-relative' data-sidebar-type='overlayed-all'>
|
||||
<Navbar />
|
||||
<VideoModal />
|
||||
<div class='overflow-x-hidden content-wrapper h-full overflow-y-scroll position-relative' use:smoothScroll>
|
||||
<slot />
|
||||
<Footer />
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import WindowsSvg from '$lib/svg/WindowsSVG.svelte'
|
||||
import Stargazers from '$lib/components/Stargazers.svelte'
|
||||
import ShowcaseCards from '$lib/components/ShowcaseCards.svelte'
|
||||
import VideoShowcase from '$lib/components/VideoShowcase.svelte'
|
||||
|
||||
/** @type {import('./$types').PageData} */
|
||||
export let data
|
||||
|
|
@ -15,10 +16,7 @@
|
|||
|
||||
<Hero />
|
||||
<div class='container-xl'>
|
||||
<div class='position-relative'>
|
||||
<img src='app.webp' alt='app' class='mw-full px-20 w-full app-image' />
|
||||
<div class='overlay-gradient position-absolute top-0 left-0 w-full h-full' />
|
||||
</div>
|
||||
<VideoShowcase />
|
||||
|
||||
<div class='my-20 pb-20' id='about'>
|
||||
<h1 class='font-weight-bold text-white content text-center'>Torrenting made simple.</h1>
|
||||
|
|
@ -54,8 +52,11 @@
|
|||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class='my-20 py-20'>
|
||||
<div class='row overflow-hidden position-relative'>
|
||||
</div>
|
||||
|
||||
<div class='overflow-hidden my-20 position-relative'>
|
||||
<div class='container-xl'>
|
||||
<div class='row'>
|
||||
<div class='col-md-7 col-12 align-items-center d-flex'>
|
||||
<div class='content pr-20'>
|
||||
<p class='font-weight-bold text-white font-size-18'>Care to try?</p>
|
||||
|
|
@ -65,13 +66,16 @@
|
|||
<p class='text-muted font-size-16'>Find and download torrents, watch trailers, manage your list, search, browse and discover anime, watch together with friends and more, all in the same interface. No need to open multiple apps, tabs, everything shipped in one package.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class='col-md-5 col-12 overflow-y-hidden overflow-y-md-unset h-600 position-relative justify-content-center justify-content-md-end d-flex'>
|
||||
<div class='col-md-5 col-12 overflow-hidden overflow-md-unset h-600 position-relative justify-content-center justify-content-md-end d-flex'>
|
||||
<ShowcaseCards />
|
||||
<div class='overlay-gradient-top-bottom position-absolute top-0 left-0 w-full h-full z-100 d-md-none' />
|
||||
</div>
|
||||
<div class='overlay-gradient-top-bottom position-absolute top-0 left-0 w-full h-full z-100 d-none d-md-block' />
|
||||
</div>
|
||||
</div>
|
||||
<div class='overlay-gradient-top-bottom position-absolute top-0 right-0 w-half h-full z-100 d-none d-md-block' />
|
||||
</div>
|
||||
|
||||
<div class='container-xl'>
|
||||
<hr />
|
||||
<div class='my-20 py-20'>
|
||||
<div class='row flex-column-reverse flex-md-row'>
|
||||
|
|
@ -119,16 +123,8 @@
|
|||
<Stargazers stargazers={data.stargazers} />
|
||||
|
||||
<style>
|
||||
.app-image {
|
||||
aspect-ratio: 2/1;
|
||||
object-fit: contain;
|
||||
}
|
||||
.overlay-gradient-top-bottom {
|
||||
background: linear-gradient(0deg, rgba(15,17,19,1) 0%, rgba(15,17,19,0) 25%, rgba(15,17,19,0) 75%, rgba(15,17,19,1) 100%);
|
||||
pointer-events: none;
|
||||
}
|
||||
.overlay-gradient {
|
||||
background: linear-gradient(0deg, #0F1113 15.27%, rgba(15, 17, 19, 0.92) 41.28%, rgba(15, 17, 19, 0.25) 74.32%);
|
||||
background: linear-gradient(0deg, #101113ff 0%, #10111300 25%, #10111300 75%, #101113ff);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
|
@ -146,8 +142,8 @@
|
|||
font-size: 5rem
|
||||
}
|
||||
@media (min-width: 769px) {
|
||||
.overflow-y-md-unset {
|
||||
overflow-y: unset !important;
|
||||
.overflow-md-unset {
|
||||
overflow: unset !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
BIN
web/static/showcase.mp4
Normal file
BIN
web/static/showcase.mp4
Normal file
Binary file not shown.
Loading…
Reference in a new issue