refactor: migrate Preact components to Astro

This commit is contained in:
Stefan Imhoff
2024-09-09 18:42:43 +02:00
parent d77c513b7b
commit 526fe22cda
130 changed files with 1154 additions and 1274 deletions

View File

@@ -0,0 +1,24 @@
---
// Cspell:words astro
import Book from './Book.astro';
import Link from './Link.astro';
interface Props {
alt?: string;
asin: string;
class?: string;
}
const { alt = '', asin, class: className, ...props } = Astro.props;
const amazonImageUrl = `https://images-na.ssl-images-amazon.com/images/P/${asin}.01.LZZZZZZZ.jpg`;
---
<Link
href={`http://www.amazon.de/gp/product/${asin}`}
class={className}
aria-label={alt}
{...props}
>
<Book alt={alt} src={amazonImageUrl} />
</Link>

View File

@@ -1,29 +0,0 @@
import type { FunctionalComponent, JSX } from 'preact';
import { Book, Link } from '.';
interface Props extends JSX.HTMLAttributes<HTMLAnchorElement> {
alt?: string;
asin: string;
class?: string;
}
export const AmazonBook: FunctionalComponent<Props> = ({
alt = '',
asin,
class: className,
...props
}) => {
const amazonImageUrl = `https://images-na.ssl-images-amazon.com/images/P/${asin}.01.LZZZZZZZ.jpg`;
return (
<Link
href={`http://www.amazon.de/gp/product/${asin}`}
class={className}
aria-label={alt}
{...props}
>
<Book alt={alt} src={amazonImageUrl} />
</Link>
);
};

View File

@@ -0,0 +1,39 @@
---
// Cspell:words astro tabindex clickarea umami classnames
import cx from 'classnames';
import { ArrowLeft } from './icons';
import Link from './Link.astro';
interface Props {
class?: string;
backLink?: string;
}
const { backLink, class: className, ...props } = Astro.props;
const classes = cx(
'col-span-2 col-start-1 h-clickarea w-clickarea items-center justify-center self-center justify-self-center transition-transform duration-500 ease-in-out hover:-translate-x-1 focus:-translate-x-1 print:hidden md:col-span-1',
className
);
---
{
backLink && (
<Link
aria-label="Back to overview"
class={classes}
data-umami-event="Back to overview"
href={backLink}
{...props}
>
<button
class="flex h-clickarea w-clickarea cursor-pointer items-center justify-center border-none text-[0]"
type="button"
tabindex={-1}
>
<ArrowLeft class="icon h-icon w-icon" />
</button>
</Link>
)
}

View File

@@ -1,40 +0,0 @@
import cx from 'classnames';
import type { FunctionalComponent, JSX } from 'preact';
import { Link } from '.';
import { ArrowLeft } from './icons';
interface Props extends JSX.HTMLAttributes<HTMLAnchorElement> {
class?: string;
backLink?: string;
}
export const Backlink: FunctionalComponent<Props> = ({ backLink, class: className, ...props }) => {
const classes = cx(
'col-span-2 col-start-1 h-clickarea w-clickarea items-center justify-center self-center justify-self-center transition-transform duration-500 ease-in-out hover:-translate-x-1 focus:-translate-x-1 print:hidden md:col-span-1',
className
);
return (
<>
{backLink && (
<Link
aria-label="Back to overview"
class={classes}
data-umami-event="Back to overview"
href={backLink}
{...props}
>
<button
class="flex h-clickarea w-clickarea cursor-pointer items-center justify-center border-none text-[0]"
type="button"
tabIndex={-1}
>
<ArrowLeft class="icon h-icon w-icon" />
</button>
</Link>
)}
</>
);
};

View File

@@ -0,0 +1,32 @@
---
// Cspell:words astro classnames
import cx from 'classnames';
export interface Props {
class?: string;
summary?: string;
open?: boolean;
}
const { class: className, open, summary, ...props } = Astro.props;
const classes = cx(
'rounded-2 bg-black/5 p-10 mbe-10 dark:bg-white/5 [&_p:last-of-type]:mbe-0',
className
);
---
<aside class={classes} {...props}>
{
summary ? (
<details open={open} class="group">
<summary class="cursor-pointer font-black leading-4 tracking-tight group-open:mbe-8">
{summary}
</summary>
<slot />
</details>
) : (
<slot />
)
}
</aside>

View File

@@ -1,38 +0,0 @@
import cx from 'classnames';
import type { ComponentChild, FunctionalComponent } from 'preact';
interface Props {
class?: string;
children: ComponentChild;
summary?: string;
open?: boolean;
}
export const Banner: FunctionalComponent<Props> = ({
class: className,
children,
open,
summary,
...props
}) => {
const classes = cx(
'rounded-2 bg-black/5 p-10 mbe-10 dark:bg-white/5 [&_p:last-of-type]:mbe-0',
className
);
return (
<aside class={classes} {...props}>
{summary ? (
<details open={open} class="group">
<summary class="cursor-pointer font-black leading-4 tracking-tight group-open:mbe-8">
{summary}
</summary>
{children}
</details>
) : (
children
)}
</aside>
);
};

View File

@@ -0,0 +1,41 @@
---
// Cspell:words astro classnames
import cx from 'classnames';
import TextLink from './TextLink.astro';
interface Props {
author?: string;
class?: string;
lang?: string;
source?: string;
sourceUrl?: string;
}
const { author, class: className, lang = 'en', source, sourceUrl, ...props } = Astro.props;
const classes = cx(
'relative overflow-hidden mbe-12 mbs-12 mie-8 mis-8 pie-8 pis-8 md:mie-10 md:mis-10',
className
);
---
<blockquote lang={lang} class={classes} {...props}>
<slot />
{
(author || source) && (
<footer class="text-2 font-normal opacity-60 mbs-6">
{(author || source) && '—'}
{author && <b class="font-normal">{author}</b>}
{author && source && ', '}
{source &&
(sourceUrl ? (
<cite>
<TextLink href={sourceUrl}>{source}</TextLink>
</cite>
) : (
<cite>{source}</cite>
))}
</footer>
)
}
</blockquote>

View File

@@ -1,50 +0,0 @@
import cx from 'classnames';
import type { ComponentChild, FunctionalComponent, JSX } from 'preact';
import { TextLink } from '.';
interface Props extends JSX.HTMLAttributes<HTMLQuoteElement> {
author?: string;
children: ComponentChild;
class?: string;
lang?: string;
source?: string;
sourceUrl?: string;
}
export const Blockquote: FunctionalComponent<Props> = ({
author,
children,
class: className,
lang = 'en',
source,
sourceUrl,
...props
}) => {
const classes = cx(
'relative overflow-hidden mbe-12 mbs-12 mie-8 mis-8 pie-8 pis-8 md:mie-10 md:mis-10',
className
);
return (
<blockquote lang={lang} class={classes} {...props}>
{children}
{(author || source) && (
<footer class="text-2 font-normal opacity-60 mbs-6">
{(author || source) && '—'}
{author && <b class="font-normal">{author}</b>}
{author && source && ', '}
{source &&
(sourceUrl ? (
<cite>
<TextLink href={sourceUrl}>{source}</TextLink>
</cite>
) : (
<cite>{source}</cite>
))}
</footer>
)}
</blockquote>
);
};

21
src/components/Book.astro Normal file
View File

@@ -0,0 +1,21 @@
---
// Cspell:words astro tabindex classnames
import cx from 'classnames';
interface Props {
alt?: string;
class?: string;
src: string;
}
const { alt = '', class: className, src, ...props } = Astro.props;
const classes = cx(
"image-shadow relative box-border grid h-auto max-w-[250px] shrink grow justify-self-center overflow-hidden align-bottom shadow-book before:absolute before:z-10 before:block before:h-full before:w-[0.5em] before:bg-gradient-to-r before:from-black/30 before:to-transparent before:shadow-book-before before:content-[''] before:rounded-is-1",
className
);
---
<div class={classes} tabindex={0}>
<img alt={alt} src={src} {...props} />
</div>

