aboutsummaryrefslogtreecommitdiff
path: root/src/lib/Landing.svelte
diff options
context:
space:
mode:
authorFuwn <[email protected]>2024-01-25 23:00:40 -0800
committerFuwn <[email protected]>2024-01-25 23:00:40 -0800
commit84ba9c37ba9c842bd38a03d6d99e6b7795df3648 (patch)
tree66b5ec477c288163cea02aa6dfe63c0f7b055762 /src/lib/Landing.svelte
parentfeat: move everything to skeletion loading ui (diff)
downloaddue.moe-84ba9c37ba9c842bd38a03d6d99e6b7795df3648.tar.xz
due.moe-84ba9c37ba9c842bd38a03d6d99e6b7795df3648.zip
feat: landing page
Diffstat (limited to 'src/lib/Landing.svelte')
-rw-r--r--src/lib/Landing.svelte265
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>