feat: add test blog post with katex support

This commit is contained in:
Youwen Wu 2024-04-03 02:16:22 -07:00
parent b60fc3d19c
commit b9affbbb6d
Signed by: youwen5
GPG key ID: 865658ED1FE61EC3
96 changed files with 783 additions and 41 deletions

View file

@ -13,6 +13,6 @@ bun install
bun dev bun dev
# for production: # for production:
bun build bun run build
bun start bun preview
``` ```

BIN
bun.lockb

Binary file not shown.

View file

@ -45,8 +45,11 @@
"@fontsource/geist-sans": "^5.0.2", "@fontsource/geist-sans": "^5.0.2",
"bits-ui": "^0.21.2", "bits-ui": "^0.21.2",
"clsx": "^2.1.0", "clsx": "^2.1.0",
"marked": "^12.0.1",
"marked-katex-extension": "^5.0.1",
"mode-watcher": "^0.3.0", "mode-watcher": "^0.3.0",
"svelte-radix": "^1.1.0", "svelte-radix": "^1.1.0",
"svelte-sonner": "^0.3.21",
"tailwind-merge": "^2.2.2", "tailwind-merge": "^2.2.2",
"tailwind-variants": "^0.2.1" "tailwind-variants": "^0.2.1"
} }

0
src/globals.d.ts vendored Normal file
View file

View file

@ -1,18 +1,13 @@
<script> <script>
import { GithubLogo } from 'svelte-radix'; import { GithubLogo } from 'svelte-radix';
import Separator from './ui/separator/separator.svelte'; import Separator from './ui/separator/separator.svelte';
import Socials from './Socials.svelte';
</script> </script>
<footer class="h-24 px-2 mb-4 text-sm md:text-md xl:text-lg"> <footer class="h-24 px-2 mb-8 text-sm md:text-md xl:text-lg">
<Separator class="mb-4" /> <Separator class="mb-4" />
<div class="flex justify-center flex-col gap-4"> <div class="flex justify-center flex-col gap-4">
<div class="flex justify-around gap-4"> <Socials center />
<a
href="https://github.com/couscousdude"
target="_blank"
class="dark:brightness-75 dark:hover:brightness-100"><GithubLogo /></a
>
</div>
<p class="text-zinc-400 dark:text-zinc-700 text-center"> <p class="text-zinc-400 dark:text-zinc-700 text-center">
&copy 2024 Youwen Wu | Built with <a &copy 2024 Youwen Wu | Built with <a
href="https://kit.svelte.dev" href="https://kit.svelte.dev"

View file