View File

@@ -1,22 +0,0 @@
import cx from 'classnames';
import type { FunctionalComponent, JSX } from 'preact';
interface Props extends JSX.HTMLAttributes<HTMLImageElement> {
alt?: string;
class?: string;
src: string;
}
export const Book: FunctionalComponent<Props> = ({ alt = '', class: className, src, ...props }) => {
const classes = cx(
"image-shadow relative box-border grid h-auto max-w-[250px] shrink grow justify-self-center overflow-hidden align-bottom shadow-book before:absolute before:z-10 before:block before:h-full before:w-[0.5em] before:bg-gradient-to-r before:from-black/30 before:to-transparent before:shadow-book-before before:content-[''] before:rounded-is-1",
className
);
return (
<div class={classes} tabIndex={0}>
<img alt={alt} src={src} {...props} />
</div>
);
};

View File

@@ -0,0 +1,15 @@
---
// Cspell:words astro minmax classnames
export interface Props {
[key: string]: any;
}
const { ...props } = Astro.props;
---
<article
class="grid grid-cols-[repeat(auto-fit,_minmax(150px,_1fr))] place-items-center justify-center gap-[20px] rounded-4 bg-white/50 p-10 mbe-13 mbs-0 mie-0 mis-0 dark:bg-black/80"
{...props}
>
<slot />
</article>

View File

@@ -1,16 +0,0 @@
import type { ComponentChild, FunctionalComponent } from 'preact';
interface Props {
children: ComponentChild;
}
export const Bookshelf: FunctionalComponent<Props> = ({ children, ...props }) => {
return (
<article
class="grid grid-cols-[repeat(auto-fit,_minmax(150px,_1fr))] place-items-center justify-center gap-[20px] rounded-4 bg-white/50 p-10 mbe-13 mbs-0 mie-0 mis-0 dark:bg-black/80"
{...props}
>
{children}
</article>
);
};

View File

@@ -0,0 +1,15 @@
---
// Cspell:words astro minmax
export interface Props {
[key: string]: any;
}
const { ...props } = Astro.props;
---
<article
class="col-start-1 col-end-17 grid grid-cols-[repeat(auto-fill,_minmax(150px,_1fr))] gap-[20px] mbe-10"
{...props}
>
<slot />
</article>

View File

@@ -1,16 +0,0 @@
import type { ComponentChild, FunctionalComponent } from 'preact';
interface Props {
children: ComponentChild;
}
export const ColorStack: FunctionalComponent<Props> = ({ children, ...props }) => {
return (
<article
class="col-start-1 col-end-17 grid grid-cols-[repeat(auto-fill,_minmax(150px,_1fr))] gap-[20px] mbe-10"
{...props}
>
{children}
</article>
);
};

View File

@@ -0,0 +1,80 @@
---
// Cspell:words astro subheadline backface flippable classnames
import cx from 'classnames';
import Subheadline from './Subheadline.astro';
import Text from './Text.astro';
interface Props {
class?: string;
color: string;
title?: string;
description?: string;
}
const { class: className, color, title, description, ...props } = Astro.props;
const classes = cx(
'h-[100vw] max-h-[200px] w-full max-w-[300px] [perspective:500px]',
{ 'cursor-pointer': description },
className
);
const flipperClasses = cx(
'relative transition-transform duration-500 ease-in-out [transform-style:preserve-3d]'
);
const cardClasses = cx(
'absolute box-border h-[100vw] max-h-[200px] w-full max-w-[300px] rounded-25 bg-white shadow-book inline-start-0 block-start-0 [perspective:500px] [backface-visibility:hidden] dark:bg-black dark:border-[1px] dark:border-white/20'
);
---
<div class={classes} data-flippable={description ? 'true' : 'false'} {...props}>
<div class={flipperClasses}>
<div class={cx(cardClasses, 'z-10')} data-side="front">
<div
class="border-be-solid min-h-[5rem] bg-white border-be-[1px] border-be-black/20 rounded-bs-25 dark:bg-black dark:border-be-white/20"
style={`background-color: ${color};`}
>
</div>
<div class="h-full min-h-[6rem] pbe-7 pbs-6 pie-6 pis-6">
{title && <Subheadline class="!text-[20px] !mbe-[8px]">{title}</Subheadline>}
<Text class="m-0 font-mono text-[16px] text-black/40 dark:text-white/40">
{color}
</Text>
</div>
</div>
{
description && (
<div
class={cx(cardClasses, 'plb-7 pli-6 [transform:rotateY(180deg)]')}
data-side="back"
>
<Text class="text-[15px]">{description}</Text>
</div>
)
}
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const flippableCards = document.querySelectorAll('[data-flippable="true"]');
flippableCards.forEach((card) => {
card.addEventListener('click', () => {
card.classList.toggle('flipped');
});
});
});
</script>
<style>
[data-flippable='true'] .relative {
transition: transform 0.5s;
transform-style: preserve-3d;
}
[data-flippable='true'].flipped .relative {
transform: rotateY(180deg);
}
</style>

View File

@@ -1,83 +0,0 @@
import cx from 'classnames';
import type { FunctionalComponent, JSX } from 'preact';
import { useState } from 'preact/hooks';
import { Subheadline, Text } from '.';
interface ColorCardProps extends JSX.HTMLAttributes<HTMLDivElement> {
class?: string;
}
interface Props extends JSX.HTMLAttributes<HTMLDivElement> {
class?: string;
color: string;
title?: string;
description?: string;
}
const ColorCard: FunctionalComponent<ColorCardProps> = ({
class: className,
children,
...props
}) => {
const classes = cx(
'absolute box-border h-[100vw] max-h-[200px] w-full max-w-[300px] rounded-25 bg-white shadow-book inline-start-0 block-start-0 [perspective:500px] [backface-visibility:hidden] dark:bg-black dark:border-[1px] dark:border-white/20',
className
);
return (
<div class={classes} {...props}>
{children}
</div>
);
};
export const ColorSwatch: FunctionalComponent<Props> = ({
class: className,
color,
title,
description,
...props
}) => {
const [active, setActive] = useState(false);
const classes = cx(
'h-[100vw] max-h-[200px] w-full max-w-[300px] [perspective:500px]',
{ 'cursor-pointer': description },
className
);
const flipperClasses = cx(
'relative transition-transform duration-500 ease-in-out [transform-style:preserve-3d]',
{ '[transform:rotateY(180deg)]': active }
);
const handleClick = () => {
description && setActive(!active);
};
return (
<div class={classes} onClick={handleClick} {...props}>
<div class={flipperClasses}>
<ColorCard class="z-10" data-info="front">
<div
class="border-be-solid min-h-[5rem] bg-white border-be-[1px] border-be-black/20 rounded-bs-25 dark:bg-black dark:border-be-white/20"
style={{ backgroundColor: color }}
></div>
<div class="h-full min-h-[6rem] pbe-7 pbs-6 pie-6 pis-6">
{title && (
<Subheadline class="!text-[20px] !mbe-[8px]">{title}</Subheadline>
)}
<Text class="m-0 font-mono text-[16px] text-black/40 dark:text-white/40">
{color}
</Text>
</div>
</ColorCard>
{description && (
<ColorCard class="plb-7 pli-6 [transform:rotateY(180deg)]" data-info="back">
<Text class="text-[15px]">{description}</Text>
</ColorCard>
)}
</div>
</div>
);
};

View File

