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:
2026-01-06 11:53:29 -03:00
parent 51ab1eb6cb
commit da387ed7e7
7 changed files with 426 additions and 110 deletions

View 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>

View 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>

67
src/lib/data/news.ts Normal file
View File

@@ -0,0 +1,67 @@
export type News = {
slug: string;
title: string;
dek: string;
date: string;
category: string;
featured?: boolean;
body?: string[];
};
export const news: News[] = [
{
slug: 'inscricoes-programas-2026',
title: 'Secretaria de Esportes abre inscrições para Bolsa Atleta, Bolsa Técnico e Time Pernambuco 2026',
dek: 'Processo de inscrição é exclusivamente online e segue até 5 de fevereiro.',
date: '2026-01-02',
category: 'Programas',
featured: true,
body: [
'As inscrições para os programas Bolsa Atleta, Bolsa Técnico e Time Pernambuco 2026 estão abertas.',
'Este conteúdo é um placeholder — você poderá substituir por dados vindos de banco/CMS futuramente.'
]
},
{
slug: 'investimento-recorde-2025',
title: 'Programas de incentivo alcançam marca recorde de beneficiados em 2025',
dek: 'Mais de 1,2 mil atletas e técnicos contemplados, com investimento aproximado de R$ 13 milhões.',
date: '2025-12-18',
category: 'Resultados',
featured: true,
body: [
'Em 2025, os programas de incentivo ao esporte alcançaram um número recorde de beneficiados.',
'Este conteúdo é um placeholder — você poderá substituir por dados vindos de banco/CMS futuramente.'
]
},
{
slug: 'comunicado-documentacao',
title: 'Comunicado: documentação e prazos para inscrição',
dek: 'Confira as orientações do edital e os documentos exigidos para cada programa.',
date: '2026-01-03',
category: 'Comunicados',
body: [
'Após preencher o formulário eletrônico, é necessário anexar a documentação exigida na plataforma.',
'Este conteúdo é um placeholder — você poderá substituir por dados vindos de banco/CMS futuramente.'
]
},
{
slug: 'duvidas-frequentes',
title: 'Dúvidas frequentes sobre inscrição e participação',
dek: 'Reunimos respostas rápidas para as principais perguntas sobre os programas.',
date: '2026-01-05',
category: 'Serviço',
body: [
'Veja perguntas e respostas sobre inscrição, documentos, prazos e acompanhamento.',
'Este conteúdo é um placeholder — você poderá substituir por dados vindos de banco/CMS futuramente.'
]
}
];
export const featuredNews = news.filter((item) => item.featured);
export function getNews(slug: string): News {
const item = news.find((n) => n.slug === slug);
if (!item) throw new Error(`Notícia não encontrada: ${slug}`);
return item;
}

View File

