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">
|
||||
import type { Post } from '$lib/posts'
|
||||
import ImagePost from './ImagePost.svelte'
|
||||
|
||||
let { post }: { post: Post } = $props()
|
||||
|
||||
|
|
@ -23,6 +24,12 @@
|
|||
</time>
|
||||
</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">
|
||||
{@html post.content}
|
||||
</div>
|
||||
|
|
@ -42,6 +49,12 @@
|
|||
font-size: 1.1rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.image {
|
||||
.post-images {
|
||||
margin-bottom: $unit-4x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.post-header {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script lang="ts">
|
||||
import type { Post } from '$lib/posts'
|
||||
import ImagePost from './ImagePost.svelte'
|
||||
|
||||
let { post }: { post: Post } = $props()
|
||||
|
||||
|
|
@ -14,15 +15,28 @@
|
|||
</script>
|
||||
|
||||
<article class="post-item {post.type}">
|
||||
<a href="/blog/{post.slug}" class="post-link">
|
||||
<div class="post-content">
|
||||
{#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}
|
||||
<p class="post-excerpt">{post.excerpt}</p>
|
||||
<time class="post-date" datetime={post.date}>
|
||||
{formatDate(post.date)}
|
||||
</time>
|
||||
</a>
|
||||
{#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>
|
||||
{/if}
|
||||
<a href="/blog/{post.slug}" class="post-date-link">
|
||||
<time class="post-date" datetime={post.date}>
|
||||
{formatDate(post.date)}
|
||||
</time>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
@ -35,32 +49,46 @@
|
|||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.post-link {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
padding: $unit-3x;
|
||||
background: $grey-100;
|
||||
border-radius: $card-corner-radius;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
// Hover styles can be added here if needed
|
||||
|
||||
&.image {
|
||||
.post-image {
|
||||
margin-bottom: $unit-2x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.post-content {
|
||||
padding: $unit-3x;
|
||||
background: $grey-100;
|
||||
border-radius: $card-corner-radius;
|
||||
}
|
||||
|
||||
.post-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $unit;
|
||||
}
|
||||
|
||||
.post-title {
|
||||
margin: 0 0 $unit;
|
||||
margin: 0 0 $unit-2x;
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.post-title-link {
|
||||
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 {
|
||||
margin: 0 0 $unit-2x;
|
||||
margin: 0;
|
||||
color: $grey-00;
|
||||
font-size: 1rem;
|
||||
line-height: 1.3;
|
||||
|
|
@ -69,11 +97,26 @@
|
|||
-webkit-line-clamp: 3;
|
||||
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 {
|
||||
display: block;
|
||||
font-size: 1rem;
|
||||
color: $grey-40;
|
||||
font-weight: 400;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
</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