Service UX: images in create form, original block look + hover zoom, styled file input
- Add image picker to the "Dodaj storitev" form (upload images while creating) - Restore original service layout on the homepage: centered uppercase title above, rounded image beside text, alternating sides; image zooms on hover - Slideshow gains a hoverZoom option (scale on hover) - Style native file inputs (::file-selector-button) to match the dashboard UI Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
7ff2ca667a
commit
a6076fbbf3
23
src/app.css
23
src/app.css
|
|
@ -92,6 +92,29 @@ body {
|
|||
background: rgba(255, 255, 255, 0.08);
|
||||
color: #e5e4ea;
|
||||
}
|
||||
|
||||
/* Style the native file input to match the rest of the dashboard UI. */
|
||||
input[type="file"] {
|
||||
color: #9aa0a6;
|
||||
font-family: inherit;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
input[type="file"]::file-selector-button {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
color: #e5e4ea;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 0.5em 1em;
|
||||
margin-right: 0.9em;
|
||||
font-family: inherit;
|
||||
font-size: 0.95em;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s ease;
|
||||
}
|
||||
input[type="file"]::file-selector-button:hover {
|
||||
background: rgba(66, 175, 56, 0.25);
|
||||
}
|
||||
.admin-msg {
|
||||
margin: 0;
|
||||
font-size: 0.9em;
|
||||
|
|
|
|||
|
|
@ -4,8 +4,15 @@
|
|||
images = [],
|
||||
interval = 4000,
|
||||
alt = '',
|
||||
fit = 'cover'
|
||||
}: { images: string[]; interval?: number; alt?: string; fit?: 'cover' | 'contain' } = $props();
|
||||
fit = 'cover',
|
||||
hoverZoom = false
|
||||
}: {
|
||||
images: string[];
|
||||
interval?: number;
|
||||
alt?: string;
|
||||
fit?: 'cover' | 'contain';
|
||||
hoverZoom?: boolean;
|
||||
} = $props();
|
||||
|
||||
let current = $state(0);
|
||||
|
||||
|
|
@ -19,7 +26,7 @@
|
|||
</script>
|
||||
|
||||
{#if images.length > 0}
|
||||
<div class="slideshow">
|
||||
<div class="slideshow" class:zoom={hoverZoom}>
|
||||
{#each images as src, i (src)}
|
||||
<img {src} {alt} class:active={i === current % images.length} style:object-fit={fit} loading="lazy" />
|
||||
{/each}
|
||||
|
|
@ -47,13 +54,17 @@
|
|||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
transition: opacity 0.8s ease;
|
||||
transition: opacity 0.8s ease, transform 0.35s ease;
|
||||
}
|
||||
|
||||
.slideshow img.active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.slideshow.zoom:hover img {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.dots {
|
||||
position: absolute;
|
||||
bottom: 0.8em;
|
||||
|
|
|
|||
|
|
@ -19,14 +19,15 @@
|
|||
{#if data.services.length > 0}
|
||||
<section class="services" id="storitve">
|
||||
{#each data.services as service, i (service.id)}
|
||||
<article class="service" class:reverse={i % 2 === 1}>
|
||||
<article class="service">
|
||||
<h2 class="service-title">{service.title}</h2>
|
||||
<div class="service-content" class:reverse={i % 2 === 1}>
|
||||
{#if service.images.length > 0}
|
||||
<div class="service-media">
|
||||
<Slideshow images={service.images} alt={service.title} />
|
||||
<Slideshow images={service.images} alt={service.title} hoverZoom />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="service-text">
|
||||
<h2>{service.title}</h2>
|
||||
{#if service.description}<p class="desc">{service.description}</p>{/if}
|
||||
{#if service.bullets.length > 0}
|
||||
<ul>
|
||||
|
|
@ -34,6 +35,7 @@
|
|||
</ul>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
{/each}
|
||||
</section>
|
||||
|
|
@ -104,34 +106,46 @@
|
|||
.services {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2.5em;
|
||||
gap: 3.5em;
|
||||
}
|
||||
|
||||
.service {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5em;
|
||||
align-items: center;
|
||||
gap: 1.5em;
|
||||
}
|
||||
|
||||
.service-title {
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
font-size: clamp(1.5em, 4vw, 2em);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.service-content {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2em;
|
||||
}
|
||||
|
||||
.service-media {
|
||||
width: 100%;
|
||||
aspect-ratio: 4 / 3;
|
||||
border-radius: 12px;
|
||||
aspect-ratio: 16 / 10;
|
||||
border-radius: 2em;
|
||||
overflow: hidden;
|
||||
background: #11161a;
|
||||
}
|
||||
|
||||
.service-text {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.service-text h2 {
|
||||
margin: 0 0 0.4em;
|
||||
font-size: 1.3em;
|
||||
color: #bcc0c2;
|
||||
}
|
||||
|
||||
.desc {
|
||||
color: #c7c9cf;
|
||||
margin: 0 0 0.8em;
|
||||
}
|
||||
|
||||
|
|
@ -142,7 +156,6 @@
|
|||
|
||||
.service-text li {
|
||||
margin-bottom: 0.5em;
|
||||
color: #c7c9cf;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
|
|
@ -251,16 +264,17 @@
|
|||
}
|
||||
|
||||
@media (min-width: 800px) {
|
||||
.service {
|
||||
.service-content {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.service.reverse {
|
||||
.service-content.reverse {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.service-media {
|
||||
width: 48%;
|
||||
width: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,13 @@
|
|||
import { fail } from '@sveltejs/kit';
|
||||
import type { Actions, PageServerLoad } from './$types';
|
||||
import { deleteUpload } from '$lib/server/storage';
|
||||
import { createService, deleteService, listServices, moveService } from '$lib/server/content';
|
||||
import { deleteUpload, saveImage } from '$lib/server/storage';
|
||||
import {
|
||||
addServiceImage,
|
||||
createService,
|
||||
deleteService,
|
||||
listServices,
|
||||
moveService
|
||||
} from '$lib/server/content';
|
||||
|
||||
export const load: PageServerLoad = () => ({
|
||||
services: listServices().map((s) => ({
|
||||
|
|
@ -17,11 +23,17 @@ export const actions: Actions = {
|
|||
const form = await request.formData();
|
||||
const title = String(form.get('title') ?? '').trim();
|
||||
if (!title) return fail(400, { error: 'Vnesite naziv storitve.' });
|
||||
createService({
|
||||
const id = createService({
|
||||
title,
|
||||
description: String(form.get('description') ?? '').trim() || null,
|
||||
bullets: String(form.get('bullets') ?? '').trim() || null
|
||||
});
|
||||
const files = form
|
||||
.getAll('images')
|
||||
.filter((e): e is File => e instanceof File && e.size > 0);
|
||||
for (const file of files) {
|
||||
addServiceImage(id, await saveImage('services', file));
|
||||
}
|
||||
return { success: 'Storitev dodana.' };
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -3,12 +3,28 @@
|
|||
import type { PageData, ActionData } from './$types';
|
||||
|
||||
let { data, form }: { data: PageData; form: ActionData } = $props();
|
||||
|
||||
let creating = $state(false);
|
||||
let fileInput: HTMLInputElement | undefined = $state();
|
||||
</script>
|
||||
|
||||
<section class="admin-section">
|
||||
<h1>Storitve</h1>
|
||||
|
||||
<form class="admin-card" method="POST" action="?/create" use:enhance>
|
||||
<form
|
||||
class="admin-card"
|
||||
method="POST"
|
||||
action="?/create"
|
||||
enctype="multipart/form-data"
|
||||
use:enhance={() => {
|
||||
creating = true;
|
||||
return async ({ update }) => {
|
||||
await update({ reset: true });
|
||||
creating = false;
|
||||
if (fileInput) fileInput.value = '';
|
||||
};
|
||||
}}
|
||||
>
|
||||
<h2>Dodaj storitev</h2>
|
||||
<label>
|
||||
Naziv
|
||||
|
|
@ -22,9 +38,15 @@
|
|||
Postavke seznama (ena na vrstico, neobvezno)
|
||||
<textarea class="admin-input" name="bullets" rows="3"></textarea>
|
||||
</label>
|
||||
<label>
|
||||
Slike (neobvezno, lahko več)
|
||||
<input bind:this={fileInput} type="file" name="images" accept="image/*" multiple />
|
||||
</label>
|
||||
{#if form?.error}<p class="admin-error">{form.error}</p>{/if}
|
||||
{#if form?.success}<p class="admin-msg">{form.success}</p>{/if}
|
||||
<button class="admin-btn" type="submit">Dodaj</button>
|
||||
<button class="admin-btn" type="submit" disabled={creating}>
|
||||
{creating ? 'Dodajanje…' : 'Dodaj'}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<h2>Obstoječe storitve</h2>
|
||||
|
|
|
|||
Loading…
Reference in New Issue