diff options
| author | Fuwn <[email protected]> | 2024-01-25 23:00:40 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2024-01-25 23:00:40 -0800 |
| commit | 84ba9c37ba9c842bd38a03d6d99e6b7795df3648 (patch) | |
| tree | 66b5ec477c288163cea02aa6dfe63c0f7b055762 /src/lib/Landing.svelte | |
| parent | feat: move everything to skeletion loading ui (diff) | |
| download | due.moe-84ba9c37ba9c842bd38a03d6d99e6b7795df3648.tar.xz due.moe-84ba9c37ba9c842bd38a03d6d99e6b7795df3648.zip | |
feat: landing page
Diffstat (limited to 'src/lib/Landing.svelte')
| -rw-r--r-- | src/lib/Landing.svelte | 265 |
1 files changed, 265 insertions, 0 deletions
diff --git a/src/lib/Landing.svelte b/src/lib/Landing.svelte new file mode 100644 index 00000000..d616df94 --- /dev/null +++ b/src/lib/Landing.svelte @@ -0,0 +1,265 @@ +<script lang="ts"> + import locale from '$stores/locale'; + import ListTitle from './List/ListTitle.svelte'; + import sampleManga from '$lib/Data/SampleMedia/manga.json'; + import sampleAnime from '$lib/Data/SampleMedia/anime.json'; + import MediaTitleDisplay from './List/MediaTitleDisplay.svelte'; + import { outboundLink } from './Media/links'; + import settings from '$stores/settings'; + import { + onMouseEnter, + onMouseLeave, + onMouseMove, + type HoverCoverResponse + } from './Media/Cover/hoverCover'; + import HoverCover from './Media/Cover/HoverCover.svelte'; + import root from './Utility/root'; + import type { Media } from './AniList/media'; + import { env } from '$env/dynamic/public'; + + const randomManga = sampleManga + .filter( + (manga) => + manga.chapters && + !manga.tags.some((tag) => tag.name === 'Nudity') && + !manga.tags.some((tag) => tag.name === 'Rape') && + !manga.tags.some((tag) => tag.name === 'Tragedy') && + !manga.tags.some((tag) => tag.name === 'Bondage') && + manga.genres.some((genre) => genre === 'Comedy') && + !manga.genres.some((genre) => genre === 'Hentai') + ) + .sort(() => 0.5 - Math.random()) + .slice(0, 8) as unknown as Media[]; + const randomAnime = sampleAnime + .filter( + (anime) => + anime.episodes && + !anime.tags.some((tag) => tag.name === 'Nudity') && + !anime.tags.some((tag) => tag.name === 'Rape') && + !anime.tags.some((tag) => tag.name === 'Tragedy') && + !anime.tags.some((tag) => tag.name === 'Bondage') && + !anime.genres.some((genre) => genre === 'Hentai') && + anime.genres.some((genre) => genre === 'Comedy') && + anime.status !== 'NOT_YET_RELEASED' + ) + .sort(() => 0.5 - Math.random()) + .slice(0, 6) as unknown as Media[]; + let hoverCoverState: HoverCoverResponse = {}; +</script> + +<div class="example-item card"> + <div class="item-content"> + <details open={true} class="list"> + <ListTitle title={$locale().lists.due.mangaAndLightNovels} count={5} hideTime /> + + <ul> + {#each randomManga as manga} + {@const readChapters = Math.floor(Math.random() * (manga.chapters || 0))} + + <li> + <a + href={outboundLink(manga, 'manga', $settings.displayOutboundLinksTo)} + target="_blank" + on:mouseenter={() => { + const response = onMouseEnter(manga); + + hoverCoverState.hovering = response.hovering; + hoverCoverState.item = response.item; + hoverCoverState.media = response.media; + }} + on:mouseleave={() => { + const response = onMouseLeave(); + + hoverCoverState.hovering = response.hovering; + hoverCoverState.item = response.item; + hoverCoverState.media = response.media; + }} + on:mousemove={(e) => { + const response = onMouseMove(e, 300); + + hoverCoverState.style = response.style; + }} + > + <MediaTitleDisplay title={manga.title} /> + </a> + <span style="opacity: 50%;">|</span> + {readChapters}<span style="opacity: 50%;">/{manga.chapters || '?'}</span> + <button class="button-square button-action">+</button> + [{Math.floor(Math.random() * ((manga.chapters ?? 0) - readChapters)) + + readChapters + + 1}] + </li> + {/each} + </ul> + </details> + </div> + + <div class="card item-description"> + <span class="big-text"> + Instantly view which manga on your lists have newly released chapters + </span> + + <p> + <a href={root('/')}>due.moe</a> will automatically check which manga on your lists have new + chapters available. <a href={root('/')}>due.moe</a> will even alert you when you need to update + your logged volume count if you are trailing behind. + </p> + + <p> + Separating concluded and publishing manga, <a href={root('/')}>due.moe</a> truly gives a one-of-a-kind + experience when it comes to staying on top of your favourite titles. + </p> + + <p class="medium-text"> + <a href={root('/')}>due.moe</a> even supports checking for new light novels chapters! + </p> + + <small>This is simulated data from concluded manga.</small> + </div> +</div> + +<p /> + +<div class="example-item card"> + <div class="card item-description"> + <span class="big-text">Let's not forget anime!</span> + + <p> + <a href={root('/')}>due.moe</a> will let you know which episodes you still need to watch, which + anime you are caught up on, and planned anime which will begin to air soon. + </p> + + <p> + We'll always let you know when the next episodes is coming out, and did we mention that all + displayed countdowns are for subtitled release times by default? Cool, right? + </p> + + <small>This is simulated data from concluded anime. Cover grid view is also supported.</small> + </div> + <div class="item-content"> + <details open={true} class="list"> + <ListTitle title={$locale().lists.due.episodes} count={5} hideTime /> + + <ul> + {#each randomAnime as anime} + {@const watchedEpisodes = Math.floor(Math.random() * (anime.episodes || 0))} + + <li> + <a + href={outboundLink(anime, 'anime', $settings.displayOutboundLinksTo)} + target="_blank" + on:mouseenter={() => { + const response = onMouseEnter(anime); + + hoverCoverState.hovering = response.hovering; + hoverCoverState.item = response.item; + hoverCoverState.media = response.media; + }} + on:mouseleave={() => { + const response = onMouseLeave(); + + hoverCoverState.hovering = response.hovering; + hoverCoverState.item = response.item; + hoverCoverState.media = response.media; + }} + on:mousemove={(e) => { + const response = onMouseMove(e, 300); + + hoverCoverState.style = response.style; + }} + > + <MediaTitleDisplay title={anime.title} /> + </a> + <span style="opacity: 50%;">|</span> + {watchedEpisodes}<span style="opacity: 50%;">/{anime.episodes || '?'}</span> + <button class="button-square button-action">+</button> + [{Math.floor(Math.random() * ((anime.episodes ?? 0) - watchedEpisodes)) + + watchedEpisodes + + 1}] + </li> + {/each} + </ul> + </details> + </div> +</div> + +<p /> + +<div class="example-item card"> + <div> + <span class="big-text">Tools & More!</span> + + <p> + <a href={root('/')}>due.moe</a> also offers a suite of helpful tools to make your life on AniList + that much easier. + </p> + + <ul> + <li> + <a href={root('/wrapped')}>AniList Wrapped — Your Year on AniList</a> + </li> + <li> + Badge Wall — A unified badge collection experience for AniList + <blockquote style="margin: 0 0 0 1.5rem;"> + Easily display all of your earned badges in a single place, with your Anime Watching Club + (AWC) badges automatically included! + </blockquote> + </li> + <li> + <a href={root('/schedule')} + >Schedule — An Simulcast Release Calendar, displaying anime release times for <b + >subtitled releases</b + >!</a + > + </li> + <li> + <a href={root('/birthdays')}>Today's Character Birthdays</a> + </li> + <li> + <a href={root('/tools/sequel_spy')}>Sequel Spy</a> + <blockquote style="margin: 0 0 0 1.5rem;"> + Find media with prequels you haven't seen for any simulcast season + </blockquote> + </li> + </ul> + + <p /> + + <span class="big-text"> + <a + href={`https://anilist.co/api/v2/oauth/authorize?client_id=${env.PUBLIC_ANILIST_CLIENT_ID}&redirect_uri=${env.PUBLIC_ANILIST_REDIRECT_URI}&response_type=code`} + on:click={() => { + localStorage.setItem( + 'redirect', + window.location.origin + window.location.pathname + window.location.search + ); + }}>Log in</a + > with AniList to get started! + </span> + </div> +</div> + +<HoverCover options={hoverCoverState} width={300} /> + +<style> + .example-item { + display: flex; + flex-wrap: wrap; + } + + .item-content { + flex: 1 1 50%; + } + + .item-description { + flex: 1 1 50%; + } + + .medium-text { + font-size: 1.125rem; + } + + .big-text { + font-size: 1.25rem; + } +</style> |