some small tweaks

This commit is contained in:
Daniel Bulant 2024-08-26 20:40:53 +02:00
parent dce011fcd3
commit 173d53b261
No known key found for this signature in database
16 changed files with 365 additions and 194 deletions

2
.nvmrc
View file

@ -1 +1 @@
17
24

View file

@ -1,8 +1,5 @@
# Mangades
Mangadex viewer and downloader. Uses svelte+routify.
Mangadex viewer and downloader website, with read-only anilist integration.
Minimalistic.
See it live [here at manga.danbulant.eu](https://manga.danbulant.eu).
See it in action [here](https://manga.danbulant.eu).

View file

@ -34,5 +34,6 @@
"svelte-local-storage-store": "^0.3.1",
"svelte-markdown": "^0.2.3",
"swiper": "^8.3.2"
}
},
"packageManager": "pnpm@9.5.0+sha1.8c155dc114e1689d18937974f6571e0ceee66f1d"
}

8
shell.nix Normal file
View file

@ -0,0 +1,8 @@
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
buildInputs = with pkgs; [
nodejs-slim
corepack
];
}

View file

@ -1,23 +1,29 @@
<script>
import { goto } from "$app/navigation";
import { goto, preloadData } from "$app/navigation";
import { flip } from "svelte/animate";
import { blur } from "svelte/transition";
import request from "../util/request";
import { blur, crossfade } from "svelte/transition";
import request, { imageproxy } from "../util/request";
import Item from "./item.svelte";
import { showType } from "./showTypeChooser.svelte";
import { quintOut } from "svelte/easing";
import { sleep } from "$lib/util/ratelimit";
import { logs } from "$lib/util/logs";
export var lists;
var isLoading = false;
let isLoading = false;
let selectedEntry;
async function find(entry) {
selectedEntry = entry;
isLoading = true;
if(typeof localStorage !== "undefined") {
let cache = localStorage.getItem("anilist-mangadex-" + entry.media.id);
if(cache) {
goto("./" + cache);
setTimeout(() => goto("./" + cache), 300);
return;
}
}
isLoading = true;
let sleeping = sleep(350);
var query = new URLSearchParams();
query.set("title", entry.media.title.romaji);
query.append("contentRating[]", "safe");
@ -32,8 +38,12 @@
result = await request("manga", query);
} catch(e) {
console.error(e);
isLoading = false;
// $logs.push({ type: 'error', text: "Failed to search mangadex." });
// $logs = $logs;
// await sleeping;
// isLoading = false;
alert("Failed to search mangadex.");
location.reload()
return;
}
console.log("anilist mangadex data", result.data);
@ -45,43 +55,92 @@
if(!item) item = result.data.find(t => t.attributes.altTitles.find(t => Object.values(t).find(t => Object.values(entry.media.title).filter(t => t).map(t => t.toLowerCase()).includes(t.toLowerCase()))));
console.log("anilist mangadex item", item);
if(!item) {
alert(`Couldn't find any mangadex entry.`);
isLoading = false;
// $logs.push({ type: 'error', text: `Couldn't find any mangadex entry.` });
// $logs = $logs;
// await sleeping;
// isLoading = false;
alert("Couldn't find any mangadex entry.");
location.reload()
return
}
if(typeof localStorage !== "undefined") {
localStorage.setItem("anilist-mangadex-" + entry.media.id, item.id);
}
goto("./" + item.id);
try {
await preloadData('./' + item.id);
await sleeping;
await goto("./" + item.id);
} catch(e) {
console.error(e)
alert("Failed to open, please try again later.");
}
isLoading = false;
}
$: console.log(selectedEntry)
const [send, receive] = crossfade({
duration: 300,
easing: quintOut
});
</script>
{#if isLoading}
<dialog open>
Finding the manga
</dialog>
{/if}
<div class="items" class:list={$showType == "list"}>
{#each lists as list}
<h2>{list.name}</h2>
{#each list.entries.sort((a, b) => a.priority - b.priority) as entry (entry.media.id)}
<div class="h-full" animate:flip transition:blur>
<Item
r18={entry.media.isAdult}
cover={entry.media.coverImage.large}
title={entry.media.title.userPreferred}
lastChapter={entry.media.chapters}
chapterProgress={entry.progress}
score={entry.score || "?"}
description={entry.notes}
coverColor={entry.media.coverImage.color == "null" ? null : entry.media.coverImage.color}
on:click={() => find(entry)}
/>
{#if selectedEntry.media.bannerImage}
<div class="banner-container">
<img class="banner" src={selectedEntry.media.bannerImage} alt="">
<div class="fader"></div>
</div>
{/if}
<div class="infoflex">
{#if selectedEntry.media.coverImage.large}
<div class="cover-container" in:send={{ key: selectedEntry.media.id }}>
<img class="cover" class:r18={selectedEntry.media.isAdult} draggable="false" src="{selectedEntry.media.coverImage.large}" alt="" >
<img class="cover-backdrop" draggable="false" src="{selectedEntry.media.coverImage.large}" alt="">
</div>
{/if}
<div class="info">
<h1>{selectedEntry.media.title.userPreferred}</h1>
<h3>
{#if selectedEntry.media.startDate?.year}
{selectedEntry.media.startDate.year} &middot;
{/if}
{#if selectedEntry?.status} {selectedEntry.status} &middot; {/if}
{selectedEntry.media.isAdult ? 'adult' : 'safe/suggestive'}
</h3>
</div>
</div>
<div class="loading">
Loading...
</div>
</dialog>
{:else}
<!-- This has to be in `if` to trigger the out transition -->
<div class="items" class:list={$showType === "list"}>
{#each lists as list}
<h2>{list.name}</h2>
{#each list.entries.sort((a, b) => a.priority - b.priority) as entry (entry.media.id)}
<div class="h-full" animate:flip in:blur out:receive={{ key: entry.media.id }}>
<Item
r18={entry.media.isAdult}
cover={entry.media.coverImage.large}
title={entry.media.title.userPreferred}
lastChapter={entry.media.chapters}
chapterProgress={entry.progress}
score={entry.score || "?"}
description={entry.notes}
coverColor={entry.media.coverImage.color === "null" ? null : entry.media.coverImage.color}
on:click={() => find(entry)}
/>
</div>
{/each}
{/each}
{/each}
</div>
</div>
{/if}
<style>
dialog {
@ -93,6 +152,80 @@
z-index: 100;
background: black;
color: white;
border: none;
padding: 0;
}
.loading {
display: flex;
align-items: center;
justify-content: center;
}
.infoflex {
display: flex;
align-items: center;
margin: calc(5rem + 15px) calc(1rem + 15px);
justify-content: start;
gap: 1rem;
z-index: 1;
position: relative;
}
.cover.r18 {
filter: blur(15px);
transition: filter .3s;
}
.cover.r18:hover {
filter: blur(0);
}
.banner-container {
width: 100%;
position: absolute;
z-index: 0;
user-select: none;
}
.banner {
width: 100%;
object-fit: cover;
object-position: center top;
overflow: hidden;
}
.banner-container .fader {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
background: linear-gradient(180deg, rgba(0,0,0,0.1) 0%, rgba(0,0,0,1) 100%);
}
.cover-container {
position: relative;
height: 20rem;
flex-shrink: 0;
margin-right: 15px;
transition: height .3s;
}
.cover {
position: relative;
border-radius: 10px;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
}
.cover-backdrop {
position: absolute;
top: 0;
left: 0;
width: calc(100% + 4px);
height: calc(100% + 4px);
z-index: 0;
filter: blur(18px) saturate(100%);
transition: filter .3s;
}
.cover-container:hover .cover-backdrop {
filter: blur(30px) saturate(150%);
}
h2 {
grid-column: 1 / -1;

View file

@ -12,6 +12,9 @@
<style>
div {
display: flex;
margin: 0 1rem;
position: relative;
z-index: 1;
}
button {
border-radius: 0;

View file

@ -1,4 +1,4 @@
import { apm } from "./tracing";
// import { apm } from "./tracing";
var isLogedInCache: boolean | null = null;
var isLogedInCacheTime: number | null = null;
@ -21,7 +21,7 @@ export function isLogedIn() {
export function getUserID() {
const token = localStorage.getItem("token")!;
let data = JSON.parse(atob(token.substring(token.indexOf(".") + 1, token.lastIndexOf("."))));
apm.setUserContext({ id: data.sub });
// apm.setUserContext({ id: data.sub });
return data.sub;
}
@ -104,8 +104,12 @@ export function getUserManga() {
status
chapters
volumes
startDate {
year
}
bannerImage
coverImage {
large
large
medium
color
}

View file

@ -1,3 +0,0 @@
import { writable } from "svelte/store";
export const logs = writable([]);

3
src/lib/util/logs.ts Normal file
View file

@ -0,0 +1,3 @@
import { writable } from "svelte/store";
export const logs = writable<{ type: string, text: string }[]>([]);

View file

@ -1,5 +1,9 @@
const ratelimits = new Map();
export function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function callback({ func }) {
const params = ratelimits.get(func);
console.log("ratelimit", params, func, ratelimits);

View file

@ -1,22 +1,22 @@
import { browser } from '$app/environment';
import { ApmBase, init as initApm } from '@elastic/apm-rum'
// import { browser } from '$app/environment';
// import { ApmBase, init as initApm } from '@elastic/apm-rum'
var apm_: ApmBase;
if(browser) {
apm_ = initApm({
// Set required service name (allowed characters: a-z, A-Z, 0-9, -, _, and space)
serviceName: 'mangades',
// Set custom APM Server URL (default: http://localhost:8200)
serverUrl: 'https://apm.elasticsearch.danbulant.cloud',
// Set the service version (required for source map feature)
// serviceVersion: import.meta.env.VITE_SENTRY_RELEASE,
// Set the service environment
environment: import.meta.env.VITE_SENTRY_ENVIRONMENT || 'production'
});
}
export const apm = apm_;
// var apm_: ApmBase;
//
// if(browser) {
// apm_ = initApm({
// // Set required service name (allowed characters: a-z, A-Z, 0-9, -, _, and space)
// serviceName: 'mangades',
//
// // Set custom APM Server URL (default: http://localhost:8200)
// serverUrl: 'https://apm.elasticsearch.danbulant.cloud',
//
// // Set the service version (required for source map feature)
// // serviceVersion: import.meta.env.VITE_SENTRY_RELEASE,
//
// // Set the service environment
// environment: import.meta.env.VITE_SENTRY_ENVIRONMENT || 'production'
// });
// }
//
// export const apm = apm_;

View file

@ -2,14 +2,8 @@
import { afterNavigate } from "$app/navigation";
import { logs } from "$lib/util/logs";
import PageTransition from "./pageTransition.svelte";
import { browser } from '$app/environment';
import { apm } from "$lib/util/tracing";
import { page } from "$app/stores";
export var data;
if(browser) {
apm.setInitialPageLoadName($page.route.id);
}
let last = typeof window !== "undefined" && window.location.pathname;
afterNavigate(page => {
@ -39,10 +33,23 @@
} else {
document.body.classList.remove("dark");
}
let inc = 0;
let lastUrl = data.url;
function increase() {
if(lastUrl == data.url) return;
let pagesToTransitionTo = ['/', '/about', '/callback', '/random']
if(pagesToTransitionTo.includes(data.url) || pagesToTransitionTo.find(t => data.url.startsWith(t + '?'))) {
inc++
}
lastUrl = data.url;
}
$: increase(data.url)
</script>
<div class:dark={darkmode} class="main">
<PageTransition url={data.url}>
<PageTransition {inc}>
<slot />
</PageTransition>
</div>
@ -91,7 +98,8 @@
position: fixed;
bottom: 0;
right: 0;
background: white;
background: black;
color: white;
border-radius: 5px 0 0 0;
padding: 5px;
box-shadow: 0 0 2px 0 black;

View file

@ -6,16 +6,20 @@
import ratelimit from '$lib/util/ratelimit';
import MangadexItems from '$lib/components/mangadexItems.svelte';
import { goto } from "$app/navigation";
import type { load } from "./+page";
import Navbar from "$lib/components/navbar.svelte";
import ShowNsfwChooser, { showNsfw } from "$lib/components/showNsfwChooser.svelte";
import type { load } from "./+page";
export var data: Awaited<ReturnType<typeof load>>;
var name: string = typeof window === "undefined" ? "" : data.url.searchParams.get("search") || "";
$: if(typeof window !== "undefined") {
const url = new URL(window.location.toString());
url.searchParams.set("search", name || "");
if(name) {
url.searchParams.set("search", name || "");
} else {
url.searchParams.delete("search");
}
history.replaceState(history.state, "", url.toString());
}
@ -131,7 +135,7 @@
<div class="nsfw">
<ShowNsfwChooser />
</div>
<a href="https://discord.gg/XKPbz5xRuK">Made by TechmandanCZ#3372</a>
<span>Made by <a href="https://discord.gg/XKPbz5xRuK">techmandancz</a></span>
</div>
<div>
<a href="/random" class="button" style="width: 100%; margin-bottom: 0.4rem; display: inline-block;">Random</a>

View file

@ -273,7 +273,7 @@
var smallScreenMode = width < 700;
$: smallScreenMode = width < 700;
var scrollY, innerHeight;
var scrollY = 0, innerHeight = 1;
let additionalImages = [];
let alReadProgress
@ -363,99 +363,102 @@
{/if}
</ArtDialog>
{#if anilistData} {#await anilistData then data}
{#if data && data.bannerImage}
<div class="banner-container">
<img class="banner" src={data.bannerImage} on:click={() => selectedImage = data.bannerImage} alt="">
<div class="fader"></div>
</div>
{/if}
{/await} {/if}
<main class:smallScreenMode>
<div class="flex infoflex">
{#if relationships.find(t => t.type === "cover_art")}
<div class="cover-container">
<img class="cover" class:r18={!["safe", "suggestive"].includes(manga.contentRating)} draggable="false" src="{imageproxy}https://uploads.mangadex.org/covers/{mangaId}/{relationships.find(t => t.type === "cover_art").attributes.fileName}.512.jpg" alt="" on:click={() => selectedImage = `${imageproxy}https://uploads.mangadex.org/covers/${mangaId}/${relationships.find(t => t.type === "cover_art").attributes.fileName}.512.jpg`}>
<img class="cover-backdrop" draggable="false" src="{imageproxy}https://uploads.mangadex.org/covers/{mangaId}/{relationships.find(t => t.type === "cover_art").attributes.fileName}.512.jpg" alt="">
<div class="header">
{#if anilistData} {#await anilistData then data}
{#if data && data.bannerImage}
<div class="banner-container">
<img class="banner" src={data.bannerImage} on:click={() => selectedImage = data.bannerImage} alt="">
<div class="fader"></div>
</div>
{/if}
{/await} {/if}
<div class="flex infoflex">
{#if relationships.find(t => t.type === "cover_art")}
<div class="cover-container">
<img class="cover" class:r18={!["safe", "suggestive"].includes(manga.contentRating)} draggable="false" src="{imageproxy}https://uploads.mangadex.org/covers/{mangaId}/{relationships.find(t => t.type === "cover_art").attributes.fileName}.512.jpg" alt="" on:click={() => selectedImage = `${imageproxy}https://uploads.mangadex.org/covers/${mangaId}/${relationships.find(t => t.type === "cover_art").attributes.fileName}.512.jpg`}>
<img class="cover-backdrop" draggable="false" src="{imageproxy}https://uploads.mangadex.org/covers/{mangaId}/{relationships.find(t => t.type === "cover_art").attributes.fileName}.512.jpg" alt="">
</div>
{/if}
<div class="info">
<h1>{title}</h1>
<h3>
{#if manga.altTitles.find(t => t.en)}
{manga.altTitles.find(t => t.en)?.en} &middot;
{/if}
{#if manga.year}
{manga.year} &middot;
{/if}
{#if anilistData} {#await anilistData then data}{#if data && data.status} {data.status} &middot; {/if} {/await} {/if}
{manga.contentRating}
</h3>
{#if relationships.find(t => t.type === "author")}
<span class="block author">Author: {relationships.find(t => t.type === "author").attributes.name}</span>
{/if}
{#if relationships.find(t => t.type === "artist")}
<span class="block author">Artist: {relationships.find(t => t.type === "artist").attributes.name}</span>
{/if}
{#if relationships.find(t => t.related === "colored" && t.type === "manga")}
<a href="/{relationships.find(t => t.related === "colored" && t.type === "manga").id}" class="block">Colored version</a>
{/if}
{#if !smallScreenMode && manga.description.en}
<p class="description"><SvelteMarkdown source={manga.description.en} isInline /></p>
{/if}
</div>
</div>
{#if smallScreenMode && manga.description.en}
<div class="fulldescription">
<ExpandableDescription source={manga.description.en} bind:expanded />
</div>
{/if}
<div class="info">
<h1>{title}</h1>
<h3>
{#if manga.altTitles.find(t => t.en)}
{manga.altTitles.find(t => t.en)?.en} &middot;
{/if}
{#if manga.year}
{manga.year} &middot;
{/if}
{#if anilistData} {#await anilistData then data}{#if data && data.status} {data.status} &middot; {/if} {/await} {/if}
{manga.contentRating}
</h3>
{#if relationships.find(t => t.type === "author")}
<span class="block author">Author: {relationships.find(t => t.type === "author").attributes.name}</span>
{/if}
{#if relationships.find(t => t.type === "artist")}
<span class="block author">Artist: {relationships.find(t => t.type === "artist").attributes.name}</span>
{/if}
{#if relationships.find(t => t.related === "colored" && t.type === "manga")}
<a href="/{relationships.find(t => t.related === "colored" && t.type === "manga").id}" class="block">Colored version</a>
{/if}
{#if !smallScreenMode && manga.description.en}
<p class="description"><SvelteMarkdown source={manga.description.en} isInline /></p>
{/if}
{#if manga.tags}
<div class="tags" class:expanded>
{#each manga.tags as tag}
<span class="tag">{tag.attributes.name.en || tag.attributes.name.jp || Object.values(tag.attributes.name)[0]}</span>
{/each}
</div>
{/if}
<div class="flex">
<div class="linklist">
{#if anilistData && isLogedIn()} {#await anilistData then data}
<a href="{data.siteUrl}" target="_blank" rel="noreferrer">
AL:
{#if data?.mediaListEntry?.status}{({
CURRENT: "Reading",
PLANNING: "Plan to read",
COMPLETED: "Completed",
DROPPED: "Dropped",
PAUSED: "Paused",
REPEATING: "Repeating"
}[data.mediaListEntry.status])}
CH {data.mediaListEntry.progress}/{data.chapters || "-"} ({uniqueChapterCount})
{:else}
Not tracking
{/if}
</a>
{/await} {/if}
</div>
<div class="copyright-header" class:copyright-header-active={copyrightOpen} on:click={() => copyrightOpen = !copyrightOpen}>Copyright infringement? (click)</div>
</div>
{#if copyrightOpen}
<p class="copyright" transition:slide={{ duration: 500 }}>
Open <a href="https://mangadex.org/title/{mangaId}">Mangadex.org page of this manga</a>, select MORE and click REPORT. I cannot delete the content, even if you report it to this website's hosting, as this is just one of many clients to mangadex.
<br>
<br>
In case of reports, I can just block the content from being loaded in this page, but that doesn't mean it's deleted nor that any other client can't access it. To properly request deletion, contact Mangadex.org. After it's deleted from Mangadex.org, this website will no longer allow access to it (since it physically cannot, as it doesn't store any content).
</p>
{/if}
<br>
</div>
{#if smallScreenMode && manga.description.en}
<div class="fulldescription">
<ExpandableDescription source={manga.description.en} bind:expanded />
</div>
{/if}
{#if manga.tags}
<div class="tags" class:expanded>
{#each manga.tags as tag}
<span class="tag">{tag.attributes.name.en || tag.attributes.name.jp || Object.values(tag.attributes.name)[0]}</span>
{/each}
</div>
{/if}
<div class="flex">
<div class="linklist">
{#if anilistData && isLogedIn()} {#await anilistData then data}
<a href="{data.siteUrl}" target="_blank" rel="noreferrer">
AL:
{#if data?.mediaListEntry?.status}{({
CURRENT: "Reading",
PLANNING: "Plan to read",
COMPLETED: "Completed",
DROPPED: "Dropped",
PAUSED: "Paused",
REPEATING: "Repeating"
}[data.mediaListEntry.status])}
CH {data.mediaListEntry.progress}/{data.chapters || "-"} ({uniqueChapterCount})
{:else}
Not tracking
{/if}
</a>
{/await} {/if}
</div>
<div class="copyright-header" class:copyright-header-active={copyrightOpen} on:click={() => copyrightOpen = !copyrightOpen}>Copyright infringement? (click)</div>
</div>
{#if copyrightOpen}
<p class="copyright" transition:slide={{ duration: 500 }}>
Open <a href="https://mangadex.org/title/{mangaId}">Mangadex.org page of this manga</a>, select MORE and click REPORT. I cannot delete the content, even if you report it to this website's hosting, as this is just one of many clients to mangadex.
<br>
<br>
In case of reports, I can just block the content from being loaded in this page, but that doesn't mean it's deleted nor that any other client can't access it. To properly request deletion, contact Mangadex.org. After it's deleted from Mangadex.org, this website will no longer allow access to it (since it physically cannot, as it doesn't store any content).
</p>
{/if}
<br>
<Tabs list={tabs} bind:selected={selectedTab} />
<Swiper
@ -546,40 +549,40 @@
<h4>Links</h4>
{#if manga.links.al}
<a href="https://anilist.co/manga/{manga.links.al}"><Favicon url="https://anilist.co" /> Anilist</a> <br>
<a target="_blank" href="https://anilist.co/manga/{manga.links.al}"><Favicon url="https://anilist.co" /> Anilist</a> <br>
{/if}
{#if manga.links.ap}
<a href="https://www.anime-planet.com/manga/{manga.links.ap}"><Favicon url="https://anime-planet.com" /> Animeplanet</a> <br>
<a target="_blank" href="https://www.anime-planet.com/manga/{manga.links.ap}"><Favicon url="https://anime-planet.com" /> Animeplanet</a> <br>
{/if}
{#if manga.links.bw}
<a href="https://bookwalker.jp/{manga.links.bw}"><Favicon url="https://bookwalker.jp" /> Bookwalker</a> <br>
<a target="_blank" href="https://bookwalker.jp/{manga.links.bw}"><Favicon url="https://bookwalker.jp" /> Bookwalker</a> <br>
{/if}
{#if manga.links.mu}
<a href="https://www.mangaupdates.com/series.html?id={manga.links.mu}"><Favicon url="https://www.mangaupdates.com" /> Manga updates</a> <br>
<a target="_blank" href="https://www.mangaupdates.com/series.html?id={manga.links.mu}"><Favicon url="https://www.mangaupdates.com" /> Manga updates</a> <br>
{/if}
{#if manga.links.nu}
<a href="https://www.novelupdates.com/series/{manga.links.nu}"><Favicon url="https://www.novelupdates.com" /> Novel updates</a> <br>
<a target="_blank" href="https://www.novelupdates.com/series/{manga.links.nu}"><Favicon url="https://www.novelupdates.com" /> Novel updates</a> <br>
{/if}
{#if manga.links.amz}
<a href={manga.links.amz}><Favicon url={manga.links.amz} /> Amazon</a> <br>
<a target="_blank" href={manga.links.amz}><Favicon url={manga.links.amz} /> Amazon</a> <br>
{/if}
{#if manga.links.ebj}
<a href={manga.links.ebj}><Favicon url={manga.links.ebj} /> Ebookjapan</a> <br>
<a target="_blank" href={manga.links.ebj}><Favicon url={manga.links.ebj} /> Ebookjapan</a> <br>
{/if}
{#if manga.links.mal}
<a href="https://myanimelist.net/manga/{manga.links.mal}"><Favicon url="https://myanimelist.net" /> MyAnimeList</a> <br>
<a target="_blank" href="https://myanimelist.net/manga/{manga.links.mal}"><Favicon url="https://myanimelist.net" /> MyAnimeList</a> <br>
{/if}
{#if manga.links.cdj}
<a href="{manga.links.cdj}"><Favicon url={manga.links.cdj} /> CDJapan</a> <br>
<a target="_blank" href="{manga.links.cdj}"><Favicon url={manga.links.cdj} /> CDJapan</a> <br>
{/if}
{#if manga.links.raw}
<a href="{manga.links.raw}"><Favicon url={manga.links.raw} /> RAW</a> <br>
<a target="_blank" href="{manga.links.raw}"><Favicon url={manga.links.raw} /> RAW</a> <br>
{/if}
{#if manga.links.engtl}
<a href="{manga.links.engtl}"><Favicon url={manga.links.engtl} /> engtl</a> <br>
<a target="_blank" href="{manga.links.engtl}"><Favicon url={manga.links.engtl} /> engtl</a> <br>
{/if}
<a href="https://mangadex.org/title/{mangaId}"><Favicon url="https://mangadex.org"/> Mangadex.org</a>
<a target="_blank" href="https://mangadex.org/title/{mangaId}"><Favicon url="https://mangadex.org"/> Mangadex.org</a>
</div>
{/if}
</div>
@ -613,17 +616,22 @@
</main>
<style>
.header {
position: relative;
padding-top: 5rem;
overflow: hidden;
}
.art-list {
margin-top: 2rem;
margin: 1rem 2rem 1rem 1rem;
}
.chapter-list {
margin-top: 1rem;
margin: 1rem;
}
.more-info {
margin-top: 2rem;
margin: 1rem 2rem 1rem 1rem;
}
.characters {
margin-top: 1rem;
margin: 1rem;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
grid-gap: 2rem;
@ -669,6 +677,9 @@
.tags {
display: flex;
overflow: auto;
margin: 0 1rem;
position: relative;
z-index: 1;
}
.tags.expanded {
flex-wrap: wrap;
@ -688,6 +699,8 @@
margin: 15px;
justify-content: start;
gap: 1rem;
position: relative;
z-index: 1;
}
.flex-wrapped {
display: flex;
@ -705,15 +718,17 @@
filter: blur(0);
}
.banner-container {
margin-top: -5rem;
width: 100%;
max-height: 40vh;
max-height: 100%;
position: absolute;
z-index: 0;
user-select: none;
}
.banner {
width: 100%;
max-height: 40vh;
max-height: 100%;
height: 100%;
object-fit: cover;
object-position: center top;
overflow: hidden;
@ -772,9 +787,6 @@
.block {
display: block;
}
.flex {
display: flex;
}
h1 {
margin: 0;
padding-top: 0.5rem;
@ -835,11 +847,10 @@
}
main {
font-size: 1.1rem;
padding-bottom: 1rem;
position: relative;
z-index: 1;
/* background: linear-gradient(to bottom, rgba(0,0,0,0.3) 0vh, rgba(0,0,0,1) 30vh); */
padding-top: 5rem;
padding: 0 0 1rem 0;
}
.no-wrap {
white-space: nowrap;

View file

@ -9,7 +9,7 @@
<Navbar title="About" />
<main style="padding-top: 6rem;">
<a href="https://discord.gg/XKPbz5xRuK">Made by TechmandanCZ#3372</a>
Made by <a href="https://discord.gg/XKPbz5xRuK">techmandancz</a>
<hr>
@ -34,6 +34,4 @@
<p>DISCLAIMER: This site isn't distributing any content and is using mangadex.org API. All of the site's requests are done client side, my servers aren't sharing any manga data. Website is open source, and I don't claim any copyright on the publications. <i>If you believe in good faith you're downloading copyrighted content, file a DMCA at yourself, your operating system (as it took a part in the download), your ISP, your browser and all the free libraries that are used in any of the previous (made by volunteers), and then here. /satire</i></p>
<p>For DMCA requests, I recommend going to the mangadex page of the selected manga instead and reporting it there, as it will be taken down more quickly. You can of course report it to me and I'll block the page from viewing it, but it's trivial to remove that (1 required software installed to run this site, edit single line, run 2 more commands and you have bypassed the block), so I really recommend removing the source instead.</p>
<a href="/error">Crash</a>
</main>

View file

@ -1,9 +1,9 @@
<script>
import { fade } from "svelte/transition";
export let url = "";
export let inc;
</script>
{#key url}
{#key inc}
<div
in:fade={{ opacity: 1, duration: 200, delay: 150 }}
out:fade={{ opacity: 0, duration: 200 }}