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 deepmerge from 'deepmerge';
|
||||||
|
|
||||||
import { ArcElement, Chart as ChartJS, Legend, Tooltip } from 'chart.js';
|
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 cx from 'classnames';
|
||||||
|
|
||||||
import { animation, animationDelay } from '../data/site';
|
import { animation, animationDelay } from '../data/site';
|
||||||
|
|
||||||
import Image from './Image.astro';
|
import Image from './Image.astro';
|
||||||
import { Headline } from '.';
|
import Headline from './Headline.astro';
|
||||||
|
|
||||||
const { entry } = Astro.props;
|
const { entry } = Astro.props;
|
||||||
|
|
||||||
@@ -41,7 +42,7 @@ const imageLength = entry.data.images.length;
|
|||||||
{
|
{
|
||||||
entry.data.images.map(({ src }: { src: string }, index: number) => (
|
entry.data.images.map(({ src }: { src: string }, index: number) => (
|
||||||
<div
|
<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':
|
'justify-self-start lg:col-start-1':
|
||||||
randomPosition() === 'start' && imageLength > 1 && index > 0,
|
randomPosition() === 'start' && imageLength > 1 && index > 0,
|
||||||
'justify-self-center lg:col-start-2':
|
'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';
|
import cx from 'classnames';
|
||||||
|
|
||||||
const { class: className, noMargin, src, ...props } = Astro.props;
|
const { class: className, noMargin, src, ...props } = Astro.props;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
// Cspell:words astro subsubheadline minmax webp
|
||||||
import { Picture } from 'astro:assets';
|
import { Picture } from 'astro:assets';
|
||||||
|
|
||||||
import type { CollectionEntry } from 'astro:content';
|
import type { CollectionEntry } from 'astro:content';
|
||||||
@@ -9,7 +10,8 @@ interface Props {
|
|||||||
import { animation } from '../data/site';
|
import { animation } from '../data/site';
|
||||||
|
|
||||||
import { pickTwoRandomColors } from '../utils';
|
import { pickTwoRandomColors } from '../utils';
|
||||||
import { Link, Subsubheadline } from '../components';
|
import Link from '../components/Link.astro';
|
||||||
|
import Subsubheadline from './Subsubheadline.astro';
|
||||||
|
|
||||||
const { entries } = Astro.props;
|
const { entries } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
import { LegalDate } from '.';
|
// Cspell:words astro
|
||||||
|
import LegalDate from './LegalDate.astro';
|
||||||
|
|
||||||
import { site } from '../data/site';
|
import { site } from '../data/site';
|
||||||
---
|
---
|
||||||
@@ -7,7 +8,7 @@ import { site } from '../data/site';
|
|||||||
<div class="flex flex-1 shrink-0 items-center">
|
<div class="flex flex-1 shrink-0 items-center">
|
||||||
<span class="relative text-[14px] mie-[0.25em] block-start-[1px]">©</span>
|
<span class="relative text-[14px] mie-[0.25em] block-start-[1px]">©</span>
|
||||||
<span class="shrink-0">
|
<span class="shrink-0">
|
||||||
<LegalDate client:idle />
|
<LegalDate />
|
||||||
</span>
|
</span>
|
||||||
<span class="mli-[0.25em]">•</span>
|
<span class="mli-[0.25em]">•</span>
|
||||||
<strong class="shrink-0 uppercase tracking-wider">{site.author}</strong>
|
<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 navigation from '../data/navigation.json';
|
||||||
import SearchLink from './SearchLink.astro';
|
import SearchLink from './SearchLink.astro';
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
// Cspell:words astro classnames
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
|
|
||||||
interface Props {
|
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 Legal from './Legal.astro';
|
||||||
import RSSLink from './RSSLink.astro';
|
import RSSLink from './RSSLink.astro';
|
||||||
|
import SocialLinks from './SocialLinks.astro';
|
||||||
import Subnavigation from './Subnavigation.astro';
|
import Subnavigation from './Subnavigation.astro';
|
||||||
import { UpLink, SocialLinks } from '.';
|
import UpLink from './UpLink.astro';
|
||||||
---
|
---
|
||||||
|
|
||||||
<footer
|
<footer
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
---
|
---
|
||||||
|
// Cspell:words astro backlink classnames shibui
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
|
|
||||||
import { Backlink } from '../components';
|
import Backlink from '../components/Backlink.astro';
|
||||||
import MainNavigation from '../components/MainNavigation.astro';
|
import MainNavigation from '../components/MainNavigation.astro';
|
||||||
import ThemeToggle from '../components/ThemeToggle.astro';
|
import ThemeToggle from '../components/ThemeToggle.astro';
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
---
|
---
|
||||||
|
// Cspell:words astro classnames
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
|
|
||||||
import { Title } from './Title';
|
import Title from './Title.astro';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
class?: string;
|
class?: string;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
// Cspell:words astro umami
|
||||||
import { ArrowLeftS, ArrowRightS } from './icons';
|
import { ArrowLeftS, ArrowRightS } from './icons';
|
||||||
|
|
||||||
export interface Props {
|
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 cx from 'classnames';
|
||||||
|
|
||||||
import type { CollectionEntry } from 'astro:content';
|
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"
|
class="col-start-1 col-end-19 row-start-1 grid grid-cols-18 gap-x-gap gap-y-halfgap"
|
||||||
{...animation}
|
{...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} />
|
<ProjectImage project={project} />
|
||||||
</div>
|
</div>
|
||||||
<figcaption class="col-start-2 col-end-18 xl:col-start-13">
|
<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"
|
class="col-start-1 col-end-19 row-start-1 grid grid-cols-18 gap-x-gap gap-y-halfgap"
|
||||||
{...animation}
|
{...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} />
|
<ProjectImage project={project} />
|
||||||
</div>
|
</div>
|
||||||
<figcaption class="col-start-2 col-end-18 xl:col-start-2 xl:col-end-7">
|
<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"
|
class="col-start-1 col-end-19 row-start-1 grid grid-cols-18 gap-x-gap gap-y-halfgap"
|
||||||
{...animation}
|
{...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} />
|
<ProjectImage project={project} />
|
||||||
</div>
|
</div>
|
||||||
<figcaption class="col-start-2 col-end-18 md:col-end-13 xl:col-end-10">
|
<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"
|
class="col-start-1 col-end-19 row-start-1 grid grid-cols-18 gap-x-gap gap-y-halfgap"
|
||||||
{...animation}
|
{...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} />
|
<ProjectImage project={project} />
|
||||||
</div>
|
</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">
|
<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 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 {
|
interface Props {
|
||||||
project: CollectionEntry<'projects'>;
|
project: CollectionEntry<'projects'>;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
// Cspell:words astro classnames
|
||||||
import type { CollectionEntry } from 'astro:content';
|
import type { CollectionEntry } from 'astro:content';
|
||||||
import Image from './Image.astro';
|
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 { Rss } from './icons';
|
||||||
|
import Link from './Link.astro';
|
||||||
|
import Subsubheadline from './Subsubheadline.astro';
|
||||||
---
|
---
|
||||||
|
|
||||||
<aside class="grow mbe-5 sm:mbe-0">
|
<aside class="grow mbe-5 sm:mbe-0">
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
// Cspell:words astro classnames stylesheet frontmatter
|
||||||
import { site } from '../data/site';
|
import { site } from '../data/site';
|
||||||
import { dateToISO } from '../utils';
|
import { dateToISO } from '../utils';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
// Cspell:words astro classnames swup animationend keydown
|
||||||
import '../styles/sal.css';
|
import '../styles/sal.css';
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
|
// Cspell:words astro umami tabindex clickarea
|
||||||
import { Search } from './icons';
|
import { Search } from './icons';
|
||||||
|
import Link from './Link.astro';
|
||||||
import { Link } from '.';
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
---
|
---
|
||||||
|
// Cspell:words astro pagefind swup shibui minmax
|
||||||
import Search from 'astro-pagefind/components/Search';
|
import Search from 'astro-pagefind/components/Search';
|
||||||
|
|
||||||
import { Link } from '.';
|
import Link from './Link.astro';
|
||||||
import { Close } from './icons';
|
import { Close } from './icons';
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -28,7 +29,7 @@ import { Close } from './icons';
|
|||||||
|
|
||||||
<style is:global>
|
<style is:global>
|
||||||
dialog::backdrop {
|
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;
|
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 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">
|
<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';
|
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 { Image } from 'astro:assets';
|
||||||
import { Link } from '.';
|
import Link from './Link.astro';
|
||||||
|
|
||||||
import type { ToolsProps } from '../data/tools';
|
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 path from 'node:path';
|
||||||
import fs from 'node:fs/promises';
|
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"]
|
tags: ["self-improvement"]
|
||||||
---
|
---
|
||||||
|
|
||||||
import { DoughnutChart } from "../../../components";
|
import { DoughnutChart } from "../../../components/DoughnutChart.tsx";
|
||||||
import {
|
import {
|
||||||
things,
|
things,
|
||||||
rooms,
|
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