feat: some shit
This commit is contained in:
@@ -18,6 +18,7 @@
|
|||||||
"astro-i18n-aut": "^0.7.0",
|
"astro-i18n-aut": "^0.7.0",
|
||||||
"astro-imagetools": "^0.9.0",
|
"astro-imagetools": "^0.9.0",
|
||||||
"svelte": "^4.2.12",
|
"svelte": "^4.2.12",
|
||||||
|
"svelte-gestures": "^4.0.0",
|
||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.4.1",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
@@ -25,6 +26,7 @@
|
|||||||
"@afordin/unocss-preset-token": "0.1.0-beta.0",
|
"@afordin/unocss-preset-token": "0.1.0-beta.0",
|
||||||
"@astrojs/sitemap": "^3.1.1",
|
"@astrojs/sitemap": "^3.1.1",
|
||||||
"@iconify-json/tabler": "^1.1.109",
|
"@iconify-json/tabler": "^1.1.109",
|
||||||
|
"@igor.dvlpr/astro-post-excerpt": "^2.1.0",
|
||||||
"@types/markdown-it": "^13.0.7",
|
"@types/markdown-it": "^13.0.7",
|
||||||
"@unocss/preset-icons": "^0.58.8",
|
"@unocss/preset-icons": "^0.58.8",
|
||||||
"@unocss/reset": "^0.58.8",
|
"@unocss/reset": "^0.58.8",
|
||||||
|
|||||||
335
pnpm-lock.yaml
generated
335
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -3,27 +3,40 @@ p {
|
|||||||
}
|
}
|
||||||
|
|
||||||
h1,
|
h1,
|
||||||
h2 {
|
h2,
|
||||||
|
h3 {
|
||||||
|
color: var(--text-light);
|
||||||
font-family: 'Roboto', sans-serif;
|
font-family: 'Roboto', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
article h2 {
|
||||||
|
font-size: 24px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
article h3 {
|
||||||
|
font-size: 20px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--neutral-1000: #000000;
|
--neutral-1000: #000000;
|
||||||
--neutral-800: #16161E;
|
--neutral-800: #16161E;
|
||||||
--neutral-500: #252530;
|
--neutral-500: #252530;
|
||||||
--neutral-400: #2C2C3A;
|
--neutral-400: #2C2C3A;
|
||||||
--neutral-300: #414152;
|
--neutral-300: #414152;
|
||||||
--neutral-100: #AAAABB;
|
--neutral-100: #D5D5D7;
|
||||||
--neutral-000: #F1F1F4;
|
--neutral-000: #F1F1F4;
|
||||||
|
--fill: #cb5a5a;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
--background-dark: var(--neutral800);
|
--background-dark: var(--neutral-800);
|
||||||
--background: var(--neutral000);
|
--background: var(--neutral-000);
|
||||||
--background-light: var(--neutral400);
|
--background-light: var(--neutral-400);
|
||||||
--outline: var(--neutral300);
|
--outline: var(--neutral-300);
|
||||||
--text: var(--neutral1000);
|
--text: var(--neutral-800);
|
||||||
--layer100: var(--neutral000);
|
--text-light: black;
|
||||||
|
|
||||||
--border-radius-md: 20px;
|
--border-radius-md: 20px;
|
||||||
|
|
||||||
@@ -34,9 +47,26 @@ body {
|
|||||||
transition: background-color 0.1s;
|
transition: background-color 0.1s;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
font-family: 'Nunito Sans', sans-serif;
|
font-family: 'Nunito Sans', sans-serif;
|
||||||
|
background-color: var(--neutral-000);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark body {
|
||||||
|
--background-dark: var(--neutral-800);
|
||||||
|
--background: var(--neutral-500);
|
||||||
|
--background-light: var(--neutral-400);
|
||||||
|
--background-gradient: linear-gradient(-30deg, var(--neutral-500) 0%, var(--neutral-400) 100%);
|
||||||
|
--outline: var(--neutral-300);
|
||||||
|
--text: var(--neutral-100);
|
||||||
|
--text-light: white;
|
||||||
|
background-color: var(--neutral-1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .noise::before {
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.noise::before {
|
.noise::before {
|
||||||
|
opacity: 0.3;
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -47,21 +77,15 @@ body {
|
|||||||
/* opacity: 0.3; */
|
/* opacity: 0.3; */
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-dark {
|
|
||||||
--background-dark: var(--neutral800);
|
|
||||||
--background: var(--neutral500);
|
|
||||||
--background-light: var(--neutral400);
|
|
||||||
--background-gradient: linear-gradient(-30deg, var(--neutral500) 0%, var(--neutral400) 100%);
|
|
||||||
--outline: var(--neutral300);
|
|
||||||
--text: var(--neutral100);
|
|
||||||
--layer100: var(--background-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
video {
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.center {
|
.center {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
BIN
public/blog/random-renders-02/palma.png
(Stored with Git LFS)
Normal file
BIN
public/blog/random-renders-02/palma.png
(Stored with Git LFS)
Normal file
Binary file not shown.
3
public/images/arrow_a.svg
Executable file
3
public/images/arrow_a.svg
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="153" height="41" viewBox="0 0 153 41" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M0.604426 7.50762C0.252586 7.28903 0.144563 6.82661 0.36315 6.47477L3.92525 0.741183C4.14384 0.389342 4.60626 0.28132 4.9581 0.499908C5.30994 0.718496 5.41796 1.18092 5.19938 1.53276L2.03307 6.62928L7.12959 9.79559C7.48143 10.0142 7.58945 10.4766 7.37087 10.8284C7.15228 11.1803 6.68986 11.2883 6.33802 11.0697L0.604426 7.50762ZM96.315 39.0315L96.551 39.7434L96.315 39.0315ZM64.2152 8.78029C57.8106 11.4477 52.836 12.1939 48.4085 11.913C44.0022 11.6333 40.1814 10.3355 36.1448 9.01019C32.0953 7.68065 27.7817 6.30471 22.2711 5.76803C16.7635 5.23164 10.0288 5.53165 1.17083 7.60089L0.829611 6.14022C9.82605 4.03863 16.7259 3.72089 22.4165 4.2751C28.1042 4.82902 32.5526 6.25203 36.6127 7.58504C40.6855 8.92225 44.3217 10.1506 48.5035 10.416C52.6643 10.68 57.409 9.99013 63.6385 7.39558L64.2152 8.78029ZM63.6385 7.39558C70.1382 4.68854 77.4556 4.66764 84.2447 6.28707C91.0332 7.90638 97.3523 11.1788 101.885 15.1378C106.387 19.0708 109.27 23.8177 108.823 28.3903C108.368 33.0384 104.53 37.0978 96.551 39.7434L96.079 38.3196C103.826 35.7512 106.961 32.0201 107.33 28.2443C107.707 24.393 105.265 20.0825 100.898 16.2675C96.5602 12.4787 90.4642 9.31274 83.8966 7.74614C77.3296 6.17966 70.3498 6.22533 64.2152 8.78029L63.6385 7.39558ZM96.551 39.7434C92.8525 40.9697 89.6552 40.9308 87.071 39.9217C84.4836 38.9113 82.5882 36.9576 81.4502 34.5026C79.1849 29.6158 79.9006 22.7402 83.9827 16.9695C92.2346 5.30416 113.921 -1.68242 152.757 19.1066L152.049 20.429C113.422 -0.247958 92.7773 7.13427 85.2073 17.8358C81.3784 23.2485 80.8105 29.5558 82.8111 33.8718C83.8061 36.0182 85.4311 37.671 87.6167 38.5245C89.8055 39.3792 92.6339 39.4619 96.079 38.3196L96.551 39.7434Z" fill="#414152"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.7 KiB |
3
public/images/arrow_b.svg
Executable file
3
public/images/arrow_b.svg
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="94" height="21" viewBox="0 0 94 21" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M93.3536 4.35355C93.5488 4.15829 93.5488 3.84171 93.3536 3.64645L90.1716 0.464466C89.9763 0.269204 89.6597 0.269204 89.4645 0.464466C89.2692 0.659728 89.2692 0.976311 89.4645 1.17157L92.2929 4L89.4645 6.82843C89.2692 7.02369 89.2692 7.34027 89.4645 7.53553C89.6597 7.7308 89.9763 7.7308 90.1716 7.53553L93.3536 4.35355ZM0.792401 20.4056C11.4392 12.73 26.4625 8.74294 42.8747 6.68361C59.2771 4.62552 76.9975 4.5 93 4.5V3.5C77.0025 3.5 59.2229 3.62448 42.7503 5.69139C26.2875 7.75706 11.0608 11.77 0.207599 19.5944L0.792401 20.4056Z" fill="#414152"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 661 B |
BIN
public/models/leaf.glb
Normal file
BIN
public/models/leaf.glb
Normal file
Binary file not shown.
BIN
public/projects/argenti.png
(Stored with Git LFS)
Executable file
BIN
public/projects/argenti.png
(Stored with Git LFS)
Executable file
Binary file not shown.
10
public/projects/invoice.svg
Normal file
10
public/projects/invoice.svg
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<svg width="119" height="112" viewBox="0 0 119 112" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="57" cy="56" r="56" fill="url(#paint0_linear_20_29)"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M65 15L108.5 15C114.023 15 118.5 19.4772 118.5 25L118.5 69L98.5 69L98.5 49.1421L64.0711 83.571C60.2105 87.4316 53.9671 87.4822 50.0445 83.6848L20 54.5991L20 104.5L-1.95608e-06 104.5L-3.49691e-07 31C-2.61938e-07 26.9849 2.40147 23.3591 6.09839 21.7925C9.7953 20.226 14.0707 21.0225 16.9555 23.8152L56.8863 62.4716L84.3579 35L65 35L65 15Z" fill="black"/>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="paint0_linear_20_29" x1="20.5" y1="2" x2="93" y2="106.5" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#FF8E3C"/>
|
||||||
|
<stop offset="1" stop-color="#D9376E"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 786 B |
BIN
public/projects/isyncrasy/favicon.ico
Normal file
BIN
public/projects/isyncrasy/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
@@ -1,3 +0,0 @@
|
|||||||
[ZoneTransfer]
|
|
||||||
ZoneId=3
|
|
||||||
HostUrl=about:internet
|
|
||||||
BIN
public/projects/plantarium/screenshot-plantarium.png
(Stored with Git LFS)
BIN
public/projects/plantarium/screenshot-plantarium.png
(Stored with Git LFS)
Binary file not shown.
60
src/components/HeroCard.astro
Normal file
60
src/components/HeroCard.astro
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
---
|
||||||
|
import markdownToText from "@helpers/markdownToText";
|
||||||
|
import { Card } from "./card";
|
||||||
|
import { useTranslatedPath } from "@i18n/utils";
|
||||||
|
import Image from "@components/Image.astro";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
post: {
|
||||||
|
data: {
|
||||||
|
title: string;
|
||||||
|
icon?: string;
|
||||||
|
headerImg?: string;
|
||||||
|
};
|
||||||
|
collection: string;
|
||||||
|
slug: string;
|
||||||
|
body: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: { title, headerImg, icon },
|
||||||
|
collection,
|
||||||
|
body,
|
||||||
|
slug,
|
||||||
|
} = Astro.props.post;
|
||||||
|
|
||||||
|
const translatePath = useTranslatedPath(Astro);
|
||||||
|
|
||||||
|
const imagePath = `../content/${collection}/${slug.split("/")[0]}/${headerImg}`;
|
||||||
|
|
||||||
|
const image = headerImg && (await import(imagePath)).default;
|
||||||
|
|
||||||
|
const link = translatePath(`/${collection}/${slug.split("/")[0]}`);
|
||||||
|
---
|
||||||
|
|
||||||
|
<Card
|
||||||
|
classes={`grid gradient border-1 border-neutral overflow-hidden ${image ? "grid-rows-[200px_1fr] sm:grid-rows-none sm:grid-cols-[1fr_200px]" : ""}`}
|
||||||
|
>
|
||||||
|
<Card.Content classes="px-8 py-7 order-last sm:order-first">
|
||||||
|
<Card.Title classes="text-4xl flex items-center gap-2">
|
||||||
|
{icon && <img src={icon} class="h-6" />}
|
||||||
|
{title}
|
||||||
|
</Card.Title>
|
||||||
|
<Card.Description>
|
||||||
|
{markdownToText(body).slice(0, 200)}
|
||||||
|
</Card.Description>
|
||||||
|
<Card.ReadMoreButton link={link} />
|
||||||
|
</Card.Content>
|
||||||
|
{
|
||||||
|
image?.format && (
|
||||||
|
<a href={link}>
|
||||||
|
<Image
|
||||||
|
src={image}
|
||||||
|
alt={"cover for " + title}
|
||||||
|
class="right-0 h-full object-cover object-center rounded-none border-l border-neutral"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</Card>
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import Card from "./Card.svelte";
|
|
||||||
|
|
||||||
export let title: string;
|
|
||||||
export let subtitle: string;
|
|
||||||
export let image: string;
|
|
||||||
export let description: string;
|
|
||||||
export let link: string;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Card></Card>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.hero-card-wrapper {
|
|
||||||
display: flex;
|
|
||||||
border-radius: var(--border-radius-md);
|
|
||||||
background-color: var(--background);
|
|
||||||
border: solid thin var(--outline);
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-card-image {
|
|
||||||
flex: 1;
|
|
||||||
background-size: cover;
|
|
||||||
background-position: center;
|
|
||||||
border-radius: var(--border-radius-md) 0px 0px var(--border-radius-md);
|
|
||||||
min-height: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-card-content {
|
|
||||||
flex: 1;
|
|
||||||
padding: var(--spacing-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: var(--primary);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,15 +1,18 @@
|
|||||||
---
|
---
|
||||||
import type { ImageMetadata } from "astro";
|
import type { ImageMetadata } from "astro";
|
||||||
import { Image as AstroImage } from "astro:assets";
|
import { Picture as AstroImage } from "astro:assets";
|
||||||
interface Props {
|
interface Props {
|
||||||
src: ImageMetadata;
|
src: ImageMetadata;
|
||||||
alt: string;
|
alt: string;
|
||||||
|
class?: string;
|
||||||
caption?: string;
|
caption?: string;
|
||||||
maxWidth?: number;
|
maxWidth?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { src: image, alt, maxWidth } = Astro.props;
|
const { src: image, alt, maxWidth } = Astro.props;
|
||||||
|
|
||||||
|
console.log({ image, alt, maxWidth });
|
||||||
|
|
||||||
const sizes = [
|
const sizes = [
|
||||||
{
|
{
|
||||||
width: 240,
|
width: 240,
|
||||||
@@ -32,14 +35,9 @@ const sizes = [
|
|||||||
<AstroImage
|
<AstroImage
|
||||||
src={image}
|
src={image}
|
||||||
alt={alt}
|
alt={alt}
|
||||||
|
class={Astro.props.class}
|
||||||
widths={sizes.map((size) => size.width)}
|
widths={sizes.map((size) => size.width)}
|
||||||
sizes={sizes
|
sizes={sizes
|
||||||
.map((size) => `${size.media || "100vw"} ${size.width}px`)
|
.map((size) => `${size.media || "100vw"} ${size.width}px`)
|
||||||
.join(", ")}
|
.join(", ")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<style>
|
|
||||||
img {
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
391
src/components/ImageGallery.svelte
Normal file
391
src/components/ImageGallery.svelte
Normal file
@@ -0,0 +1,391 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import { fade } from "svelte/transition";
|
||||||
|
import normalizeWheel from "@helpers/normalizeWheel";
|
||||||
|
let images = [];
|
||||||
|
let progress = [];
|
||||||
|
let currentIndex = -1;
|
||||||
|
const maxZoom = 5;
|
||||||
|
import { swipe } from "svelte-gestures";
|
||||||
|
|
||||||
|
const mod = (a: number, b: number) => ((a % b) + b) % b;
|
||||||
|
|
||||||
|
const lerp = (a: number, b: number, t: number) => a * t + b * (1 - t);
|
||||||
|
|
||||||
|
const addIndex = (offset: number) => {
|
||||||
|
return setIndex(mod(currentIndex + offset, images.length));
|
||||||
|
};
|
||||||
|
|
||||||
|
let mx = 0,
|
||||||
|
my = 0,
|
||||||
|
_mx = 0,
|
||||||
|
_my = 0,
|
||||||
|
scale = 1;
|
||||||
|
|
||||||
|
const setIndex = (index: number) => {
|
||||||
|
mx = window.innerWidth / 2;
|
||||||
|
my = window.innerHeight / 2;
|
||||||
|
scale = 1;
|
||||||
|
if (index < 0) {
|
||||||
|
document.body.style.overflowY = "auto";
|
||||||
|
} else {
|
||||||
|
document.body.style.overflowY = "hidden";
|
||||||
|
}
|
||||||
|
currentIndex = index;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyDown = ({ key }) => {
|
||||||
|
if (currentIndex < 0) return;
|
||||||
|
if (key === "Escape" && currentIndex > -1) setIndex(-1);
|
||||||
|
if (key === "ArrowLeft") addIndex(-1);
|
||||||
|
if (key === "ArrowRight") addIndex(+1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOriginalLoading = async (image) => {
|
||||||
|
if (!image.startedLoading) {
|
||||||
|
image.startedLoading = true;
|
||||||
|
let cIndex = currentIndex;
|
||||||
|
if (image.original) {
|
||||||
|
const response = await fetch(image.original, {
|
||||||
|
headers: {
|
||||||
|
"Accept-Encoding": "gzip, deflate, br",
|
||||||
|
"Content-Type":
|
||||||
|
"image/avif,image/webp,image/apng,image/svg+xml,image/jpeg,image/png,image/*,*/*;q=0.8",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const total = Number(response.headers.get("content-length"));
|
||||||
|
const reader = response.body.getReader();
|
||||||
|
let bytesReceived = 0;
|
||||||
|
let chunks = [];
|
||||||
|
console.log("[SLIDER] started loading " + image.original);
|
||||||
|
while (true) {
|
||||||
|
const { done, value } = await reader.read();
|
||||||
|
if (done) {
|
||||||
|
console.log("[SLIDER] Image complete");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
chunks.push(value);
|
||||||
|
if (total) {
|
||||||
|
bytesReceived += value.length;
|
||||||
|
progress[cIndex] = bytesReceived / total;
|
||||||
|
console.log(
|
||||||
|
"[SLIDER] " + Math.floor(progress[cIndex] * 1000) / 10 + "%",
|
||||||
|
);
|
||||||
|
progress = progress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const b = new Blob(chunks);
|
||||||
|
images[cIndex].loaded = URL.createObjectURL(b);
|
||||||
|
images = images;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function handleSwipe(ev: any) {
|
||||||
|
if (currentIndex === -1) return;
|
||||||
|
if (ev.detail.direction === "right") addIndex(-1);
|
||||||
|
if (ev.detail.direction === "left") addIndex(+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleScroll = (ev: WheelEvent) => {
|
||||||
|
const { pixelY } = normalizeWheel(ev);
|
||||||
|
scale = Math.min(Math.max(scale - pixelY / 100, 1), maxZoom);
|
||||||
|
if (scale > 3) {
|
||||||
|
handleOriginalLoading(images[currentIndex]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let isUpdating = false;
|
||||||
|
const update = () => {
|
||||||
|
isUpdating && requestAnimationFrame(update);
|
||||||
|
|
||||||
|
let dist = Math.abs(mx - _mx) + Math.abs(my - _my);
|
||||||
|
|
||||||
|
if (dist < 0.1 || scale == 1) {
|
||||||
|
mx = _mx;
|
||||||
|
my = _my;
|
||||||
|
isUpdating = false;
|
||||||
|
} else {
|
||||||
|
let s = (0.2 + (1 - scale / maxZoom) * 0.8) * 0.3;
|
||||||
|
mx = lerp(mx, _mx, 1 - s);
|
||||||
|
my = lerp(my, _my, 1 - s);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const startUpdate = () => {
|
||||||
|
if (!isUpdating && scale !== 1) {
|
||||||
|
isUpdating = true;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseMove = (ev: MouseEvent) => {
|
||||||
|
_mx = ev.clientX;
|
||||||
|
_my = ev.clientY;
|
||||||
|
startUpdate();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePointerMove = () => {
|
||||||
|
// console.log(ev);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
const wrappers = Array.prototype.slice.call(
|
||||||
|
document.querySelectorAll("[data-image-component]"),
|
||||||
|
);
|
||||||
|
console.log({ wrappers });
|
||||||
|
|
||||||
|
images = wrappers.map((image, i: number) => {
|
||||||
|
console.log({ image });
|
||||||
|
image.classList.add("image-active");
|
||||||
|
image.addEventListener("click", () => setIndex(i));
|
||||||
|
image.addEventListener("touch", () => setIndex(i));
|
||||||
|
image.style.cursor = "pointer";
|
||||||
|
|
||||||
|
image.addEventListener("error", () => {
|
||||||
|
console.log("Error loading", image);
|
||||||
|
});
|
||||||
|
|
||||||
|
let exif = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
let rawExif = image.getAttribute("data-exif");
|
||||||
|
exif = JSON.parse(rawExif);
|
||||||
|
} catch (error) {
|
||||||
|
// No biggie
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
exif,
|
||||||
|
// preview: preview.getAttribute("src"),
|
||||||
|
src: image.getAttribute("srcset"),
|
||||||
|
alt: image.getAttribute("alt"),
|
||||||
|
sizes: image.getAttribute("sizes"),
|
||||||
|
original: image.getAttribute("data-original"),
|
||||||
|
originalLoaded: false,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
wrappers.forEach(({ children: [_, image] }, i: number) => {
|
||||||
|
image.removeEventListener("click", () => setIndex(i));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:window on:keydown={handleKeyDown} />
|
||||||
|
|
||||||
|
<div class="image-wrapper">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="gallery-wrapper" class:visible={currentIndex > -1}>
|
||||||
|
{#if images.length > 1}
|
||||||
|
<div class="controls">
|
||||||
|
{#each images as _, i}
|
||||||
|
<button
|
||||||
|
class:active={currentIndex === i}
|
||||||
|
on:click={() => {
|
||||||
|
currentIndex = i;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<button class="left" on:click={() => addIndex(-1)}><</button>
|
||||||
|
<button class="right" on:click={() => addIndex(+1)}>></button>
|
||||||
|
<button class="close" on:click={() => setIndex(-1)}>X</button>
|
||||||
|
|
||||||
|
{#if currentIndex > -1}
|
||||||
|
<div
|
||||||
|
class="image"
|
||||||
|
use:swipe
|
||||||
|
on:swipe={handleSwipe}
|
||||||
|
on:wheel|passive={handleScroll}
|
||||||
|
on:mousemove={handleMouseMove}
|
||||||
|
on:pointermove={handlePointerMove}
|
||||||
|
>
|
||||||
|
{#if progress[currentIndex] && progress[currentIndex] < 0.99}
|
||||||
|
<div
|
||||||
|
transition:fade
|
||||||
|
id="progress"
|
||||||
|
style={`transform: scaleX(${progress[currentIndex]});`}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<img
|
||||||
|
class="background"
|
||||||
|
src={images[currentIndex].preview}
|
||||||
|
alt="background blur"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
<img
|
||||||
|
style={`transform: scale(${scale}); transform-origin: ${
|
||||||
|
window.innerWidth - mx
|
||||||
|
}px ${window.innerHeight - my}px`}
|
||||||
|
srcset={images[currentIndex].loaded ? "" : images[currentIndex].src}
|
||||||
|
src={images[currentIndex].loaded}
|
||||||
|
alt={images[currentIndex].alt}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{#if images[currentIndex].exif}
|
||||||
|
{@const exif = images[currentIndex].exif}
|
||||||
|
<div class="exif" on:click={() => console.log(exif)}>
|
||||||
|
{#if "FocalLength" in exif}
|
||||||
|
{exif.FocalLength}mm |
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if "FNumber" in exif}
|
||||||
|
<i>f</i>{exif.FNumber} |
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if "ExposureTime" in exif}
|
||||||
|
{exif.ExposureTime.replace(" s", "s")} |
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if "Date" in exif}
|
||||||
|
{exif.Date}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#progress {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0px;
|
||||||
|
height: 10px;
|
||||||
|
z-index: 99;
|
||||||
|
left: 47px;
|
||||||
|
background-color: black;
|
||||||
|
width: calc(100% - 94px);
|
||||||
|
transform-origin: left;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
.gallery-wrapper {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 199;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
background-color: rgba(24, 24, 24, 0.99);
|
||||||
|
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.1s ease;
|
||||||
|
pointer-events: none;
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-wrapper.visible {
|
||||||
|
pointer-events: all;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image {
|
||||||
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image > span {
|
||||||
|
height: calc(100vh - 70px);
|
||||||
|
width: 95%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image img {
|
||||||
|
position: relative;
|
||||||
|
max-width: 100vw;
|
||||||
|
max-height: 100vh;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 98;
|
||||||
|
object-fit: contain;
|
||||||
|
image-rendering: pixelated;
|
||||||
|
transition:
|
||||||
|
transform 0.2s ease,
|
||||||
|
transform-origin 0.1s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image > .background {
|
||||||
|
filter: brightness(0.2);
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0.3;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 98;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: fill;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
width: fit-content;
|
||||||
|
left: 50vw;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background: black;
|
||||||
|
padding: 5px;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 99;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls > button {
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
border-style: none;
|
||||||
|
background: black;
|
||||||
|
border: solid 1px white;
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 2px;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls > button.active {
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exif {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0px;
|
||||||
|
left: 50vw;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
z-index: 99;
|
||||||
|
background-color: black;
|
||||||
|
color: white;
|
||||||
|
padding: 5px;
|
||||||
|
font-size: 0.8em;
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-wrapper > button {
|
||||||
|
background: black;
|
||||||
|
color: white;
|
||||||
|
border-radius: 0px;
|
||||||
|
border: none;
|
||||||
|
padding: 10px 20px;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left {
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
122
src/components/ImageSlider.svelte
Normal file
122
src/components/ImageSlider.svelte
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
|
let slot: HTMLDivElement;
|
||||||
|
let images: HTMLImageElement[];
|
||||||
|
|
||||||
|
export let title: string;
|
||||||
|
|
||||||
|
let altText: string;
|
||||||
|
let height: number;
|
||||||
|
let loaded = false;
|
||||||
|
|
||||||
|
function hide(img: HTMLImageElement) {
|
||||||
|
img.classList.remove("active");
|
||||||
|
}
|
||||||
|
|
||||||
|
function show(img: HTMLImageElement) {
|
||||||
|
img.classList.add("active");
|
||||||
|
if (img?.alt) altText = img.alt;
|
||||||
|
else altText = "";
|
||||||
|
height = img.getBoundingClientRect().height;
|
||||||
|
setTimeout(() => {
|
||||||
|
height = img.getBoundingClientRect().height;
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
let index = 0;
|
||||||
|
function setIndex(i: number) {
|
||||||
|
if (i < 0) i = images.length - 1;
|
||||||
|
if (i >= images.length) i = 0;
|
||||||
|
hide(images[index]);
|
||||||
|
index = i;
|
||||||
|
show(images[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$: if (slot) {
|
||||||
|
images = Array.from(slot.querySelectorAll("img"));
|
||||||
|
if (images?.length) {
|
||||||
|
images.forEach(hide);
|
||||||
|
show(images[index]);
|
||||||
|
images[index].onload = () => {
|
||||||
|
loaded = true;
|
||||||
|
height = images[index].getBoundingClientRect().height;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
loaded = true;
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="wrapper grid overflow-hidden rounded-xl border border-light"
|
||||||
|
class:title
|
||||||
|
class:loaded
|
||||||
|
style={`--height:${height}px`}
|
||||||
|
>
|
||||||
|
{#if title}
|
||||||
|
<div class="flex items-center p-x-4 bg">
|
||||||
|
<h3>{title}</h3>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="images" bind:this={slot}>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
<div class="p-2 flex place-content-between bg">
|
||||||
|
<p>{index + 1}/{images?.length}</p>
|
||||||
|
{#if altText}
|
||||||
|
<p class="text-right">{altText}</p>
|
||||||
|
{/if}
|
||||||
|
<div class="overflow-hidden rounded-md bg-light gap-2 flex p-1">
|
||||||
|
<button
|
||||||
|
class="flex-1 i-tabler-arrow-left"
|
||||||
|
aria-label="previous image"
|
||||||
|
on:click={() => setIndex(index - 1)}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
class="flex-1 i-tabler-arrow-right"
|
||||||
|
aria-label="next image"
|
||||||
|
on:click={() => setIndex(index + 1)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.wrapper {
|
||||||
|
grid-template-rows: 1fr 40px;
|
||||||
|
transition: height 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper :global(.image-wrapper) {
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper.title {
|
||||||
|
grid-template-rows: 40px 1fr 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loaded {
|
||||||
|
height: calc(var(--height) + 40px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper.title.loaded {
|
||||||
|
height: calc(var(--height) + 80px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.images :global(img) {
|
||||||
|
border-radius: 0px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.images :global(.active) {
|
||||||
|
position: relative;
|
||||||
|
display: block !important;
|
||||||
|
opacity: 1;
|
||||||
|
transition:
|
||||||
|
opacity 0.3s ease,
|
||||||
|
display 0.3s 0.3s;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
33
src/components/LinkCard.astro
Normal file
33
src/components/LinkCard.astro
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
interface Props {
|
||||||
|
link: string;
|
||||||
|
title: string;
|
||||||
|
icon?: string;
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
<a
|
||||||
|
href={Astro.props.link}
|
||||||
|
class="rounded-diag-md border border-neutral p-4 overflow-hidden flex items-center justify-center pacity-50"
|
||||||
|
>
|
||||||
|
<h2
|
||||||
|
class="text-2xl flex gap-3 transition-gap items-center line-clamp text-ellipsis overflow-hidden"
|
||||||
|
>
|
||||||
|
{Astro.props.title}
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
class="icon icon-tabler icons-tabler-outline icon-tabler-circle-arrow-right"
|
||||||
|
><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path
|
||||||
|
d="M12 3a9 9 0 1 0 0 18a9 9 0 0 0 0 -18"></path><path d="M16 12l-4 -4"
|
||||||
|
></path><path d="M16 12h-8"></path><path d="M12 16l4 -4"></path></svg
|
||||||
|
>
|
||||||
|
</h2>
|
||||||
|
</a>
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</defs>
|
</defs>
|
||||||
|
<title>home icon</title>
|
||||||
<path
|
<path
|
||||||
id="l1"
|
id="l1"
|
||||||
d="M1 1H30C41.0457 1 50 9.95431 50 21V50H21C9.9543 50 1 41.0457 1 30V1Z"
|
d="M1 1H30C41.0457 1 50 9.95431 50 21V50H21C9.9543 50 1 41.0457 1 30V1Z"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -10,7 +10,7 @@ const t = useTranslations(Astro);
|
|||||||
---
|
---
|
||||||
|
|
||||||
<Card
|
<Card
|
||||||
classes="googley-eye-target relative rounded-diag-md border border-light gradient grid grid-cols-[250px_1fr] h-[180px] my-8xl"
|
classes="googley-eye-target relative rounded-diag-md border border-neutral gradient grid grid-cols-[250px_1fr] h-[180px] mt-8"
|
||||||
>
|
>
|
||||||
<div class="image">
|
<div class="image">
|
||||||
<Image src={MaxImg} alt="its mee" maxWidth={700} />
|
<Image src={MaxImg} alt="its mee" maxWidth={700} />
|
||||||
@@ -39,6 +39,16 @@ const t = useTranslations(Astro);
|
|||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.image::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
height: 80%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: linear-gradient(-12deg, var(--background) 0%, transparent 50%);
|
||||||
|
}
|
||||||
|
|
||||||
.image > :global(img) {
|
.image > :global(img) {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
@@ -50,7 +60,7 @@ const t = useTranslations(Astro);
|
|||||||
}
|
}
|
||||||
.eye.left {
|
.eye.left {
|
||||||
top: 29%;
|
top: 29%;
|
||||||
right: 27%;
|
right: 28%;
|
||||||
}
|
}
|
||||||
.eye.right {
|
.eye.right {
|
||||||
top: 31%;
|
top: 31%;
|
||||||
|
|||||||
45
src/components/SmallCard.astro
Normal file
45
src/components/SmallCard.astro
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
---
|
||||||
|
import markdownToText from "@helpers/markdownToText";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
post: {
|
||||||
|
data: {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
icon: string;
|
||||||
|
tags: string[];
|
||||||
|
};
|
||||||
|
collection: string;
|
||||||
|
body: string;
|
||||||
|
slug: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const { post } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<a
|
||||||
|
href={`/${post.collection}/${post.slug.split("/").pop()}`}
|
||||||
|
class="rounded-diag-md border border-neutral p-4 overflow-hidden"
|
||||||
|
>
|
||||||
|
<h2
|
||||||
|
class="text-2xl flex gap-2 items-center line-clamp text-ellipsis overflow-hidden"
|
||||||
|
>
|
||||||
|
{post.data.icon && <img src={post.data.icon} class="h-6" />}
|
||||||
|
{post.data.title}
|
||||||
|
</h2>
|
||||||
|
<p class="text-ellipsis overflow-hidden line-clamp-2">
|
||||||
|
{post.data.description || markdownToText(post.body).slice(0, 200)}
|
||||||
|
</p>
|
||||||
|
{
|
||||||
|
post.data.tags && (
|
||||||
|
<div class="flex gap-2 mt-2">
|
||||||
|
{post.data.tags.map((tag) => (
|
||||||
|
<span class="text-xs border border-neutral p-2 rounded-md">
|
||||||
|
{tag}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</a>
|
||||||
21
src/components/arrows/ArrowA.astro
Normal file
21
src/components/arrows/ArrowA.astro
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
import { useTranslations } from "@i18n/utils";
|
||||||
|
const t = useTranslations(Astro);
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="arrow flex items-center gap-2">
|
||||||
|
<img src="/images/arrow_a.svg" alt="Arrow" />
|
||||||
|
<p class="text-xl">{t("latest-projects")}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.arrow {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
transform: rotate(10deg) translateX(110%) translateY(50px);
|
||||||
|
}
|
||||||
|
.arrow > p {
|
||||||
|
color: #414152;
|
||||||
|
transform: rotate(15deg) translateY(16px) translateX(4px);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
21
src/components/arrows/ArrowB.astro
Normal file
21
src/components/arrows/ArrowB.astro
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
import { useTranslations } from "@i18n/utils";
|
||||||
|
const t = useTranslations(Astro);
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="arrow flex items-center gap-2">
|
||||||
|
<p class="text-xl">{t("latest-posts")}</p>
|
||||||
|
<img src="/images/arrow_b.svg" alt="Arrow" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.arrow {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
transform: translateX(-110%) translateY(-160px);
|
||||||
|
}
|
||||||
|
.arrow > p {
|
||||||
|
color: #414152;
|
||||||
|
transform: rotate(-26deg) translateY(34px) translateX(-10px);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -2,6 +2,6 @@
|
|||||||
export let classes = "";
|
export let classes = "";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-1 flex-col gap-4 {classes} w-[66%]">
|
<div class="flex flex-2 flex-col gap-4 {classes}">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let alt: string;
|
export let alt: string;
|
||||||
export let src: string;
|
export let src: string;
|
||||||
export let width: number;
|
export let width: number | undefined;
|
||||||
export let height: number;
|
export let height: number | undefined;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<img {src} {alt} {width} {height} />
|
<img {src} {alt} {width} {height} class="flex-1" />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
img {
|
img {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let link: string;
|
export let link: string;
|
||||||
|
export let text = "read more";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<a
|
<a
|
||||||
href={link}
|
href={link}
|
||||||
data-astro-prefetch
|
data-astro-prefetch
|
||||||
class="bg-light p-2 text-s rounded-md px-4 flex flex-0 items-center gap-2 w-fit"
|
class="bg-light p-2 text-s rounded-md px-4 flex flex-0 items-center gap-2 w-fit"
|
||||||
>
|
>{text}<span class="i-tabler-arrow-right inline-block w-4 h-4" />
|
||||||
read more <span class="i-tabler-arrow-right inline-block w-4 h-4" />
|
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
export let classes = "";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h2 class="text-2xl">
|
<h2 class="text-3xl {classes}">
|
||||||
<slot />
|
<slot />
|
||||||
</h2>
|
</h2>
|
||||||
|
|||||||
@@ -2,18 +2,8 @@
|
|||||||
export let classes = "flex bg";
|
export let classes = "flex bg";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="relative rounded-diag-md {classes} noise">
|
<div
|
||||||
|
class="relative rounded-diag-md {classes} noise card-wrapper items-stretch"
|
||||||
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
|
||||||
.card-wrapper {
|
|
||||||
align-items: stretch;
|
|
||||||
|
|
||||||
min-height: 200px;
|
|
||||||
border: solid thin var(--outline);
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 300px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -5,7 +5,19 @@ headerImg: "images/Render.png"
|
|||||||
comments: true
|
comments: true
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
Die Inspiration diesen Render zu machen kam von einem Bild was 2013 auf dem Weg nach Italien entstanden ist.
|
||||||
|
|
||||||
|
<ImageGallery client:load/>
|
||||||
|
|
||||||
|
<Image
|
||||||
|
loading="eager"
|
||||||
|
src={ValleyView}
|
||||||
|
alt="ValleyView"
|
||||||
|
/>
|
||||||
|
|
||||||
import Image from "@components/Image.astro";
|
import Image from "@components/Image.astro";
|
||||||
|
import ImageGallery from "@components/ImageGallery.svelte";
|
||||||
import ValleyView from "./images/Render.png";
|
import ValleyView from "./images/Render.png";
|
||||||
import DSC_1601 from "./images/DSC_1601.jpg";
|
import DSC_1601 from "./images/DSC_1601.jpg";
|
||||||
import Unbenannt1 from "./images/Unbenannt-1.jpg";
|
import Unbenannt1 from "./images/Unbenannt-1.jpg";
|
||||||
@@ -13,15 +25,8 @@ import Render4 from "./images/Render4.png";
|
|||||||
import UntitledPng from "./images/untitled1-1.png"
|
import UntitledPng from "./images/untitled1-1.png"
|
||||||
import Render6 from "./images/Render6.png";
|
import Render6 from "./images/Render6.png";
|
||||||
|
|
||||||
|
|
||||||
<Image
|
|
||||||
src={ValleyView}
|
|
||||||
alt="ValleyView"
|
|
||||||
/>
|
|
||||||
|
|
||||||
Die Inspiration diesen Render zu machen kam von einem Bild was 2013 auf dem Weg nach Italien entstanden ist.
|
|
||||||
|
|
||||||
<Image
|
<Image
|
||||||
|
loading="eager"
|
||||||
src={DSC_1601}
|
src={DSC_1601}
|
||||||
alt="DSC_1601"
|
alt="DSC_1601"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
---
|
---
|
||||||
title: "Random Renders NO°2"
|
title: "Random Renders NO°2"
|
||||||
date: 2023-05-11
|
date: 2023-05-11
|
||||||
headerImg: "images/render_05.png"
|
headerImg: "images/palma.png"
|
||||||
|
featured: true
|
||||||
comments: true
|
comments: true
|
||||||
---
|
---
|
||||||
|
|
||||||
|
Last month I felt like creating some renderings again. I also found a few old renderings that haven't ended up on my blog yet.
|
||||||
|
|
||||||
import Image from "@components/Image.astro"
|
import Image from "@components/Image.astro"
|
||||||
import Palma from "./images/palma.png"
|
import Palma from "./images/palma.png"
|
||||||
import Render05 from "./images/render_05.png"
|
import Render05 from "./images/render_05.png"
|
||||||
@@ -13,7 +17,6 @@ import Cabin_new from "./images/Cabin_new.png";
|
|||||||
import Home from "./images/Home.png";
|
import Home from "./images/Home.png";
|
||||||
import Workroom from "./images/Workroom.png";
|
import Workroom from "./images/Workroom.png";
|
||||||
|
|
||||||
Last month I felt like creating some renderings again. I also found a few old renderings that haven't ended up on my blog yet.
|
|
||||||
|
|
||||||
## Palma
|
## Palma
|
||||||
<Image src={Palma} alt="Duude" />
|
<Image src={Palma} alt="Duude" />
|
||||||
|
|||||||
@@ -1,38 +1,41 @@
|
|||||||
---
|
---
|
||||||
title: "Random Renders NO°2"
|
title: "Random Renders NO°2"
|
||||||
date: 2023-05-11
|
date: 2023-05-11
|
||||||
headerImg: "images/render_05.png"
|
headerImg: "images/palma.png"
|
||||||
|
featured: true
|
||||||
comments: true
|
comments: true
|
||||||
---
|
---
|
||||||
|
|
||||||
|
Im letzten Monat hatte ich wieder Lust, einige Renderings zu erstellen. Außerdem habe ich noch ein paar alte Renderings gefunden die noch nicht auf meinem Blog gelandet sind.
|
||||||
|
|
||||||
|
import ImageGallery from "@components/ImageGallery.svelte"
|
||||||
import Image from "@components/Image.astro"
|
import Image from "@components/Image.astro"
|
||||||
import Palma from "./images/palma.png"
|
import Palma from "./images/palma.png"
|
||||||
import Render05 from "./images/render_05.png"
|
import Render05 from "./images/render_05.png"
|
||||||
|
import Poster_var2 from "./images/Poster_var2.jpg";
|
||||||
|
import Cabin_old from "./images/Cabin_old.jpg";
|
||||||
|
import Cabin_new from "./images/Cabin_new.png";
|
||||||
|
import Home from "./images/Home.png";
|
||||||
|
import Workroom from "./images/Workroom.png";
|
||||||
|
|
||||||
Im letzten Monat hatte ich wieder Lust, einige Renderings zu erstellen. Außerdem habe ich noch ein paar alte Renderings gefunden die noch nicht auf meinem Blog gelandet sind.
|
<ImageGallery client:load/>
|
||||||
|
|
||||||
|
|
||||||
## Palma
|
## Palma
|
||||||
<Image
|
<Image src={Palma} alt="Duude" />
|
||||||
src={Palma}
|
|
||||||
alt="Duude"
|
|
||||||
/>
|
|
||||||
|
|
||||||
## [Sudoku.nvim](https://github.com/jim-fx/sudoku.nvim)
|
## [Sudoku.nvim](https://github.com/jim-fx/sudoku.nvim)
|
||||||
|
|
||||||
<Image
|
<Image src={Render05} alt="Duude" />
|
||||||
src={Render05}
|
|
||||||
alt="Duude"
|
|
||||||
/>
|
|
||||||
|
|
||||||

|
<Image src={Poster_var2} alt="Poster_var2" />
|
||||||
|
|
||||||
## Super old renders:
|
## Super old renders:
|
||||||
|
|
||||||

|
<Image src={Cabin_old} alt="Cabin_old" />
|
||||||
|
|
||||||

|
<Image src={Cabin_new} alt="Cabin_new" />
|
||||||
|
|
||||||

|
<Image src={Home} alt="Home" />
|
||||||
|
|
||||||
|
<Image src={Workroom} alt="Workroom" />
|
||||||
|
|
||||||

|
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
---
|
---
|
||||||
title: "Wie ich mein Server-Setup komplett neu aufbaue."
|
title: "Mein neues Server-Setup"
|
||||||
date: 2020-06-09
|
date: 2020-06-09
|
||||||
comments: true
|
comments: true
|
||||||
---
|
---
|
||||||
|
|
||||||
import Image from "@components/Image.astro"
|
import Image from "@components/Image.astro"
|
||||||
|
|
||||||
|
import ServerSetup from "./images/new-server.svg"
|
||||||
|
|
||||||
Dieses Thema beschäftigt mich also schon seit längerem. Und in den letzten zwei Wochen ist es mir endlich klar geworden. Dieser Blogbeitrag ist nicht als Leitfaden gedacht, sondern als Möglichkeit für mein zukünftiges Ich, die Entscheidungsprozesse meines jetzigen Ichs zu verstehen.
|
Dieses Thema beschäftigt mich also schon seit längerem. Und in den letzten zwei Wochen ist es mir endlich klar geworden. Dieser Blogbeitrag ist nicht als Leitfaden gedacht, sondern als Möglichkeit für mein zukünftiges Ich, die Entscheidungsprozesse meines jetzigen Ichs zu verstehen.
|
||||||
|
|
||||||
> **Mein Plan ist es, die Leitfäden später als separate Beiträge zu veröffentlichen:**
|
> **Mein Plan ist es, die Leitfäden später als separate Beiträge zu veröffentlichen:**
|
||||||
@@ -44,7 +46,7 @@ In meinem vorherigen Setup habe ich eine Mischung aus externen Diensten verwende
|
|||||||
|
|
||||||
## My new Setup
|
## My new Setup
|
||||||
|
|
||||||
<img src="./images/new-server.svg"/>
|
<Image src={ServerSetup} alt="Server Setup" />
|
||||||
|
|
||||||
Eine Frage, die man sich jetzt stellen könnte wäre: „Warum benutzt du Cloud und lokale Server?“ Beide haben einige Nachteile und einige Vorteile. Cloud-Server bieten schnelle Netzwerkgeschwindigkeiten und statische öffentliche IPs, aber die Speicherung großer Datenmengen ist recht teuer. Der Festplattenspeicher ist bei lokalen Servern vergleichsweise günstig. Ich habe etwa 60 € für meine 1-TB-Festplatte bezahlt, die speziell für NAS-Situationen entwickelt wurde, bei denen die Laufwerke rund um die Uhr laufen. Außerdem gefällt mir die Idee sehr gut, physischen Zugriff auf meine eigenen Daten zu haben und diese nicht einem Dritten anvertrauen zu müssen. Ein weiterer Vorteil des physischen Zugriffs auf meinen eigenen Server besteht darin, dass ich an Hardwareaspekten wie Netzwerk, Laufwerken und Kühlung herumbasteln und physische Messungen wie Raumtemperatur und Luftfeuchtigkeit vornehmen kann.
|
Eine Frage, die man sich jetzt stellen könnte wäre: „Warum benutzt du Cloud und lokale Server?“ Beide haben einige Nachteile und einige Vorteile. Cloud-Server bieten schnelle Netzwerkgeschwindigkeiten und statische öffentliche IPs, aber die Speicherung großer Datenmengen ist recht teuer. Der Festplattenspeicher ist bei lokalen Servern vergleichsweise günstig. Ich habe etwa 60 € für meine 1-TB-Festplatte bezahlt, die speziell für NAS-Situationen entwickelt wurde, bei denen die Laufwerke rund um die Uhr laufen. Außerdem gefällt mir die Idee sehr gut, physischen Zugriff auf meine eigenen Daten zu haben und diese nicht einem Dritten anvertrauen zu müssen. Ein weiterer Vorteil des physischen Zugriffs auf meinen eigenen Server besteht darin, dass ich an Hardwareaspekten wie Netzwerk, Laufwerken und Kühlung herumbasteln und physische Messungen wie Raumtemperatur und Luftfeuchtigkeit vornehmen kann.
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import createFishes from "./fishes/webgl-fishes";
|
import createFishes from "./fishes/webgl-fishes";
|
||||||
import { Color } from "ogl";
|
import { Color } from "ogl";
|
||||||
|
import { rgbToHex } from "@helpers/colors";
|
||||||
|
|
||||||
let canvasBottom: HTMLCanvasElement;
|
let canvasBottom: HTMLCanvasElement;
|
||||||
|
|
||||||
@@ -26,20 +27,7 @@
|
|||||||
fishCanvasBack.resize();
|
fishCanvasBack.resize();
|
||||||
};
|
};
|
||||||
|
|
||||||
// function to turn css rgb() strings to hex
|
let loaded = false;
|
||||||
function rgbToHex(rgb: string) {
|
|
||||||
let hex = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
|
|
||||||
if (!hex) return rgb;
|
|
||||||
return (
|
|
||||||
"#" +
|
|
||||||
hex
|
|
||||||
.slice(1)
|
|
||||||
.map((x) => {
|
|
||||||
return ("0" + parseInt(x).toString(16)).slice(-2);
|
|
||||||
})
|
|
||||||
.join("")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
const background = window.getComputedStyle(document.body);
|
const background = window.getComputedStyle(document.body);
|
||||||
@@ -56,6 +44,7 @@
|
|||||||
speed *= 0.99;
|
speed *= 0.99;
|
||||||
render && fishCanvasBack.update(t, timeOffset);
|
render && fishCanvasBack.update(t, timeOffset);
|
||||||
}
|
}
|
||||||
|
loaded = true;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -69,7 +58,7 @@
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<canvas id="bottom" bind:this={canvasBottom} />
|
<canvas id="bottom" bind:this={canvasBottom} class:loaded />
|
||||||
|
|
||||||
<!-- <canvas id="top" bind:this={canvasTop} /> -->
|
<!-- <canvas id="top" bind:this={canvasTop} /> -->
|
||||||
<style>
|
<style>
|
||||||
@@ -80,6 +69,11 @@
|
|||||||
top: 0px;
|
top: 0px;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
opacity: 0.5;
|
opacity: 0;
|
||||||
|
transition: opacity 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.loaded {
|
||||||
|
opacity: 0.2;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: "Zentralwerk_2051"
|
title: "Zentralwerk_2051"
|
||||||
date: 2021-01-07
|
date: 2021-01-07
|
||||||
_layout: "transparent"
|
|
||||||
links: [["PDF", "/Zentralwerk_2051.pdf"]]
|
links: [["PDF", "/Zentralwerk_2051.pdf"]]
|
||||||
license: "CC-BY-SA:4.0"
|
license: "CC-BY-SA:4.0"
|
||||||
comments: true
|
comments: true
|
||||||
|
|||||||
@@ -5,8 +5,11 @@ const blogCollection = defineCollection({
|
|||||||
title: z.string(),
|
title: z.string(),
|
||||||
date: z.date(),
|
date: z.date(),
|
||||||
headerImg: z.string().optional(),
|
headerImg: z.string().optional(),
|
||||||
|
description: z.string().optional(),
|
||||||
|
icon: z.string().optional(),
|
||||||
draft: z.boolean().optional(),
|
draft: z.boolean().optional(),
|
||||||
featured: z.boolean().optional(),
|
featured: z.boolean().optional(),
|
||||||
|
tags: z.array(z.string()).optional(),
|
||||||
_layout: z.enum(['normal', 'transparent']).optional(),
|
_layout: z.enum(['normal', 'transparent']).optional(),
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
BIN
src/content/photos/bigge-changes/index.mdx
(Stored with Git LFS)
BIN
src/content/photos/bigge-changes/index.mdx
(Stored with Git LFS)
Binary file not shown.
BIN
src/content/photos/bilder-caen-ogrove/index.mdx
(Stored with Git LFS)
BIN
src/content/photos/bilder-caen-ogrove/index.mdx
(Stored with Git LFS)
Binary file not shown.
BIN
src/content/photos/erasmus-valencia/index.en.mdx
(Stored with Git LFS)
BIN
src/content/photos/erasmus-valencia/index.en.mdx
(Stored with Git LFS)
Binary file not shown.
BIN
src/content/photos/erasmus-valencia/index.mdx
(Stored with Git LFS)
BIN
src/content/photos/erasmus-valencia/index.mdx
(Stored with Git LFS)
Binary file not shown.
BIN
src/content/photos/point-de-lisle/index.en.mdx
(Stored with Git LFS)
BIN
src/content/photos/point-de-lisle/index.en.mdx
(Stored with Git LFS)
Binary file not shown.
BIN
src/content/photos/point-de-lisle/index.mdx
(Stored with Git LFS)
BIN
src/content/photos/point-de-lisle/index.mdx
(Stored with Git LFS)
Binary file not shown.
BIN
src/content/photos/portugal-2021/index.mdx
(Stored with Git LFS)
BIN
src/content/photos/portugal-2021/index.mdx
(Stored with Git LFS)
Binary file not shown.
@@ -1,7 +1,10 @@
|
|||||||
---
|
---
|
||||||
title: "Argenti"
|
title: "Argenti"
|
||||||
date: 2023-08-21
|
date: 2023-08-21
|
||||||
|
icon: "/projects/argenti.png"
|
||||||
draft: true
|
draft: true
|
||||||
|
description: "A central database for things I enjoy stored in Markdown/Redis"
|
||||||
|
tags: ["deno", "fresh", "redis"]
|
||||||
links:
|
links:
|
||||||
[
|
[
|
||||||
["live", "https://invoice.app.max-richter.dev"],
|
["live", "https://invoice.app.max-richter.dev"],
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
title: "Invoice"
|
title: "Invoice"
|
||||||
date: 2023-08-21
|
date: 2023-08-21
|
||||||
headerImg: "bg.jpg"
|
headerImg: "bg.jpg"
|
||||||
|
icon: "/projects/invoice.svg"
|
||||||
|
tags: ["sveltekit", "unocss", "prisma", "sqlite"]
|
||||||
draft: true
|
draft: true
|
||||||
links:
|
links:
|
||||||
[
|
[
|
||||||
|
|||||||
10
src/content/projects/isyncrasy/index.mdx
Normal file
10
src/content/projects/isyncrasy/index.mdx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
date: 2019-06-10
|
||||||
|
title: "Isyncrasy"
|
||||||
|
draft: false
|
||||||
|
icon: "/projects/isyncrasy/favicon.ico"
|
||||||
|
description: "A small fun virtual OS build with svelte"
|
||||||
|
tags: ["svelte", "web", "os"]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Isyncrasy
|
||||||
23
src/content/projects/plantarium/_components/Leaf.frag
Normal file
23
src/content/projects/plantarium/_components/Leaf.frag
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
precision highp float;
|
||||||
|
precision highp int;
|
||||||
|
|
||||||
|
uniform vec3 uBackground;
|
||||||
|
|
||||||
|
varying vec3 vNormal;
|
||||||
|
varying vec2 vUv;
|
||||||
|
varying float fog;
|
||||||
|
|
||||||
|
void main(){
|
||||||
|
|
||||||
|
vec3 normal=normalize(vNormal);
|
||||||
|
|
||||||
|
float lighting=dot(normal,normalize(vec3(-.3,.8,.6)));
|
||||||
|
|
||||||
|
vec3 col = (vec3(.308*vUv.x,.712,.5)+lighting*.5)-.25;
|
||||||
|
/* gl_FragColor.rgb=vec3(vUv.x,vUv.y,vNormal.x); */
|
||||||
|
gl_FragColor.rgb=mix(col, uBackground,fog);
|
||||||
|
|
||||||
|
gl_FragColor.a=1.;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
49
src/content/projects/plantarium/_components/Leaf.vert
Normal file
49
src/content/projects/plantarium/_components/Leaf.vert
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
attribute vec2 uv;
|
||||||
|
attribute vec3 position;
|
||||||
|
// Add instanced attributes just like any attribute
|
||||||
|
attribute vec3 offset;
|
||||||
|
attribute vec3 random;
|
||||||
|
attribute vec3 normal;
|
||||||
|
|
||||||
|
uniform mat4 modelViewMatrix;
|
||||||
|
uniform mat4 projectionMatrix;
|
||||||
|
uniform mat4 normalMatrix;
|
||||||
|
|
||||||
|
uniform float uTime;
|
||||||
|
uniform float uRot;
|
||||||
|
varying vec2 vUv;
|
||||||
|
varying vec3 vNormal;
|
||||||
|
varying float fog;
|
||||||
|
|
||||||
|
void rotate2d(inout vec2 v, float a){
|
||||||
|
mat2 m = mat2(cos(a), -sin(a), sin(a), cos(a));
|
||||||
|
v = m * v;
|
||||||
|
}
|
||||||
|
void main() {
|
||||||
|
vUv = uv;
|
||||||
|
|
||||||
|
vNormal = normal;
|
||||||
|
|
||||||
|
/* vNormal = normalize(normalMatrix * normal); */
|
||||||
|
|
||||||
|
// copy position so that we can modify the instances
|
||||||
|
vec3 pos = position;
|
||||||
|
|
||||||
|
// scale first
|
||||||
|
fog = (offset.z+1.0)/2.0;
|
||||||
|
pos *= 0.4 + (1.0-fog) * 0.6;
|
||||||
|
|
||||||
|
// rotate around y axis
|
||||||
|
rotate2d(pos.xz, random.x * 6.28 + 1.0 * uTime * (random.y - 0.5));
|
||||||
|
|
||||||
|
rotate2d(pos.xy, random.y * 6.28 + 1.0 * uTime * (random.y - 0.5));
|
||||||
|
// rotate around x axis just to add some extra variation
|
||||||
|
rotate2d(pos.zy, random.z * 0.5 * sin(uTime * random.x + random.z * 3.14));
|
||||||
|
|
||||||
|
pos.y += sin(uRot/500.0+random.y*50.0)/5.0+(1.0-fog)*uRot;
|
||||||
|
|
||||||
|
pos += offset;
|
||||||
|
|
||||||
|
|
||||||
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
|
||||||
|
}
|
||||||
44
src/content/projects/plantarium/_components/Leaves.svelte
Normal file
44
src/content/projects/plantarium/_components/Leaves.svelte
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import { createLeaves, setRotation } from "./leaves";
|
||||||
|
|
||||||
|
let canvas: HTMLCanvasElement;
|
||||||
|
|
||||||
|
let loaded = false;
|
||||||
|
|
||||||
|
function handleScroll() {
|
||||||
|
setRotation(window.scrollY / window.innerHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
createLeaves({ canvas, num: 20, minZ: 0, maxZ: 1 });
|
||||||
|
setTimeout(() => {
|
||||||
|
loaded = true;
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:window on:scroll={handleScroll} />
|
||||||
|
|
||||||
|
<canvas bind:this={canvas} class:loaded />
|
||||||
|
|
||||||
|
<style>
|
||||||
|
:global(body) {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
canvas {
|
||||||
|
width: 100vw !important;
|
||||||
|
height: 100vh !important;
|
||||||
|
position: fixed;
|
||||||
|
top: 0px;
|
||||||
|
left: 0px;
|
||||||
|
z-index: -1;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loaded {
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
5
src/content/projects/plantarium/_components/leave.json
Normal file
5
src/content/projects/plantarium/_components/leave.json
Normal file
File diff suppressed because one or more lines are too long
88
src/content/projects/plantarium/_components/leaves.ts
Normal file
88
src/content/projects/plantarium/_components/leaves.ts
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import { Camera, Color, Geometry, GLTFLoader, Mesh, Program, Renderer, Transform } from 'ogl';
|
||||||
|
import { rand, randMinMax } from "./random";
|
||||||
|
|
||||||
|
import fragment from "./Leaf.frag";
|
||||||
|
import vertex from "./Leaf.vert";
|
||||||
|
import { rgbToHex } from '@helpers/colors';
|
||||||
|
|
||||||
|
let rotation = 0;
|
||||||
|
|
||||||
|
export function setRotation(r: number) {
|
||||||
|
rotation = r;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createLeaves({ canvas, num = 20, alpha = false, minZ = -1, maxZ = 1 }: { canvas: HTMLCanvasElement, num?: number, alpha?: boolean, minZ?: number, maxZ?: number }) {
|
||||||
|
|
||||||
|
|
||||||
|
const renderer = new Renderer({ dpr: 1, canvas, alpha: true });
|
||||||
|
const gl = renderer.gl;
|
||||||
|
|
||||||
|
const background = window.getComputedStyle(document.body);
|
||||||
|
const d = new Color(rgbToHex(background.backgroundColor));
|
||||||
|
|
||||||
|
const camera = new Camera(gl, { fov: 15 });
|
||||||
|
camera.position.z = 5;
|
||||||
|
|
||||||
|
function resize() {
|
||||||
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||||
|
camera.perspective({ aspect: gl.canvas.width / gl.canvas.height });
|
||||||
|
}
|
||||||
|
window.addEventListener('resize', resize, false);
|
||||||
|
resize();
|
||||||
|
|
||||||
|
const scene = new Transform();
|
||||||
|
|
||||||
|
const program = new Program(gl, {
|
||||||
|
vertex,
|
||||||
|
fragment,
|
||||||
|
cullFace: gl.NONE,
|
||||||
|
depthTest: true,
|
||||||
|
uniforms: {
|
||||||
|
uTime: { value: 0 },
|
||||||
|
uRot: { value: 0 },
|
||||||
|
uBackground: { value: d },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let mesh: Mesh;
|
||||||
|
loadModel();
|
||||||
|
async function loadModel() {
|
||||||
|
|
||||||
|
const model = await GLTFLoader.load(gl, "/models/leaf.glb");
|
||||||
|
|
||||||
|
const data = model.nodes[0].children[0].geometry.attributes;
|
||||||
|
|
||||||
|
let offset = new Float32Array(num * 3);
|
||||||
|
let random = new Float32Array(num * 3);
|
||||||
|
for (let i = 0; i < num; i++) {
|
||||||
|
offset.set([rand(8), rand(4), randMinMax(minZ, maxZ)], i * 3);
|
||||||
|
|
||||||
|
// unique random values are always handy for instances.
|
||||||
|
// Here they will be used for rotation, scale and movement.
|
||||||
|
random.set([Math.random(), Math.random(), Math.random()], i * 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
const geometry = new Geometry(gl, {
|
||||||
|
...data,
|
||||||
|
|
||||||
|
// simply add the 'instanced' property to flag as an instanced attribute.
|
||||||
|
// set the value as the divisor number
|
||||||
|
offset: { instanced: 1, size: 3, data: offset },
|
||||||
|
random: { instanced: 1, size: 3, data: random },
|
||||||
|
});
|
||||||
|
|
||||||
|
mesh = new Mesh(gl, { geometry, program });
|
||||||
|
mesh.scale.set(0.2, 0.2, 0.2)
|
||||||
|
mesh.setParent(scene);
|
||||||
|
}
|
||||||
|
|
||||||
|
requestAnimationFrame(update);
|
||||||
|
function update(t: number) {
|
||||||
|
requestAnimationFrame(update);
|
||||||
|
|
||||||
|
program.uniforms.uTime.value = t * 0.001;
|
||||||
|
program.uniforms.uRot.value = rotation;
|
||||||
|
renderer.render({ scene, camera });
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
3
src/content/projects/plantarium/_components/random.ts
Normal file
3
src/content/projects/plantarium/_components/random.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export const rand = (r = 1) => (Math.random() * 2 - 1) * r;
|
||||||
|
|
||||||
|
export const randMinMax = (min = 0, max = 1) => min + Math.random() * (max - min)
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: "Plantarium"
|
title: "Plantarium"
|
||||||
date: 2022-08-31
|
date: 2022-08-31
|
||||||
headerImg: "/projects/plantarium/plantarium.png"
|
headerImg: "images/plantarium.png"
|
||||||
featured: true
|
featured: true
|
||||||
links: [["website", "https://plant.max-richter.com"], ["git", "https://github.com/jim-fx/plantarium"]]
|
links: [["website", "https://plant.max-richter.com"], ["git", "https://github.com/jim-fx/plantarium"]]
|
||||||
draft: true
|
draft: true
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: "Plantarium"
|
title: "Plantarium"
|
||||||
date: 2022-08-31T20:46:26+02:00
|
date: 2022-08-31T20:46:26+02:00
|
||||||
headerImg: "/projects/plantarium/plantarium.png"
|
headerImg: "images/plantarium.png"
|
||||||
featured: true
|
featured: true
|
||||||
links: [["website", "https://plant.max-richter.dev"], ["git", "https://github.com/jim-fx/plantarium"]]
|
links: [["website", "https://plant.max-richter.dev"], ["git", "https://github.com/jim-fx/plantarium"]]
|
||||||
tags: ["Web", "3D", "Svelte", "Node-Systeme"]
|
tags: ["Web", "3D", "Svelte", "Node-Systeme"]
|
||||||
@@ -14,14 +14,31 @@ Plantarium ist wohl das Hobby Projekt, mit dem ich am meisten Zeit verbracht hab
|
|||||||
|
|
||||||
Plantarium ist eine WebApp mit der Nutzer 3D Model von Pflanzen generieren können. Der erste Prototyp war innerhalb von zwei Wochen intensiver Arbeit fertig und sah ungefähr so aus:
|
Plantarium ist eine WebApp mit der Nutzer 3D Model von Pflanzen generieren können. Der erste Prototyp war innerhalb von zwei Wochen intensiver Arbeit fertig und sah ungefähr so aus:
|
||||||
|
|
||||||
<svelte component="image-slider" title="Prototype Design">
|
import ImageSlider from "@components/ImageSlider.svelte"
|
||||||
<img src="./images/page01-0.jpg" alt="Stem Page"/>
|
import Leaves from "./_components/Leaves.svelte"
|
||||||
<img src="./images/page01-1.jpg" alt="Branches page"/>
|
import Image from "@components/Image.astro"
|
||||||
<img src="./images/page01-2.jpg" alt="Leaves page"/>
|
import page01_0 from "./images/page01-0.jpg"
|
||||||
<img src="./images/page01-3.jpg" alt="Import/Export page"/>
|
import page01_1 from "./images/page01-1.jpg"
|
||||||
<img src="./images/page01-5.jpg" alt="Design of UI Components"/>
|
import page01_2 from "./images/page01-2.jpg"
|
||||||
<img src="./images/page01-6.jpg" alt="Data flow inside app"/>
|
import page01_3 from "./images/page01-3.jpg"
|
||||||
</svelte>
|
import page01_5 from "./images/page01-5.jpg"
|
||||||
|
import page01_6 from "./images/page01-6.jpg"
|
||||||
|
import screenshot_geometry_nodes from "./images/screenshot-geometry-nodes.jpg"
|
||||||
|
import screenshot_houdini from "./images/screenshot-houdini.jpg"
|
||||||
|
import screenshot_unreal from "./images/screenshot-unreal.jpg"
|
||||||
|
import screenshot_davinci from "./images/screenshot-davinci.jpg"
|
||||||
|
|
||||||
|
<Leaves client:load/>
|
||||||
|
|
||||||
|
<ImageSlider title="Erster Prototyp" client:load>
|
||||||
|
<Image src={page01_0} alt="Stem Page"/>
|
||||||
|
<Image src={page01_1} alt="Branches page"/>
|
||||||
|
<Image src={page01_2} alt="Leaves page"/>
|
||||||
|
<Image src={page01_3} alt="Import/Export page"/>
|
||||||
|
<Image src={page01_5} alt="Design of UI Components"/>
|
||||||
|
<Image src={page01_6} alt="Data flow inside app"/>
|
||||||
|
</ImageSlider>
|
||||||
|
|
||||||
|
|
||||||
Schon gar nicht schlecht, aber wie das mit Prototypen so ist gab es noch einiges zu verbessern. Also eine Kurze Historie der größten Änderungen bis heute:
|
Schon gar nicht schlecht, aber wie das mit Prototypen so ist gab es noch einiges zu verbessern. Also eine Kurze Historie der größten Änderungen bis heute:
|
||||||
|
|
||||||
@@ -57,12 +74,12 @@ Das coole ist das man dieses System sehr generisch gestalten kann und zum beispi
|
|||||||
|
|
||||||
<img src="/projects/plantarium/screenshot-plantarium.png" alt="Plantariums uses nodes to create plants"/>
|
<img src="/projects/plantarium/screenshot-plantarium.png" alt="Plantariums uses nodes to create plants"/>
|
||||||
|
|
||||||
<svelte component="image-slider" title="Beispiele von Node Systemen">
|
<ImageSlider title="Beispiele von Node Systemen" client:load>
|
||||||
<img src="images/screenshot-geometry-nodes.jpg" alt="Blenders uses nodes to create geometry"/>
|
<Image src={screenshot_geometry_nodes} alt="Blenders uses nodes to create geometry"/>
|
||||||
<img src="images/screenshot-houdini.jpg" alt="Houdini uses nodes for vfx/simulations"/>
|
<Image src={screenshot_houdini} alt="Houdini uses nodes for vfx/simulations"/>
|
||||||
<img src="images/screenshot-unreal.jpg" alt="Unreal uses nodes for game logic"/>
|
<Image src={screenshot_unreal} alt="Unreal uses nodes for game logic"/>
|
||||||
<img src="images/screenshot-davinci.jpg" alt="Davinvi uses nodes for vfx"/>
|
<Image src={screenshot_davinci} alt="Davinvi uses nodes for vfx"/>
|
||||||
</svelte>
|
</ImageSlider>
|
||||||
|
|
||||||
### Svelte-Kit Rewrite
|
### Svelte-Kit Rewrite
|
||||||
|
|
||||||
|
|||||||
14
src/helpers/colors.ts
Normal file
14
src/helpers/colors.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// function to turn css rgb() strings to hex
|
||||||
|
export function rgbToHex(rgb: string) {
|
||||||
|
let hex = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
|
||||||
|
if (!hex) return rgb;
|
||||||
|
return (
|
||||||
|
"#" +
|
||||||
|
hex
|
||||||
|
.slice(1)
|
||||||
|
.map((x) => {
|
||||||
|
return ("0" + parseInt(x).toString(16)).slice(-2);
|
||||||
|
})
|
||||||
|
.join("")
|
||||||
|
);
|
||||||
|
}
|
||||||
1033
src/helpers/exif.ts
Normal file
1033
src/helpers/exif.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,25 @@
|
|||||||
|
import MarkdownIt from 'markdown-it';
|
||||||
|
const parser = new MarkdownIt();
|
||||||
|
|
||||||
export default function markdownToText(markdown: string): string {
|
export default function markdownToText(markdown: string): string {
|
||||||
return markdown.replace(/#|`|\*|_|~/g, '');
|
return parser
|
||||||
|
.render(markdown)
|
||||||
|
.split('\n')
|
||||||
|
.map((str) => str.trim())
|
||||||
|
.map((str) => {
|
||||||
|
return str.replace(/<\/?[^>]+(>|$)/g, '').split('\n');
|
||||||
|
})
|
||||||
|
.flat()
|
||||||
|
.filter((str) => !str.startsWith("import")
|
||||||
|
&& !str.startsWith("export")
|
||||||
|
&& !str.startsWith("#")
|
||||||
|
&& !str.startsWith("const")
|
||||||
|
&& !str.startsWith("function")
|
||||||
|
&& !str.startsWith("export")
|
||||||
|
&& !str.startsWith("import")
|
||||||
|
&& !str.startsWith("<")
|
||||||
|
&& !str.startsWith("let")
|
||||||
|
&& str.length > 0
|
||||||
|
)
|
||||||
|
.join(' ');
|
||||||
}
|
}
|
||||||
|
|||||||
49
src/helpers/normalizeWheel.ts
Normal file
49
src/helpers/normalizeWheel.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
|
||||||
|
// Reasonable defaults
|
||||||
|
var PIXEL_STEP = 10;
|
||||||
|
var LINE_HEIGHT = 40;
|
||||||
|
var PAGE_HEIGHT = 800;
|
||||||
|
|
||||||
|
export default function normalizeWheel(/*object*/ event) /*object*/ {
|
||||||
|
var sX = 0, sY = 0, // spinX, spinY
|
||||||
|
pX = 0, pY = 0; // pixelX, pixelY
|
||||||
|
|
||||||
|
// Legacy
|
||||||
|
if ('detail' in event) { sY = event.detail; }
|
||||||
|
if ('wheelDelta' in event) { sY = -event.wheelDelta / 120; }
|
||||||
|
if ('wheelDeltaY' in event) { sY = -event.wheelDeltaY / 120; }
|
||||||
|
if ('wheelDeltaX' in event) { sX = -event.wheelDeltaX / 120; }
|
||||||
|
|
||||||
|
// side scrolling on FF with DOMMouseScroll
|
||||||
|
if ('axis' in event && event.axis === event.HORIZONTAL_AXIS) {
|
||||||
|
sX = sY;
|
||||||
|
sY = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pX = sX * PIXEL_STEP;
|
||||||
|
pY = sY * PIXEL_STEP;
|
||||||
|
|
||||||
|
if ('deltaY' in event) { pY = event.deltaY; }
|
||||||
|
if ('deltaX' in event) { pX = event.deltaX; }
|
||||||
|
|
||||||
|
if ((pX || pY) && event.deltaMode) {
|
||||||
|
if (event.deltaMode == 1) { // delta in LINE units
|
||||||
|
pX *= LINE_HEIGHT;
|
||||||
|
pY *= LINE_HEIGHT;
|
||||||
|
} else { // delta in PAGE units
|
||||||
|
pX *= PAGE_HEIGHT;
|
||||||
|
pY *= PAGE_HEIGHT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall-back if spin cannot be determined
|
||||||
|
if (pX && !sX) { sX = (pX < 1) ? -1 : 1; }
|
||||||
|
if (pY && !sY) { sY = (pY < 1) ? -1 : 1; }
|
||||||
|
|
||||||
|
return {
|
||||||
|
spinX: sX,
|
||||||
|
spinY: sY,
|
||||||
|
pixelX: pX,
|
||||||
|
pixelY: pY
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -10,6 +10,11 @@ export const ui = {
|
|||||||
en: {
|
en: {
|
||||||
"en": "English",
|
"en": "English",
|
||||||
"de": "Deutsch",
|
"de": "Deutsch",
|
||||||
|
"more-posts": "More Posts",
|
||||||
|
"more-projects": "More Projects",
|
||||||
|
"read-more": "Read More",
|
||||||
|
"latest-posts": "Latest Posts",
|
||||||
|
"latest-projects": "Latest Projects",
|
||||||
'home.title': 'Hi, I’m Max :)',
|
'home.title': 'Hi, I’m Max :)',
|
||||||
'home.subtitle': 'Trained Media Designer, Blender Nerd, Developer and Hardware Tinkerer.',
|
'home.subtitle': 'Trained Media Designer, Blender Nerd, Developer and Hardware Tinkerer.',
|
||||||
'nav.blog': 'Blog',
|
'nav.blog': 'Blog',
|
||||||
@@ -20,6 +25,11 @@ export const ui = {
|
|||||||
de: {
|
de: {
|
||||||
"en": "English",
|
"en": "English",
|
||||||
"de": "Deutsch",
|
"de": "Deutsch",
|
||||||
|
"more-posts": "Mehr Posts",
|
||||||
|
"more-projects": "Mehr Projekte",
|
||||||
|
"latest-posts": "Neueste Posts",
|
||||||
|
"latest-projects": "Neueste Projekte",
|
||||||
|
"read-more": "weiterlesen",
|
||||||
'home.title': 'Hi, ich bin Max :)',
|
'home.title': 'Hi, ich bin Max :)',
|
||||||
'home.subtitle': 'Ausgebildeter Mediengestalter, Blender Nerd, Entwickler und Hardware Bastler.',
|
'home.subtitle': 'Ausgebildeter Mediengestalter, Blender Nerd, Entwickler und Hardware Bastler.',
|
||||||
'nav.blog': 'Blog',
|
'nav.blog': 'Blog',
|
||||||
|
|||||||
@@ -29,5 +29,7 @@ export function filterCollection<T extends { id: string }>(collection: T[], loca
|
|||||||
return collection.filter(post => {
|
return collection.filter(post => {
|
||||||
const [_, lang] = parseSlug(post?.id);
|
const [_, lang] = parseSlug(post?.id);
|
||||||
return lang === locale;
|
return lang === locale;
|
||||||
|
}).sort((a, b) => {
|
||||||
|
return a.data.date > b.data.date ? -1 : 1;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ const { title, width = "compact" } = Astro.props;
|
|||||||
<header>
|
<header>
|
||||||
<Nav />
|
<Nav />
|
||||||
</header>
|
</header>
|
||||||
<main class="flex flex-col gap-y-2xl">
|
<main class="flex flex-col mt-4xl gap-y-2xl">
|
||||||
<slot />
|
<slot />
|
||||||
</main>
|
</main>
|
||||||
<LanguagePicker />
|
<LanguagePicker />
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
import type { CollectionEntry } from "astro:content";
|
import type { CollectionEntry } from "astro:content";
|
||||||
import Layout from "./Layout.astro";
|
import Layout from "./Layout.astro";
|
||||||
import { useTranslatedPath } from "@i18n/utils";
|
import { useTranslatedPath } from "@i18n/utils";
|
||||||
import { getLocale } from "astro-i18n-aut";
|
|
||||||
|
|
||||||
type CustomProps = {
|
type CustomProps = {
|
||||||
layout?: "normal" | "transparent";
|
layout?: "normal" | "transparent";
|
||||||
@@ -10,52 +9,52 @@ type CustomProps = {
|
|||||||
};
|
};
|
||||||
type Props = CollectionEntry<"blog">["data"] & CustomProps;
|
type Props = CollectionEntry<"blog">["data"] & CustomProps;
|
||||||
|
|
||||||
const { title, date, _layout = "normal", backlink = "/blog" } = Astro.props;
|
const { title, date, _layout, backlink = "/blog" } = Astro.props;
|
||||||
const path = useTranslatedPath(Astro);
|
const path = useTranslatedPath(Astro);
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title={title}>
|
<Layout title={title}>
|
||||||
<article class={`layout-${_layout}`}>
|
<div
|
||||||
<div class="top-info">
|
class="top-info flex items-center place-content-between opacity-50 m-y-4"
|
||||||
<a href={path(backlink)}>← overview</a>
|
>
|
||||||
<div class="date">
|
<a class="flex items-center gap-1" href={path(backlink)}
|
||||||
{
|
><span class="i-tabler-arrow-left"></span> overview</a
|
||||||
date.toLocaleString("en-US", {
|
>
|
||||||
month: "long",
|
<div class="date">
|
||||||
day: "numeric",
|
{
|
||||||
year: "numeric",
|
date.toLocaleString("en-US", {
|
||||||
})
|
month: "long",
|
||||||
}
|
day: "numeric",
|
||||||
</div>
|
year: "numeric",
|
||||||
|
})
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<h1>{title}</h1>
|
</div>
|
||||||
|
<article class={`layout-${_layout} flex flex-col gap-2`}>
|
||||||
|
<h1 class="text-4xl my-4">{title}</h1>
|
||||||
<slot />
|
<slot />
|
||||||
</article>
|
</article>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
article {
|
article :global(h2) {
|
||||||
margin-top: 50px;
|
@apply text-3xl;
|
||||||
display: flex;
|
}
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
article :global(h3) {
|
||||||
|
@apply text-2xl;
|
||||||
|
}
|
||||||
|
|
||||||
|
article :global(h4) {
|
||||||
|
@apply text-xl;
|
||||||
|
}
|
||||||
|
|
||||||
|
article :global(img) {
|
||||||
|
border-radius: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
article.layout-transparent {
|
article.layout-transparent {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-info {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
color: var(--outline);
|
|
||||||
}
|
|
||||||
|
|
||||||
.top-info a {
|
|
||||||
text-decoration: none;
|
|
||||||
color: var(--outline);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
import { getCollection } from "astro:content";
|
import { getCollection } from "astro:content";
|
||||||
const pages = await getCollection("blog");
|
const pages = await getCollection("blog");
|
||||||
import Layout from "@layouts/Layout.astro";
|
import Layout from "@layouts/Layout.astro";
|
||||||
|
import HeroCard from "@components/HeroCard.astro";
|
||||||
|
import SmallCard from "@components/SmallCard.astro";
|
||||||
|
|
||||||
import { getLocale } from "astro-i18n-aut";
|
import { getLocale } from "astro-i18n-aut";
|
||||||
import { filterCollection } from "@i18n/utils";
|
import { filterCollection } from "@i18n/utils";
|
||||||
@@ -9,17 +11,29 @@ import { filterCollection } from "@i18n/utils";
|
|||||||
const locale = getLocale(Astro.url);
|
const locale = getLocale(Astro.url);
|
||||||
|
|
||||||
const posts = filterCollection(pages, locale);
|
const posts = filterCollection(pages, locale);
|
||||||
|
|
||||||
|
const featuredPosts = await Promise.all(
|
||||||
|
posts.slice(0, 3).map(async (post) => {
|
||||||
|
if (!post.data.headerImg) {
|
||||||
|
return post;
|
||||||
|
}
|
||||||
|
const { default: image } = await import(
|
||||||
|
`../../content/blog/${post.slug.split("/")[0]}/${post.data.headerImg}`
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
...post,
|
||||||
|
image,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const otherPosts = posts.slice(3);
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title="Dude">
|
<Layout title="Dude">
|
||||||
<hr />
|
{featuredPosts.map((post) => <HeroCard post={post} />)}
|
||||||
{
|
|
||||||
posts.map((post) => (
|
<div class="grid grid-cols-2 gap-4 mt-4">
|
||||||
<>
|
{otherPosts.map((post) => <SmallCard post={post} />)}
|
||||||
<a href={"blog/" + post.slug.split("/")[0]}>{post.data.title}</a>
|
</div>
|
||||||
<br />
|
|
||||||
</>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
<hr />
|
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|||||||
@@ -1,54 +1,71 @@
|
|||||||
---
|
---
|
||||||
import Layout from "@layouts/Layout.astro";
|
import Layout from "@layouts/Layout.astro";
|
||||||
import Max from "@components/Max.astro";
|
import Max from "@components/Max.astro";
|
||||||
import { Card } from "@components/card";
|
|
||||||
import { getCollection } from "astro:content";
|
import { getCollection } from "astro:content";
|
||||||
import { filterCollection, useTranslatedPath } from "@i18n/utils";
|
import {
|
||||||
import markdownToText from "@helpers/markdownToText";
|
filterCollection,
|
||||||
|
useTranslatedPath,
|
||||||
|
useTranslations,
|
||||||
|
} from "@i18n/utils";
|
||||||
import { getLocale } from "astro-i18n-aut";
|
import { getLocale } from "astro-i18n-aut";
|
||||||
|
import HeroCard from "@components/HeroCard.astro";
|
||||||
|
import SmallCard from "@components/SmallCard.astro";
|
||||||
|
import LinkCard from "@components/LinkCard.astro";
|
||||||
|
import ArrowA from "@components/arrows/ArrowA.astro";
|
||||||
|
import ArrowB from "@components/arrows/ArrowB.astro";
|
||||||
|
|
||||||
const projects = filterCollection(
|
const projects = filterCollection(
|
||||||
await getCollection("projects"),
|
await getCollection("projects"),
|
||||||
getLocale(Astro.url),
|
getLocale(Astro.url),
|
||||||
);
|
);
|
||||||
|
|
||||||
const translatePath = useTranslatedPath(Astro);
|
const t = useTranslations(Astro);
|
||||||
|
const tp = useTranslatedPath(Astro);
|
||||||
const locale = getLocale(Astro.url);
|
|
||||||
|
|
||||||
const projectSlides = await Promise.all(
|
|
||||||
projects.map(async (project) => ({
|
|
||||||
title: project.data.title,
|
|
||||||
description: markdownToText(project.body),
|
|
||||||
image: project.data.headerImg,
|
|
||||||
link: translatePath(project.slug),
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
|
|
||||||
const featuredProject = projects.find((project) => project.data?.featured);
|
const featuredProject = projects.find((project) => project.data?.featured);
|
||||||
|
const otherProjects = projects
|
||||||
|
.filter((project) => featuredProject !== project)
|
||||||
|
.sort((a) => (a?.data?.icon ? -1 : 1))
|
||||||
|
.slice(0, 3);
|
||||||
|
|
||||||
|
const posts = filterCollection(
|
||||||
|
await getCollection("blog"),
|
||||||
|
getLocale(Astro.url),
|
||||||
|
);
|
||||||
|
|
||||||
|
const featuredPost = posts.find((post) => post.data?.featured);
|
||||||
|
const otherPosts = posts.filter((post) => featuredPost !== post).slice(0, 3);
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title="Max Richter">
|
<Layout title="Max Richter">
|
||||||
<Max />
|
<Max />
|
||||||
{
|
<section class="relative my-8">
|
||||||
featuredProject && (
|
<span class="i-tabler-circle-arrow-right-thin"></span>
|
||||||
<Card classes="flex overflow-hidden gradient border-1 border-light">
|
<ArrowA />
|
||||||
<Card.Content classes="p-8">
|
{featuredProject && <HeroCard post={featuredProject} />}
|
||||||
<Card.Title>{featuredProject.data.title}</Card.Title>
|
<div class="grid grid-cols-2 gap-4 mt-4">
|
||||||
<Card.Description>
|
{
|
||||||
{markdownToText(featuredProject.body).slice(0, 200)}
|
otherProjects.length > 0 &&
|
||||||
</Card.Description>
|
otherProjects.map((project) => <SmallCard post={project} />)
|
||||||
<Card.ReadMoreButton
|
}
|
||||||
link={translatePath(
|
<LinkCard
|
||||||
`/projects/${featuredProject.slug.split("/")[0]}`,
|
link={tp("/projects")}
|
||||||
)}
|
title={t("more-projects")}
|
||||||
/>
|
icon="circle-arrow-right"
|
||||||
</Card.Content>
|
/>
|
||||||
<Card.Image
|
</div>
|
||||||
src={featuredProject.data.headerImg}
|
</section>
|
||||||
alt={featuredProject.data.title}
|
|
||||||
/>
|
<section class="relative my-8">
|
||||||
</Card>
|
{featuredPost && <HeroCard post={featuredPost} />}
|
||||||
)
|
|
||||||
}
|
<ArrowB />
|
||||||
|
<div class="grid grid-cols-2 gap-4 mt-4">
|
||||||
|
{
|
||||||
|
otherPosts.length > 0 &&
|
||||||
|
otherPosts.map((post) => <SmallCard post={post} />)
|
||||||
|
}
|
||||||
|
<LinkCard link={tp("/blog")} title={t("more-posts")} />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { getCollection } from "astro:content";
|
|||||||
import Layout from "@layouts/Layout.astro";
|
import Layout from "@layouts/Layout.astro";
|
||||||
import { getLocale } from "astro-i18n-aut";
|
import { getLocale } from "astro-i18n-aut";
|
||||||
import { filterCollection } from "@i18n/utils";
|
import { filterCollection } from "@i18n/utils";
|
||||||
|
import HeroCard from "@components/HeroCard.astro";
|
||||||
|
|
||||||
const locale = getLocale(Astro.url);
|
const locale = getLocale(Astro.url);
|
||||||
const pages = await getCollection("photos");
|
const pages = await getCollection("photos");
|
||||||
@@ -10,16 +11,5 @@ const posts = filterCollection(pages, locale);
|
|||||||
---
|
---
|
||||||
|
|
||||||
<Layout title="Dude">
|
<Layout title="Dude">
|
||||||
<hr />
|
{posts.map((post) => <HeroCard post={post} />)}
|
||||||
{
|
|
||||||
posts.map((post) => (
|
|
||||||
<>
|
|
||||||
<>
|
|
||||||
<a href={"photos/" + post.slug.split("/")[0]}>{post.data.title}</a>
|
|
||||||
<br />
|
|
||||||
</>
|
|
||||||
</>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
<hr />
|
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { getCollection } from "astro:content";
|
|||||||
import Layout from "@layouts/Layout.astro";
|
import Layout from "@layouts/Layout.astro";
|
||||||
import { getLocale } from "astro-i18n-aut";
|
import { getLocale } from "astro-i18n-aut";
|
||||||
import { filterCollection } from "@i18n/utils";
|
import { filterCollection } from "@i18n/utils";
|
||||||
|
import HeroCard from "@components/HeroCard.astro";
|
||||||
|
|
||||||
const locale = getLocale(Astro.url);
|
const locale = getLocale(Astro.url);
|
||||||
const pages = await getCollection("projects");
|
const pages = await getCollection("projects");
|
||||||
@@ -10,16 +11,5 @@ const posts = filterCollection(pages, locale);
|
|||||||
---
|
---
|
||||||
|
|
||||||
<Layout title="Dude">
|
<Layout title="Dude">
|
||||||
<hr />
|
{posts.map((post) => <HeroCard post={post} />)}
|
||||||
{
|
|
||||||
posts.map((post) => (
|
|
||||||
<>
|
|
||||||
<>
|
|
||||||
<a href={"projects/" + post.slug.split("/")[0]}>{post.data.title}</a>
|
|
||||||
<br />
|
|
||||||
</>
|
|
||||||
</>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
<hr />
|
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ export default defineConfig({
|
|||||||
"bg": ['bg-neutral-000', "dark:bg-neutral-500"],
|
"bg": ['bg-neutral-000', "dark:bg-neutral-500"],
|
||||||
"bg-light": ['bg-neutral-100', "dark:bg-neutral-400"],
|
"bg-light": ['bg-neutral-100', "dark:bg-neutral-400"],
|
||||||
"text-neutral": ['text-neutral-900', "dark:text-neutral-100"],
|
"text-neutral": ['text-neutral-900', "dark:text-neutral-100"],
|
||||||
"border-neutral": ['border-neutral-300', "dark:border-neutral-1000"],
|
"border-neutral": "border-neutral-300 dark:border-neutral-1000",
|
||||||
"border-light": "border-neutral-200 dark:border-neutral-500",
|
"border-light": "border-neutral-100 dark:border-neutral-500",
|
||||||
"divide-x-neutral": ['divide-x-neutral-300', "dark:divide-x-neutral-1000"],
|
"divide-x-neutral": ['divide-x-neutral-300', "dark:divide-x-neutral-1000"],
|
||||||
"gradient": "bg-gradient-to-br from-neutral-000 dark:from-neutral-500 to-neutral-200 dark:to-neutral-800",
|
"gradient": "bg-gradient-to-br from-neutral-000 dark:from-neutral-500 to-neutral-200 dark:to-neutral-800",
|
||||||
},
|
},
|
||||||
@@ -24,6 +24,7 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
colors: {
|
colors: {
|
||||||
neutral: {
|
neutral: {
|
||||||
|
"000": "var(--neutral-000)",
|
||||||
"100": "var(--neutral-100)",
|
"100": "var(--neutral-100)",
|
||||||
"200": "var(--neutral-200)",
|
"200": "var(--neutral-200)",
|
||||||
"300": "var(--neutral-300)",
|
"300": "var(--neutral-300)",
|
||||||
|
|||||||
Reference in New Issue
Block a user