feat(content): organize platform media sections

This commit is contained in:
poyrazavsever
2026-08-31 10:13:35 +03:00
parent 79dd81240d
commit 5c48b029ca
41 changed files with 737 additions and 17354 deletions
+2
View File
@@ -1,5 +1,6 @@
import { ContentContent } from "@/components/content-content"; import { ContentContent } from "@/components/content-content";
import { YOUTUBE_VIDEO_LINKS } from "@/data/youtube-videos"; import { YOUTUBE_VIDEO_LINKS } from "@/data/youtube-videos";
import { X_JAVASCRIPT_ANATOMY_VIDEOS } from "@/data/x-videos";
import { getPdfNotes } from "@/lib/content-page"; import { getPdfNotes } from "@/lib/content-page";
export default async function ContentPage() { export default async function ContentPage() {
@@ -9,6 +10,7 @@ export default async function ContentPage() {
<ContentContent <ContentContent
youtubeLinks={YOUTUBE_VIDEO_LINKS} youtubeLinks={YOUTUBE_VIDEO_LINKS}
pdfFiles={pdfFiles} pdfFiles={pdfFiles}
xVideos={X_JAVASCRIPT_ANATOMY_VIDEOS}
/> />
); );
} }
+138 -142
View File
@@ -1,107 +1,72 @@
"use client"; "use client";
import { useEffect, useMemo, useRef, useState } from "react"; import { useMemo, useState } from "react";
import Image from "next/image";
import { Icon } from "@iconify/react"; import { Icon } from "@iconify/react";
import { Button, ButtonIcon, ButtonLabel, Card, Typography } from "poyraz-ui/atoms"; import { Button, ButtonIcon, ButtonLabel, Card, Typography } from "poyraz-ui/atoms";
import { Modal, ModalContent, ModalTitle } from "poyraz-ui/molecules"; import { Modal, ModalContent, ModalTitle } from "poyraz-ui/molecules";
import { YoutubeLiteEmbed } from "@/components/youtube-lite-embed";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { YoutubeLiteEmbed } from "@/components/youtube-lite-embed";
import type { XVideo } from "@/data/x-videos";
import { X_JAVASCRIPT_ANATOMY_URL } from "@/data/x-videos";
import type { PdfNote } from "@/lib/content-page";
type ContentContentProps = { type ContentContentProps = {
youtubeLinks: readonly string[]; youtubeLinks: readonly string[];
pdfFiles: string[]; pdfFiles: PdfNote[];
xVideos: readonly XVideo[];
}; };
function PdfFirstPagePreview({ src, title }: { src: string; title: string }) { type SectionHeadingProps = {
const t = useTranslations("Content"); title: string;
const canvasRef = useRef<HTMLCanvasElement | null>(null); titlePrefix?: string;
const [failed, setFailed] = useState(false); titleIcon: string;
titleIconClassName?: string;
useEffect(() => { href: string;
let cancelled = false; label: string;
let loadingTask: { handle: string;
promise: Promise<unknown>; icon: string;
destroy?: () => void; };
} | null = null;
const render = async () => {
try {
const pdfjs = await import("pdfjs-dist");
const lib = pdfjs as unknown as {
version: string;
getDocument: (src: string) => {
promise: Promise<unknown>;
destroy?: () => void;
};
GlobalWorkerOptions: { workerSrc: string };
};
lib.GlobalWorkerOptions.workerSrc = `https://unpkg.com/pdfjs-dist@${lib.version}/build/pdf.worker.min.mjs`;
loadingTask = lib.getDocument(src);
const pdf = (await loadingTask.promise) as {
getPage: (page: number) => Promise<{
getViewport: (opts: { scale: number }) => {
width: number;
height: number;
};
render: (opts: {
canvasContext: CanvasRenderingContext2D;
viewport: { width: number; height: number };
}) => { promise: Promise<void> };
}>;
};
const page = await pdf.getPage(1);
const viewport = page.getViewport({ scale: 1 });
const canvas = canvasRef.current;
if (!canvas || cancelled) return;
const context = canvas.getContext("2d");
if (!context) return;
const ratio = window.devicePixelRatio || 1;
canvas.width = Math.floor(viewport.width * ratio);
canvas.height = Math.floor(viewport.height * ratio);
canvas.style.width = "100%";
canvas.style.height = "auto";
context.setTransform(ratio, 0, 0, ratio, 0, 0);
await page.render({ canvasContext: context, viewport }).promise;
} catch {
if (!cancelled) {
setFailed(true);
}
}
};
void render();
return () => {
cancelled = true;
if (loadingTask?.destroy) {
loadingTask.destroy();
}
};
}, [src]);
if (failed) {
return (
<div className="flex h-full w-full items-center justify-center bg-muted/20 p-2">
<Typography variant="small" className="text-muted-foreground">
{t("pdfPreviewError")}
</Typography>
</div>
);
}
function SectionHeading({
title,
titlePrefix,
titleIcon,
titleIconClassName,
href,
label,
handle,
icon,
}: SectionHeadingProps) {
return ( return (
<div className="w-full bg-white p-2"> <div className="flex items-center justify-between gap-3">
<canvas <Typography
ref={canvasRef} variant="large"
aria-label={title} className="inline-flex items-center gap-1.5 text-base"
className="block h-auto w-full" >
/> {titlePrefix ? <span>{titlePrefix}</span> : null}
<Icon
icon={titleIcon}
width={18}
height={18}
aria-hidden="true"
className={titleIconClassName}
/>
<span>{title}</span>
</Typography>
<Button asChild variant="outline" effect="swap" size="xs" radius="sm">
<a
href={href}
target="_blank"
rel="noopener noreferrer"
aria-label={label}
>
<ButtonIcon>
<Icon icon={icon} width={15} height={15} />
</ButtonIcon>
<ButtonLabel>{handle}</ButtonLabel>
</a>
</Button>
</div> </div>
); );
} }
@@ -109,6 +74,7 @@ function PdfFirstPagePreview({ src, title }: { src: string; title: string }) {
export function ContentContent({ export function ContentContent({
youtubeLinks, youtubeLinks,
pdfFiles, pdfFiles,
xVideos,
}: ContentContentProps) { }: ContentContentProps) {
const t = useTranslations("Content"); const t = useTranslations("Content");
const [pdfModalOpen, setPdfModalOpen] = useState(false); const [pdfModalOpen, setPdfModalOpen] = useState(false);
@@ -117,7 +83,6 @@ export function ContentContent({
const activePdf = pdfFiles[activePdfIndex] ?? null; const activePdf = pdfFiles[activePdfIndex] ?? null;
const canGoPrev = activePdfIndex > 0; const canGoPrev = activePdfIndex > 0;
const canGoNext = activePdfIndex < pdfFiles.length - 1; const canGoNext = activePdfIndex < pdfFiles.length - 1;
const embeddedVideos = useMemo( const embeddedVideos = useMemo(
() => youtubeLinks.slice(0, 3), () => youtubeLinks.slice(0, 3),
[youtubeLinks], [youtubeLinks],
@@ -129,25 +94,19 @@ export function ContentContent({
}; };
return ( return (
<section className="flex h-full flex-col gap-3 overflow-y-auto"> <section className="flex h-full flex-col gap-4 overflow-y-auto">
<section className="space-y-2"> <section className="space-y-2" aria-labelledby="youtube-section-title">
<div className="flex items-center justify-between gap-3"> <div id="youtube-section-title">
<Typography variant="large" className="text-base"> <SectionHeading
{t("youtubeTitle")} title={t("youtubeTitle")}
</Typography> titlePrefix={t("youtubeTitlePrefix")}
<Button asChild variant="outline" effect="swap" size="xs" radius="sm"> titleIcon="mdi:youtube"
<a titleIconClassName="text-red-600"
href="https://youtube.com/@poyrazavsever" href="https://youtube.com/@poyrazavsever"
target="_blank" label={t("youtubeChannel")}
rel="noopener noreferrer" handle="@poyrazavsever"
aria-label={t("youtubeChannel")} icon="mdi:youtube"
> />
<ButtonIcon>
<Icon icon="mdi:youtube" width={15} height={15} />
</ButtonIcon>
<ButtonLabel>@poyrazavsever</ButtonLabel>
</a>
</Button>
</div> </div>
<div className="grid gap-2 md:grid-cols-3"> <div className="grid gap-2 md:grid-cols-3">
{embeddedVideos.map((link) => ( {embeddedVideos.map((link) => (
@@ -161,48 +120,84 @@ export function ContentContent({
</div> </div>
</section> </section>
<section className="space-y-2"> <section className="space-y-2" aria-labelledby="linkedin-section-title">
<div className="flex items-center justify-between gap-3"> <div id="linkedin-section-title">
<Typography variant="large" className="text-base"> <SectionHeading
{t("pdfTitle")} title={t("pdfTitle")}
</Typography> titleIcon="mdi:linkedin"
<Button asChild variant="outline" effect="swap" size="xs" radius="sm"> titleIconClassName="text-[#0a66c2]"
<a href="https://www.linkedin.com/in/poyrazavsever/"
href="https://www.linkedin.com/in/poyrazavsever/" label={t("linkedinProfile")}
target="_blank" handle="@poyrazavsever"
rel="noopener noreferrer" icon="mdi:linkedin"
aria-label={t("linkedinProfile")} />
>
<ButtonIcon>
<Icon icon="mdi:linkedin" width={15} height={15} />
</ButtonIcon>
<ButtonLabel>@poyrazavsever</ButtonLabel>
</a>
</Button>
</div> </div>
<div className="grid grid-cols-2 gap-2 lg:grid-cols-3"> <div className="grid grid-cols-2 gap-2 lg:grid-cols-3">
{pdfFiles.map((pdf, index) => ( {pdfFiles.map((pdf, index) => (
<button <button
key={pdf} key={pdf.fileName}
type="button" type="button"
onClick={() => openPdfModal(index)} onClick={() => openPdfModal(index)}
className="cursor-pointer text-left" className="cursor-pointer text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
aria-label={t("openPdf", { name: pdf.title })}
> >
<Card className="overflow-hidden rounded-sm border-border p-0 transition-colors hover:border-zinc-700"> <Card className="relative aspect-4/5 overflow-hidden rounded-sm border-border bg-muted/20 p-0 transition-colors hover:border-zinc-700">
<PdfFirstPagePreview {pdf.thumbnailSrc ? (
src={`/pdf/${pdf}`} <Image
title={t("pdfPreviewTitle", { name: pdf })} src={pdf.thumbnailSrc}
/> alt={t("pdfPreviewTitle", { name: pdf.title })}
fill
className="object-cover"
sizes="(max-width: 768px) 50vw, 33vw"
/>
) : (
<span className="flex h-full w-full flex-col items-center justify-center gap-2 p-4 text-center text-muted-foreground">
<Icon icon="mdi:file-pdf-box" width={34} height={34} />
<Typography variant="small">{pdf.title}</Typography>
</span>
)}
</Card> </Card>
</button> </button>
))} ))}
</div> </div>
</section> </section>
<section className="space-y-2" aria-labelledby="x-section-title">
<div id="x-section-title">
<SectionHeading
title={t("xTitle")}
titleIcon="ri:twitter-x-fill"
href={X_JAVASCRIPT_ANATOMY_URL}
label={t("xSeries")}
handle="@poyrazavsever"
icon="ri:twitter-x-fill"
/>
</div>
<div className="grid gap-2 md:grid-cols-2">
{xVideos.map((video) => (
<Card
key={video.src}
className="relative aspect-video overflow-hidden rounded-sm border-border bg-black p-0"
>
<video
controls
playsInline
preload="metadata"
className="absolute inset-0 h-full w-full object-cover"
aria-label={t("xVideoTitle", { episode: video.episode })}
>
<source src={video.src} type="video/mp4" />
{t("videoUnsupported")}
</video>
</Card>
))}
</div>
</section>
<Modal open={pdfModalOpen} onOpenChange={setPdfModalOpen}> <Modal open={pdfModalOpen} onOpenChange={setPdfModalOpen}>
<ModalContent size="xl" className="rounded-sm p-4"> <ModalContent size="xl" className="rounded-sm p-4">
<ModalTitle> <ModalTitle>
{activePdf ? activePdf.replace(/\.pdf$/i, "") : t("pdfModalDefaultTitle")} {activePdf?.title ?? t("pdfModalDefaultTitle")}
</ModalTitle> </ModalTitle>
<div className="mt-3 flex flex-wrap items-center justify-between gap-2"> <div className="mt-3 flex flex-wrap items-center justify-between gap-2">
@@ -218,7 +213,7 @@ export function ContentContent({
className="rounded-sm" className="rounded-sm"
disabled={!canGoPrev} disabled={!canGoPrev}
onClick={() => onClick={() =>
setActivePdfIndex((prev) => Math.max(0, prev - 1)) setActivePdfIndex((previous) => Math.max(0, previous - 1))
} }
> >
{t("prev")} {t("prev")}
@@ -229,8 +224,8 @@ export function ContentContent({
className="rounded-sm" className="rounded-sm"
disabled={!canGoNext} disabled={!canGoNext}
onClick={() => onClick={() =>
setActivePdfIndex((prev) => setActivePdfIndex((previous) =>
Math.min(pdfFiles.length - 1, prev + 1), Math.min(pdfFiles.length - 1, previous + 1),
) )
} }
> >
@@ -242,9 +237,10 @@ export function ContentContent({
{activePdf ? ( {activePdf ? (
<div className="mt-3 h-[70dvh] overflow-hidden rounded-sm border border-border"> <div className="mt-3 h-[70dvh] overflow-hidden rounded-sm border border-border">
<iframe <iframe
src={`/pdf/${activePdf}`} src={activePdf.href}
title={activePdf} title={activePdf.title}
className="h-full w-full" className="h-full w-full"
loading="lazy"
/> />
</div> </div>
) : ( ) : (
+17
View File
@@ -0,0 +1,17 @@
export type XVideo = {
src: string;
episode: number;
};
export const X_JAVASCRIPT_ANATOMY_VIDEOS: readonly XVideo[] = [
{
src: "/video/bolum11render.mp4",
episode: 11,
},
{
src: "/video/bolum12Render.mp4",
episode: 12,
},
];
export const X_JAVASCRIPT_ANATOMY_URL = "https://x.com/poyrazavsever";
+43 -3
View File
@@ -2,9 +2,49 @@ import { readdir } from "node:fs/promises";
import path from "node:path"; import path from "node:path";
const PDF_DIR = path.join(process.cwd(), "public", "pdf"); const PDF_DIR = path.join(process.cwd(), "public", "pdf");
const PDF_THUMBNAIL_DIR = path.join(
process.cwd(),
"public",
"pdf-thumbnails",
);
const PDF_LIMIT = 3;
export async function getPdfNotes() { export type PdfNote = {
const files = await readdir(PDF_DIR); fileName: string;
title: string;
href: string;
thumbnailSrc: string | null;
};
function formatPdfTitle(fileName: string) {
return fileName
.replace(/\.pdf$/i, "")
.split(/[-_]+/)
.map((part) => part.charAt(0).toLocaleUpperCase("tr-TR") + part.slice(1))
.join(" ");
}
export async function getPdfNotes(): Promise<PdfNote[]> {
const [files, thumbnailFiles] = await Promise.all([
readdir(PDF_DIR),
readdir(PDF_THUMBNAIL_DIR).catch(() => [] as string[]),
]);
const thumbnails = new Set(thumbnailFiles);
const pdfFiles = files.filter((file) => file.toLowerCase().endsWith(".pdf")); const pdfFiles = files.filter((file) => file.toLowerCase().endsWith(".pdf"));
return pdfFiles.sort((a, b) => a.localeCompare(b));
return pdfFiles
.sort((a, b) => a.localeCompare(b, "tr"))
.slice(0, PDF_LIMIT)
.map((fileName) => {
const thumbnailName = `${fileName.replace(/\.pdf$/i, "")}.jpg`;
return {
fileName,
title: formatPdfTitle(fileName),
href: `/pdf/${fileName}`,
thumbnailSrc: thumbnails.has(thumbnailName)
? `/pdf-thumbnails/${thumbnailName}`
: null,
};
});
} }
+8 -3
View File
@@ -137,14 +137,19 @@
"footer": "Press {shortcut} to open quickly" "footer": "Press {shortcut} to open quickly"
}, },
"Content": { "Content": {
"youtubeTitle": "Latest YouTube Videos", "youtubeTitlePrefix": "Latest",
"youtubeTitle": "Videos",
"youtubeChannel": "My YouTube channel", "youtubeChannel": "My YouTube channel",
"youtubeEmbedTitle": "YouTube video player", "youtubeEmbedTitle": "YouTube video player",
"pdfTitle": "LinkedIn PDF Notes", "pdfTitle": "PDF Notes",
"linkedinProfile": "My LinkedIn profile", "linkedinProfile": "My LinkedIn profile",
"pdfPreviewError": "Preview could not be loaded",
"pdfPreviewTitle": "{name} preview", "pdfPreviewTitle": "{name} preview",
"openPdf": "Open the {name} PDF note",
"pdfModalDefaultTitle": "PDF Note", "pdfModalDefaultTitle": "PDF Note",
"xTitle": "JavaScript Anatomy",
"xSeries": "My JavaScript Anatomy series on X",
"xVideoTitle": "JavaScript Anatomy, episode {episode}",
"videoUnsupported": "Your browser does not support video playback.",
"prev": "Previous", "prev": "Previous",
"next": "Next", "next": "Next",
"pdfNotFound": "No PDF files found inside `/public/pdf`." "pdfNotFound": "No PDF files found inside `/public/pdf`."
+8 -3
View File
@@ -137,14 +137,19 @@
"footer": "Hızlıca açmak için {shortcut} kullan" "footer": "Hızlıca açmak için {shortcut} kullan"
}, },
"Content": { "Content": {
"youtubeTitle": "Son YouTube Videoları", "youtubeTitlePrefix": "Son",
"youtubeTitle": "Videoları",
"youtubeChannel": "YouTube kanalım", "youtubeChannel": "YouTube kanalım",
"youtubeEmbedTitle": "YouTube video oynatıcı", "youtubeEmbedTitle": "YouTube video oynatıcı",
"pdfTitle": "LinkedIn PDF Notları", "pdfTitle": "PDF Notları",
"linkedinProfile": "LinkedIn profilim", "linkedinProfile": "LinkedIn profilim",
"pdfPreviewError": "Önizleme yüklenemedi",
"pdfPreviewTitle": "{name} önizleme", "pdfPreviewTitle": "{name} önizleme",
"openPdf": "{name} PDF notunu aç",
"pdfModalDefaultTitle": "PDF Notu", "pdfModalDefaultTitle": "PDF Notu",
"xTitle": "JavaScript Anatomisi",
"xSeries": "X'teki JavaScript Anatomisi serim",
"xVideoTitle": "JavaScript Anatomisi, bölüm {episode}",
"videoUnsupported": "Tarayıcınız video oynatmayı desteklemiyor.",
"prev": "Önceki", "prev": "Önceki",
"next": "Sonraki", "next": "Sonraki",
"pdfNotFound": "/public/pdf içinde PDF bulunamadı." "pdfNotFound": "/public/pdf içinde PDF bulunamadı."
-1
View File
@@ -25,7 +25,6 @@
"mermaid": "^11.13.0", "mermaid": "^11.13.0",
"next": "16.1.6", "next": "16.1.6",
"next-intl": "4.13.0", "next-intl": "4.13.0",
"pdfjs-dist": "^5.5.207",
"poyraz-ui": "3.0.2", "poyraz-ui": "3.0.2",
"react": "19.2.3", "react": "19.2.3",
"react-dom": "19.2.3", "react-dom": "19.2.3",
+1 -134
View File
@@ -26,9 +26,6 @@ importers:
next-intl: next-intl:
specifier: 4.13.0 specifier: 4.13.0
version: 4.13.0(next@16.1.6(@babel/core@7.29.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)(typescript@5.9.3) version: 4.13.0(next@16.1.6(@babel/core@7.29.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)(typescript@5.9.3)
pdfjs-dist:
specifier: ^5.5.207
version: 5.7.284
poyraz-ui: poyraz-ui:
specifier: 3.0.2 specifier: 3.0.2
version: 3.0.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(mermaid@11.16.0)(react-dom@19.2.3(react@19.2.3))(react-hook-form@7.81.0(react@19.2.3))(react@19.2.3)(tailwindcss@4.3.2)(zod@4.4.3) version: 3.0.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(mermaid@11.16.0)(react-dom@19.2.3(react@19.2.3))(react-hook-form@7.81.0(react@19.2.3))(react@19.2.3)(tailwindcss@4.3.2)(zod@4.4.3)
@@ -529,81 +526,6 @@ packages:
'@mixmark-io/domino@2.2.0': '@mixmark-io/domino@2.2.0':
resolution: {integrity: sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==} resolution: {integrity: sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==}
'@napi-rs/canvas-android-arm64@0.1.100':
resolution: {integrity: sha512-hjhCKhntPv9+t4ckHymdx0phYNcVW+GKQR6Lzw2zE+pOVjOplSmtx9nNNknTjbEDLcuLZqA1y8ufKg1XfgftzQ==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [android]
'@napi-rs/canvas-darwin-arm64@0.1.100':
resolution: {integrity: sha512-2PcswRaC7Ly645DGt88///zuFDhJxJYdKAs1uU3mfk1atYkXufgcgLfBpk6Tm12nCQBaNt1wpybuPZ4qOhTo8A==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
'@napi-rs/canvas-darwin-x64@0.1.100':
resolution: {integrity: sha512-ePNZtj7pNIva/siZMg+HmbeozkIjqUIYdoymH8HaA3qK7LfzFN4WMBM8G6HQ9ZC+H3+Dnn5pqtiXpgLykaPOhw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
'@napi-rs/canvas-linux-arm-gnueabihf@0.1.100':
resolution: {integrity: sha512-d5cDB48oWFGU8/XPhUOFAlySgb/VAu7D+s8fi55K1Pcfg8aPplHWqMgibhVLU8ky7Pyg/fuiVLz4Nf3JrSTuUA==}
engines: {node: '>= 10'}
cpu: [arm]
os: [linux]
'@napi-rs/canvas-linux-arm64-gnu@0.1.100':
resolution: {integrity: sha512-rDxgxRu69RvDlX/bh9o22DxLsGr8EqsNgotL9+RwQE1S0b0cqeatqsw6aW45mukm0B42DIAaAacKaYQ8cqS1nw==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@napi-rs/canvas-linux-arm64-musl@0.1.100':
resolution: {integrity: sha512-K3mDW66N+xT2/V439u1alFANiBUjdEx2gLiNYnCmUsva5jZMxWTjafBYwTzYK+EMFMHrUoabuU+T1BIP5CgbYQ==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@napi-rs/canvas-linux-riscv64-gnu@0.1.100':
resolution: {integrity: sha512-mooqUBTIsccZpnoQC4NgrC1v6C1vof39etLNMnBwCY+p0gajWJvAHLGQ6g/gGyS5YrpDW+GefSN4+Cvcr08UWw==}
engines: {node: '>= 10'}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@napi-rs/canvas-linux-x64-gnu@0.1.100':
resolution: {integrity: sha512-1eCvkDCazm7FFhsT7DfGOdSaHgZVK3bt/dSBl5EWHOWmnz+I7j8tPseJqqD81NF+MH21jKUK4wQSDjN0mdhnTg==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@napi-rs/canvas-linux-x64-musl@0.1.100':
resolution: {integrity: sha512-20arT6lnI19S68qNlii73TSEDbECNgzMz2EpldC1V3mZFuRkeujXkcebRk0LRJe9SEUAooYiLokfMViY8IX7yA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [musl]
'@napi-rs/canvas-win32-arm64-msvc@0.1.100':
resolution: {integrity: sha512-DZFFT1wIAg37LJw37yhMRFfjATd3vTQzjZ1Yki8u2vhO6Hi5VE6BVaGQ1aaDu7xb4iMErz+9EOwjpS7xcxFeBw==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
'@napi-rs/canvas-win32-x64-msvc@0.1.100':
resolution: {integrity: sha512-MyT1j3mHC2+Lu4pBi9mKyMJhtP6U7k7EldY7sj/uS5gJA65gTXt8MefJQXLJo5d/vZbuWmfxzkEUNc/urV3pHA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
'@napi-rs/canvas@0.1.100':
resolution: {integrity: sha512-xglYA6q3XO5P3BNJYxVZ1IV7DLVjp1Py6nwag88YntrS+3vKHyYcMqXVS4ZztJmwz2uGvz1FWhI/4LgbR5uQDA==}
engines: {node: '>= 10'}
'@napi-rs/wasm-runtime@1.1.6': '@napi-rs/wasm-runtime@1.1.6':
resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==}
peerDependencies: peerDependencies:
@@ -2885,6 +2807,7 @@ packages:
eslint@9.39.5: eslint@9.39.5:
resolution: {integrity: sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==} resolution: {integrity: sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options.
hasBin: true hasBin: true
peerDependencies: peerDependencies:
jiti: '*' jiti: '*'
@@ -4243,10 +4166,6 @@ packages:
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
engines: {node: '>=16 || 14 >=14.18'} engines: {node: '>=16 || 14 >=14.18'}
pdfjs-dist@5.7.284:
resolution: {integrity: sha512-h4EdYQczmGhbOlqc3PPZwxevn7ApdWPbovAuWXOB/DjIyigSnwfy2oze7c6mRcSr9XgLp3eN3EeL4DyySTPMFw==}
engines: {node: '>=22.13.0 || >=24'}
pend@1.2.0: pend@1.2.0:
resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
@@ -5714,54 +5633,6 @@ snapshots:
'@mixmark-io/domino@2.2.0': {} '@mixmark-io/domino@2.2.0': {}
'@napi-rs/canvas-android-arm64@0.1.100':
optional: true
'@napi-rs/canvas-darwin-arm64@0.1.100':
optional: true
'@napi-rs/canvas-darwin-x64@0.1.100':
optional: true
'@napi-rs/canvas-linux-arm-gnueabihf@0.1.100':
optional: true
'@napi-rs/canvas-linux-arm64-gnu@0.1.100':
optional: true
'@napi-rs/canvas-linux-arm64-musl@0.1.100':
optional: true
'@napi-rs/canvas-linux-riscv64-gnu@0.1.100':
optional: true
'@napi-rs/canvas-linux-x64-gnu@0.1.100':
optional: true
'@napi-rs/canvas-linux-x64-musl@0.1.100':
optional: true
'@napi-rs/canvas-win32-arm64-msvc@0.1.100':
optional: true
'@napi-rs/canvas-win32-x64-msvc@0.1.100':
optional: true
'@napi-rs/canvas@0.1.100':
optionalDependencies:
'@napi-rs/canvas-android-arm64': 0.1.100
'@napi-rs/canvas-darwin-arm64': 0.1.100
'@napi-rs/canvas-darwin-x64': 0.1.100
'@napi-rs/canvas-linux-arm-gnueabihf': 0.1.100
'@napi-rs/canvas-linux-arm64-gnu': 0.1.100
'@napi-rs/canvas-linux-arm64-musl': 0.1.100
'@napi-rs/canvas-linux-riscv64-gnu': 0.1.100
'@napi-rs/canvas-linux-x64-gnu': 0.1.100
'@napi-rs/canvas-linux-x64-musl': 0.1.100
'@napi-rs/canvas-win32-arm64-msvc': 0.1.100
'@napi-rs/canvas-win32-x64-msvc': 0.1.100
optional: true
'@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)':
dependencies: dependencies:
'@emnapi/core': 1.10.0 '@emnapi/core': 1.10.0
@@ -9922,10 +9793,6 @@ snapshots:
lru-cache: 10.4.3 lru-cache: 10.4.3
minipass: 7.1.3 minipass: 7.1.3
pdfjs-dist@5.7.284:
optionalDependencies:
'@napi-rs/canvas': 0.1.100
pend@1.2.0: {} pend@1.2.0: {}
performance-now@2.1.0: {} performance-now@2.1.0: {}
Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.