Compare commits
142 Commits
main
..
9eadefdb51
| Author | SHA1 | Date | |
|---|---|---|---|
|
9eadefdb51
|
|||
|
83c099873d
|
|||
|
cf336da3c4
|
|||
|
c33691c23c
|
|||
|
1ca044fe13
|
|||
|
467dcbfd32
|
|||
|
190edd0915
|
|||
|
5c1df62fd2
|
|||
|
f52da7f3aa
|
|||
|
6230126d38
|
|||
|
231bdb09a3
|
|||
|
6aa48cc60e
|
|||
|
7d0ccac81f
|
|||
|
c542408f6a
|
|||
|
3b583eade5
|
|||
|
342f83c783
|
|||
|
ae266dbdc5
|
|||
|
3a120e32fc
|
|||
|
d9a2f63865
|
|||
|
24a66940e9
|
|||
|
2446629515
|
|||
|
7048db9d76
|
|||
|
0db489269b
|
|||
|
1c3f136f57
|
|||
|
038e78f27d
|
|||
|
93c00e1c7e
|
|||
|
c914ee6719
|
|||
|
9ca190550d
|
|||
|
edecf0bf75
|
|||
|
a27e9046c0
|
|||
|
5ba54fee6e
|
|||
|
06e5126fe0
|
|||
|
61251e2c85
|
|||
|
a1b8eb22e5
|
|||
|
246fc3ae44
|
|||
|
48f451ceb0
|
|||
|
fba2337b9c
|
|||
| 546b36f44f | |||
|
f84704d20b
|
|||
|
666b618a73
|
|||
| b32ca7d65e | |||
| c74b314b1e | |||
| 50ce8b3ff7 | |||
| 8e293c204d | |||
| 972c2382f3 | |||
| e0543f2a58 | |||
| 3d78b9e56c | |||
| 84e56f2668 | |||
| 8af8db0714 | |||
| 59eeadd4b3 | |||
| 6aa6ddabb0 | |||
| 19a703367d | |||
| aa333eb2cd | |||
| 4c3e35efbf | |||
| dccd816f6e | |||
| d57cded06d | |||
| 57ef1426ee | |||
| e68f433f1e | |||
| 6f433e08ce | |||
| f55ae8a267 | |||
| e5726437ed | |||
| 08c852a9e8 | |||
| 91d0e55d3c | |||
| de16fcea56 | |||
| f9c5d4c421 | |||
| f1c6bfdf87 | |||
| d1d6867130 | |||
| e41ef2fceb | |||
| 146c8d5279 | |||
| 187fd302fb | |||
| 44c03ff4c6 | |||
| 3216d79bd0 | |||
| cf26695719 | |||
| d1e4e93e4a | |||
| 6fb457c7ce | |||
| 13f3312bb2 | |||
| f9833fa5eb | |||
| cf74b0f7ef | |||
| 93bb879baf | |||
| 015acaf14d | |||
| f49a25cfc8 | |||
| 21eb2e8c0c | |||
| 1cdce4c223 | |||
| 94b60be9b6 | |||
| 2df825187e | |||
| c513605de6 | |||
| 0f1ece8a3e | |||
| db17e56559 | |||
| 5e7a63230b | |||
| dcd19b6da0 | |||
| 4b0f214568 | |||
| 5b11664a3f | |||
| ff1188b4b2 | |||
| 7c331406a5 | |||
| 0ab1e1068d | |||
| f76477db98 | |||
| 96c053d5ff | |||
| 68431e6b9c | |||
| f86661bbba | |||
| 5d59e2171d | |||
| 82eb0657e2 | |||
| 613ab7aef9 | |||
| 38a7f83096 | |||
| 74ab286f15 | |||
| 67d82f05f1 | |||
| fe1c2cc218 | |||
| 3a8a3987b1 | |||
| d1c4d505fe | |||
| 7b7e02ad28 | |||
| f76f3dd96e | |||
| 2a09d65863 | |||
| ee391e128f | |||
| f9072c3cfc | |||
| 3ee3879db7 | |||
| 1e04a7be6f | |||
| d025f7e01b | |||
| 46230f2140 | |||
| 4c1a6ed9f6 | |||
| d1a235369d | |||
| f52a13171c | |||
| feb9b21ff8 | |||
| 58b74bb801 | |||
| 5e931f15d3 | |||
| 6168a386fa | |||
| ced2d230a3 | |||
| 54ac6555c4 | |||
| 31864f616a | |||
| da8a16c416 | |||
| ba4a3c0af2 | |||
| 9105249c87 | |||
| e02321f5c3 | |||
| fe3454f046 | |||
| 116ea85df7 | |||
| 59d6095278 | |||
| 91e60bb090 | |||
| e4b941a1dc | |||
| dba9d51133 | |||
| e800314589 | |||
| 93baa3b6b0 | |||
| d4128840b9 | |||
| 31b24de86c | |||
| f0129ecc76 |
@@ -3,7 +3,3 @@
|
|||||||
*.gif filter=lfs diff=lfs merge=lfs -text
|
*.gif filter=lfs diff=lfs merge=lfs -text
|
||||||
*.woff2 filter=lfs diff=lfs merge=lfs -text
|
*.woff2 filter=lfs diff=lfs merge=lfs -text
|
||||||
*.ttf filter=lfs diff=lfs merge=lfs -text
|
*.ttf filter=lfs diff=lfs merge=lfs -text
|
||||||
*.jpeg filter=lfs diff=lfs merge=lfs -text
|
|
||||||
*.mp4 filter=lfs diff=lfs merge=lfs -text
|
|
||||||
*.JPEG filter=lfs diff=lfs merge=lfs -text
|
|
||||||
*.webm filter=lfs diff=lfs merge=lfs -text
|
|
||||||
|
|||||||
@@ -19,28 +19,37 @@ jobs:
|
|||||||
|
|
||||||
- name: 🔄 Checkout code
|
- name: 🔄 Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
|
||||||
lfs: true
|
|
||||||
|
|
||||||
- name: 🔢 Prepare cache keys
|
- name: 🔢 Calculate cache IDs
|
||||||
run: |
|
run: |
|
||||||
|
# Calculate cache IDs for Git LFS and PNPM
|
||||||
|
git lfs ls-files -l | cut -d' ' -f1 | sort > .lfs-assets-id
|
||||||
|
LFS_CACHE_ID=$(cat .lfs-assets-id | md5sum)-v1
|
||||||
|
PNPM_CACHE_ID=$(cat pnpm-lock.yaml | md5sum)-v1
|
||||||
|
echo "LFS_CACHE_ID=$LFS_CACHE_ID" >> $GITHUB_ENV
|
||||||
echo "PNPM_STORE_PATH=$(pnpm store path)" >> $GITHUB_ENV
|
echo "PNPM_STORE_PATH=$(pnpm store path)" >> $GITHUB_ENV
|
||||||
|
echo "PNPM_CACHE_ID=$PNPM_CACHE_ID" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: 🗄️ Cache Git LFS objects
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: .git/lfs
|
||||||
|
key: ${{ runner.os }}-lfs-${{ env.LFS_CACHE_ID }}
|
||||||
|
|
||||||
- name: 🛠️ Cache PNPM dependencies
|
- name: 🛠️ Cache PNPM dependencies
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ${{ env.PNPM_STORE_PATH }}
|
path: ${{ env.PNPM_STORE_PATH }}
|
||||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-pnpm-store-
|
|
||||||
|
|
||||||
- name: 📷 Cache Astro Images
|
- name: 📷 Cache Astro Images
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: node_modules/.astro
|
path: node_modules/.astro
|
||||||
key: ${{ runner.os }}-astro-${{ hashFiles('**/pnpm-lock.yaml') }}
|
key: ${{ runner.os }}-astro-v1
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-astro-
|
- name: 🔄 Pull Git LFS files
|
||||||
|
run: git lfs pull
|
||||||
|
|
||||||
- name: 🏗️ Build site
|
- name: 🏗️ Build site
|
||||||
run: |
|
run: |
|
||||||
@@ -61,8 +70,8 @@ jobs:
|
|||||||
- name: 🚀 Deploy Changed Files via rclone
|
- name: 🚀 Deploy Changed Files via rclone
|
||||||
run: |
|
run: |
|
||||||
echo "Uploading _astro assets"
|
echo "Uploading _astro assets"
|
||||||
rclone sync --update -v --progress --size-only --fast-list --stats 2s --stats-one-line ./dist/_astro sftp-remote:${REMOTE_DIR}/_astro --transfers 4
|
rclone sync --update -v --progress --size-only --stats 2s --stats-one-line ./dist/_astro sftp-remote:${REMOTE_DIR}/_astro --transfers 4
|
||||||
echo "Uploading the rest"
|
echo "Uploading the rest"
|
||||||
rclone sync --update -v --progress --exclude _astro/** --fast-list --stats 2s --stats-one-line ./dist/ sftp-remote:${REMOTE_DIR} --transfers 4
|
rclone sync --update -v --progress --exclude _astro/** --stats 2s --stats-one-line ./dist/ sftp-remote:${REMOTE_DIR} --transfers 4
|
||||||
env:
|
env:
|
||||||
REMOTE_DIR: ${{ vars.REMOTE_DIR }}
|
REMOTE_DIR: ${{ vars.REMOTE_DIR }}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM node:25-alpine
|
FROM node:21-alpine
|
||||||
|
|
||||||
# Install necessary packages
|
# Install necessary packages
|
||||||
RUN apk add --no-cache \
|
RUN apk add --no-cache \
|
||||||
|
|||||||
@@ -42,7 +42,9 @@ export default defineConfig({
|
|||||||
],
|
],
|
||||||
server: {
|
server: {
|
||||||
watch: {
|
watch: {
|
||||||
|
// Customize watch behavior to reduce file watchers
|
||||||
ignored: ["**/node_modules/**", "**/dist/**", "**/.git/**"],
|
ignored: ["**/node_modules/**", "**/dist/**", "**/.git/**"],
|
||||||
|
usePolling: process.env.NODE_ENV === "production",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,31 +10,33 @@
|
|||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/check": "^0.9.6",
|
"@astrojs/check": "^0.9.5",
|
||||||
"@astrojs/mdx": "^4.3.13",
|
"@astrojs/mdx": "^4.3.7",
|
||||||
"@astrojs/svelte": "^7.2.4",
|
"@astrojs/svelte": "^7.2.0",
|
||||||
"astro": "^5.16.6",
|
"@astrojs/tailwind": "^6.0.2",
|
||||||
|
"astro": "^5.14.8",
|
||||||
"astro-i18n-aut": "^0.7.3",
|
"astro-i18n-aut": "^0.7.3",
|
||||||
"exifreader": "^4.33.1",
|
"exifreader": "^4.32.0",
|
||||||
"svelte": "^5.46.1",
|
"svelte": "^5.39.8",
|
||||||
"svelte-gestures": "^5.2.2",
|
"svelte-gestures": "^5.2.2",
|
||||||
|
"tailwindcss": "^4.1.14",
|
||||||
"thumbhash": "^0.1.1",
|
"thumbhash": "^0.1.1",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "^5.9.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@astrojs/sitemap": "^3.6.0",
|
"@astrojs/sitemap": "^3.6.0",
|
||||||
"@iconify-json/tabler": "^1.2.26",
|
"@iconify-json/tabler": "^1.2.23",
|
||||||
"@types/markdown-it": "^14.1.2",
|
"@types/markdown-it": "^14.1.2",
|
||||||
"@unocss/preset-icons": "^66.5.12",
|
"@unocss/preset-icons": "^66.5.2",
|
||||||
"@unocss/reset": "^66.5.12",
|
"@unocss/reset": "^66.5.2",
|
||||||
"astro-font": "^1.1.0",
|
"astro-font": "^1.1.0",
|
||||||
"markdown-it": "^14.1.0",
|
"markdown-it": "^14.1.0",
|
||||||
"ogl": "^1.0.11",
|
"ogl": "^1.0.11",
|
||||||
"prettier": "^3.7.4",
|
"prettier": "^3.6.2",
|
||||||
"prettier-plugin-astro": "^0.14.1",
|
"prettier-plugin-astro": "^0.14.1",
|
||||||
"sharp": "^0.34.5",
|
"sharp": "^0.34.4",
|
||||||
"unocss": "^66.5.12",
|
"unocss": "^66.5.2",
|
||||||
"unplugin-icons": "^22.5.0",
|
"unplugin-icons": "^22.4.2",
|
||||||
"vite-plugin-glsl": "^1.5.5"
|
"vite-plugin-glsl": "^1.5.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { onMount } from "svelte";
|
|
||||||
|
|
||||||
let searchTerm = "";
|
|
||||||
let staticContainer: HTMLElement | null;
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
staticContainer = document.getElementById("resource-list-static");
|
|
||||||
});
|
|
||||||
|
|
||||||
function search() {
|
|
||||||
if (!staticContainer) return;
|
|
||||||
|
|
||||||
const lowerCaseSearchTerm = searchTerm.toLowerCase().trim();
|
|
||||||
|
|
||||||
if (!lowerCaseSearchTerm) {
|
|
||||||
[...staticContainer?.children].forEach((element: HTMLElement) => {
|
|
||||||
element.style.display = "";
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const element of staticContainer?.children) {
|
|
||||||
const el = element as HTMLElement;
|
|
||||||
const isMatch = el.dataset.searchTerm?.includes(lowerCaseSearchTerm);
|
|
||||||
if (isMatch) {
|
|
||||||
el.style.display = "";
|
|
||||||
} else {
|
|
||||||
el.style.display = "none";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$: search();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="search-wrapper my-4 noise relative">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
bind:value={searchTerm}
|
|
||||||
placeholder={`Search...`}
|
|
||||||
class="w-full p-2 gradient border-1 border-neutral rounded noise" />
|
|
||||||
</div>
|
|
||||||
@@ -25,14 +25,10 @@ const translatePath = useTranslatedPath(Astro.url);
|
|||||||
const t = useTranslations(Astro.url);
|
const t = useTranslations(Astro.url);
|
||||||
|
|
||||||
const link = translatePath(`/${collection}/${id.split("/")[0]}`);
|
const link = translatePath(`/${collection}/${id.split("/")[0]}`);
|
||||||
|
|
||||||
const image = cover as unknown;
|
|
||||||
|
|
||||||
const hasCover = typeof image === "string" ? !!image?.length : !!cover?.src;
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<Card
|
<Card
|
||||||
classes={`grid gradient border-1 border-neutral overflow-hidden ${hasCover ? "grid-rows-[200px_1fr] xs:grid-rows-none xs:grid-cols-[1fr_200px]" : ""}`}>
|
classes={`grid gradient border-1 border-neutral overflow-hidden ${cover ? "grid-rows-[200px_1fr] xs:grid-rows-none xs:grid-cols-[1fr_200px]" : ""}`}>
|
||||||
<Card.Content classes="px-8 py-7 order-last xs:order-first">
|
<Card.Content classes="px-8 py-7 order-last xs:order-first">
|
||||||
{
|
{
|
||||||
(date || body || rating !== undefined) && (
|
(date || body || rating !== undefined) && (
|
||||||
@@ -61,16 +57,14 @@ const hasCover = typeof image === "string" ? !!image?.length : !!cover?.src;
|
|||||||
<Card.ReadMoreButton link={link} text={t("read-more")} />
|
<Card.ReadMoreButton link={link} text={t("read-more")} />
|
||||||
</Card.Content>
|
</Card.Content>
|
||||||
{
|
{
|
||||||
hasCover && (
|
cover && (
|
||||||
<a href={link}>
|
<a href={link}>
|
||||||
<Image
|
<Image
|
||||||
hash
|
hash
|
||||||
loader={false}
|
loader={false}
|
||||||
src={cover as ImageMetadata}
|
src={cover as ImageMetadata}
|
||||||
alt={"cover for " + title}
|
alt={"cover for " + title}
|
||||||
class="h-full right-0 object-cover object-center rounded-none border-l border-neutral"
|
class="right-0 h-full object-cover object-center rounded-none border-l border-neutral"
|
||||||
pictureClass="h-full"
|
|
||||||
thumbnail
|
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import type { ImageMetadata } from "astro";
|
import type { ImageMetadata } from "astro";
|
||||||
import { Picture as AstroImage } from "astro:assets";
|
import { Picture as AstroImage } from "astro:assets";
|
||||||
import { inferRemoteSize } from "astro/assets/utils";
|
import { inferRemoteSize } from "astro/assets/utils";
|
||||||
import { getProcessedImage } from "@helpers/image";
|
import { generateThumbHash, getExifData } from "@helpers/image";
|
||||||
interface Props {
|
interface Props {
|
||||||
src: ImageMetadata & { fsPath?: string; src?: string };
|
src: ImageMetadata & { fsPath?: string; src?: string };
|
||||||
alt: string;
|
alt: string;
|
||||||
@@ -12,48 +12,44 @@ interface Props {
|
|||||||
hash?: boolean;
|
hash?: boolean;
|
||||||
loader?: boolean;
|
loader?: boolean;
|
||||||
maxWidth?: number;
|
maxWidth?: number;
|
||||||
thumbnail?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkImage(
|
async function checkImage(image: ImageMetadata) {
|
||||||
image: ImageMetadata,
|
|
||||||
): Promise<{ height: number; width: number } | undefined> {
|
|
||||||
const src = typeof image === "string" ? image : image.src;
|
const src = typeof image === "string" ? image : image.src;
|
||||||
if (!src) return;
|
if (!src) return false;
|
||||||
try {
|
try {
|
||||||
if (src.startsWith("/@fs") || src.startsWith("/_astro")) return image;
|
if (src.startsWith("/@fs") || src.startsWith("/_astro")) return true;
|
||||||
const res = await inferRemoteSize(src);
|
const res = await inferRemoteSize(src);
|
||||||
if (res.format) {
|
if (res.format) {
|
||||||
image.format = res.format;
|
image.format = res.format;
|
||||||
return res;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to load: ", src);
|
console.log("Failed to load: ", src);
|
||||||
}
|
}
|
||||||
return;
|
return false;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("\n");
|
console.log("\n");
|
||||||
console.log("Failed to fetch: ", src);
|
console.log("Failed to fetch: ", src);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
src: image,
|
src: image,
|
||||||
loader = true,
|
loader = true,
|
||||||
|
caption,
|
||||||
pictureClass = "",
|
pictureClass = "",
|
||||||
hash = true,
|
hash = true,
|
||||||
alt,
|
alt,
|
||||||
maxWidth,
|
maxWidth,
|
||||||
thumbnail = false,
|
|
||||||
} = Astro.props;
|
} = Astro.props;
|
||||||
|
|
||||||
|
let thumbhash = hash && (await generateThumbHash(image));
|
||||||
const imageOk = await checkImage(image);
|
const imageOk = await checkImage(image);
|
||||||
|
|
||||||
const { thumbhash, exif } = imageOk
|
let exif = imageOk && (await getExifData(image));
|
||||||
? await getProcessedImage(image)
|
|
||||||
: { thumbhash: undefined, exif: undefined };
|
|
||||||
|
|
||||||
const definedSizes = [
|
const sizes = [
|
||||||
{
|
{
|
||||||
width: 240,
|
width: 240,
|
||||||
media: "(max-width: 360px)",
|
media: "(max-width: 360px)",
|
||||||
@@ -69,11 +65,7 @@ const definedSizes = [
|
|||||||
{
|
{
|
||||||
width: image.width,
|
width: image.width,
|
||||||
},
|
},
|
||||||
];
|
].filter((size) => !maxWidth || size.width <= maxWidth);
|
||||||
|
|
||||||
const sizes = thumbnail
|
|
||||||
? [definedSizes[1]]
|
|
||||||
: definedSizes.filter((size) => !maxWidth || size.width <= maxWidth);
|
|
||||||
---
|
---
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -83,19 +75,16 @@ const sizes = thumbnail
|
|||||||
alt={alt}
|
alt={alt}
|
||||||
data-thumbhash={thumbhash}
|
data-thumbhash={thumbhash}
|
||||||
data-exif={JSON.stringify(exif)}
|
data-exif={JSON.stringify(exif)}
|
||||||
width={imageOk?.width}
|
inferSize={true}
|
||||||
height={imageOk?.height}
|
|
||||||
pictureAttributes={{
|
pictureAttributes={{
|
||||||
class: `${hash ? "block h-full relative" : ""} ${loader ? "thumb" : ""} ${pictureClass}`,
|
class: `${hash ? "block h-full relative" : ""} ${loader ? "thumb" : ""} ${pictureClass}`,
|
||||||
}}
|
}}
|
||||||
class={`${Astro.props.class} w-full`}
|
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(", ")}>
|
||||||
<slot />
|
<slot />
|
||||||
</AstroImage>
|
</AstroImage>
|
||||||
) : (
|
) : undefined
|
||||||
<div>{JSON.stringify({ imageOk: imageOk, image })}</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -218,9 +218,7 @@
|
|||||||
{#each images as _, i}
|
{#each images as _, i}
|
||||||
<button
|
<button
|
||||||
aria-label={`Image ${i + 1}`}
|
aria-label={`Image ${i + 1}`}
|
||||||
class="rounded bg-light"
|
|
||||||
class:active={currentIndex === i}
|
class:active={currentIndex === i}
|
||||||
class:bg-light={currentIndex === i}
|
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
currentIndex = i;
|
currentIndex = i;
|
||||||
}}></button>
|
}}></button>
|
||||||
@@ -228,24 +226,9 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<button
|
<button class="left" on:click={() => addIndex(-1)}><</button>
|
||||||
class="left flex-1 bg-light"
|
<button class="right" on:click={() => addIndex(+1)}>></button>
|
||||||
aria-label="previous image"
|
<button class="close" on:click={() => setIndex(-1)}>X</button>
|
||||||
on:click={() => addIndex(- 1)}>
|
|
||||||
<span class="i-tabler-arrow-left" />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="right flex-1 bg-light"
|
|
||||||
aria-label="next image"
|
|
||||||
on:click={() => addIndex( + 1)}>
|
|
||||||
<span class="i-tabler-arrow-right" />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="close bg-light"
|
|
||||||
aria-label="close gallery"
|
|
||||||
on:click={() => setIndex(-1)}>
|
|
||||||
<span class="i-tabler-x" />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{#if currentIndex > -1}
|
{#if currentIndex > -1}
|
||||||
<div
|
<div
|
||||||
@@ -295,7 +278,6 @@
|
|||||||
transform-origin: left;
|
transform-origin: left;
|
||||||
transition: transform 0.3s ease;
|
transition: transform 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gallery-wrapper {
|
.gallery-wrapper {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 199;
|
z-index: 199;
|
||||||
@@ -393,31 +375,27 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.gallery-wrapper > button {
|
.gallery-wrapper > button {
|
||||||
border-radius: 6px;
|
background: var(--neutral-800);
|
||||||
padding: 0;
|
color: white;
|
||||||
|
border-radius: 0px;
|
||||||
|
border: none;
|
||||||
|
padding: 10px 20px;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 200;
|
z-index: 200;
|
||||||
font-size: 1.5rem;
|
|
||||||
width: 44px;
|
|
||||||
height: 44px;
|
|
||||||
display: grid;
|
|
||||||
place-items: center;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.close {
|
.close {
|
||||||
top: 10px;
|
top: 0;
|
||||||
right: 10px;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.left {
|
.left {
|
||||||
bottom: 10px;
|
bottom: 0;
|
||||||
left: 10px;
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.right {
|
.right {
|
||||||
bottom: 10px;
|
bottom: 0;
|
||||||
right: 10px;
|
right: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -8,14 +8,11 @@
|
|||||||
let height: number;
|
let height: number;
|
||||||
let loaded = false;
|
let loaded = false;
|
||||||
|
|
||||||
function hide(index: number) {
|
function hide(img: HTMLPictureElement) {
|
||||||
const img = images[index];
|
|
||||||
img.classList.remove("active");
|
img.classList.remove("active");
|
||||||
}
|
}
|
||||||
|
|
||||||
const heightCache = [];
|
function show(img: HTMLPictureElement) {
|
||||||
function show(index: number) {
|
|
||||||
const img = images[index];
|
|
||||||
img.classList.add("active");
|
img.classList.add("active");
|
||||||
const _img = img.querySelector("img") || img;
|
const _img = img.querySelector("img") || img;
|
||||||
if (!_img) return;
|
if (!_img) return;
|
||||||
@@ -24,12 +21,9 @@
|
|||||||
_img.style.opacity = "1";
|
_img.style.opacity = "1";
|
||||||
});
|
});
|
||||||
altText = _img["alt"] ?? _img.getAttribute("alt") ?? "";
|
altText = _img["alt"] ?? _img.getAttribute("alt") ?? "";
|
||||||
if (heightCache[index]) {
|
height = _img.getBoundingClientRect().height;
|
||||||
height = heightCache[index];
|
|
||||||
}
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
height = heightCache[index] ?? _img.getBoundingClientRect().height;
|
height = _img.getBoundingClientRect().height;
|
||||||
heightCache[index] = height;
|
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,16 +31,16 @@
|
|||||||
function setIndex(i: number) {
|
function setIndex(i: number) {
|
||||||
if (i < 0) i = images.length - 1;
|
if (i < 0) i = images.length - 1;
|
||||||
if (i >= images.length) i = 0;
|
if (i >= images.length) i = 0;
|
||||||
hide(index);
|
hide(images[index]);
|
||||||
index = i;
|
index = i;
|
||||||
show(index);
|
show(images[index]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if (slot && !images?.length) {
|
$: if (slot && !images?.length) {
|
||||||
images = Array.from(slot.querySelectorAll("picture"));
|
images = Array.from(slot.querySelectorAll("picture"));
|
||||||
if (images?.length) {
|
if (images?.length) {
|
||||||
images.forEach((_, i) => hide(i));
|
images.forEach(hide);
|
||||||
show(index);
|
show(images[index]);
|
||||||
images[index].onload = () => {
|
images[index].onload = () => {
|
||||||
loaded = true;
|
loaded = true;
|
||||||
height = images[index].getBoundingClientRect().height;
|
height = images[index].getBoundingClientRect().height;
|
||||||
@@ -60,10 +54,11 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
class="wrapper grid overflow-hidden rounded-xl border border-neutral"
|
class="wrapper grid overflow-hidden rounded-xl border border-neutral"
|
||||||
class:title={true}
|
class:title
|
||||||
class:not-loaded={!loaded}
|
class:not-loaded={!loaded}
|
||||||
class:loaded
|
class:loaded
|
||||||
style={`--height:${height}px`}>
|
style={`--height:${height}px`}>
|
||||||
|
{#if title}
|
||||||
<div class="flex items-center p-x-4 p-y-6 bg justify-between">
|
<div class="flex items-center p-x-4 p-y-6 bg justify-between">
|
||||||
<h3>{title}</h3>
|
<h3>{title}</h3>
|
||||||
|
|
||||||
@@ -72,15 +67,16 @@
|
|||||||
<button
|
<button
|
||||||
class="flex-1 i-tabler-arrow-left"
|
class="flex-1 i-tabler-arrow-left"
|
||||||
aria-label="previous image"
|
aria-label="previous image"
|
||||||
on:click={() => setIndex(index - 1)}></button>
|
on:click={() => setIndex(index - 1)} />
|
||||||
<button
|
<button
|
||||||
class="flex-1 i-tabler-arrow-right"
|
class="flex-1 i-tabler-arrow-right"
|
||||||
aria-label="next image"
|
aria-label="next image"
|
||||||
on:click={() => setIndex(index + 1)}></button>
|
on:click={() => setIndex(index + 1)} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div class="images border-block border-neutral" bind:this={slot}>
|
<div class="images border-t-1 border-b-1 border-neutral" bind:this={slot}>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
<div class="px-4 flex items-center place-content-between bg">
|
<div class="px-4 flex items-center place-content-between bg">
|
||||||
|
|||||||
@@ -1,20 +1,13 @@
|
|||||||
---
|
---
|
||||||
interface Props {
|
interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
cover?: string;
|
cover: string;
|
||||||
description?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
|
||||||
title,
|
|
||||||
cover,
|
|
||||||
description = "A personal blog and portfolio by Max Richter.",
|
|
||||||
} = Astro.props;
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<meta property="og:title" content={title} />
|
<meta property="og:title" content={Astro.props.title} />
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
<meta property="og:url" content={Astro.url} />
|
<meta property="og:url" content={Astro.url} />
|
||||||
{cover && <meta property="og:image" content={cover} />}
|
<meta property="og:image" content={Astro.props.cover} />
|
||||||
<meta property="og:description" content={description} />
|
<meta property="og:description" content="Max Richters personal blog" />
|
||||||
<meta property="og:site_name" content="Max Richter" />
|
<meta property="og:site_name" content="Max Richter" />
|
||||||
|
|||||||
@@ -8,9 +8,9 @@
|
|||||||
typeof d === "string" ? new Date(d) : d;
|
typeof d === "string" ? new Date(d) : d;
|
||||||
|
|
||||||
const iso = (d: string | Date) => {
|
const iso = (d: string | Date) => {
|
||||||
if (!d) return "";
|
if(!d) return ""
|
||||||
const v = toDate(d);
|
const v = toDate(d);
|
||||||
if (!v?.getTime) return "";
|
if(!v?.getTime) return ""
|
||||||
return isNaN(v.getTime()) ? "" : v.toISOString();
|
return isNaN(v.getTime()) ? "" : v.toISOString();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -34,8 +34,7 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if rating || date || readDuration || author}
|
<div class="flex gap-3 wrapper">
|
||||||
<div class="flex flex-wrap gap-3 wrapper">
|
|
||||||
{#if rating}
|
{#if rating}
|
||||||
<div class="text-sm bg-light">{formatRating(rating)}</div>
|
<div class="text-sm bg-light">{formatRating(rating)}</div>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -50,7 +49,7 @@
|
|||||||
<div class="text-sm bg-light">{author}</div>
|
<div class="text-sm bg-light">{author}</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.wrapper > * {
|
.wrapper > * {
|
||||||
@@ -58,6 +57,5 @@
|
|||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
white-space: nowrap;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -6,6 +6,6 @@
|
|||||||
<a
|
<a
|
||||||
href={link}
|
href={link}
|
||||||
data-astro-prefetch
|
data-astro-prefetch
|
||||||
class="mt-auto 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"></span>
|
>{text}<span class="i-tabler-arrow-right inline-block w-4 h-4"></span>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -13,17 +13,12 @@ const { resource } = Astro.props;
|
|||||||
resource?.content?.image && (
|
resource?.content?.image && (
|
||||||
<Image
|
<Image
|
||||||
hash
|
hash
|
||||||
src={
|
src={{ src: memorium.getImageUrl(resource.content.image) } as ImageMetadata}
|
||||||
{ src: memorium.getImageUrl(resource.content.image) } as ImageMetadata
|
|
||||||
}
|
|
||||||
alt="Cover for {resource?.content?.name}"
|
alt="Cover for {resource?.content?.name}"
|
||||||
class="rounded-2xl overflow-hidden"
|
class="rounded-2xl overflow-hidden"
|
||||||
pictureClass="rounded-2xl box-shadow"
|
pictureClass="rounded-2xl"
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div set:html={markdownToHtml(resource?.content?.articleBody)} />
|
||||||
class="flex flex-col gap-4"
|
|
||||||
set:html={markdownToHtml(resource?.content?.articleBody)}
|
|
||||||
/>
|
|
||||||
|
|||||||
@@ -4,44 +4,29 @@ import { markdownToHtml } from "@helpers/markdown";
|
|||||||
import Image from "@components/Image.astro";
|
import Image from "@components/Image.astro";
|
||||||
import type { ImageMetadata } from "astro";
|
import type { ImageMetadata } from "astro";
|
||||||
|
|
||||||
const { resource } = Astro.props;
|
const { resource } = Astro.props
|
||||||
const ingredients = resource?.content?.recipeIngredient || [];
|
const ingredients = resource?.content?.recipeIngredient || [];
|
||||||
const instructions = resource?.content?.recipeInstructions || [];
|
const instructions = resource?.content?.recipeInstructions || [];
|
||||||
---
|
---
|
||||||
|
|
||||||
<h1 class="text-4xl">{resource?.content?.name}</h1>
|
<h1 class="text-4xl">{resource?.content?.name}</h1>
|
||||||
<div>
|
<div>
|
||||||
{
|
{resource?.content?.image && <Image hash src={{src: memorium.getImageUrl(resource.content.image)} as ImageMetadata} alt="Cover for {resource?.content?.name}" class="rounded-2xl overflow-hidden" pictureClass="rounded-2xl" />}
|
||||||
resource?.content?.image && (
|
|
||||||
<Image
|
|
||||||
hash
|
|
||||||
src={
|
|
||||||
{ src: memorium.getImageUrl(resource.content.image) } as ImageMetadata
|
|
||||||
}
|
|
||||||
alt="Cover for {resource?.content?.name}"
|
|
||||||
class="rounded-2xl overflow-hidden"
|
|
||||||
pictureClass="rounded-2xl box-shadow"
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<p>{resource?.content?.description}</p>
|
||||||
class="flex flex-col gap-4 px-4"
|
<h2 class="text-2xl">Ingredients</h2>
|
||||||
set:html={markdownToHtml(resource?.content?.description ?? "")}
|
<ul>
|
||||||
/>
|
|
||||||
<h2 class="text-2xl px-4">Ingredients</h2>
|
|
||||||
<ul class="list-disc px-10">
|
|
||||||
{
|
{
|
||||||
ingredients.filter((s:string) => !!s?.length).map((ingredient: string) => (
|
ingredients.map((ingredient) => (
|
||||||
<li set:html={markdownToHtml(ingredient)}/>
|
<li set:html={markdownToHtml(ingredient)}/>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h2 class="text-2xl px-4">Steps</h2>
|
<h2 class="text-2xl">Steps</h2>
|
||||||
<ol class="list-decimal px-10">
|
<ol>
|
||||||
{
|
{
|
||||||
instructions.filter((s:string) => !!s?.length).map((ingredient: string) => (
|
instructions.map((ingredient) => (
|
||||||
<li set:html={markdownToHtml(ingredient)}/>
|
<li set:html={markdownToHtml(ingredient)}/>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,6 @@ import Image from "@components/Image.astro";
|
|||||||
import type { ImageMetadata } from "astro";
|
import type { ImageMetadata } from "astro";
|
||||||
|
|
||||||
const { resource } = Astro.props;
|
const { resource } = Astro.props;
|
||||||
|
|
||||||
function formatRating(rating: string | number) {
|
|
||||||
if (typeof rating === "number") {
|
|
||||||
return "⭐".repeat(rating);
|
|
||||||
}
|
|
||||||
return rating;
|
|
||||||
}
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@@ -24,17 +17,12 @@ function formatRating(rating: string | number) {
|
|||||||
}
|
}
|
||||||
alt="Cover for {resource?.content?.name}"
|
alt="Cover for {resource?.content?.name}"
|
||||||
class="rounded-2xl overflow-hidden"
|
class="rounded-2xl overflow-hidden"
|
||||||
pictureClass="rounded-2xl w-1/2 mr-4 mb-4 float-left box-shadow"
|
pictureClass="rounded-2xl w-1/2 mr-4 mb-4 float-left"
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
<h1 class="text-4xl mb-4">
|
<h1 class="text-4xl mb-4">{resource?.content?.itemReviewed?.name || "Unknown Name"}</h1>
|
||||||
{resource?.content?.itemReviewed?.name || "Unknown Name"}
|
{ resource?.content?.reviewRating?.ratingValue !== undefined && <div>{resource?.content?.reviewRating?.ratingValue}</div>}
|
||||||
</h1>
|
|
||||||
{
|
|
||||||
resource?.content?.reviewRating?.ratingValue !== undefined && (
|
|
||||||
<div>{formatRating(resource?.content?.reviewRating?.ratingValue)}</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
<div set:html={markdownToHtml(resource?.content?.reviewBody)} />
|
<div set:html={markdownToHtml(resource?.content?.reviewBody)} />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 6.5 MiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 6.9 MiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 8.2 MiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 5.8 MiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 5.2 MiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 4.8 MiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 6.6 MiB |
|
Before Width: | Height: | Size: 133 B After Width: | Height: | Size: 11 MiB |
@@ -1,5 +0,0 @@
|
|||||||
WEBVTT
|
|
||||||
|
|
||||||
00:00.000 --> 00:01.000
|
|
||||||
Voyaging
|
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 2.5 MiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 4.4 MiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 4.4 MiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 6.1 MiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 3.4 MiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 8.0 MiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 2.2 MiB |
@@ -1,47 +0,0 @@
|
|||||||
WEBVTT
|
|
||||||
|
|
||||||
00:00.000 --> 00:09.000
|
|
||||||
Guten Morgen, liebe Familie. Guten Morgen, wir haben gerade 6.42 Uhr.
|
|
||||||
|
|
||||||
00:13.000 --> 00:20.000
|
|
||||||
Das ist mein Frühstück. Das ist ein Bulgur von gestern. Kalt jetzt.
|
|
||||||
|
|
||||||
00:20.000 --> 00:27.000
|
|
||||||
Hier, da drüben ist das alte Refugio. Man darf hier nur über 1600 campen.
|
|
||||||
|
|
||||||
00:27.000 --> 00:33.000
|
|
||||||
Und da unten ist das neue Refugio irgendwo. Das ist die Aussicht.
|
|
||||||
|
|
||||||
00:33.000 --> 00:36.000
|
|
||||||
Da hinten ist das Wolkenmeer.
|
|
||||||
|
|
||||||
00:36.000 --> 00:42.000
|
|
||||||
Hinten sind die Boys oder eigentlich nur noch ein Boy, der Kilian.
|
|
||||||
|
|
||||||
00:42.000 --> 00:48.000
|
|
||||||
Weil Kilian und ich ziehen jetzt alleine weiter. Der Rest will ich mehr.
|
|
||||||
|
|
||||||
00:48.000 --> 00:52.000
|
|
||||||
Er hat keine Lust mehr. Die gehen jetzt runter ins Tal.
|
|
||||||
|
|
||||||
00:52.000 --> 00:57.000
|
|
||||||
Wir sind alle ein bisschen verschnuppt.
|
|
||||||
|
|
||||||
00:57.000 --> 01:00.000
|
|
||||||
Niki ist aber so richtig verschnuppt.
|
|
||||||
|
|
||||||
01:00.000 --> 01:04.000
|
|
||||||
Und Joni hatte irgendwas mit Gottseriehtes gestern.
|
|
||||||
|
|
||||||
01:04.000 --> 01:09.000
|
|
||||||
Deswegen werden Kilian und ich jetzt den Berg allein weiter bekämpfen.
|
|
||||||
|
|
||||||
01:10.000 --> 01:13.000
|
|
||||||
Und für uns geht es jetzt heute nach Vega Banjo.
|
|
||||||
|
|
||||||
01:13.000 --> 01:18.000
|
|
||||||
1000 Höhenmeter runter, 800 hoch.
|
|
||||||
|
|
||||||
01:18.000 --> 01:22.000
|
|
||||||
Es wird heute wahrscheinlich so ein 11-Stunden-Ritt.
|
|
||||||
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
WEBVTT
|
|
||||||
|
|
||||||
00:00.000 --> 00:09.000
|
|
||||||
Good morning, dear family. Good morning, it’s currently 6:42 a.m.
|
|
||||||
|
|
||||||
00:13.000 --> 00:20.000
|
|
||||||
This is my breakfast. This is bulgur from yesterday. Cold now.
|
|
||||||
|
|
||||||
00:20.000 --> 00:27.000
|
|
||||||
Here, over there is the old refugio. You’re only allowed to camp above 1,600 meters here.
|
|
||||||
|
|
||||||
00:27.000 --> 00:33.000
|
|
||||||
And down there somewhere is the new refugio. That’s the view.
|
|
||||||
|
|
||||||
00:33.000 --> 00:36.000
|
|
||||||
Back there is the sea of clouds.
|
|
||||||
|
|
||||||
00:36.000 --> 00:42.000
|
|
||||||
In the back are the boys, or actually just one boy now, Kilian.
|
|
||||||
|
|
||||||
00:42.000 --> 00:48.000
|
|
||||||
Because Kilian and I are continuing on alone now. The rest don’t want to continue anymore.
|
|
||||||
|
|
||||||
00:48.000 --> 00:52.000
|
|
||||||
They’ve had enough. They’re heading down into the valley now.
|
|
||||||
|
|
||||||
00:52.000 --> 00:57.000
|
|
||||||
We’re all a bit sniffly.
|
|
||||||
|
|
||||||
00:57.000 --> 01:00.000
|
|
||||||
But Niki is really properly sick.
|
|
||||||
|
|
||||||
01:00.000 --> 01:04.000
|
|
||||||
And Joni had something serious yesterday.
|
|
||||||
|
|
||||||
01:04.000 --> 01:09.000
|
|
||||||
So Kilian and I are going to keep fighting our way up the mountain alone.
|
|
||||||
|
|
||||||
01:10.000 --> 01:13.000
|
|
||||||
And today we’re heading to Vega Banjo.
|
|
||||||
|
|
||||||
01:13.000 --> 01:18.000
|
|
||||||
1,000 meters of descent, 800 meters up.
|
|
||||||
|
|
||||||
01:18.000 --> 01:22.000
|
|
||||||
Today will probably be an 11-hour grind.
|
|
||||||
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 3.6 MiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 3.8 MiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 3.6 MiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 2.8 MiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 3.2 MiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 5.8 MiB |
@@ -1,44 +0,0 @@
|
|||||||
WEBVTT
|
|
||||||
|
|
||||||
00:00.000 --> 00:25.000
|
|
||||||
Mit die erste Nacht im Wald hinter uns, ja, ja, wir sind jetzt angezogen, die Schuhe haben wir jetzt für den ganzen Spaß gebraucht am Morgen, 6.30 Uhr war der Tag, 2 Stunden, das ist ein Prozess, es ist halt alles nass,
|
|
||||||
|
|
||||||
00:26.000 --> 00:35.000
|
|
||||||
sich da irgendwie das Essen irgendwann in die Sockenquellen, in die Nassen Schuhequellen, in die Nasse Hosequellen, alles zusammenpacken.
|
|
||||||
|
|
||||||
00:36.000 --> 00:38.000
|
|
||||||
Ja, die Nassen Schlafsack einpacken.
|
|
||||||
|
|
||||||
00:39.000 --> 00:44.000
|
|
||||||
Ich glaube, in der Sonne ist das alles etwas geiler und geht etwas schneller und ist bestimmt auch ein bisschen trockener.
|
|
||||||
|
|
||||||
00:45.000 --> 00:49.000
|
|
||||||
Ja, ich hoffe mal, dass wir das heute Abend herausfinden werden.
|
|
||||||
|
|
||||||
00:50.000 --> 00:52.000
|
|
||||||
Oh, Sonne, Sonne heute irgendwann.
|
|
||||||
|
|
||||||
00:53.000 --> 00:57.000
|
|
||||||
Könnte das da hinten blauer Himmel sein? Man weiß es nicht genau, eventuell.
|
|
||||||
|
|
||||||
00:58.000 --> 00:59.000
|
|
||||||
Ja, das ist ja.
|
|
||||||
|
|
||||||
01:00.000 --> 01:02.000
|
|
||||||
Auf jeden Fall, das ist unsere Behausung.
|
|
||||||
|
|
||||||
01:03.000 --> 01:04.000
|
|
||||||
Ich meine, eine Nacht im Märchenwald hier, ne?
|
|
||||||
|
|
||||||
01:05.000 --> 01:06.000
|
|
||||||
Eine Nacht im Märchenwald.
|
|
||||||
|
|
||||||
01:07.000 --> 01:10.000
|
|
||||||
Und wir wurden nicht verhext von der Hexe im Hexenhaus.
|
|
||||||
|
|
||||||
01:11.000 --> 01:12.000
|
|
||||||
Ne.
|
|
||||||
|
|
||||||
01:13.000 --> 01:14.000
|
|
||||||
Jo.
|
|
||||||
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
WEBVTT
|
|
||||||
|
|
||||||
00:00.000 --> 00:25.000
|
|
||||||
With the first night in the forest behind us, yes, yes, we’re dressed now, we needed the shoes for all the fun this morning, it was 6:30 a.m., two hours—that’s a process, everything is just wet,
|
|
||||||
|
|
||||||
00:26.000 --> 00:35.000
|
|
||||||
somehow packing the food into the socks, into the wet shoes, into the wet pants, packing everything together.
|
|
||||||
|
|
||||||
00:36.000 --> 00:38.000
|
|
||||||
Yes, packing up the wet sleeping bag.
|
|
||||||
|
|
||||||
00:39.000 --> 00:44.000
|
|
||||||
I think in the sun it’s all a bit nicer, goes a bit faster, and is probably a bit drier too.
|
|
||||||
|
|
||||||
00:45.000 --> 00:49.000
|
|
||||||
Yes, I hope we’ll find that out this evening.
|
|
||||||
|
|
||||||
00:50.000 --> 00:52.000
|
|
||||||
Oh, sun, sun will come today at some point.
|
|
||||||
|
|
||||||
00:53.000 --> 00:57.000
|
|
||||||
Could that be blue sky back there? You can’t tell exactly, maybe.
|
|
||||||
|
|
||||||
00:58.000 --> 00:59.000
|
|
||||||
Yes, that’s…
|
|
||||||
|
|
||||||
01:00.000 --> 01:02.000
|
|
||||||
Anyway, this is our shelter.
|
|
||||||
|
|
||||||
01:03.000 --> 01:04.000
|
|
||||||
I mean, one night in a fairy-tale forest here, right?
|
|
||||||
|
|
||||||
01:05.000 --> 01:06.000
|
|
||||||
One night in a fairy-tale forest.
|
|
||||||
|
|
||||||
01:07.000 --> 01:10.000
|
|
||||||
And we weren’t cursed by the witch in the witch’s house.
|
|
||||||
|
|
||||||
01:11.000 --> 01:12.000
|
|
||||||
No.
|
|
||||||
|
|
||||||
01:13.000 --> 01:14.000
|
|
||||||
Yep.
|
|
||||||
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 2.9 MiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 4.5 MiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 3.2 MiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 3.2 MiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 4.0 MiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 4.5 MiB |
@@ -11,49 +11,45 @@ tags: ["picos-de-europa", "spain", "hiking", "travel"]
|
|||||||
Unsorted pictures from our hike through the Picos de Europa.
|
Unsorted pictures from our hike through the Picos de Europa.
|
||||||
|
|
||||||
import Image from "@components/Image.astro";
|
import Image from "@components/Image.astro";
|
||||||
import ImageGallery from "@components/ImageGallery.svelte";
|
import ImageGallery from "@components/ImageGallery.svelte"
|
||||||
import ImageSlider from "@components/ImageSlider.svelte";
|
import ImageSlider from "@components/ImageSlider.svelte"
|
||||||
|
|
||||||
<ImageGallery client:load/>
|
<ImageGallery client:load/>
|
||||||
|
|
||||||
|
|
||||||
## May 27th
|
## May 27th
|
||||||
|
|
||||||
import image1 from "images/20250527_125228.jpg";
|
First day, arrival at the sea.
|
||||||
import image16 from "images/PXL_20250527_101057540.MP.jpg";
|
|
||||||
import image17 from "images/PXL_20250527_100728883.jpg";
|
|
||||||
|
|
||||||
<ImageSlider title="First day, arrival at the sea." client:load>
|
import image1 from "images/20250527_125228.jpg"
|
||||||
|
import image16 from "images/PXL_20250527_101057540.MP.jpg"
|
||||||
|
import image17 from "images/PXL_20250527_100728883.jpg"
|
||||||
|
|
||||||
|
<ImageSlider title="Hike" client:load>
|
||||||
<Image src={image1} alt="A cow on a meadow in front of the sea"/>
|
<Image src={image1} alt="A cow on a meadow in front of the sea"/>
|
||||||
<Image
|
<Image src={image17} alt="A person on flysch rock in front of a cave at the sea"/>
|
||||||
src={image17}
|
<Image src={image16} alt="A person in a cave from which you can see the sea"/>
|
||||||
alt="A person on flysch rock in front of a cave at the sea"
|
|
||||||
/>
|
|
||||||
<Image
|
|
||||||
src={image16}
|
|
||||||
alt="A person in a cave from which you can see the sea"
|
|
||||||
/>
|
|
||||||
</ImageSlider>
|
</ImageSlider>
|
||||||
|
|
||||||
## May 28th
|
## May 28th
|
||||||
|
|
||||||
import image15 from "images/PXL_20250528_121633744.MP.jpg";
|
First day of hiking
|
||||||
import image19 from "images/20250528_164715.jpg";
|
|
||||||
|
|
||||||
<ImageSlider title="First day of hiking" client:load>
|
import image15 from "images/PXL_20250528_121633744.MP.jpg"
|
||||||
|
import image19 from "images/20250528_164715.jpg"
|
||||||
|
|
||||||
|
<ImageSlider title="Hike" client:load>
|
||||||
<Image src={image15} alt="Us after the first 300 meters"/>
|
<Image src={image15} alt="Us after the first 300 meters"/>
|
||||||
<Image
|
<Image src={image19} alt="Many mountain goats that like to lick the salt from the skin"/>
|
||||||
src={image19}
|
|
||||||
alt="Many mountain goats that like to lick the salt from the skin"
|
|
||||||
/>
|
|
||||||
</ImageSlider>
|
</ImageSlider>
|
||||||
|
|
||||||
## May 29th
|
## May 29th
|
||||||
|
|
||||||
Hard ascent
|
Hard ascent
|
||||||
|
|
||||||
import videoUrl1 from "images/PXL_20250529_125633973_small_x265.mp4";
|
import videoUrl1 from "images/PXL_20250529_125633973_small_x265.mp4"
|
||||||
import image12 from "images/PXL_20250530_082919731.jpg";
|
import image12 from "images/PXL_20250530_082919731.jpg"
|
||||||
import image13 from "images/PXL_20250529_201559403.jpg";
|
import image13 from "images/PXL_20250529_201559403.jpg"
|
||||||
|
|
||||||
<video src={videoUrl1} controls alt=""/>
|
<video src={videoUrl1} controls alt=""/>
|
||||||
<Image alt="" src={image12} alt="Finally arrived at the Refugio"/>
|
<Image alt="" src={image12} alt="Finally arrived at the Refugio"/>
|
||||||
@@ -62,18 +58,15 @@ import image13 from "images/PXL_20250529_201559403.jpg";
|
|||||||
|
|
||||||
Thunderstorm and rest day
|
Thunderstorm and rest day
|
||||||
|
|
||||||
import image11 from "images/PXL_20250530_110041174.jpg";
|
import image11 from "images/PXL_20250530_110041174.jpg"
|
||||||
import image10 from "images/PXL_20250530_135631186.MP.jpg";
|
import image10 from "images/PXL_20250530_135631186.MP.jpg"
|
||||||
import image8 from "images/PXL_20250530_170114907.MP.jpg";
|
import image8 from "images/PXL_20250530_170114907.MP.jpg"
|
||||||
import image9 from "images/PXL_20250530_141432767.jpg";
|
import image9 from "images/PXL_20250530_141432767.jpg"
|
||||||
import image18 from "images/IMG-20250531-WA0020.jpeg";
|
import image18 from "images/IMG-20250531-WA0020.jpeg"
|
||||||
|
|
||||||
<ImageSlider title="Thunderstorm and rest day" client:load>
|
<ImageSlider title="Thunderstorm and rest day" client:load>
|
||||||
<Image src={image11} alt="Departure to the mountains"/>
|
<Image src={image11} alt="Departure to the mountains"/>
|
||||||
<Image
|
<Image src={image10} alt="Surprisingly saved under the tarp after a thunderstorm"/>
|
||||||
src={image10}
|
|
||||||
alt="Surprisingly saved under the tarp after a thunderstorm"
|
|
||||||
/>
|
|
||||||
<Image src={image9} alt="Second departure to the mountains"/>
|
<Image src={image9} alt="Second departure to the mountains"/>
|
||||||
<Image src={image8} alt="Waiting for the second thunderstorm"/>
|
<Image src={image8} alt="Waiting for the second thunderstorm"/>
|
||||||
<Image src={image18} alt="Arrival at the Refugio"/>
|
<Image src={image18} alt="Arrival at the Refugio"/>
|
||||||
@@ -83,31 +76,13 @@ import image18 from "images/IMG-20250531-WA0020.jpeg";
|
|||||||
|
|
||||||
We continue as a pair.
|
We continue as a pair.
|
||||||
|
|
||||||
import videoUrl2 from "images/PXL_20250601_044050514_small_x265.mp4";
|
import videoUrl2 from "images/PXL_20250601_044050514_small_x265.mp4"
|
||||||
import videoUrl2TrackDe from "images/PXL_20250601_044050514_small_x265.vtt";
|
import image3 from "images/PXL_20250601_124002445.jpg"
|
||||||
import videoUrl2TrackEn from "images/PXL_20250601_044050514_small_x265_en.vtt";
|
import image4 from "images/PXL_20250601_095307910.jpg"
|
||||||
import image3 from "images/PXL_20250601_124002445.jpg";
|
import image5 from "images/PXL_20250601_082528934.jpg"
|
||||||
import image4 from "images/PXL_20250601_095307910.jpg";
|
import image2 from "images/PXL_20250601_132646197.jpg"
|
||||||
import image5 from "images/PXL_20250601_082528934.jpg";
|
|
||||||
import image2 from "images/PXL_20250601_132646197.jpg";
|
|
||||||
|
|
||||||
<video src={videoUrl2} controls alt="14">
|
<video src={videoUrl2} controls alt="14"/>
|
||||||
<track
|
|
||||||
type="caption"
|
|
||||||
label="German"
|
|
||||||
kind="subtitles"
|
|
||||||
srclang="de"
|
|
||||||
src={videoUrl2TrackDe}
|
|
||||||
/>
|
|
||||||
<track
|
|
||||||
type="caption"
|
|
||||||
label="English"
|
|
||||||
kind="subtitles"
|
|
||||||
srclang="de"
|
|
||||||
src={videoUrl2TrackEn}
|
|
||||||
default
|
|
||||||
/>
|
|
||||||
</video>
|
|
||||||
<ImageSlider title="Over the peaks" client:load>
|
<ImageSlider title="Over the peaks" client:load>
|
||||||
<Image src={image4} alt="First ascent-done, about 1000 meters in altitude"/>
|
<Image src={image4} alt="First ascent-done, about 1000 meters in altitude"/>
|
||||||
<Image src={image5} alt="In the middle of the mountain basin"/>
|
<Image src={image5} alt="In the middle of the mountain basin"/>
|
||||||
@@ -115,43 +90,25 @@ import image2 from "images/PXL_20250601_132646197.jpg";
|
|||||||
<Image src={image2} alt="Immersed in the fog"/>
|
<Image src={image2} alt="Immersed in the fog"/>
|
||||||
</ImageSlider>
|
</ImageSlider>
|
||||||
|
|
||||||
|
|
||||||
## June 2nd
|
## June 2nd
|
||||||
|
|
||||||
Tarping in the forest and wet-hiking then drying out in the hotel room
|
Tarping in the forest and wet-hiking then drying out in the hotel room
|
||||||
|
|
||||||
import image21 from "images/PXL_20250602_064221551.jpg";
|
import image21 from "images/PXL_20250602_064221551.jpg"
|
||||||
import videoUrl3 from "images/PXL_20250602_064236132_small_x265.mp4";
|
import videoUrl3 from "images/PXL_20250602_064236132_small_x265.mp4"
|
||||||
import videoUrl3TrackDe from "images/PXL_20250602_064236132_small_x265.vtt";
|
|
||||||
import videoUrl3TrackEn from "images/PXL_20250602_064236132_small_x265_en.vtt";
|
|
||||||
|
|
||||||
<Image alt="" src={image21} alt="20"/>
|
<Image alt="" src={image21} alt="20"/>
|
||||||
|
<video src={videoUrl3} controls alt=""/>
|
||||||
<video src={videoUrl3} controls alt="">
|
|
||||||
<track
|
|
||||||
type="caption"
|
|
||||||
label="German"
|
|
||||||
kind="subtitles"
|
|
||||||
srclang="de"
|
|
||||||
src={videoUrl3TrackDe}
|
|
||||||
/>
|
|
||||||
<track
|
|
||||||
type="caption"
|
|
||||||
label="English"
|
|
||||||
kind="subtitles"
|
|
||||||
srclang="de"
|
|
||||||
src={videoUrl3TrackEn}
|
|
||||||
default
|
|
||||||
/>
|
|
||||||
</video>
|
|
||||||
|
|
||||||
## June 3rd
|
## June 3rd
|
||||||
|
|
||||||
Ascent to lofty heights
|
Ascent to lofty heights
|
||||||
|
|
||||||
import image22 from "images/PXL_20250603_083830771.jpg";
|
import image22 from "images/PXL_20250603_083830771.jpg"
|
||||||
import image23 from "images/PXL_20250603_093606665.jpg";
|
import image23 from "images/PXL_20250603_093606665.jpg"
|
||||||
import image25 from "images/PXL_20250603_194635918.jpg";
|
import image25 from "images/PXL_20250603_194635918.jpg"
|
||||||
import image26 from "images/PXL_20250603_194636284.jpg";
|
import image26 from "images/PXL_20250603_194636284.jpg"
|
||||||
|
|
||||||
<ImageSlider title="Over the peaks" client:load>
|
<ImageSlider title="Over the peaks" client:load>
|
||||||
<Image src={image22} alt="The ascent was completely in the fog"/>
|
<Image src={image22} alt="The ascent was completely in the fog"/>
|
||||||
@@ -168,6 +125,7 @@ Overnight stay at Refugio Los Cabrones
|
|||||||
|
|
||||||
Final-descent
|
Final-descent
|
||||||
|
|
||||||
import image27 from "images/PXL_20250605_100650266.jpg";
|
import image27 from "images/PXL_20250605_100650266.jpg"
|
||||||
|
|
||||||
|
<Image alt="" src={image27} alt="27"/>
|
||||||
|
|
||||||
<Image alt="" src={image27} alt="The last walk down the mountain" />
|
|
||||||
|
|||||||
@@ -11,71 +11,62 @@ tags: ["picos-de-europa", "spain", "hiking", "travel"]
|
|||||||
Unsortierte Bilder aus unserer Wanderung durch die Picos de Europa.
|
Unsortierte Bilder aus unserer Wanderung durch die Picos de Europa.
|
||||||
|
|
||||||
import Image from "@components/Image.astro";
|
import Image from "@components/Image.astro";
|
||||||
import ImageGallery from "@components/ImageGallery.svelte";
|
import ImageGallery from "@components/ImageGallery.svelte"
|
||||||
import ImageSlider from "@components/ImageSlider.svelte";
|
import ImageSlider from "@components/ImageSlider.svelte"
|
||||||
|
|
||||||
<ImageGallery client:load/>
|
<ImageGallery client:load/>
|
||||||
|
|
||||||
|
|
||||||
## 27. May
|
## 27. May
|
||||||
|
|
||||||
Erster Tag, Ankunft am Meer.
|
Erster Tag, Ankunft am Meer.
|
||||||
|
|
||||||
import image1 from "images/20250527_125228.jpg";
|
import image1 from "images/20250527_125228.jpg"
|
||||||
import image16 from "images/PXL_20250527_101057540.MP.jpg";
|
import image16 from "images/PXL_20250527_101057540.MP.jpg"
|
||||||
import image17 from "images/PXL_20250527_100728883.jpg";
|
import image17 from "images/PXL_20250527_100728883.jpg"
|
||||||
|
|
||||||
<ImageSlider client:load>
|
<ImageSlider title="Wanderung" client:load>
|
||||||
<Image src={image1} alt="Bild einer Kuh auf einer Wiese vor dem Meer"/>
|
<Image src={image1} alt="Bild einer Kuh auf einer Wiese vor dem Meer"/>
|
||||||
<Image
|
<Image src={image17} alt="Person auf flysch gestein vor einer Höhle am Meer"/>
|
||||||
src={image17}
|
<Image src={image16} alt="Bild von Person in einer Höhle aus der man das Meer sieht"/>
|
||||||
alt="Person auf flysch gestein vor einer Höhle am Meer"
|
|
||||||
/>
|
|
||||||
<Image
|
|
||||||
src={image16}
|
|
||||||
alt="Bild von Person in einer Höhle aus der man das Meer sieht"
|
|
||||||
/>
|
|
||||||
</ImageSlider>
|
</ImageSlider>
|
||||||
|
|
||||||
## 28. May
|
## 28. May
|
||||||
|
|
||||||
Erster Wandertag
|
Erster Wandertag
|
||||||
|
|
||||||
import image15 from "images/PXL_20250528_121633744.MP.jpg";
|
import image15 from "images/PXL_20250528_121633744.MP.jpg"
|
||||||
import image19 from "images/20250528_164715.jpg";
|
import image19 from "images/20250528_164715.jpg"
|
||||||
|
|
||||||
<ImageSlider client:load>
|
<ImageSlider title="Wanderung" client:load>
|
||||||
<Image src={image15} alt="Wir nach den ersten 300 Metern"/>
|
<Image src={image15} alt="Wir nach den ersten 300 Metern"/>
|
||||||
<Image
|
<Image src={image19} alt="Viele Bergziegen die gerne das Salz von der Haut lecken"/>
|
||||||
src={image19}
|
|
||||||
alt="Viele Bergziegen die gerne das Salz von der Haut lecken"
|
|
||||||
/>
|
|
||||||
</ImageSlider>
|
</ImageSlider>
|
||||||
|
|
||||||
## 29. May
|
## 29. May
|
||||||
|
|
||||||
Harter Aufstieg
|
Harter Aufstieg
|
||||||
|
|
||||||
import videoUrl1 from "images/PXL_20250529_125633973_small_x265.mp4";
|
import videoUrl1 from "images/PXL_20250529_125633973_small_x265.mp4"
|
||||||
import image12 from "images/PXL_20250530_082919731.jpg";
|
import image12 from "images/PXL_20250530_082919731.jpg"
|
||||||
import image13 from "images/PXL_20250529_201559403.jpg";
|
import image13 from "images/PXL_20250529_201559403.jpg"
|
||||||
|
|
||||||
<video src={videoUrl1} controls alt=""/>
|
<video src={videoUrl1} controls alt=""/>
|
||||||
<Image alt="" src={image12} alt="Endlich am Refugio angekommen"/>
|
<Image alt="" src={image12} alt="Endlich am Refugio angekommen"/>
|
||||||
|
|
||||||
## 30-31. May
|
## 30-31. May
|
||||||
|
|
||||||
import image11 from "images/PXL_20250530_110041174.jpg";
|
Gewitter und Restday
|
||||||
import image10 from "images/PXL_20250530_135631186.MP.jpg";
|
|
||||||
import image8 from "images/PXL_20250530_170114907.MP.jpg";
|
import image11 from "images/PXL_20250530_110041174.jpg"
|
||||||
import image9 from "images/PXL_20250530_141432767.jpg";
|
import image10 from "images/PXL_20250530_135631186.MP.jpg"
|
||||||
import image18 from "images/IMG-20250531-WA0020.jpeg";
|
import image8 from "images/PXL_20250530_170114907.MP.jpg"
|
||||||
|
import image9 from "images/PXL_20250530_141432767.jpg"
|
||||||
|
import image18 from "images/IMG-20250531-WA0020.jpeg"
|
||||||
|
|
||||||
<ImageSlider title="Gewitter und Restday" client:load>
|
<ImageSlider title="Gewitter und Restday" client:load>
|
||||||
<Image src={image11} alt="Aufbruch in die Berge"/>
|
<Image src={image11} alt="Aufbruch in die Berge"/>
|
||||||
<Image
|
<Image src={image10} alt="Überrascht nach einem Gewitter unters Tarp gerettet"/>
|
||||||
src={image10}
|
|
||||||
alt="Überrascht nach einem Gewitter unters Tarp gerettet"
|
|
||||||
/>
|
|
||||||
<Image src={image9} alt="Zweiter Aufbruch in die Berge"/>
|
<Image src={image9} alt="Zweiter Aufbruch in die Berge"/>
|
||||||
<Image src={image8} alt="Zweites Gewitter abwarten"/>
|
<Image src={image8} alt="Zweites Gewitter abwarten"/>
|
||||||
<Image src={image18} alt="Ankommen am Refugio"/>
|
<Image src={image18} alt="Ankommen am Refugio"/>
|
||||||
@@ -85,31 +76,13 @@ import image18 from "images/IMG-20250531-WA0020.jpeg";
|
|||||||
|
|
||||||
Es geht zu zweit weiter.
|
Es geht zu zweit weiter.
|
||||||
|
|
||||||
import videoUrl2 from "images/PXL_20250601_044050514_small_x265.mp4";
|
import videoUrl2 from "images/PXL_20250601_044050514_small_x265.mp4"
|
||||||
import videoUrl2TrackDe from "images/PXL_20250601_044050514_small_x265.vtt";
|
import image3 from "images/PXL_20250601_124002445.jpg"
|
||||||
import videoUrl2TrackEn from "images/PXL_20250601_044050514_small_x265_en.vtt";
|
import image4 from "images/PXL_20250601_095307910.jpg"
|
||||||
import image3 from "images/PXL_20250601_124002445.jpg";
|
import image5 from "images/PXL_20250601_082528934.jpg"
|
||||||
import image4 from "images/PXL_20250601_095307910.jpg";
|
import image2 from "images/PXL_20250601_132646197.jpg"
|
||||||
import image5 from "images/PXL_20250601_082528934.jpg";
|
|
||||||
import image2 from "images/PXL_20250601_132646197.jpg";
|
|
||||||
|
|
||||||
<video src={videoUrl2} controls alt="14">
|
<video src={videoUrl2} controls alt="14"/>
|
||||||
<track
|
|
||||||
type="caption"
|
|
||||||
label="Deutsch"
|
|
||||||
kind="subtitles"
|
|
||||||
srclang="de"
|
|
||||||
src={videoUrl2TrackDe}
|
|
||||||
default
|
|
||||||
/>
|
|
||||||
<track
|
|
||||||
type="caption"
|
|
||||||
label="Englisch"
|
|
||||||
kind="subtitles"
|
|
||||||
srclang="de"
|
|
||||||
src={videoUrl2TrackEn}
|
|
||||||
/>
|
|
||||||
</video>
|
|
||||||
<ImageSlider title="Über die Gipfel" client:load>
|
<ImageSlider title="Über die Gipfel" client:load>
|
||||||
<Image src={image4} alt="Erster Aufstieg geschafft, circa 1000 Höhenmeter"/>
|
<Image src={image4} alt="Erster Aufstieg geschafft, circa 1000 Höhenmeter"/>
|
||||||
<Image src={image5} alt="Mittem im Bergkessel"/>
|
<Image src={image5} alt="Mittem im Bergkessel"/>
|
||||||
@@ -117,42 +90,25 @@ import image2 from "images/PXL_20250601_132646197.jpg";
|
|||||||
<Image src={image2} alt="Eingetaucht in den Nebel"/>
|
<Image src={image2} alt="Eingetaucht in den Nebel"/>
|
||||||
</ImageSlider>
|
</ImageSlider>
|
||||||
|
|
||||||
|
|
||||||
## 2. Juni
|
## 2. Juni
|
||||||
|
|
||||||
Tarpen im Wald und nass-wandern dann Austrocken im Hotelzimmer
|
Tarpen im Wald und nass-wandern dann Austrocken im Hotelzimmer
|
||||||
|
|
||||||
import image21 from "images/PXL_20250602_064221551.jpg";
|
import image21 from "images/PXL_20250602_064221551.jpg"
|
||||||
import videoUrl3 from "images/PXL_20250602_064236132_small_x265.mp4";
|
import videoUrl3 from "images/PXL_20250602_064236132_small_x265.mp4"
|
||||||
import videoUrl3TrackDe from "images/PXL_20250602_064236132_small_x265.vtt";
|
|
||||||
import videoUrl3TrackEn from "images/PXL_20250602_064236132_small_x265_en.vtt";
|
|
||||||
|
|
||||||
<Image alt="" src={image21} alt="20"/>
|
<Image alt="" src={image21} alt="20"/>
|
||||||
<video src={videoUrl3} controls alt="">
|
<video src={videoUrl3} controls alt=""/>
|
||||||
<track
|
|
||||||
type="caption"
|
|
||||||
label="Deutsch"
|
|
||||||
kind="subtitles"
|
|
||||||
srclang="de"
|
|
||||||
src={videoUrl3TrackDe}
|
|
||||||
default
|
|
||||||
/>
|
|
||||||
<track
|
|
||||||
type="caption"
|
|
||||||
label="Englisch"
|
|
||||||
kind="subtitles"
|
|
||||||
srclang="de"
|
|
||||||
src={videoUrl3TrackEn}
|
|
||||||
/>
|
|
||||||
</video>
|
|
||||||
|
|
||||||
## 3. Juni
|
## 3. Juni
|
||||||
|
|
||||||
Aufstieg in lichte Höhen
|
Aufstieg in lichte Höhen
|
||||||
|
|
||||||
import image22 from "images/PXL_20250603_083830771.jpg";
|
import image22 from "images/PXL_20250603_083830771.jpg"
|
||||||
import image23 from "images/PXL_20250603_093606665.jpg";
|
import image23 from "images/PXL_20250603_093606665.jpg"
|
||||||
import image25 from "images/PXL_20250603_194635918.jpg";
|
import image25 from "images/PXL_20250603_194635918.jpg"
|
||||||
import image26 from "images/PXL_20250603_194636284.jpg";
|
import image26 from "images/PXL_20250603_194636284.jpg"
|
||||||
|
|
||||||
<ImageSlider title="Über die Gipfel" client:load>
|
<ImageSlider title="Über die Gipfel" client:load>
|
||||||
<Image src={image22} alt="Der Aufstieg war komplett im Nebel"/>
|
<Image src={image22} alt="Der Aufstieg war komplett im Nebel"/>
|
||||||
@@ -169,6 +125,6 @@ import image26 from "images/PXL_20250603_194636284.jpg";
|
|||||||
|
|
||||||
Finaler Abstieg
|
Finaler Abstieg
|
||||||
|
|
||||||
import image27 from "images/PXL_20250605_100650266.jpg";
|
import image27 from "images/PXL_20250605_100650266.jpg"
|
||||||
|
|
||||||
<Image alt="" src={image27} alt="The last walk down the mountain" />
|
<Image alt="" src={image27} alt="27"/>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 1007 KiB |
|
Before Width: | Height: | Size: 131 B After Width: | Height: | Size: 339 KiB |
|
Before Width: | Height: | Size: 131 B After Width: | Height: | Size: 142 KiB |
|
Before Width: | Height: | Size: 131 B After Width: | Height: | Size: 175 KiB |
|
Before Width: | Height: | Size: 131 B After Width: | Height: | Size: 151 KiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 130 B After Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 130 B After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 4.6 MiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 2.9 MiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 4.5 MiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 1.8 MiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 131 B After Width: | Height: | Size: 664 KiB |
|
Before Width: | Height: | Size: 131 B After Width: | Height: | Size: 701 KiB |
|
Before Width: | Height: | Size: 131 B After Width: | Height: | Size: 743 KiB |
|
Before Width: | Height: | Size: 131 B After Width: | Height: | Size: 646 KiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 3.7 MiB |
|
Before Width: | Height: | Size: 131 B After Width: | Height: | Size: 584 KiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 4.5 MiB |
|
Before Width: | Height: | Size: 131 B After Width: | Height: | Size: 376 KiB |
|
Before Width: | Height: | Size: 131 B After Width: | Height: | Size: 322 KiB |
|
Before Width: | Height: | Size: 131 B After Width: | Height: | Size: 287 KiB |
|
Before Width: | Height: | Size: 131 B After Width: | Height: | Size: 844 KiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 1.1 MiB |
@@ -3,21 +3,41 @@ import ExifReader from "exifreader";
|
|||||||
import type { ImageMetadata } from "astro";
|
import type { ImageMetadata } from "astro";
|
||||||
import { readFile } from "node:fs/promises";
|
import { readFile } from "node:fs/promises";
|
||||||
import sharp from "sharp";
|
import sharp from "sharp";
|
||||||
import { createHash } from "node:crypto";
|
|
||||||
import { promises as fs } from "node:fs";
|
|
||||||
import path from "node:path";
|
|
||||||
|
|
||||||
export async function generateThumbHash(
|
export async function generateThumbHash(
|
||||||
buffer: ArrayBuffer,
|
image: ImageMetadata & { fsPath?: string },
|
||||||
): Promise<string | undefined> {
|
) {
|
||||||
if (!buffer) return;
|
const scaleFactor = 100 / Math.max(image.width, image.height);
|
||||||
|
|
||||||
const sp = sharp(buffer);
|
let smallWidth = Math.floor(image.width * scaleFactor);
|
||||||
|
let smallHeight = Math.floor(image.height * scaleFactor);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const imagePath = (image as ImageMetadata & { fsPath: string }).fsPath ??
|
||||||
|
image.src;
|
||||||
|
|
||||||
|
if (!imagePath) return;
|
||||||
|
|
||||||
|
if (imagePath.endsWith(".svg")) return;
|
||||||
|
|
||||||
|
let sp: ReturnType<typeof sharp>;
|
||||||
|
if (imagePath.startsWith("https://") || imagePath.startsWith("http://")) {
|
||||||
|
const res = await fetch(imagePath);
|
||||||
|
if (!res.ok) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const buffer = await res.arrayBuffer();
|
||||||
|
sp = sharp(buffer);
|
||||||
|
} else {
|
||||||
|
sp = sharp(imagePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!smallWidth || !smallHeight) {
|
||||||
const meta = await sp.metadata();
|
const meta = await sp.metadata();
|
||||||
const scaleFactor = 100 / Math.max(meta.width, meta.height);
|
const scaleFactor = 100 / Math.max(meta.width, meta.height);
|
||||||
const smallWidth = Math.floor(meta.width * scaleFactor);
|
smallWidth = Math.floor(meta.width * scaleFactor);
|
||||||
const smallHeight = Math.floor(meta.height * scaleFactor);
|
smallHeight = Math.floor(meta.height * scaleFactor);
|
||||||
|
}
|
||||||
|
|
||||||
const smallImg = await sp
|
const smallImg = await sp
|
||||||
.resize(smallWidth, smallHeight)
|
.resize(smallWidth, smallHeight)
|
||||||
@@ -26,8 +46,14 @@ export async function generateThumbHash(
|
|||||||
.ensureAlpha()
|
.ensureAlpha()
|
||||||
.toBuffer();
|
.toBuffer();
|
||||||
|
|
||||||
const hashBuffer = rgbaToThumbHash(smallWidth, smallHeight, smallImg);
|
const buffer = rgbaToThumbHash(smallWidth, smallHeight, smallImg);
|
||||||
return Buffer.from(hashBuffer).toString("base64");
|
return Buffer.from(buffer).toString("base64");
|
||||||
|
} catch (_error) {
|
||||||
|
console.log(
|
||||||
|
`Could not generate thumbhash for ${image.fsPath ?? image.src}`,
|
||||||
|
);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const allowedExif = [
|
const allowedExif = [
|
||||||
@@ -46,9 +72,7 @@ const allowedExif = [
|
|||||||
"Model",
|
"Model",
|
||||||
];
|
];
|
||||||
|
|
||||||
export async function getImageBuffer(
|
export async function getExifData(image: ImageMetadata) {
|
||||||
image: ImageMetadata,
|
|
||||||
): Promise<ArrayBuffer> {
|
|
||||||
if (image.format === "svg") return undefined; // SVGs don't have EXIF data
|
if (image.format === "svg") return undefined; // SVGs don't have EXIF data
|
||||||
const imagePath = (image as ImageMetadata & { fsPath: string }).fsPath ??
|
const imagePath = (image as ImageMetadata & { fsPath: string }).fsPath ??
|
||||||
image.src;
|
image.src;
|
||||||
@@ -56,24 +80,20 @@ export async function getImageBuffer(
|
|||||||
if (!imagePath) return undefined;
|
if (!imagePath) return undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
let buffer: ArrayBufferLike;
|
||||||
if (imagePath.startsWith("https://") || imagePath.startsWith("http://")) {
|
if (imagePath.startsWith("https://") || imagePath.startsWith("http://")) {
|
||||||
const res = await fetch(imagePath, { signal: AbortSignal.timeout(5000) });
|
const res = await fetch(imagePath);
|
||||||
return await res.arrayBuffer();
|
buffer = await res.arrayBuffer();
|
||||||
} else {
|
} else {
|
||||||
const b = await readFile(imagePath);
|
const b = await readFile(imagePath);
|
||||||
return b.buffer as ArrayBuffer;
|
buffer = b.buffer;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getExifData(buffer: ArrayBuffer) {
|
|
||||||
if (!buffer) return undefined;
|
|
||||||
|
|
||||||
const tags = await ExifReader.load(buffer, { async: true });
|
const tags = await ExifReader.load(buffer, { async: true });
|
||||||
|
|
||||||
const out: Record<string, unknown> = {};
|
if (!buffer) return undefined;
|
||||||
|
|
||||||
|
const out: Record<string, any> = {};
|
||||||
let hasExif = false;
|
let hasExif = false;
|
||||||
|
|
||||||
for (const key of allowedExif) {
|
for (const key of allowedExif) {
|
||||||
@@ -83,37 +103,8 @@ export async function getExifData(buffer: ArrayBuffer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return hasExif ? out : undefined;
|
return hasExif ? out : undefined;
|
||||||
}
|
} catch (error) {
|
||||||
|
console.log(`Error reading EXIF data from ${JSON.stringify(image)}`, error);
|
||||||
const CACHE_DIR = path.join(process.cwd(), 'node_modules', '.astro', 'image-cache');
|
return undefined;
|
||||||
|
|
||||||
export async function getProcessedImage(image: ImageMetadata) {
|
|
||||||
const buffer = await getImageBuffer(image);
|
|
||||||
if (!buffer) {
|
|
||||||
return { thumbhash: undefined, exif: undefined };
|
|
||||||
}
|
|
||||||
|
|
||||||
const hash = createHash('sha256').update(new Uint8Array(buffer)).digest('hex');
|
|
||||||
const cacheFile = path.join(CACHE_DIR, `${hash}.json`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const cachedData = await fs.readFile(cacheFile, 'utf-8');
|
|
||||||
return JSON.parse(cachedData);
|
|
||||||
} catch (e) {
|
|
||||||
if (e.code !== 'ENOENT') {
|
|
||||||
console.error("Failed to read from image cache:", e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const thumbhash = await generateThumbHash(buffer);
|
|
||||||
const exif = await getExifData(buffer);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await fs.mkdir(CACHE_DIR, { recursive: true });
|
|
||||||
await fs.writeFile(cacheFile, JSON.stringify({ thumbhash, exif }), 'utf-8');
|
|
||||||
} catch (writeError) {
|
|
||||||
console.error("Failed to write to image cache:", writeError);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { thumbhash, exif };
|
|
||||||
}
|
|
||||||
|
|||||||