@@ -0,0 +1,17 @@
---
// Cspell:words astro classnames
import cx from 'classnames';
export interface Props {
class?: string;
[key: string]: any;
}
const { class: className, ...props } = Astro.props;
const classes = cx('[&_img]:bg-gray-100 [&_img]:p-10', className);
---
<div class={classes} {...props}>
<slot />
</div>

View File

@@ -1,22 +0,0 @@
import cx from 'classnames';
import type { ComponentChild, FunctionalComponent } from 'preact';
interface Props {
class?: string;
children: ComponentChild;
}
export const DisplayBox: FunctionalComponent<Props> = ({
class: className,
children,
...props
}) => {
const classes = cx('[&_img]:bg-gray-100 [&_img]:p-10', className);
return (
<div class={classes} {...props}>
{children}
</div>
);
};

View File

@@ -0,0 +1,17 @@
---
// Cspell:words astro classnames
import cx from 'classnames';
interface Props {
class?: string;
}
const { class: className, ...props } = Astro.props;
const classes = cx(
'border-solid border-black/[0.1] mbe-14 mbs-14 border-bs-0 border-be-1 border-is-0 border-ie-0 dark:border-white/[0.1]',
className
);
---
<hr class={classes} {...props} />

View File

@@ -1,16 +0,0 @@
import cx from 'classnames';
import type { FunctionalComponent } from 'preact';
interface Props {
class?: string;
}
export const Divider: FunctionalComponent<Props> = ({ class: className, ...props }) => {
const classes = cx(
'border-solid border-black/[0.1] mbe-14 mbs-14 border-bs-0 border-be-1 border-is-0 border-ie-0 dark:border-white/[0.1]',
className
);
return <hr class={classes} {...props} />;
};

View File

@@ -1,3 +1,4 @@
// Cspell:words deepmerge autocolors chartjs
import deepmerge from 'deepmerge';
import { ArcElement, Chart as ChartJS, Legend, Tooltip } from 'chart.js';

View File

@@ -0,0 +1,22 @@
---
// Cspell:words astro
import { Download } from './icons';
import TextLink from './TextLink.astro';
interface Props {
href: string;
text: string;
}
const { href, text, ...props } = Astro.props;
---
<TextLink class="group" href={href} {...props}>
{text}
<span class="more-icon relative inline-flex mis-[0.2em] block-start-[0.2em]">
<Download
aria-hidden="true"
class="icon h-icon w-icon transition-transform duration-500 ease-in-out group-hover:translate-y-1 group-focus:translate-y-1"
/>
</span>
</TextLink>

View File

@@ -1,21 +0,0 @@
import type { FunctionalComponent, JSX } from 'preact';
import { TextLink } from '.';
import { Download } from './icons';
interface Props extends JSX.HTMLAttributes<HTMLAnchorElement> {
href: string;
text: string;
}
export const DownloadLink: FunctionalComponent<Props> = ({ href, text, ...props }) => (
<TextLink class="group" href={href} {...props}>
{text}
<span class="more-icon relative inline-flex mis-[0.2em] block-start-[0.2em]">
<Download
aria-hidden="true"
class="icon h-icon w-icon transition-transform duration-500 ease-in-out group-hover:translate-y-1 group-focus:translate-y-1"
/>
</span>
</TextLink>
);

View File

@@ -0,0 +1,26 @@
---
// Cspell:words astro
import { MailSend } from './icons';
import TextLink from './TextLink.astro';
interface Props {
text?: string;
icon?: boolean;
}
const { icon = true, text = 'Email', ...props } = Astro.props;
---
<TextLink class="group" href="#protected-email" data-email-link {...props}>
{text}
{
icon && (
<span class="more-icon relative inline-flex mis-[0.2em] block-start-[0.3em] inline-start-[0.2em]">
<MailSend
aria-hidden="true"
class="icon h-icon w-icon transition-transform duration-500 ease-in-out group-hover:translate-x-1 group-focus:translate-x-1"
/>
</span>
)
}
</TextLink>

View File

@@ -1,30 +0,0 @@
import type { FunctionalComponent, JSX } from 'preact';
import { TextLink } from '.';
import { MailSend } from './icons';
interface Props extends Omit<JSX.HTMLAttributes<HTMLAnchorElement>, 'icon'> {
text?: string;
}
interface EmailLinkProps extends Props {
icon?: boolean;
}
export const EmailLink: FunctionalComponent<EmailLinkProps> = ({
icon = true,
text = 'Email',
...props
}) => (
<TextLink class="group" href="#protected-email" data-email-link {...props}>
{text}
{icon && (
<span class="more-icon relative inline-flex mis-[0.2em] block-start-[0.3em] inline-start-[0.2em]">
<MailSend
aria-hidden="true"
class="icon h-icon w-icon transition-transform duration-500 ease-in-out group-hover:translate-x-1 group-focus:translate-x-1"
/>
</span>
)}
</TextLink>
);

View File

@@ -0,0 +1,29 @@
---
// Cspell:words astro fullsize classnames nowrap figcaption
import cx from 'classnames';
export interface Props {
caption?: string;
class?: string;
size?: 'regular' | 'wide' | 'fullsize';
}
const { class: className, caption, size = 'regular', ...props } = Astro.props;
const classes = cx(
'rounded-4 bg-white/50 p-8 mbe-13 mbs-0 mie-0 mis-0 dark:bg-black/80',
{ 'figure-wide': size === 'wide', 'figure-fullsize': size === 'fullsize' },
className
);
---
<figure class={classes} {...props}>
<div class="flex flex-wrap gap-6 md:flex-nowrap [&_div]:flex-grow">
<slot />
</div>
{
caption && (
<figcaption class="text-center text-2 mbs-2 [text-wrap:balance]">{caption}</figcaption>
)
}
</figure>

View File

@@ -1,35 +0,0 @@
import cx from 'classnames';
import type { ComponentChild, FunctionalComponent } from 'preact';
interface Props {
caption?: string;
children: ComponentChild;
class?: string;
size?: 'regular' | 'wide' | 'fullsize';
}
export const Figure: FunctionalComponent<Props> = ({
children,
class: className,
caption,
size = 'regular',
...props
}) => {
const classes = cx(
'rounded-4 bg-white/50 p-8 mbe-13 mbs-0 mie-0 mis-0 dark:bg-black/80',
{ 'figure-wide': size === 'wide', 'figure-fullsize': size === 'fullsize' },
className
);
return (
<figure class={classes} {...props}>
<div class="flex flex-wrap gap-6 md:flex-nowrap [&_div]:flex-grow">{children}</div>
{caption && (
<figcaption class="text-center text-2 mbs-2 [text-wrap:balance]">
{caption}
</figcaption>
)}
</figure>
);
};

43
src/components/Flag.astro Normal file
View File

@@ -0,0 +1,43 @@
---
// Cspell:words astro classnames darkgrey
import cx from 'classnames';
import Link from './Link.astro';
export interface Props {
class?: string;
href?: string;
label: string;
[key: string]: any;
}
const { label, class: className, href, ...props } = Astro.props;
const classes = cx(
'rounded-1 border-1 border-solid border-[darkgrey] bg-[lightgrey] font-mono text-[0.7em] text-black decoration-0 pli-[0.3em] pbe-0 pbs-[0.1em] dark:bg-[lightgrey]/80',
className
);
---
{
href ? (
<Link class={classes} href={href} title={label} {...props}>
<span class="hidden" aria-hidden="true">
[
</span>
{label}
<span class="hidden" aria-hidden="true">
]
</span>
</Link>
) : (
<span class={classes} title={label} {...props}>
<span class="hidden" aria-hidden="true">
[
</span>
{label}
<span class="hidden" aria-hidden="true">
]
</span>
</span>
)
}

View File

@@ -1,48 +0,0 @@
import cx from 'classnames';
import type { ComponentChild, FunctionalComponent } from 'preact';
import { Link } from '.';
interface Props {
class?: string;
href?: string;
label: string;
}
interface InnerFlagProps {
children: ComponentChild;
}
const InnerFlag: FunctionalComponent<InnerFlagProps> = ({ children }) => (
<>
<span class="hidden" aria-hidden="true">
[
</span>
{children}
<span class="hidden" aria-hidden="true">
]
</span>
</>
);
export const Flag: FunctionalComponent<Props> = ({ label, class: className, href, ...props }) => {
const classes = cx(
'rounded-1 border-1 border-solid border-[darkgrey] bg-[lightgrey] font-mono text-[0.7em] text-black decoration-0 pli-[0.3em] pbe-0 pbs-[0.1em] dark:bg-[lightgrey]/80',
className
);
return (
<>
{href ? (
<Link class={classes} href={href} title={label} {...props}>
<InnerFlag>{label}</InnerFlag>
</Link>
) : (
<span class={classes} title={label} {...props}>
<InnerFlag>{label}</InnerFlag>
</span>
)}
</>
);
};

View File

@@ -1,10 +1,11 @@
---
// Cspell:words astro classnames
import cx from 'classnames';
import { animation, animationDelay } from '../data/site';
import Image from './Image.astro';
import { Headline } from '.';
import Headline from './Headline.astro';
const { entry } = Astro.props;

View File

@@ -0,0 +1,21 @@
---
// Cspell:words astro classnames keyof
import cx from 'classnames';
export interface Props {
as?: keyof astroHTML.JSX.IntrinsicElements;
class?: string;
[key: string]: any;
}
const { as: Tag = 'h2', class: className, children, ...props } = Astro.props;
const classes = cx(
'text-5 font-black tracking-tight mbe-10 dark:font-extrabold [text-wrap:balance]',
className
);
---
<Tag class={classes} {...props}>
<slot />
</Tag>

View File

@@ -1,28 +0,0 @@
import cx from 'classnames';
import type { ComponentChild, FunctionalComponent } from 'preact';
interface Props {
as?: any;
class?: string;
children: ComponentChild;
}
export const Headline: FunctionalComponent<Props> = ({
as = 'h2',
class: className,
children,
...props
}) => {
const Tag = as;
const classes = cx(
'text-5 font-black tracking-tight mbe-10 dark:font-extrabold [text-wrap:balance]',
className
);
return (
<Tag class={classes} {...props}>
{children}
</Tag>
);
};

View File

@@ -1,4 +1,5 @@
---
// Cspell:words astro classnames
import cx from 'classnames';
const { class: className, noMargin, src, ...props } = Astro.props;

View File

@@ -1,4 +1,5 @@
---
// Cspell:words astro subsubheadline minmax webp
import { Picture } from 'astro:assets';
import type { CollectionEntry } from 'astro:content';
@@ -9,7 +10,8 @@ interface Props {
import { animation } from '../data/site';
import { pickTwoRandomColors } from '../utils';
import { Link, Subsubheadline } from '../components';
import Link from '../components/Link.astro';
import Subsubheadline from './Subsubheadline.astro';
const { entries } = Astro.props;
---

View File

@@ -1,5 +1,6 @@
---
import { LegalDate } from '.';
// Cspell:words astro
import LegalDate from './LegalDate.astro';
import { site } from '../data/site';
---
@@ -7,7 +8,7 @@ import { site } from '../data/site';
<div class="flex flex-1 shrink-0 items-center">
<span class="relative text-[14px] mie-[0.25em] block-start-[1px]">©</span>
<span class="shrink-0">
<LegalDate client:idle />
<LegalDate />
</span>
<span class="mli-[0.25em]">•</span>
<strong class="shrink-0 uppercase tracking-wider">{site.author}</strong>

View File

@@ -0,0 +1,19 @@
---
---
<time id="legal-date">2006</time>
<script is:inline>
(function () {
const currentDate = new Date();
const isoDate = currentDate.toISOString();
const currentYear = currentDate.getFullYear();
const timeElement = document.getElementById('legal-date');
if (timeElement) {
timeElement.setAttribute('datetime', isoDate);
timeElement.textContent = `2006${currentYear}`;
}
})();
</script>

View File

@@ -1,7 +0,0 @@
export const LegalDate = () => {
const currentDate = new Date();
const isoDate = currentDate.toISOString();
const currentYear = currentDate.getFullYear();
return <time dateTime={isoDate}>2006{currentYear}</time>;
};

25
src/components/Link.astro Normal file
View File

@@ -0,0 +1,25 @@
---
// Cspell:words astro classnames noopener noreferrer
import cx from 'classnames';
interface Props {
class?: string;
href?: string;
[key: string]: any;
}
const { class: className, href = '#', ...props } = Astro.props;
const isExternal = (href as string).startsWith('http');
const classes = cx('link', { external: isExternal }, className);
---
<a
class={classes}
href={href}
rel={isExternal ? 'nofollow noopener noreferrer' : undefined}
target={isExternal ? '_blank' : undefined}
{...props}
>
<slot />
</a>

View File

@@ -1,27 +0,0 @@
import cx from 'classnames';
import type { FunctionalComponent, JSX } from 'preact';
interface Props extends JSX.HTMLAttributes<HTMLAnchorElement> {}
export const Link: FunctionalComponent<Props> = ({
class: className,
children,
href = '#',
...props
}: Props) => {
const isExternal = (href as string).startsWith('http');
const classes = cx('link', { external: isExternal }, className as string);
return (
<a
class={classes}
href={href}
rel={isExternal ? 'nofollow noopener noreferrer' : undefined}
target={isExternal ? '_blank' : undefined}
{...props}
>
{children}
</a>
);
};

View File

@@ -0,0 +1,17 @@
---
// Cspell:words astro classnames
import cx from 'classnames';
export interface Props {
class?: string;
[key: string]: any;
}
const { class: className, ...props } = Astro.props;
const classes = cx('mbe-2', className);
---
<li class={classes} {...props}>
<slot />
</li>

View File

@@ -1,18 +0,0 @@
import cx from 'classnames';
import type { ComponentChild, FunctionalComponent } from 'preact';
interface Props {
class?: string;
children: ComponentChild;
}
export const ListItem: FunctionalComponent<Props> = ({ class: className, children, ...props }) => {
const classes = cx('mbe-2', className);
return (
<li class={classes} {...props}>
{children}
</li>
);
};

View File

@@ -1,5 +1,6 @@
---
import { Link } from '.';
// Cspell:words astro umami shibui
import Link from './Link.astro';
import navigation from '../data/navigation.json';
import SearchLink from './SearchLink.astro';

View File

@@ -1,4 +1,5 @@
---
// Cspell:words astro classnames
import cx from 'classnames';
interface Props {

View File

@@ -0,0 +1,22 @@
---
// Cspell:words astro
import { ArrowRight } from './icons';
import TextLink from './TextLink.astro';
interface Props {
href: string;
text: string;
}
const { href, text, ...props } = Astro.props;
---
<TextLink class="group" href={href} {...props}>
{text}
<span class="more-icon relative inline-flex mis-[0.2em] block-start-[0.2em]">
<ArrowRight
aria-hidden="true"
class="icon h-icon w-icon transition-transform duration-500 ease-in-out group-hover:translate-x-1 group-focus:translate-x-1"
/>
</span>
</TextLink>

View File

@@ -1,21 +0,0 @@
import type { FunctionalComponent, JSX } from 'preact';
import { TextLink } from '.';
import { ArrowRight } from './icons';
interface Props extends JSX.HTMLAttributes<HTMLAnchorElement> {
href: string;
text: string;
}
export const MoreLink: FunctionalComponent<Props> = ({ href, text, ...props }) => (
<TextLink class="group" href={href} {...props}>
{text}
<span class="more-icon relative inline-flex mis-[0.2em] block-start-[0.2em]">
<ArrowRight
aria-hidden="true"
class="icon h-icon w-icon transition-transform duration-500 ease-in-out group-hover:translate-x-1 group-focus:translate-x-1"
/>
</span>
</TextLink>
);

View File

@@ -0,0 +1,24 @@
---
// Cspell:words astro etflix classnames
import cx from 'classnames';
import Link from './Link.astro';
export interface Props {
class?: string;
id: string;
[key: string]: any;
}
const { class: className, id, ...props } = Astro.props;
const classes = cx(
'rounded-1 border-1 border-solid border-red-600 bg-red-600 font-mono text-[0.7em] text-white decoration-0 pli-[0.3em] pbe-0 pbs-[0.1em] print:bg-transparent print:border-gray-500',
className
);
---
<Link class={classes} href={`https://www.netflix.com/title/${id}`} title="Netflix" {...props}>
<span class="hidden" aria-hidden="true">[</span>
N
<span class="hidden" aria-hidden="true">etflix]</span>
</Link>

View File

@@ -1,34 +0,0 @@
import cx from 'classnames';
import type { FunctionalComponent } from 'preact';
import { Link } from '.';
interface Props {
class?: string;
id: string;
}
export const NetflixFlag: FunctionalComponent<Props> = ({ class: className, id, ...props }) => {
const classes = cx(
'rounded-1 border-1 border-solid border-red-600 bg-red-600 font-mono text-[0.7em] text-white decoration-0 pli-[0.3em] pbe-0 pbs-[0.1em] print:bg-transparent print:border-gray-500',
className
);
return (
<Link
class={classes}
href={`https://www.netflix.com/title/${id}`}
title="Netflix"
{...props}
>
<span class="hidden" aria-hidden="true">
[
</span>
N
<span class="hidden" aria-hidden="true">
etflix]
</span>
</Link>
);
};

View File

@@ -0,0 +1,23 @@
---
// Cspell:words astro classnames autoplay allowfullscreen
import cx from 'classnames';
export interface Props {
class?: string;
id: string;
[key: string]: any;
}
const { class: className, id, ...props } = Astro.props;
const classes = cx('relative aspect-video mbe-10', className);
---
<div class={classes} {...props}>
<iframe
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
class="absolute h-full w-full"
frameborder="0"
src={`https://odysee.com/$/embed/${id}`}></iframe>
</div>

View File

@@ -1,24 +0,0 @@
import cx from 'classnames';
import type { FunctionalComponent } from 'preact';
interface Props {
class?: string;
id: string;
}
export const OdyseeVideo: FunctionalComponent<Props> = ({ class: className, id, ...props }) => {
const classes = cx('relative aspect-video mbe-10', className);
return (
<div class={classes} {...props}>
<iframe
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
class="absolute h-full w-full"
frameBorder="0"
src={`https://odysee.com/$/embed/${id}`}
></iframe>
</div>
);
};

View File

@@ -0,0 +1,20 @@
---
// Cspell:words astro classnames
import cx from 'classnames';
export interface Props {
class?: string;
[key: string]: any;
}
const { class: className, children, ...props } = Astro.props;
const classes = cx(
'list-decimal text-3 mbe-12 pis-[1.5rem] md:pis-0 [li>&]:mbe-0 [li>&]:pis-[1.5rem]',
className
);
---
<ol class={classes} {...props}>
<slot />
</ol>

View File

@@ -1,25 +0,0 @@
import cx from 'classnames';
import type { ComponentChild, FunctionalComponent } from 'preact';
interface Props {
class?: string;
children: ComponentChild;
}
export const OrderedList: FunctionalComponent<Props> = ({
class: className,
children,
...props
}) => {
const classes = cx(
'list-decimal text-3 mbe-12 pis-[1.5rem] md:pis-0 [li>&]:mbe-0 [li>&]:pis-[1.5rem]',
className
);
return (
<ol class={classes} {...props}>
{children}
</ol>
);
};

View File

@@ -1,8 +1,10 @@
---
// Cspell:words astro subnavigation shibui
import Legal from './Legal.astro';
import RSSLink from './RSSLink.astro';
import SocialLinks from './SocialLinks.astro';
import Subnavigation from './Subnavigation.astro';
import { UpLink, SocialLinks } from '.';
import UpLink from './UpLink.astro';
---
<footer

View File

@@ -1,7 +1,8 @@
---
// Cspell:words astro backlink classnames shibui
import cx from 'classnames';
import { Backlink } from '../components';
import Backlink from '../components/Backlink.astro';
import MainNavigation from '../components/MainNavigation.astro';
import ThemeToggle from '../components/ThemeToggle.astro';

View File

@@ -1,7 +1,8 @@
---
// Cspell:words astro classnames
import cx from 'classnames';
import { Title } from './Title';
import Title from './Title.astro';
export interface Props {
class?: string;

View File

@@ -1,4 +1,5 @@
---
// Cspell:words astro umami
import { ArrowLeftS, ArrowRightS } from './icons';
export interface Props {

View File

@@ -0,0 +1,29 @@
---
// Cspell:words astro classnames
import cx from 'classnames';
import Link from './Link.astro';
export interface Props {
class?: string;
id: string;
[key: string]: any;
}
const { class: className, id, ...props } = Astro.props;
const classes = cx(
'rounded-1 border-1 border-solid border-sky-500 bg-sky-500 font-mono text-[0.7em] text-white decoration-0 pli-[0.3em] pbe-0 pbs-[0.1em] print:bg-transparent print:border-gray-500',
className
);
---
<Link
class={classes}
href={`https://www.amazon.de/gp/video/detail/${id}`}
title="Prime Video"
{...props}
>
<span class="hidden" aria-hidden="true">[</span>
P
<span class="hidden" aria-hidden="true">rime Video]</span>
</Link>

View File

@@ -1,34 +0,0 @@
import cx from 'classnames';
import type { FunctionalComponent } from 'preact';
import { Link } from '.';
interface Props {
class?: string;
id: string;
}
export const PrimeVideoFlag: FunctionalComponent<Props> = ({ class: className, id, ...props }) => {
const classes = cx(
'rounded-1 border-1 border-solid border-sky-500 bg-sky-500 font-mono text-[0.7em] text-white decoration-0 pli-[0.3em] pbe-0 pbs-[0.1em] print:bg-transparent print:border-gray-500',
className
);
return (
<Link
class={classes}
href={`https://www.amazon.de/gp/video/detail/${id}`}
title="Netflix"
{...props}
>
<span class="hidden" aria-hidden="true">
[
</span>
P
<span class="hidden" aria-hidden="true">
rime Video]
</span>
</Link>
);
};

View File

@@ -0,0 +1,20 @@
---
// Cspell:words astro classnames
import cx from 'classnames';
import TextLink from './TextLink.astro';
export interface Props {
asin: string;
class?: string;
text: string;
[key: string]: any;
}
const { asin, class: className, text, ...props } = Astro.props;
const classes = cx('product', className);
---
<TextLink class={classes} href={`https://www.amazon.de/gp/product/${asin}`} {...props}>
{text}
</TextLink>

View File

@@ -1,26 +0,0 @@
import cx from 'classnames';
import type { FunctionalComponent } from 'preact';
import { TextLink } from '.';
interface Props {
asin: string;
class?: string;
text: string;
}
export const ProductLink: FunctionalComponent<Props> = ({
asin,
class: className,
text,
...props
}) => {
const classes = cx('product', className);
return (
<TextLink class={classes} href={`https://www.amazon.de/gp/product/${asin}`} {...props}>
{text}
</TextLink>
);
};

View File

@@ -1,4 +1,5 @@
---
// Cspell:words astro classnames halfgap figcaption
import cx from 'classnames';
import type { CollectionEntry } from 'astro:content';

View File

@@ -1,7 +1,10 @@
---
// Cspell:words astro classnames
import type { CollectionEntry } from 'astro:content';
import { Headline, Text, MoreLink } from '../components';
import Headline from '../components/Headline.astro';
import MoreLink from '../components/MoreLink.astro';
import Text from '../components/Text.astro';
interface Props {
project: CollectionEntry<'projects'>;

View File

@@ -1,4 +1,5 @@
---
// Cspell:words astro classnames
import type { CollectionEntry } from 'astro:content';
import Image from './Image.astro';

View File

@@ -0,0 +1,17 @@
---
// Cspell:words astro classnames
import cx from 'classnames';
export interface Props {
class?: string;
[key: string]: any;
}
const { class: className, ...props } = Astro.props;
const classes = cx('max-w-[66ch]', className);
---
<div class={classes} {...props}>
<slot />
</div>

View File

@@ -1,22 +0,0 @@
import cx from 'classnames';
import type { ComponentChild, FunctionalComponent } from 'preact';
interface Props {
class?: string;
children: ComponentChild;
}
export const ProjectIntro: FunctionalComponent<Props> = ({
class: className,
children,
...props
}) => {
const classes = cx('max-w-[66ch]', className);
return (
<div class={classes} {...props}>
{children}
</div>
);
};

View File

@@ -0,0 +1,38 @@
---
// Cspell:words astro classnames
import cx from 'classnames';
import TextLink from './TextLink.astro';
interface Props {
author?: string;
class?: string;
lang?: string;
source?: string;
sourceUrl?: string;
text: string;
}
const { author, class: className, lang = 'en', source, sourceUrl, text, ...props } = Astro.props;
const classes = cx('pullquote mbe-10 p-9 text-center', className);
---
<blockquote lang={lang} class={classes} {...props}>
<p set:html={text} />
{
(author || source) && (
<footer class="text-2 font-normal opacity-60 mbs-6">
{author && <b class="font-normal">{author}</b>}
{author && source && ', '}
{source &&
(sourceUrl ? (
<cite>
<TextLink href={sourceUrl}>{source}</TextLink>
</cite>
) : (
<cite>{source}</cite>
))}
</footer>
)
}
</blockquote>

View File

@@ -1,48 +0,0 @@
import cx from 'classnames';
import type { ComponentChild, FunctionalComponent, JSX } from 'preact';
import { TextLink } from '.';
interface Props extends JSX.HTMLAttributes<HTMLQuoteElement> {
author?: string;
children: ComponentChild;
class?: string;
lang?: string;
source?: string;
sourceUrl?: string;
text: string;
}
export const Pullquote: FunctionalComponent<Props> = ({
author,
children,
class: className,
lang = 'en',
source,
sourceUrl,
text,
...props
}) => {
const classes = cx('pullquote mbe-10 p-9 text-center', className);
return (
<blockquote lang={lang} class={classes} {...props}>
<p dangerouslySetInnerHTML={{ __html: text }} />
{(author || source) && (
<footer class="text-2 font-normal opacity-60 mbs-6">
{author && <b class="font-normal">{author}</b>}
{author && source && ', '}
{source &&
(sourceUrl ? (
<cite>
<TextLink href={sourceUrl}>{source}</TextLink>
</cite>
) : (
<cite>{source}</cite>
))}
</footer>
)}
</blockquote>
);
};

View File

@@ -1,6 +1,8 @@
---
import { Link, Subsubheadline } from '.';
// Cspell:words astro classnames subsubheadline umami shibui
import { Rss } from './icons';
import Link from './Link.astro';
import Subsubheadline from './Subsubheadline.astro';
---
<aside class="grow mbe-5 sm:mbe-0">

View File

@@ -1,4 +1,5 @@
---
// Cspell:words astro classnames stylesheet frontmatter
import { site } from '../data/site';
import { dateToISO } from '../utils';

View File

@@ -1,4 +1,5 @@
---
// Cspell:words astro classnames swup animationend keydown
import '../styles/sal.css';
---

View File

@@ -1,7 +1,7 @@
---
// Cspell:words astro umami tabindex clickarea
import { Search } from './icons';
import { Link } from '.';
import Link from './Link.astro';
---
<Link

View File

@@ -1,7 +1,8 @@
---
// Cspell:words astro pagefind swup shibui minmax
import Search from 'astro-pagefind/components/Search';
import { Link } from '.';
import Link from './Link.astro';
import { Close } from './icons';
---

View File

@@ -0,0 +1,48 @@
---
// Cspell:words astro clickarea umami stefanimhoff onclick
import { Github, Instagram, Mail, TwitterX } from './icons';
import Link from '../components/Link.astro';
---
<nav class="flex flex-1 sm:justify-center" aria-label="Social Links">
<Link
aria-label="Mail"
class="flex h-clickarea w-clickarea cursor-pointer items-center justify-center"
data-umami-event="Mail"
href="#protected-email"
title="Mail"
data-domain="stefanimhoff"
data-name="hey"
data-tld="de"
onclick="window.open(`mailto:${this.dataset.name}@${this.dataset.domain}.${this.dataset.tld}`, '_blank')"
>
<Mail class="icon h-icon-small w-icon-small" />
</Link>
<Link
aria-label="𝕏"
class="flex h-clickarea w-clickarea cursor-pointer items-center justify-center"
data-umami-event="𝕏"
href="https://x.com/kogakure"
title="𝕏"
>
<TwitterX class="icon h-icon-small w-icon-small" />
</Link>
<Link
aria-label="Instagram"
class="flex h-clickarea w-clickarea cursor-pointer items-center justify-center"
data-umami-event="Instagram"
href="https://instagram.com/kogakure"
title="Instagram"
>
<Instagram class="icon h-icon-small w-icon-small" />
</Link>
<Link
aria-label="GitHub"
class="flex h-clickarea w-clickarea cursor-pointer items-center justify-center"
data-umami-event="GitHub"
href="https://github.com/kogakure"
title="GitHub"
>
<Github class="icon h-icon-small w-icon-small" />
</Link>
</nav>

View File

@@ -1,40 +0,0 @@
import type { FunctionalComponent, JSX } from 'preact';
import type { SVGProps } from 'preact/compat';
import { Link } from '.';
import { Github, Instagram, Mail, TwitterX } from './icons';
import data from '../data/social-links.json';
type IconComponent = FunctionalComponent<SVGProps<SVGSVGElement>>;
type IconMap = { [key: string]: IconComponent };
const renderIcon = (iconName: string): JSX.Element => {
const iconMap: IconMap = {
mail: Mail,
twitter: TwitterX,
github: Github,
instagram: Instagram,
};
const IconComponent = iconMap[iconName];
return <IconComponent class="icon h-icon-small w-icon-small" />;
};
export const SocialLinks: FunctionalComponent = () => (
<nav class="flex flex-1 sm:justify-center" aria-label="Social Links">
{data.map((item, index) => (
<Link
aria-label={item.text}
class="flex h-clickarea w-clickarea cursor-pointer items-center justify-center"
data-umami-event={item.text}
href={item.url}
key={index}
title={item.text}
{...(item.props ? item.props : {})}
>
{renderIcon(item.icon)}
</Link>
))}
</nav>
);

View File

@@ -0,0 +1,23 @@
---
// Cspell:words astro classnames autoplay fullscreen
import cx from 'classnames';
interface Props {
class?: string;
id: string;
}
const { class: className, id, ...props } = Astro.props;
const classes = cx('mbe-10 w-full', className);
---
<iframe
allow="accelerometer; autoplay; encrypted-media; fullscreeen; picture-in-picture"
class={classes}
frameborder="0"
height="352"
loading="lazy"
src={`https://open.spotify.com/embed/show/${id}?utm_source=generator&theme=0`}
width="100%"
{...props}></iframe>

View File

@@ -1,25 +0,0 @@
import cx from 'classnames';
import type { FunctionalComponent } from 'preact';
interface Props {
class?: string;
id: string;
}
export const Spotify: FunctionalComponent<Props> = ({ class: className, id, ...props }) => {
const classes = cx('mbe-10 w-full', className);
return (
<iframe
allow="accelerometer; autoplay; encrypted-media; fullscreeen; picture-in-picture"
class={classes}
frameBorder="0"
height="352"
loading="lazy"
src={`https://open.spotify.com/embed/show/${id}?utm_source=generator&theme=0`}
width="100%"
{...props}
></iframe>
);
};

View File

@@ -0,0 +1,21 @@
---
// Cspell:words astro classnames keyof
import cx from 'classnames';
export interface Props {
as?: keyof astroHTML.JSX.IntrinsicElements;
class?: string;
[key: string]: any;
}
const { as: Tag = 'h3', class: className, ...props } = Astro.props;
const classes = cx(
'text-4 font-black tracking-tight mbe-8 dark:font-extrabold [text-wrap:balance]',
className
);
---
<Tag class={classes} {...props}>
<slot />
</Tag>

View File

@@ -1,28 +0,0 @@
import cx from 'classnames';
import type { ComponentChild, FunctionalComponent } from 'preact';
interface Props {
as?: any;
class?: string;
children: ComponentChild;
}
export const Subheadline: FunctionalComponent<Props> = ({
as = 'h3',
class: className,
children,
...props
}) => {
const Tag = as;
const classes = cx(
'text-4 font-black tracking-tight mbe-8 dark:font-extrabold [text-wrap:balance]',
className
);
return (
<Tag class={classes} {...props}>
{children}
</Tag>
);
};

View File

@@ -1,7 +1,8 @@
---
// Cspell:words astro subnavigation shibui
import data from '../data/subnavigation.json';
import { Link } from '.';
import Link from './Link.astro';
---
<nav class="navigation glow flex gap-12" aria-label="Subnavigation" role="navigation">

View File

@@ -0,0 +1,21 @@
---
// Cspell:words astro classnames keyof
import cx from 'classnames';
export interface Props {
as?: keyof astroHTML.JSX.IntrinsicElements;
class?: string;
[key: string]: any;
}
const { as: Tag = 'h4', class: className, ...props } = Astro.props;
const classes = cx(
'text-3 font-black tracking-tight mbe-5 dark:font-extrabold [text-wrap:balance]',
className
);
---
<Tag class={classes} {...props}>
<slot />
</Tag>

View File

@@ -1,28 +0,0 @@
import cx from 'classnames';
import type { ComponentChild, FunctionalComponent } from 'preact';
interface Props {
as?: any;
class?: string;
children: ComponentChild;
}
export const Subsubheadline: FunctionalComponent<Props> = ({
as = 'h4',
class: className,
children,
...props
}) => {
const Tag = as;
const classes = cx(
'text-3 font-black tracking-tight mbe-5 dark:font-extrabold [text-wrap:balance]',
className
);
return (
<Tag class={classes} {...props}>
{children}
</Tag>
);
};

36
src/components/Tag.astro Normal file
View File

@@ -0,0 +1,36 @@
---
// Cspell:words astro classnames shibui
import cx from 'classnames';
import Link from './Link.astro';
interface Props {
active?: boolean;
class?: string;
href?: string;
[key: string]: any;
}
const { active, class: className, href, ...props } = Astro.props;
const classes = cx(
'capitalize border-3 relative inline-flex items-center justify-center rounded-25 border-2 border-shibui-350 bg-shibui-200 px-3 py-1 text-1 font-normal leading-none text-black mie-1 pbs-3 dark:border-shibui-750 dark:bg-shibui-950 dark:text-white min-w-[50px] print:hidden',
{
'!bg-shibui-550 !text-white hover:!bg-shibui-650 focus:!bg-shibui-650 dark:!border-shibui-500 dark:!bg-shibui-700 dark:hover:!bg-shibui-600 dark:focus:!bg-shibui-600':
href,
'active [&.active]:!border-black/25 !text-white [&.active]:!bg-accent': active,
},
className
);
---
{
href ? (
<Link href={href} class={classes} {...props}>
<slot />
</Link>
) : (
<span class={classes} {...props}>
<slot />
</span>
)
}

View File

@@ -1,44 +0,0 @@
import cx from 'classnames';
import type { ComponentChild, FunctionalComponent } from 'preact';
import { Link } from '.';
interface Props {
active?: boolean;
children: ComponentChild;
class?: string;
href?: string;
}
export const Tag: FunctionalComponent<Props> = ({
active,
class: className,
children,
href,
...props
}) => {
const classes = cx(
'capitalize border-3 relative inline-flex items-center justify-center rounded-25 border-2 border-shibui-350 bg-shibui-200 px-3 py-1 text-1 font-normal leading-none text-black mie-1 pbs-3 dark:border-shibui-750 dark:bg-shibui-950 dark:text-white min-w-[50px] print:hidden',
{
'!bg-shibui-550 !text-white hover:!bg-shibui-650 focus:!bg-shibui-650 dark:!border-shibui-500 dark:!bg-shibui-700 dark:hover:!bg-shibui-600 dark:focus:!bg-shibui-600':
href,
'active [&.active]:!border-black/25 !text-white [&.active]:!bg-accent': active,
},
className
);
return (
<>
{href ? (
<Link href={href} class={classes} {...props}>
{children}
</Link>
) : (
<span class={classes} {...props}>
{children}
</span>
)}
</>
);
};

18
src/components/Text.astro Normal file
View File

@@ -0,0 +1,18 @@
---
// Cspell:words astro classnames keyof
import cx from 'classnames';
interface Props {
as?: keyof astroHTML.JSX.IntrinsicElements;
class?: string;
[key: string]: any;
}
const { as: Tag = 'p', class: className, ...props } = Astro.props;
const classes = cx('text-3 font-normal tracking-normal mbe-10 mbs-0 dark:font-light', className);
---
<Tag class={classes} {...props}>
<slot />
</Tag>

View File

@@ -1,28 +0,0 @@
import cx from 'classnames';
import type { ComponentChild, FunctionalComponent } from 'preact';
interface Props {
as?: any;
class?: string;
children: ComponentChild;
}
export const Text: FunctionalComponent<Props> = ({
as = 'p',
class: className,
children,
...props
}) => {
const Tag = as;
const classes = cx(
'text-3 font-normal tracking-normal mbe-10 mbs-0 dark:font-light',
className
);
return (
<Tag class={classes} {...props}>
{children}
</Tag>
);
};

View File

@@ -0,0 +1,21 @@
---
// Cspell:words astro classnames shibui
import cx from 'classnames';
import Link from './Link.astro';
interface Props {
class?: string;
[key: string]: any;
}
const { class: className, ...props } = Astro.props;
const classes = cx(
'inline font-semibold text-shibui-950 underline decoration-shibui-900/20 decoration-4 underline-offset-auto no-common-ligatures hover:!decoration-accent focus:!decoration-accent dark:text-shibui-200/[0.87] dark:decoration-shibui-100/20',
className
);
---
<Link class={classes} {...props}>
<slot />
</Link>

View File

@@ -1,24 +0,0 @@
import cx from 'classnames';
import type { FunctionalComponent, JSX } from 'preact';
import { Link } from '.';
interface Props extends JSX.HTMLAttributes<HTMLAnchorElement> {}
export const TextLink: FunctionalComponent<Props> = ({
class: className,
children,
...props
}: Props) => {
const classes = cx(
'inline font-semibold text-shibui-950 underline decoration-shibui-900/20 decoration-4 underline-offset-auto no-common-ligatures hover:!decoration-accent focus:!decoration-accent dark:text-shibui-200/[0.87] dark:decoration-shibui-100/20',
className as string
);
return (
<Link class={classes} {...props}>
{children}
</Link>
);
};

View File

@@ -1,4 +1,5 @@
---
// Cspell:words clickarea umami
import { Circle } from './icons';
---

View File

@@ -0,0 +1,21 @@
---
// Cspell:words astro classnames keyof
import cx from 'classnames';
export interface Props {
as?: keyof astroHTML.JSX.IntrinsicElements;
class?: string;
[key: string]: any;
}
const { as: Tag = 'h1', class: className, ...props } = Astro.props;
const classes = cx(
'text-7 font-black tracking-tight mbe-13 dark:font-extrabold [text-wrap:balance]',
className
);
---
<Tag class={classes} {...props}>
<slot />
</Tag>

View File

@@ -1,28 +0,0 @@
import cx from 'classnames';
import type { ComponentChild, FunctionalComponent } from 'preact';
interface Props {
as?: any;
class?: string;
children: ComponentChild;
}
export const Title: FunctionalComponent<Props> = ({
as = 'h1',
class: className,
children,
...props
}) => {
const Tag = as;
const classes = cx(
'text-7 font-black tracking-tight mbe-13 dark:font-extrabold [text-wrap:balance]',
className
);
return (
<Tag class={classes} {...props}>
{children}
</Tag>
);
};

View File

@@ -1,6 +1,7 @@
---
// Cspell:words astro minmax
import { Image } from 'astro:assets';
import { Link } from '.';
import Link from './Link.astro';
import type { ToolsProps } from '../data/tools';

View File

@@ -0,0 +1,20 @@
---
// Cspell:words astro classnames
import cx from 'classnames';
export interface Props {
class?: string;
[key: string]: any;
}
const { class: className, ...props } = Astro.props;
const classes = cx(
'list-square text-3 mbe-12 pis-[1.5rem] md:pis-0 [li>&]:mbe-0 [li>&]:pis-[1.5rem]',
className
);
---
<ul class={classes} {...props}>
<slot />
</ul>

View File

@@ -1,25 +0,0 @@
import cx from 'classnames';
import type { ComponentChild, FunctionalComponent } from 'preact';
interface Props {
class?: string;
children: ComponentChild;
}
export const UnorderedList: FunctionalComponent<Props> = ({
class: className,
children,
...props
}) => {
const classes = cx(
'list-square text-3 mbe-12 pis-[1.5rem] md:pis-0 [li>&]:mbe-0 [li>&]:pis-[1.5rem]',
className
);
return (
<ul class={classes} {...props}>
{children}
</ul>
);
};

View File

@@ -0,0 +1,25 @@
---
// Cspell:words astro umami clickarea
import { ArrowUp } from './icons';
import Link from './Link.astro';
export interface Props {
[key: string]: any;
}
const { ...props } = Astro.props;
---
<div class="flex flex-1 justify-end" {...props}>
<Link
aria-label="Back to top"
class="transition-transform duration-500 ease-in-out hover:-translate-y-1 focus:-translate-y-1"
data-umami-event="Back to top"
href="#top"
id="up-link"
>
<button class="flex h-clickarea w-clickarea cursor-pointer items-center justify-center">
<ArrowUp class="icon h-icon w-icon" />
</button>
</Link>
</div>

View File

@@ -1,20 +0,0 @@
import type { FunctionalComponent } from 'preact';
import { Link } from '.';
import { ArrowUp } from './icons';
export const UpLink: FunctionalComponent = ({ ...props }) => (
<div class="flex flex-1 justify-end" {...props}>
<Link
aria-label="Back to top"
class="transition-transform duration-500 ease-in-out hover:-translate-y-1 focus:-translate-y-1"
data-umami-event="Back to top"
href="#top"
id="up-link"
>
<button class="flex h-clickarea w-clickarea cursor-pointer items-center justify-center">
<ArrowUp class="icon h-icon w-icon" />
</button>
</Link>
</div>
);

View File

@@ -0,0 +1,28 @@
---
// Cspell:words astro classnames
import cx from 'classnames';
export interface Props {
class?: string;
variant?: 'center' | 'left';
}
const { class: className, variant = 'center', ...props } = Astro.props;
const classes = cx(
'flex italic [&_p]:mbe-0',
{
'm-10': variant === 'center',
'mbs-10 mbe-10 mis-0 mie-0': variant === 'left',
},
className
);
const preClasses = cx('font-sans mis-0 mie-0 whitespace-pre', {
'mbs-auto mbe-auto': variant === 'center',
});
---
<blockquote class={classes} {...props}>
<pre class={preClasses}><slot /></pre>
</blockquote>

View File

@@ -1,34 +0,0 @@
import cx from 'classnames';
import type { ComponentChild, FunctionalComponent } from 'preact';
interface Props {
class?: string;
children: ComponentChild;
variant?: 'center' | 'left';
}
export const Verse: FunctionalComponent<Props> = ({
class: className,
children,
variant = 'center',
...props
}) => {
const classes = cx(
'flex italic [&_p]:mbe-0',
{
'm-10': variant === 'center',
'mbs-10 mbe-10 mis-0 mie-0': variant === 'left',
},
className
);
const preClasses = cx('font-sans mis-0 mie-0 whitespace-pre', {
'mbs-auto mbe-auto': variant === 'center',
});
return (
<blockquote class={classes} {...props}>
<pre class={preClasses}>{children}</pre>
</blockquote>
);
};

View File

@@ -1,4 +1,5 @@
---
// Cspell:words astro
import path from 'node:path';
import fs from 'node:fs/promises';

View File

@@ -1,38 +0,0 @@
export * from './AmazonBook';
export * from './Backlink';
export * from './Banner';
export * from './Blockquote';
export * from './Book';
export * from './Bookshelf';
export * from './ColorStack';
export * from './ColorSwatch';
export * from './DisplayBox';
export * from './Divider';
export * from './DoughnutChart';
export * from './DownloadLink';
export * from './EmailLink';
export * from './Figure';
export * from './Flag';
export * from './Headline';
export * from './LegalDate';
export * from './Link';
export * from './ListItem';
export * from './MoreLink';
export * from './NetflixFlag';
export * from './OdyseeVideo';
export * from './OrderedList';
export * from './PrimeVideoFlag';
export * from './ProductLink';
export * from './ProjectIntro';
export * from './Pullquote';
export * from './SocialLinks';
export * from './Spotify';
export * from './Subheadline';
export * from './Subsubheadline';
export * from './Tag';
export * from './Text';
export * from './TextLink';
export * from './Title';
export * from './UnorderedList';
export * from './UpLink';
export * from './Verse';

View File

@@ -10,7 +10,7 @@ cover: /assets/images/cover/i-counted-everything-i-own.webp
tags: ["self-improvement"]
---
import { DoughnutChart } from "../../../components";
import { DoughnutChart } from "../../../components/DoughnutChart.tsx";
import {
things,
rooms,

View File

@@ -1,28 +0,0 @@
[
{
"text": "Mail",
"url": "#protected-email",
"icon": "mail",
"props": {
"data-domain": "stefanimhoff",
"data-name": "hey",
"data-tld": "de",
"onclick": "window.open(`mailto:${this.dataset.name}@${this.dataset.domain}.${this.dataset.tld}`, '_blank')"
}
},
{
"text": "𝕏",
"url": "https://x.com/kogakure",
"icon": "twitter"
},
{
"text": "Instagram",
"url": "https://instagram.com/kogakure",
"icon": "instagram"
},
{
"text": "GitHub",
"url": "https://github.com/kogakure",
"icon": "github"
}
]

Some files were not shown because too many files have changed in this diff Show More