Image posts and slideshows
This commit is contained in:
parent
bfd03cda87
commit
f970913cc8
5 changed files with 237 additions and 23 deletions
133
src/lib/components/ImagePost.svelte
Normal file
133
src/lib/components/ImagePost.svelte
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
<script lang="ts">
|
||||||
|
let {
|
||||||
|
images = [],
|
||||||
|
alt = ''
|
||||||
|
}: {
|
||||||
|
images: string[]
|
||||||
|
alt?: string
|
||||||
|
} = $props()
|
||||||
|
|
||||||
|
let selectedIndex = $state(0)
|
||||||
|
|
||||||
|
const selectImage = (index: number) => {
|
||||||
|
selectedIndex = index
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if images.length === 1}
|
||||||
|
<!-- Single image -->
|
||||||
|
<div class="single-image">
|
||||||
|
<img src={images[0]} {alt} />
|
||||||
|
</div>
|
||||||
|
{:else if images.length > 1}
|
||||||
|
<!-- Slideshow -->
|
||||||
|
<div class="slideshow">
|
||||||
|
<div class="main-image">
|
||||||
|
<img src={images[selectedIndex]} alt="{alt} {selectedIndex + 1}" />
|
||||||
|
</div>
|
||||||
|
<div class="thumbnails">
|
||||||
|
{#each images as image, index}
|
||||||
|
<button
|
||||||
|
class="thumbnail"
|
||||||
|
class:active={index === selectedIndex}
|
||||||
|
onclick={() => selectImage(index)}
|
||||||
|
aria-label="View image {index + 1}"
|
||||||
|
>
|
||||||
|
<img src={image} alt="{alt} thumbnail {index + 1}" />
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.single-image,
|
||||||
|
.main-image {
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: 4 / 3;
|
||||||
|
border-radius: $card-corner-radius;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.slideshow {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: $unit-2x;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnails {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(calc(25% - 12px), 1fr));
|
||||||
|
gap: $unit-2x;
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(calc(33.33% - 11px), 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 400px) {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(calc(50% - 8px), 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnail {
|
||||||
|
position: relative;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
border-radius: $card-corner-radius;
|
||||||
|
overflow: hidden;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
background: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
border-radius: $card-corner-radius;
|
||||||
|
border: 4px solid transparent;
|
||||||
|
z-index: 2;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: border-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 4px;
|
||||||
|
border-radius: calc($card-corner-radius - 4px);
|
||||||
|
border: 4px solid transparent;
|
||||||
|
z-index: 3;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: border-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
&::before {
|
||||||
|
border-color: $red-60;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
border-color: $grey-100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Post } from '$lib/posts'
|
import type { Post } from '$lib/posts'
|
||||||
|
import ImagePost from './ImagePost.svelte'
|
||||||
|
|
||||||
let { post }: { post: Post } = $props()
|
let { post }: { post: Post } = $props()
|
||||||
|
|
||||||
|
|
@ -23,6 +24,12 @@
|
||||||
</time>
|
</time>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
{#if post.type === 'image' && post.images}
|
||||||
|
<div class="post-images">
|
||||||
|
<ImagePost images={post.images} alt={post.title || 'Post image'} />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div class="post-body">
|
<div class="post-body">
|
||||||
{@html post.content}
|
{@html post.content}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -42,6 +49,12 @@
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.image {
|
||||||
|
.post-images {
|
||||||
|
margin-bottom: $unit-4x;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-header {
|
.post-header {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Post } from '$lib/posts'
|
import type { Post } from '$lib/posts'
|
||||||
|
import ImagePost from './ImagePost.svelte'
|
||||||
|
|
||||||
let { post }: { post: Post } = $props()
|
let { post }: { post: Post } = $props()
|
||||||
|
|
||||||
|
|
@ -14,15 +15,28 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<article class="post-item {post.type}">
|
<article class="post-item {post.type}">
|
||||||
<a href="/blog/{post.slug}" class="post-link">
|
<div class="post-content">
|
||||||
{#if post.title}
|
{#if post.title}
|
||||||
<h2 class="post-title">{post.title}</h2>
|
<h2 class="post-title">
|
||||||
|
<a href="/blog/{post.slug}" class="post-title-link">{post.title}</a>
|
||||||
|
</h2>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if post.type === 'image' && post.images}
|
||||||
|
<div class="post-image">
|
||||||
|
<ImagePost images={post.images} alt={post.title || 'Post image'} />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<div class="post-text">
|
||||||
|
{#if post.excerpt}
|
||||||
<p class="post-excerpt">{post.excerpt}</p>
|
<p class="post-excerpt">{post.excerpt}</p>
|
||||||
|
{/if}
|
||||||
|
<a href="/blog/{post.slug}" class="post-date-link">
|
||||||
<time class="post-date" datetime={post.date}>
|
<time class="post-date" datetime={post.date}>
|
||||||
{formatDate(post.date)}
|
{formatDate(post.date)}
|
||||||
</time>
|
</time>
|
||||||
</a>
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
@ -35,32 +49,46 @@
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.image {
|
||||||
|
.post-image {
|
||||||
|
margin-bottom: $unit-2x;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-link {
|
.post-content {
|
||||||
display: block;
|
|
||||||
text-decoration: none;
|
|
||||||
color: inherit;
|
|
||||||
padding: $unit-3x;
|
padding: $unit-3x;
|
||||||
background: $grey-100;
|
background: $grey-100;
|
||||||
border-radius: $card-corner-radius;
|
border-radius: $card-corner-radius;
|
||||||
transition: all 0.2s ease;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
// Hover styles can be added here if needed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.post-text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: $unit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-title {
|
.post-title {
|
||||||
margin: 0 0 $unit;
|
margin: 0 0 $unit-2x;
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-title-link {
|
||||||
color: $red-60;
|
color: $red-60;
|
||||||
transition: color 0.2s ease;
|
text-decoration: none;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
text-decoration-style: wavy;
|
||||||
|
text-underline-offset: 0.15em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-excerpt {
|
.post-excerpt {
|
||||||
margin: 0 0 $unit-2x;
|
margin: 0;
|
||||||
color: $grey-00;
|
color: $grey-00;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
|
|
@ -70,10 +98,25 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.post-date-link {
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.post-date {
|
||||||
|
color: $red-60;
|
||||||
|
text-decoration: underline;
|
||||||
|
text-decoration-style: wavy;
|
||||||
|
text-underline-offset: 0.15em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.post-date {
|
.post-date {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
color: $grey-40;
|
color: $grey-40;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
15
src/lib/posts/beautiful-sunset-gallery.md
Normal file
15
src/lib/posts/beautiful-sunset-gallery.md
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
---
|
||||||
|
title: "Beautiful Sunset Gallery"
|
||||||
|
type: "image"
|
||||||
|
date: "2024-01-19T18:30:00Z"
|
||||||
|
slug: "beautiful-sunset-gallery"
|
||||||
|
published: true
|
||||||
|
images:
|
||||||
|
- "https://images.unsplash.com/photo-1495616811223-4d98c6e9c869?w=800&h=600&fit=crop"
|
||||||
|
- "https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800&h=600&fit=crop"
|
||||||
|
- "https://images.unsplash.com/photo-1470252649378-9c29740c9fa8?w=800&h=600&fit=crop"
|
||||||
|
- "https://images.unsplash.com/photo-1475924156734-496f6cac6ec1?w=800&h=600&fit=crop"
|
||||||
|
- "https://images.unsplash.com/photo-1472214103451-9374bd1c798e?w=800&h=600&fit=crop"
|
||||||
|
---
|
||||||
|
|
||||||
|
Caught these stunning sunsets during my recent travels. Each one tells its own story of endings and beginnings.
|
||||||
10
src/lib/posts/minimalist-workspace.md
Normal file
10
src/lib/posts/minimalist-workspace.md
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
type: "image"
|
||||||
|
date: "2024-01-17T10:00:00Z"
|
||||||
|
slug: "minimalist-workspace"
|
||||||
|
published: true
|
||||||
|
images:
|
||||||
|
- "https://images.unsplash.com/photo-1555212697-194d092e3b8f?w=800&h=600&fit=crop"
|
||||||
|
---
|
||||||
|
|
||||||
|
My workspace this morning. Sometimes less really is more.
|
||||||
Loading…
Reference in a new issue