@ -0,0 +1,57 @@
<script lang="ts">
import * as AlertDialog from '$lib/components/ui/alert-dialog';
import { onMount } from 'svelte';
import { toast } from 'svelte-sonner';
import ScrollArea from './ui/scroll-area/scroll-area.svelte';
let gpg: Promise<string>;
async function getGpg() {
const res = await fetch(`/youwen.gpg`);
const text = await res.text();
if (res.ok) {
return text;
} else {
throw new Error(text);
}
}
async function copyGpg() {
try {
const text = await gpg;
navigator.clipboard.writeText(text);
toast('Copied GPG key to clipboard');
} catch (e) {
console.error(e);
toast('Failed to copy GPG key to clipboard');
}
}
onMount(() => {
gpg = getGpg();
});
</script>
<AlertDialog.Root>
<AlertDialog.Trigger><slot /></AlertDialog.Trigger>
<AlertDialog.Content>
<AlertDialog.Header>
<AlertDialog.Title>GPG Key</AlertDialog.Title>
<AlertDialog.Description class="flex justify-center">
{#await gpg then text}
<ScrollArea class="max-h-60 mb-4 max-w-60" orientation="both">
<p>{text}</p>
</ScrollArea>
{:catch}
<p class="text-destructive">Couldn't fetch GPG key.</p>
{/await}
</AlertDialog.Description>
</AlertDialog.Header>
<AlertDialog.Footer class="gap-1">
<a href="/youwen.gpg" download>
<AlertDialog.Action>Download</AlertDialog.Action>
</a>
<AlertDialog.Action on:click={copyGpg}>Copy</AlertDialog.Action>
<AlertDialog.Cancel>Close</AlertDialog.Cancel>
</AlertDialog.Footer>
</AlertDialog.Content>
</AlertDialog.Root>

View file

@ -25,7 +25,7 @@
}); });
</script> </script>
<nav class="h-20 bg-background bg-opacity-50 backdrop-blur-md fixed w-full"> <nav class="h-20 bg-background bg-opacity-50 backdrop-blur-md fixed w-full z-50">
<div class="container mx-auto flex justify-between items-center h-full gap-4"> <div class="container mx-auto flex justify-between items-center h-full gap-4">
{#if current === 'blog'} {#if current === 'blog'}
<Coredump height="95%" href="/blog" /> <Coredump height="95%" href="/blog" />

View file

@ -0,0 +1,63 @@
<script>
import {
InstagramLogo,
TwitterLogo,
GithubLogo,
LinkedinLogo,
DiscordLogo,
EnvelopeClosed
} from 'svelte-radix';
import * as Popover from '$lib/components/ui/popover';
export let center = false;
import Button from './ui/button/button.svelte';
import PopoverContent from './ui/popover/popover-content.svelte';
</script>
<div class="flex gap-2 mt-2" class:justify-center={center}>
<Button variant="ghost" size="icon" href="https://github.com/couscousdude">
<GithubLogo />
</Button>
<Button variant="ghost" size="icon" href="https://www.instagram.com/uncle_uwon/"
><InstagramLogo /></Button
>
<Button variant="ghost" size="icon" href="https://twitter.com/couscousdude"
><TwitterLogo /></Button
>
<Button variant="ghost" size="icon" href="https://www.linkedin.com/in/youwen-wu-306221288/>">
<LinkedinLogo />
</Button>
<Popover.Root>
<Popover.Trigger>
<Button variant="ghost" size="icon">
<DiscordLogo />
</Button>
</Popover.Trigger>
<Popover.Content>
Discord (for some reason) doesn't support direct links to profiles. You can find me on discord
with my username, <strong>@couscousdude</strong>.
</Popover.Content>
</Popover.Root>
<Popover.Root>
<Popover.Trigger>
<Button variant="ghost" size="icon">
<EnvelopeClosed />
</Button>
</Popover.Trigger>
<Popover.Content>
<p>
You can reach my Gmail at <a class="text-link" href="mailto:youwenw@gmail.com"
>youwenw@gmail.com</a
>
</p>
<br />
<p>
Or, if you prefer, you can securely email me on Protonmail at <a
class="text-link"
href="mailto:youwenw@protonmail.com">youwenw@protonmail.com</a
>
</p>
</Popover.Content>
</Popover.Root>
</div>

View file

@ -0,0 +1,21 @@
<script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
import { buttonVariants } from "$lib/components/ui/button/index.js";
import { cn } from "$lib/utils.js";
type $$Props = AlertDialogPrimitive.ActionProps;
type $$Events = AlertDialogPrimitive.ActionEvents;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<AlertDialogPrimitive.Action
class={cn(buttonVariants(), className)}
{...$$restProps}
on:click
on:keydown
let:builder
>
<slot {builder} />
</AlertDialogPrimitive.Action>

View file

@ -0,0 +1,21 @@
<script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
import { buttonVariants } from "$lib/components/ui/button/index.js";
import { cn } from "$lib/utils.js";
type $$Props = AlertDialogPrimitive.CancelProps;
type $$Events = AlertDialogPrimitive.CancelEvents;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<AlertDialogPrimitive.Cancel
class={cn(buttonVariants({ variant: "outline" }), "mt-2 sm:mt-0", className)}
{...$$restProps}
on:click
on:keydown
let:builder
>
<slot {builder} />
</AlertDialogPrimitive.Cancel>

View file

@ -0,0 +1,27 @@
<script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
import * as AlertDialog from "./index.js";
import { cn, flyAndScale } from "$lib/utils.js";
type $$Props = AlertDialogPrimitive.ContentProps;
let className: $$Props["class"] = undefined;
export let transition: $$Props["transition"] = flyAndScale;
export let transitionConfig: $$Props["transitionConfig"] = undefined;
export { className as class };
</script>
<AlertDialog.Portal>
<AlertDialog.Overlay />
<AlertDialogPrimitive.Content
{transition}
{transitionConfig}
class={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg sm:rounded-lg md:w-full",
className
)}
{...$$restProps}
>
<slot />
</AlertDialogPrimitive.Content>
</AlertDialog.Portal>

View file

@ -0,0 +1,16 @@
<script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = AlertDialogPrimitive.DescriptionProps;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<AlertDialogPrimitive.Description
class={cn("text-sm text-muted-foreground", className)}
{...$$restProps}
>
<slot />
</AlertDialogPrimitive.Description>

View file

@ -0,0 +1,16 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/utils.js";
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<div
class={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
{...$$restProps}
>
<slot />
</div>

View file

@ -0,0 +1,13 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/utils.js";
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<div class={cn("flex flex-col space-y-2 text-center sm:text-left", className)} {...$$restProps}>
<slot />
</div>

View file

@ -0,0 +1,21 @@
<script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
import { fade } from "svelte/transition";
import { cn } from "$lib/utils.js";
type $$Props = AlertDialogPrimitive.OverlayProps;
let className: $$Props["class"] = undefined;
export let transition: $$Props["transition"] = fade;
export let transitionConfig: $$Props["transitionConfig"] = {
duration: 150,
};
export { className as class };
</script>
<AlertDialogPrimitive.Overlay
{transition}
{transitionConfig}
class={cn("fixed inset-0 z-50 bg-background/80 backdrop-blur-sm", className)}
{...$$restProps}
/>

View file

@ -0,0 +1,9 @@
<script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
type $$Props = AlertDialogPrimitive.PortalProps;
</script>
<AlertDialogPrimitive.Portal {...$$restProps}>
<slot />
</AlertDialogPrimitive.Portal>

View file

@ -0,0 +1,14 @@
<script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = AlertDialogPrimitive.TitleProps;
let className: $$Props["class"] = undefined;
export let level: $$Props["level"] = "h3";
export { className as class };
</script>
<AlertDialogPrimitive.Title class={cn("text-lg font-semibold", className)} {level} {...$$restProps}>
<slot />
</AlertDialogPrimitive.Title>

View file

@ -0,0 +1,40 @@
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
import Title from "./alert-dialog-title.svelte";
import Action from "./alert-dialog-action.svelte";
import Cancel from "./alert-dialog-cancel.svelte";
import Portal from "./alert-dialog-portal.svelte";
import Footer from "./alert-dialog-footer.svelte";
import Header from "./alert-dialog-header.svelte";
import Overlay from "./alert-dialog-overlay.svelte";
import Content from "./alert-dialog-content.svelte";
import Description from "./alert-dialog-description.svelte";
const Root = AlertDialogPrimitive.Root;
const Trigger = AlertDialogPrimitive.Trigger;
export {
Root,
Title,
Action,
Cancel,
Portal,
Footer,
Header,
Trigger,
Overlay,
Content,
Description,
//
Root as AlertDialog,
Title as AlertDialogTitle,
Action as AlertDialogAction,
Cancel as AlertDialogCancel,
Portal as AlertDialogPortal,
Footer as AlertDialogFooter,
Header as AlertDialogHeader,
Trigger as AlertDialogTrigger,
Overlay as AlertDialogOverlay,
Content as AlertDialogContent,
Description as AlertDialogDescription,
};

View file

@ -0,0 +1,18 @@
<script lang="ts">
import { type Variant, badgeVariants } from "./index.js";
import { cn } from "$lib/utils.js";
let className: string | undefined | null = undefined;
export let href: string | undefined = undefined;
export let variant: Variant = "default";
export { className as class };
</script>
<svelte:element
this={href ? "a" : "span"}
{href}
class={cn(badgeVariants({ variant, className }))}
{...$$restProps}
>
<slot />
</svelte:element>

View file

@ -0,0 +1,22 @@
import { type VariantProps, tv } from "tailwind-variants";
export { default as Badge } from "./badge.svelte";
export const badgeVariants = tv({
base: "inline-flex select-none items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive:
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
});
export type Variant = VariantProps<typeof badgeVariants>["variant"];

View file

@ -0,0 +1,17 @@
import { Popover as PopoverPrimitive } from "bits-ui";
import Content from "./popover-content.svelte";
const Root = PopoverPrimitive.Root;
const Trigger = PopoverPrimitive.Trigger;
const Close = PopoverPrimitive.Close;
export {
Root,
Content,
Trigger,
Close,
//
Root as Popover,
Content as PopoverContent,
Trigger as PopoverTrigger,
Close as PopoverClose,
};

View file

@ -0,0 +1,27 @@
<script lang="ts">
import { Popover as PopoverPrimitive } from "bits-ui";
import { cn, flyAndScale } from "$lib/utils.js";
type $$Props = PopoverPrimitive.ContentProps;
let className: $$Props["class"] = undefined;
export let transition: $$Props["transition"] = flyAndScale;
export let transitionConfig: $$Props["transitionConfig"] = undefined;
export let align: $$Props["align"] = "center";
export let sideOffset: $$Props["sideOffset"] = 4;
export { className as class };
</script>
<PopoverPrimitive.Content
{transition}
{transitionConfig}
{align}
{sideOffset}
{...$$restProps}
class={cn(
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none",
className
)}
>
<slot />
</PopoverPrimitive.Content>

View file

@ -0,0 +1,10 @@
import Scrollbar from "./scroll-area-scrollbar.svelte";
import Root from "./scroll-area.svelte";
export {
Root,
Scrollbar,
//,
Root as ScrollArea,
Scrollbar as ScrollAreaScrollbar,
};

View file

@ -0,0 +1,27 @@
<script lang="ts">
import { ScrollArea as ScrollAreaPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = ScrollAreaPrimitive.ScrollbarProps & {
orientation?: "vertical" | "horizontal";
};
let className: $$Props["class"] = undefined;
export let orientation: $$Props["orientation"] = "vertical";
export { className as class };
</script>
<ScrollAreaPrimitive.Scrollbar
{orientation}
class={cn(
"flex touch-none select-none transition-colors",
orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent p-px",
orientation === "horizontal" && "h-2.5 w-full border-t border-t-transparent p-px",
className
)}
>
<slot />
<ScrollAreaPrimitive.Thumb
class={cn("relative rounded-full bg-border", orientation === "vertical" && "flex-1")}
/>
</ScrollAreaPrimitive.Scrollbar>

View file

@ -0,0 +1,32 @@
<script lang="ts">
import { ScrollArea as ScrollAreaPrimitive } from "bits-ui";
import { Scrollbar } from "./index.js";
import { cn } from "$lib/utils.js";
type $$Props = ScrollAreaPrimitive.Props & {
orientation?: "vertical" | "horizontal" | "both";
scrollbarXClasses?: string;
scrollbarYClasses?: string;
};
let className: $$Props["class"] = undefined;
export { className as class };
export let orientation = "vertical";
export let scrollbarXClasses: string = "";
export let scrollbarYClasses: string = "";
</script>
<ScrollAreaPrimitive.Root {...$$restProps} class={cn("relative overflow-hidden", className)}>
<ScrollAreaPrimitive.Viewport class="h-full w-full rounded-[inherit]">
<ScrollAreaPrimitive.Content>
<slot />
</ScrollAreaPrimitive.Content>
</ScrollAreaPrimitive.Viewport>
{#if orientation === "vertical" || orientation === "both"}
<Scrollbar orientation="vertical" class={scrollbarYClasses} />
{/if}
{#if orientation === "horizontal" || orientation === "both"}
<Scrollbar orientation="horizontal" class={scrollbarXClasses} />
{/if}
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>

View file

@ -0,0 +1 @@
export { default as Toaster } from "./sonner.svelte";

View file

@ -0,0 +1,20 @@
<script lang="ts">
import { Toaster as Sonner, type ToasterProps as SonnerProps } from "svelte-sonner";
import { mode } from "mode-watcher";
type $$Props = SonnerProps;
</script>
<Sonner
theme={$mode}
class="toaster group"
toastOptions={{
classes: {
toast: "group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
description: "group-[.toast]:text-muted-foreground",
actionButton: "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
cancelButton: "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
},
}}
{...$$restProps}
/>

1
src/lib/styles/katex.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -1,7 +1,7 @@
import { type ClassValue, clsx } from "clsx"; import { type ClassValue, clsx } from 'clsx';
import { twMerge } from "tailwind-merge"; import { twMerge } from 'tailwind-merge';
import { cubicOut } from "svelte/easing"; import { cubicOut } from 'svelte/easing';
import type { TransitionConfig } from "svelte/transition"; import type { TransitionConfig } from 'svelte/transition';
export function cn(...inputs: ClassValue[]) { export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)); return twMerge(clsx(inputs));
@ -19,13 +19,9 @@ export const flyAndScale = (
params: FlyAndScaleParams = { y: -8, x: 0, start: 0.95, duration: 150 } params: FlyAndScaleParams = { y: -8, x: 0, start: 0.95, duration: 150 }
): TransitionConfig => { ): TransitionConfig => {
const style = getComputedStyle(node); const style = getComputedStyle(node);
const transform = style.transform === "none" ? "" : style.transform; const transform = style.transform === 'none' ? '' : style.transform;
const scaleConversion = ( const scaleConversion = (valueA: number, scaleA: [number, number], scaleB: [number, number]) => {
valueA: number,
scaleA: [number, number],
scaleB: [number, number]
) => {
const [minA, maxA] = scaleA; const [minA, maxA] = scaleA;
const [minB, maxB] = scaleB; const [minB, maxB] = scaleB;
@ -35,13 +31,11 @@ export const flyAndScale = (
return valueB; return valueB;
}; };
const styleToString = ( const styleToString = (style: Record<string, number | string | undefined>): string => {
style: Record<string, number | string | undefined>
): string => {
return Object.keys(style).reduce((str, key) => { return Object.keys(style).reduce((str, key) => {
if (style[key] === undefined) return str; if (style[key] === undefined) return str;
return str + `${key}:${style[key]};`; return str + `${key}:${style[key]};`;
}, ""); }, '');
}; };
return { return {
@ -59,4 +53,4 @@ export const flyAndScale = (
}, },
easing: cubicOut easing: cubicOut
}; };
}; };

View file

@ -5,11 +5,14 @@
import '@fontsource/geist-sans/latin.css'; import '@fontsource/geist-sans/latin.css';
import '@fontsource/geist-mono/latin.css'; import '@fontsource/geist-mono/latin.css';
import Footer from '$lib/components/Footer.svelte'; import Footer from '$lib/components/Footer.svelte';
import { Toaster } from '$lib/components/ui/sonner';
</script> </script>
<Toaster />
<ModeWatcher /> <ModeWatcher />
<Navbar /> <Navbar />
<div class="px-4 pt-24"> <div class="pt-20">
<slot /> <slot />
</div> </div>

View file

@ -1,5 +1,6 @@
<script> <script lang="ts">
import Footer from '$lib/components/Footer.svelte'; import Gpg from '$lib/components/Gpg.svelte';
import Socials from '$lib/components/Socials.svelte';
import Button from '$lib/components/ui/button/button.svelte'; import Button from '$lib/components/ui/button/button.svelte';
import * as Card from '$lib/components/ui/card'; import * as Card from '$lib/components/ui/card';
import { ArrowRight, GithubLogo } from 'svelte-radix'; import { ArrowRight, GithubLogo } from 'svelte-radix';
@ -12,9 +13,9 @@
<meta name="author" content="Youwen Wu" /> <meta name="author" content="Youwen Wu" />
</svelte:head> </svelte:head>
<main class="background py-2"> <main class="background px-4">
<div class="container max-w-3xl mx-auto p-10"> <div class="container max-w-4xl 2xl:max-w-5xl mx-auto p-10">
<Typewriter mode="scramble" scrambleDuration={500}> <Typewriter mode="scramble" scrambleDuration={750}>
<h1 class="text-4xl md:text-6xl font-bold text-center tracking-tight mt-20"> <h1 class="text-4xl md:text-6xl font-bold text-center tracking-tight mt-20">
👋 Hi, I'm Youwen. 👋 Hi, I'm Youwen.
</h1> </h1>
@ -29,11 +30,11 @@
</Typewriter> </Typewriter>
<br /> <br />
<Typewriter mode="scramble" scrambleDuration={1000}> <Typewriter mode="scramble" scrambleDuration={1000}>
I'm interested in systems programming, web design, data science, and statistics. Interested in systems programming, web design, data science, and statistics.
</Typewriter> </Typewriter>
</div> </div>
<span class="flex gap-2 justify-center my-8"> <span class="flex gap-2 justify-center my-8 flex-wrap">
<Button href="/about" size="lg" class="text-xl">About Me</Button> <Button href="/about" size="lg" class="text-xl">More About Me</Button>
<Button <Button
href="https://github.com/couscousdude" href="https://github.com/couscousdude"
target="_blank" target="_blank"
@ -42,8 +43,8 @@
class="text-xl"><GithubLogo class="mr-2" />My GitHub</Button class="text-xl"><GithubLogo class="mr-2" />My GitHub</Button
> >
</span> </span>
<div class="grid sm:grid-cols-2 gap-4 grid-cols-1"> <div class="grid grid-cols-2 gap-4">
<Card.Root> <Card.Root class="col-span-2 sm:col-span-1">
<Card.Header> <Card.Header>
<Card.Title>Blog</Card.Title> <Card.Title>Blog</Card.Title>
</Card.Header> </Card.Header>
@ -57,7 +58,7 @@
<Button variant="outline" href="/blog">Go<ArrowRight class="ml-2" /></Button> <Button variant="outline" href="/blog">Go<ArrowRight class="ml-2" /></Button>
</Card.Footer> </Card.Footer>
</Card.Root> </Card.Root>
<Card.Root> <Card.Root class="col-span-2 sm:col-span-1">
<Card.Header> <Card.Header>
<Card.Title>Projects</Card.Title> <Card.Title>Projects</Card.Title>
</Card.Header> </Card.Header>
@ -90,6 +91,18 @@
</span> </span>
</Card.Footer> </Card.Footer>
</Card.Root> </Card.Root>
<Card.Root class="col-span-2">
<Card.Header>
<Card.Title>Contact Me</Card.Title>
</Card.Header>
<Card.Content>
<h3>Find me on:</h3>
<Socials />
<Gpg>
<Button variant="outline" class="mt-4"><span class="mr-2">🔑</span> My GPG Key</Button>
</Gpg>
</Card.Content>
</Card.Root>
</div> </div>
</div> </div>
</main> </main>

View file

@ -0,0 +1,5 @@
<script lang="ts">
import '$lib/styles/katex.min.css';
</script>
<slot />

View file

@ -1,5 +1,19 @@
<script> <script lang="ts">
import UnderConstruction from '$lib/components/UnderConstruction.svelte'; import ChevronRight from 'svelte-radix/ChevronRight.svelte';
import Code from 'svelte-radix/Code.svelte';
import type { PageData } from './$types.js';
// import { config } from '$lib/stores/index.js';
import { cn } from '$lib/utils.js';
let doc = {
title: 'Test Post',
description: ''
};
export let data: PageData;
$: markdown = data.markdown;
// $: doc = data.metadata;
// $: componentSource = data.metadata.source?.replace('default', $config.style ?? 'default');
</script> </script>
<svelte:head> <svelte:head>
@ -8,4 +22,33 @@
<meta name="author" content="Youwen Wu" /> <meta name="author" content="Youwen Wu" />
</svelte:head> </svelte:head>
<UnderConstruction /> <main class="container max-w-5xl mx-auto my-8">
<div class="mx-auto w-full min-w-0 mb-4">
<div class="mb-4 flex items-center space-x-1 text-sm text-muted-foreground">
<div class="overflow-hidden text-ellipsis whitespace-nowrap">Docs</div>
<ChevronRight class="h-4 w-4" />
<div class="font-medium text-foreground">{doc.title}</div>
</div>
<div class="space-y-2">
<h1 class={cn('scroll-m-20 text-4xl font-bold tracking-tight')}>
{doc.title}
</h1>
{#if doc.description}
<p class="text-balance text-lg text-muted-foreground">
{doc.description}
</p>
{/if}
</div>
<!-- <div class="pb-12 pt-8 mx-auto"> -->
<!-- </div> -->
<!-- <div class="hidden text-sm xl:block">
<div class="sticky top-16 -mt-10 h-[calc(100vh-3.5rem)] overflow-hidden pt-4">
{#key $page.url.pathname}
<TableOfContents />
{/key}
</div>
</div> -->
</div>
{@html markdown}
</main>

17
src/routes/blog/+page.ts Normal file
View file

@ -0,0 +1,17 @@
import type { PageLoad } from './$types.js';
import { marked } from 'marked';
import markedKatex from 'marked-katex-extension';
const options = {
throwOnError: false
};
marked.use(markedKatex(options));
export const load: PageLoad = async ({ fetch }) => {
const data = await fetch('/test.md');
const markdown = await marked.parse(await data.text());
return {
markdown
};
};

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.

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.

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.

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.

135
static/test.md Normal file
View file

@ -0,0 +1,135 @@
The following content is a test post for the blog's markdown and $\KaTeX$ rendering capabilities. There's a few errors, and wikilinks aren't supported. This file was copy pasted directly out of my `obsidian.md` notebook, so it contains some weird formatting.
<br />
The methods in [[9.8 Power Series]] and [[9.9 Representation of (Rational) Functions by Power Series]] allow us to find power series for rational functions, $\ln$, and $\arctan$. To get power series for other elementary functions, we need a more general method.
We can approximate some non-polynomial functions by constructing a polynomial with the _same derivatives_ as the function. This is called a _Taylor Polynomial_.
> [!NOTE]
> In general, if $c \neq 0$, it's called a Taylor Polynomial. If $c = 0$, then it's a Maclaurin Polynomial.
Consider $f(x) = e^x$. Let's find the best cubic approximation $P_3(x)$ for $f(x)$ at $c=0$.
$$
P_3(0) = f(0)
$$
$$
f'(x) = e^x,\,P_3'(0)=f'(0)
$$
$$
f''(x) = e^x,\,P_3''(0) = f''(0)
$$
$$
f'''(x) = e^x,\,P_3'''(0) = f'''(0)
$$
$$
f(0) = f'(0) = f''(0) = f'''(0) = 1
$$
We know that $P_3(x)$ will be of the form
$$
P_3(x) = a_0 + a_1 x + a_2 x^2 + a_3 x^3
$$
We can differentiate both sides $n$ times to find the values of $a_0$, $a_1$, $\dots$, $a_n$.
$$
P_3(x) = 1 + x + \frac{1}{2} x^2 + \frac{1}{6} x^3
$$
You can confirm that this polynomial has the same first, second, and third derivatives as $e^x$.
## Generalizing
Taking repeated derivatives like this leads to a common pattern in all Taylor polynomials.
> [!NOTE]
> We use the notation $P_n(x)$ to denote the $n^{th}$ Taylor polynomial
Taylor polynomials take the form:
$$
P_n(x) = a_0 + a_1 (x-c) + a_2(x-c)^2 + a_3 (x-c)^3 + \dots + a_n(x-c)^n
$$
where $a_n$ is the coefficient of the $n^{th}$ term (indexed from 0). It turns out that these coefficients are actually common across Taylor polynomials.
$$
P_n(c) = a_0 = f(c)
$$
$$
P_n'(c) = a_1 = f'(c)
$$
$$
P_n''(c) = 2 a_2 = 2!\,a_2
$$
$$
P_n'''(c) = 6a_3 = 3!\,a_3 = f'''(c)
$$
$$
P_n^{(n)}(c) = n!\,a_n = f^{(n)}(c)
$$
$$
\implies a_n = \frac{f^{(n)}(c)}{n!}
$$
## Tying up loose ends
We've seen Taylor and Maclaurin polynomials. We will eventually extend them to [[9.10 Taylor and Maclaurin Series|Maclaurin and Taylor series]]. Before this is mathematically valid (at least, valid enough), we need to do some stuff first.
We can use Maclaurin and Taylor polynomials (or series) to estimate the value of functions by hand.
### Taylor Remainder
If you have $f(x)$ and $P_n(x)$ (centered at $c$), then $f(x) = P_n(x) + R_n(x)$, where $R_n$ is
$$
R_n(x) = \frac{f^{(n+1)}(z)}{(n+1)!}\,(x-c)^{n+1}
$$
$$
z \in [c,\,x]
$$
> [!TIP]
> This is a fancy way of saying that $z$ is between $c$ and $x$.
$$
\text{Error} = \left|R_n(x)\right|
$$
> [!NOTE] > $R_n$ would be the "next term" in $P_n(x)$ except we put $z$ instead of $c$.
#### Applying to $e^x$
How many terms of the Maclaurin Polynomial for $f(x) = e^x$ do we need to guarantee that our estimate for $f(1) = e$ is within $\displaystyle\frac{1}{1000000}$?
The error is
$$
|R_n(1)| = \frac{|f^{(n+1)}(z)|}{(n+1)!}\,\cancel{|1-0|^{n+1}}
$$
for some $z$ between $c=0$ and $x=1$
So we want an $n$ such that
$$
\frac{|e^z|}{(n+1)!} \le \frac{1}{1000000}
$$
The worst case scenario is $z=1$.
If $\displaystyle\frac{e}{(n+1)!} \le \frac{1}{1000000}$, we're all set.
Assume that $e \le 3$, which implies that if $\displaystyle\frac{3}{(n+1)!} \le \frac{1}{1000000}$, we're all set.
After trying terms, we find that $n=9$ works.
So $P_9(1) = 1 + 1 + \frac{1}{2!} + \frac{1}{3!} + \dots + \frac{1}{9!}$ is within $\frac{1}{1000000}$ of $e$.

41
static/youwen.gpg Normal file
View file

@ -0,0 +1,41 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
1Aslp42CY3IG
=wGsS
-----END PGP PUBLIC KEY BLOCK-----