@@ -1,19 +1,29 @@
<script lang="ts">
import ProgramCard from '$lib/components/ProgramCard.svelte';
import { enrollment2026, programs, programs2026Dek, programs2026Headline, results2025Summary } from '$lib/data/programs';
import NewsCarousel from '$lib/components/NewsCarousel.svelte';
import ProgramMiniCard from '$lib/components/ProgramMiniCard.svelte';
import { featuredNews, news } from '$lib/data/news';
import { programs } from '$lib/data/programs';
</script>
<svelte:head>
<title>Secretaria de Esportes de Pernambuco</title>
<meta
name="description"
content="Página institucional da Secretaria de Esportes de Pernambuco (SESP-PE) com acesso aos programas Bolsa Atleta, Bolsa Técnico e Time Pernambuco."
content="Página institucional da Secretaria de Esportes de Pernambuco (SESP-PE) com notícias e acesso aos programas Bolsa Atleta, Bolsa Técnico e Time Pernambuco."
/>
</svelte:head>
<div class="min-h-dvh bg-(--page-bg)">
<header class="border-b border-black/5 bg-white/70 backdrop-blur">
<div class="mx-auto flex w-full max-w-6xl items-center justify-between px-4 py-5 sm:px-6">
<div class="flex min-w-0 items-center gap-4">
<img
src="/brand/gov-pe.png"
alt="Governo de Pernambuco"
class="h-10 w-auto object-contain"
loading="eager"
decoding="async"
/>
<div class="min-w-0">
<p class="text-xs font-semibold uppercase tracking-widest text-(--text-muted)">
Governo de Pernambuco
@@ -21,9 +31,7 @@
<h1 class="mt-1 text-xl font-extrabold tracking-tight text-(--text-strong) sm:text-2xl">
Secretaria de Esportes de Pernambuco
</h1>
<p class="mt-1 text-sm text-(--text)">
Informações e acesso rápido aos programas institucionais.
</p>
</div>
</div>
<div class="hidden items-center gap-3 sm:flex">
@@ -37,106 +45,78 @@
</header>
<main class="mx-auto w-full max-w-6xl px-4 pb-14 pt-10 sm:px-6 sm:pt-14">
<section class="overflow-hidden rounded-3xl bg-white/80 p-6 shadow-sm ring-1 ring-black/5 sm:p-10">
<div class="flex flex-col gap-6 md:flex-row md:items-center md:justify-between">
<div class="max-w-3xl">
<h2 class="text-2xl font-bold tracking-tight text-(--text-strong) sm:text-3xl">
Programas da Secretaria
</h2>
<p class="mt-2 text-base leading-relaxed text-(--text)">
Selecione um programa para acessar a página com os links oficiais de <strong>inscrição</strong> e
<strong>edital</strong>.
</p>
</div>
<div class="flex flex-wrap gap-2">
<span class="pill">Links oficiais</span>
<span class="pill">Inscrição e edital</span>
<span class="pill">Nova aba</span>
</div>
</div>
<div class="grid gap-8 lg:grid-cols-[1fr_360px]">
<section class="space-y-8">
<NewsCarousel items={featuredNews} title="Notícias em destaque" />
<div class="mt-7 rounded-2xl bg-white/70 p-5 ring-1 ring-black/5">
<p class="text-xs font-semibold uppercase tracking-widest text-(--text-muted)">Comunicado</p>
<h3 class="mt-1 text-lg font-extrabold tracking-tight text-(--text-strong)">
{programs2026Headline}
</h3>
<p class="mt-1 text-sm text-(--text)">{programs2026Dek}</p>
<div class="mt-4 grid gap-3 sm:grid-cols-2">
<div class="rounded-xl bg-white/80 p-4 ring-1 ring-black/5">
<p class="text-sm font-bold text-(--text-strong)">Período</p>
<section class="rounded-3xl bg-white/80 p-6 shadow-sm ring-1 ring-black/5 sm:p-8">
<div class="flex flex-col gap-2 sm:flex-row sm:items-end sm:justify-between">
<div>
<h2 class="text-lg font-extrabold tracking-tight text-(--text-strong)">Notícias</h2>
<p class="mt-1 text-sm text-(--text)">
De <strong>{enrollment2026.start}</strong> até <strong>{enrollment2026.end}</strong>.
</p>
<p class="mt-2 text-xs text-(--text-muted)">
Processo de inscrição exclusivamente online, conforme orientações disponíveis nos editais.
</p>
</div>
<div class="rounded-xl bg-white/80 p-4 ring-1 ring-black/5">
<p class="text-sm font-bold text-(--text-strong)">Documentação</p>
<p class="mt-1 text-sm text-(--text)">
Após o preenchimento do formulário eletrônico, atletas e técnicos devem anexar a documentação
exigida na própria plataforma.
</p>
<p class="mt-2 text-xs text-(--text-muted)">
No Time PE, além do cadastro online, o envio da documentação segue procedimento específico
descrito no edital.
Acompanhe comunicados, resultados e novidades.
</p>
</div>
<span class="pill">Atualizado</span>
</div>
<blockquote class="mt-4 rounded-xl bg-white/80 p-4 ring-1 ring-black/5">
<p class="text-sm italic text-(--text)">
“Os programas de incentivo ao esporte são ferramentas essenciais para garantir que atletas, paratletas
e técnicos tenham condições de se dedicar aos treinamentos e às competições.”
</p>
<p class="mt-2 text-xs font-semibold text-(--text-muted)">
Ivete Lacerda — Secretária de Esportes
</p>
</blockquote>
<div class="mt-6 space-y-3">
{#each news as item (item.slug)}
<a
href={`/noticias/${item.slug}`}
class="group block rounded-2xl bg-white/80 p-5 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={`Abrir notícia: ${item.title}`}
>
<div class="flex flex-wrap items-center gap-2">
<span
class="rounded-full bg-(--surface-muted) px-3 py-1 text-xs font-semibold text-(--text-muted) ring-1 ring-black/5"
>
{item.category}
</span>
<span
class="rounded-full bg-white/80 px-3 py-1 text-xs font-semibold text-(--text-muted) ring-1 ring-black/5"
>
{new Date(item.date).toLocaleDateString('pt-BR')}
</span>
</div>
<div class="mt-4 space-y-2 text-sm text-(--text)">
{#each results2025Summary as line (line)}
<p>{line}</p>
<p class="mt-3 text-base font-extrabold tracking-tight text-(--text-strong)">
{item.title}
</p>
<p class="mt-1 text-sm leading-relaxed text-(--text)">{item.dek}</p>
<div class="mt-3 inline-flex items-center gap-2 text-sm font-semibold text-(--link)">
<span class="underline-offset-4 group-hover:underline">Ler</span>
<span aria-hidden="true" class="transition group-hover:translate-x-0.5"></span>
</div>
</a>
{/each}
</div>
</div>
</section>
</section>
<div class="mt-8 grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
<aside class="space-y-6">
<section class="rounded-3xl bg-white/80 p-6 shadow-sm ring-1 ring-black/5 sm:p-8">
<h2 class="text-lg font-extrabold tracking-tight text-(--text-strong)">Programas</h2>
<p class="mt-1 text-sm text-(--text)">
Acesso rápido aos links oficiais de inscrição e edital.
</p>
<div class="mt-5 space-y-3">
{#each programs as program (program.id)}
<ProgramCard {program} />
<ProgramMiniCard {program} />
{/each}
</div>
</section>
<section class="mt-10 grid gap-4 md:grid-cols-2">
<div class="rounded-2xl bg-white/80 p-6 ring-1 ring-black/5">
<h3 class="text-base font-bold text-(--text-strong)">Como participar</h3>
<ul class="mt-3 space-y-2 text-sm text-(--text)">
<li class="flex gap-2">
<span aria-hidden="true" class="mt-0.5 text-(--text-muted)">1.</span>
<span>Escolha o programa e leia o edital.</span>
</li>
<li class="flex gap-2">
<span aria-hidden="true" class="mt-0.5 text-(--text-muted)">2.</span>
<span>Realize a inscrição no formulário oficial.</span>
</li>
<li class="flex gap-2">
<span aria-hidden="true" class="mt-0.5 text-(--text-muted)">3.</span>
<span>Acompanhe comunicados e prazos conforme o edital.</span>
</li>
</ul>
</div>
<div class="rounded-2xl bg-white/80 p-6 ring-1 ring-black/5">
<h3 class="text-base font-bold text-(--text-strong)">Atendimento e transparência</h3>
<p class="mt-3 text-sm leading-relaxed text-(--text)">
Esta página reúne acessos para facilitar a participação. Para regras, documentos exigidos e
cronogramas, consulte sempre o <strong>edital</strong> do programa.
<section class="rounded-3xl bg-white/80 p-6 shadow-sm ring-1 ring-black/5 sm:p-8">
<h3 class="text-base font-extrabold tracking-tight text-(--text-strong)">Sobre esta página</h3>
<p class="mt-2 text-sm leading-relaxed text-(--text)">
Conteúdo de notícias estático por enquanto. Depois você poderá ligar ao banco/CMS sem mudar o layout.
</p>
</div>
</section>
</aside>
</div>
</main>
<footer class="border-t border-black/5 bg-white/70 backdrop-blur">
@@ -153,13 +133,11 @@
/>
<div>
<p class="text-sm font-bold text-(--text-strong)">Secretaria de Esportes de Pernambuco</p>
<p class="text-xs text-(--text-muted)">Página institucional • Programas oficiais</p>
<p class="text-xs text-(--text-muted)">Página institucional • Notícias e programas</p>
</div>
</div>
<p class="text-xs text-(--text-muted)">
© {new Date().getFullYear()} Governo de Pernambuco — SESP-PE.
</p>
<p class="text-xs text-(--text-muted)">© {new Date().getFullYear()} Governo de Pernambuco — SESP-PE.</p>
</div>
</footer>
</div>

View File

@@ -0,0 +1,73 @@
<script lang="ts">
import type { PageData } from './$types';
type Props = {
data: PageData;
};
const { data }: Props = $props();
</script>
<svelte:head>
<title>{data.item.title} | Secretaria de Esportes de Pernambuco</title>
<meta name="description" content={data.item.dek} />
</svelte:head>
<main class="mx-auto w-full max-w-6xl px-4 pb-14 pt-10 sm:px-6 sm:pt-14">
<a
href="/"
class="inline-flex items-center gap-2 rounded-lg px-2 py-1 text-sm font-semibold text-(--link) hover:bg-white/70 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--focus-ring)"
>
<span aria-hidden="true"></span>
Voltar para a página inicial
</a>
<section class="mt-6 overflow-hidden rounded-3xl bg-white/80 shadow-sm ring-1 ring-black/5">
<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-72"
></div>
<div class="absolute inset-0 bg-linear-to-t from-black/30 via-black/0 to-black/0"></div>
<div class="absolute bottom-0 left-0 right-0 px-6 py-6 sm:px-10 sm:py-10">
<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"
>
{data.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(data.item.date).toLocaleDateString('pt-BR')}
</span>
</div>
<h1 class="mt-4 text-2xl font-extrabold tracking-tight text-white sm:text-3xl">
{data.item.title}
</h1>
<p class="mt-2 max-w-3xl text-sm leading-relaxed text-white/90">
{data.item.dek}
</p>
</div>
</div>
<div class="px-6 py-8 sm:px-10">
<div class="prose prose-slate max-w-none">
{#if data.item.body?.length}
{#each data.item.body as paragraph (paragraph)}
<p>{paragraph}</p>
{/each}
{:else}
<p>Conteúdo em breve. Este texto é um placeholder — depois você pode alimentar via banco/CMS.</p>
{/if}
</div>
<div class="mt-8 rounded-2xl bg-white/80 p-5 ring-1 ring-black/5">
<p class="text-sm text-(--text)">
Quer que essa notícia puxe conteúdo de um banco? A UI já está pronta — é só trocar a fonte de dados.
</p>
</div>
</div>
</section>
</main>

View File

@@ -0,0 +1,13 @@
import { error } from '@sveltejs/kit';
import { getNews } from '$lib/data/news';
import type { PageLoad } from './$types';
export const load: PageLoad = ({ params }) => {
try {
const item = getNews(params.slug);
return { item };
} catch {
throw error(404, 'Notícia não encontrada');
}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB