mirror of
https://github.com/kogakure/website-astro-stefanimhoff.de.git
synced 2026-02-03 20:15:27 +00:00
refactor: migrate Preact components to Astro
This commit is contained in:
24
src/components/AmazonBook.astro
Normal file
24
src/components/AmazonBook.astro
Normal 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>
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
39
src/components/Backlink.astro
Normal file
39
src/components/Backlink.astro
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
32
src/components/Banner.astro
Normal file
32
src/components/Banner.astro
Normal 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>
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
41
src/components/Blockquote.astro
Normal file
41
src/components/Blockquote.astro
Normal 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>
|
||||
@@ -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
21
src/components/Book.astro
Normal 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>
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
15
src/components/Bookshelf.astro
Normal file
15
src/components/Bookshelf.astro
Normal 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>
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
15
src/components/ColorStack.astro
Normal file
15
src/components/ColorStack.astro
Normal 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>
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
80
src/components/ColorSwatch.astro
Normal file
80
src/components/ColorSwatch.astro
Normal 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>
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
17
src/components/DisplayBox.astro
Normal file
17
src/components/DisplayBox.astro
Normal 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>
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
17
src/components/Divider.astro
Normal file
17
src/components/Divider.astro
Normal 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} />
|
||||
@@ -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} />;
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
// Cspell:words deepmerge autocolors chartjs
|
||||
import deepmerge from 'deepmerge';
|
||||
|
||||
import { ArcElement, Chart as ChartJS, Legend, Tooltip } from 'chart.js';
|
||||
|
||||
22
src/components/DownloadLink.astro
Normal file
22
src/components/DownloadLink.astro
Normal 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>
|
||||
@@ -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>
|
||||
);
|
||||
26
src/components/EmailLink.astro
Normal file
26
src/components/EmailLink.astro
Normal 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>
|
||||
@@ -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>
|
||||
);
|
||||
29
src/components/Figure.astro
Normal file
29
src/components/Figure.astro
Normal 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>
|
||||
@@ -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
43
src/components/Flag.astro
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -41,7 +42,7 @@ const imageLength = entry.data.images.length;
|
||||
{
|
||||
entry.data.images.map(({ src }: { src: string }, index: number) => (
|
||||
<div
|
||||
class={cx('col-span-full w-full !mbe-0 lg:col-span-3 lg:max-w-full', {
|
||||
class={cx('col-span-full w-full !mbe-0 lg:col-span-3 lg:max-w-full', {
|
||||
'justify-self-start lg:col-start-1':
|
||||
randomPosition() === 'start' && imageLength > 1 && index > 0,
|
||||
'justify-self-center lg:col-start-2':
|
||||
|
||||
21
src/components/Headline.astro
Normal file
21
src/components/Headline.astro
Normal 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>
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
// Cspell:words astro classnames
|
||||
import cx from 'classnames';
|
||||
|
||||
const { class: className, noMargin, src, ...props } = Astro.props;
|
||||
|
||||
@@ -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;
|
||||
---
|
||||
|
||||
@@ -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>
|
||||
|
||||
19
src/components/LegalDate.astro
Normal file
19
src/components/LegalDate.astro
Normal 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>
|
||||
@@ -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
25
src/components/Link.astro
Normal 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>
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
17
src/components/ListItem.astro
Normal file
17
src/components/ListItem.astro
Normal 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>
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
// Cspell:words astro classnames
|
||||
import cx from 'classnames';
|
||||
|
||||
interface Props {
|
||||
|
||||
22
src/components/MoreLink.astro
Normal file
22
src/components/MoreLink.astro
Normal 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>
|
||||
@@ -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>
|
||||
);
|
||||
24
src/components/NetflixFlag.astro
Normal file
24
src/components/NetflixFlag.astro
Normal 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>
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
23
src/components/OdyseeVideo.astro
Normal file
23
src/components/OdyseeVideo.astro
Normal 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>
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
20
src/components/OrderedList.astro
Normal file
20
src/components/OrderedList.astro
Normal 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>
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
// Cspell:words astro umami
|
||||
import { ArrowLeftS, ArrowRightS } from './icons';
|
||||
|
||||
export interface Props {
|
||||
|
||||
29
src/components/PrimeVideoFlag.astro
Normal file
29
src/components/PrimeVideoFlag.astro
Normal 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>
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
20
src/components/ProductLink.astro
Normal file
20
src/components/ProductLink.astro
Normal 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>
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
// Cspell:words astro classnames halfgap figcaption
|
||||
import cx from 'classnames';
|
||||
|
||||
import type { CollectionEntry } from 'astro:content';
|
||||
@@ -56,7 +57,7 @@ const classes = cx('col-start-1 col-end-19 grid grid-cols-18', className);
|
||||
class="col-start-1 col-end-19 row-start-1 grid grid-cols-18 gap-x-gap gap-y-halfgap"
|
||||
{...animation}
|
||||
>
|
||||
<div class=" col-start-1 col-end-19 grid self-start xl:col-end-13">
|
||||
<div class="col-start-1 col-end-19 grid self-start xl:col-end-13">
|
||||
<ProjectImage project={project} />
|
||||
</div>
|
||||
<figcaption class="col-start-2 col-end-18 xl:col-start-13">
|
||||
@@ -71,7 +72,7 @@ const classes = cx('col-start-1 col-end-19 grid grid-cols-18', className);
|
||||
class="col-start-1 col-end-19 row-start-1 grid grid-cols-18 gap-x-gap gap-y-halfgap"
|
||||
{...animation}
|
||||
>
|
||||
<div class=" col-start-1 col-end-19 row-start-1 grid self-start xl:col-start-7 xl:col-end-19">
|
||||
<div class="col-start-1 col-end-19 row-start-1 grid self-start xl:col-start-7 xl:col-end-19">
|
||||
<ProjectImage project={project} />
|
||||
</div>
|
||||
<figcaption class="col-start-2 col-end-18 xl:col-start-2 xl:col-end-7">
|
||||
@@ -86,7 +87,7 @@ const classes = cx('col-start-1 col-end-19 grid grid-cols-18', className);
|
||||
class="col-start-1 col-end-19 row-start-1 grid grid-cols-18 gap-x-gap gap-y-halfgap"
|
||||
{...animation}
|
||||
>
|
||||
<div class=" col-start-1 col-end-19 row-start-1">
|
||||
<div class="col-start-1 col-end-19 row-start-1">
|
||||
<ProjectImage project={project} />
|
||||
</div>
|
||||
<figcaption class="col-start-2 col-end-18 md:col-end-13 xl:col-end-10">
|
||||
@@ -101,7 +102,7 @@ const classes = cx('col-start-1 col-end-19 grid grid-cols-18', className);
|
||||
class="col-start-1 col-end-19 row-start-1 grid grid-cols-18 gap-x-gap gap-y-halfgap"
|
||||
{...animation}
|
||||
>
|
||||
<div class=" col-start-1 col-end-19 row-start-1">
|
||||
<div class="col-start-1 col-end-19 row-start-1">
|
||||
<ProjectImage project={project} />
|
||||
</div>
|
||||
<figcaption class="col-start-2 col-end-18 md:col-start-8 md:col-end-18 xl:col-start-10 xl:col-end-18">
|
||||
|
||||
@@ -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'>;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
// Cspell:words astro classnames
|
||||
import type { CollectionEntry } from 'astro:content';
|
||||
import Image from './Image.astro';
|
||||
|
||||
|
||||
17
src/components/ProjectIntro.astro
Normal file
17
src/components/ProjectIntro.astro
Normal 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>
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
38
src/components/Pullquote.astro
Normal file
38
src/components/Pullquote.astro
Normal 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>
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
// Cspell:words astro classnames stylesheet frontmatter
|
||||
import { site } from '../data/site';
|
||||
import { dateToISO } from '../utils';
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
// Cspell:words astro classnames swup animationend keydown
|
||||
import '../styles/sal.css';
|
||||
---
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
// Cspell:words astro umami tabindex clickarea
|
||||
import { Search } from './icons';
|
||||
|
||||
import { Link } from '.';
|
||||
import Link from './Link.astro';
|
||||
---
|
||||
|
||||
<Link
|
||||
|
||||
@@ -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';
|
||||
---
|
||||
|
||||
@@ -28,7 +29,7 @@ import { Close } from './icons';
|
||||
|
||||
<style is:global>
|
||||
dialog::backdrop {
|
||||
@apply bg-shibui-100 dark:bg-shibui-900;
|
||||
@apply bg-shibui-100 dark:bg-shibui-900;
|
||||
animation: show-dimmer 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
|
||||
48
src/components/SocialLinks.astro
Normal file
48
src/components/SocialLinks.astro
Normal 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>
|
||||
@@ -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>
|
||||
);
|
||||
23
src/components/Spotify.astro
Normal file
23
src/components/Spotify.astro
Normal 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>
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
21
src/components/Subheadline.astro
Normal file
21
src/components/Subheadline.astro
Normal 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>
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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">
|
||||
|
||||
21
src/components/Subsubheadline.astro
Normal file
21
src/components/Subsubheadline.astro
Normal 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>
|
||||
@@ -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
36
src/components/Tag.astro
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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
18
src/components/Text.astro
Normal 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>
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
21
src/components/TextLink.astro
Normal file
21
src/components/TextLink.astro
Normal 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>
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
// Cspell:words clickarea umami
|
||||
import { Circle } from './icons';
|
||||
---
|
||||
|
||||
|
||||
21
src/components/Title.astro
Normal file
21
src/components/Title.astro
Normal 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>
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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';
|
||||
|
||||
|
||||
20
src/components/UnorderedList.astro
Normal file
20
src/components/UnorderedList.astro
Normal 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>
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
25
src/components/UpLink.astro
Normal file
25
src/components/UpLink.astro
Normal 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>
|
||||
@@ -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>
|
||||
);
|
||||
28
src/components/Verse.astro
Normal file
28
src/components/Verse.astro
Normal 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>
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
// Cspell:words astro
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs/promises';
|
||||
|
||||
|
||||
@@ -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';
|
||||
@@ -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,
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user