Revamp main page layout: Integrate NewsCarousel and ProgramMiniCard components, update content to include news and program access, and enhance visual elements for the Secretaria de Esportes de Pernambuco.
This commit is contained in:
137
src/lib/components/NewsCarousel.svelte
Normal file
137
src/lib/components/NewsCarousel.svelte
Normal file
@@ -0,0 +1,137 @@
|
||||
<script lang="ts">
|
||||
import type { News } from '$lib/data/news';
|
||||
|
||||
type Props = {
|
||||
items: News[];
|
||||
title?: string;
|
||||
};
|
||||
|
||||
const { items, title = 'Destaques' }: Props = $props();
|
||||
|
||||
let index = $state(0);
|
||||
|
||||
function clamp(next: number) {
|
||||
const n = items.length;
|
||||
if (n === 0) return 0;
|
||||
return ((next % n) + n) % n;
|
||||
}
|
||||
|
||||
function goTo(next: number) {
|
||||
index = clamp(next);
|
||||
}
|
||||
|
||||
function next() {
|
||||
goTo(index + 1);
|
||||
}
|
||||
|
||||
function prev() {
|
||||
goTo(index - 1);
|
||||
}
|
||||
|
||||
function onKeydown(event: KeyboardEvent) {
|
||||
if (event.key === 'ArrowRight') {
|
||||
event.preventDefault();
|
||||
next();
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowLeft') {
|
||||
event.preventDefault();
|
||||
prev();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<section aria-label={title} class="overflow-hidden rounded-3xl bg-white/80 shadow-sm ring-1 ring-black/5">
|
||||
<header class="flex items-center justify-between gap-3 px-6 py-5 sm:px-8">
|
||||
<h2 class="text-lg font-extrabold tracking-tight text-(--text-strong)">{title}</h2>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex h-9 w-9 items-center justify-center rounded-xl bg-white/80 text-(--text-strong) ring-1 ring-black/10 shadow-sm transition hover:bg-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--focus-ring)"
|
||||
onclick={prev}
|
||||
aria-label="Notícia anterior"
|
||||
disabled={items.length < 2}
|
||||
>
|
||||
<span aria-hidden="true">←</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex h-9 w-9 items-center justify-center rounded-xl bg-white/80 text-(--text-strong) ring-1 ring-black/10 shadow-sm transition hover:bg-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--focus-ring)"
|
||||
onclick={next}
|
||||
aria-label="Próxima notícia"
|
||||
disabled={items.length < 2}
|
||||
>
|
||||
<span aria-hidden="true">→</span>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{#if items.length === 0}
|
||||
<div class="px-6 pb-7 sm:px-8">
|
||||
<p class="rounded-2xl bg-white/80 p-6 text-sm text-(--text) ring-1 ring-black/5">
|
||||
Nenhuma notícia disponível.
|
||||
</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-label="Carrossel de destaques. Use as setas esquerda e direita para navegar."
|
||||
class="px-6 pb-7 sm:px-8"
|
||||
onkeydown={onKeydown}
|
||||
>
|
||||
{#each items as item, i (item.slug)}
|
||||
<a
|
||||
href={`/noticias/${item.slug}`}
|
||||
class={`group block overflow-hidden rounded-3xl ring-1 ring-black/10 shadow-sm transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--focus-ring) ${i === index ? '' : 'hidden'}`}
|
||||
aria-label={`Ler notícia: ${item.title}`}
|
||||
>
|
||||
<div class="relative">
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="h-56 w-full bg-linear-to-br from-[color:var(--accent-blue)]/15 via-white/40 to-[color:var(--accent-yellow)]/20 sm:h-64"
|
||||
></div>
|
||||
<div class="absolute inset-0 bg-linear-to-t from-black/35 via-black/0 to-black/0"></div>
|
||||
<div class="absolute bottom-0 left-0 right-0 p-6 sm:p-7">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<span
|
||||
class="rounded-full bg-white/90 px-3 py-1 text-xs font-semibold text-(--text-strong) ring-1 ring-black/10"
|
||||
>
|
||||
{item.category}
|
||||
</span>
|
||||
<span
|
||||
class="rounded-full bg-white/85 px-3 py-1 text-xs font-semibold text-(--text-muted) ring-1 ring-black/10"
|
||||
>
|
||||
{new Date(item.date).toLocaleDateString('pt-BR')}
|
||||
</span>
|
||||
</div>
|
||||
<p class="mt-3 text-xl font-extrabold tracking-tight text-white sm:text-2xl">
|
||||
{item.title}
|
||||
</p>
|
||||
<p class="mt-2 max-w-3xl text-sm leading-relaxed text-white/90">
|
||||
{item.dek}
|
||||
</p>
|
||||
<div class="mt-4 inline-flex items-center gap-2 text-sm font-semibold text-white">
|
||||
<span class="underline-offset-4 group-hover:underline">Ler notícia</span>
|
||||
<span aria-hidden="true" class="transition group-hover:translate-x-0.5">→</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
{/each}
|
||||
|
||||
<div class="mt-4 flex items-center justify-center gap-2">
|
||||
{#each items as item, i (item.slug)}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => goTo(i)}
|
||||
aria-label={`Ir para o destaque ${i + 1}: ${item.title}`}
|
||||
aria-current={i === index ? 'true' : undefined}
|
||||
class={`h-2.5 w-2.5 rounded-full ring-1 ring-black/10 transition ${i === index ? 'bg-(--link)' : 'bg-white/80 hover:bg-white'}`}
|
||||
></button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
48
src/lib/components/ProgramMiniCard.svelte
Normal file
48
src/lib/components/ProgramMiniCard.svelte
Normal file
@@ -0,0 +1,48 @@
|
||||
<script lang="ts">
|
||||
import type { Program } from '$lib/data/programs';
|
||||
|
||||
type Props = {
|
||||
program: Program;
|
||||
};
|
||||
|
||||
const { program }: Props = $props();
|
||||
|
||||
const accentDotByAccent = {
|
||||
blue: 'bg-[color:var(--accent-blue)]',
|
||||
yellow: 'bg-[color:var(--accent-yellow)]',
|
||||
green: 'bg-[color:var(--accent-green)]',
|
||||
red: 'bg-[color:var(--accent-red)]'
|
||||
} as const;
|
||||
</script>
|
||||
|
||||
<a
|
||||
href={`/${program.id}`}
|
||||
class="group flex items-start gap-3 rounded-2xl bg-white/80 p-4 shadow-sm ring-1 ring-black/5 transition hover:bg-white hover:shadow-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--focus-ring)"
|
||||
aria-label={`Acessar programa ${program.name}`}
|
||||
>
|
||||
<div class="grid h-10 w-10 shrink-0 place-items-center rounded-xl bg-(--surface-muted) ring-1 ring-black/5">
|
||||
<img
|
||||
src={program.brandImageSrc}
|
||||
alt={`Logomarca ${program.name}`}
|
||||
class="h-7 w-7 object-contain"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class={`h-2 w-2 rounded-full ${accentDotByAccent[program.accent]}`}
|
||||
></span>
|
||||
<p class="truncate text-sm font-bold text-(--text-strong)">{program.name}</p>
|
||||
</div>
|
||||
<p class="mt-1 line-clamp-2 text-xs leading-relaxed text-(--text)">{program.shortDescription}</p>
|
||||
<div class="mt-2 inline-flex items-center gap-2 text-xs font-semibold text-(--link)">
|
||||
<span class="underline-offset-4 group-hover:underline">Abrir</span>
|
||||
<span aria-hidden="true" class="transition group-hover:translate-x-0.5">→</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
Reference in New Issue
Block a user