Projeto Completo: GuiaSenior Web App (Docker + Spring Boot)

This commit is contained in:
2025-12-17 13:48:19 -03:00
commit d012a092e5
15 changed files with 824 additions and 0 deletions

11
GuiaSeniorWeb/Dockerfile Normal file
View File

@@ -0,0 +1,11 @@
FROM maven:3.9.6-eclipse-temurin-17 AS build
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN mvn clean package -DskipTests
FROM eclipse-temurin:17-jdk-alpine
WORKDIR /app
COPY --from=build /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","app.jar"]

80
GuiaSeniorWeb/README.md Normal file
View File

@@ -0,0 +1,80 @@
# GuiaSenior - Web App para Idosos
O **GuiaSenior** é uma aplicação web desenvolvida em Java (Spring Boot) com foco em acessibilidade digital para a terceira idade. O objetivo é facilitar o acesso a sites governamentais e serviços de utilidade pública, além de educar sobre segurança digital contra golpes.
## 📱 Destaques e Funcionalidades
1. **Acessibilidade**:
* Botões grandes e de alto contraste.
* Textos claros e linguagem direta.
* Animações suaves para guiar o olhar do usuário.
2. **Sites Governamentais**:
* Acesso facilitado ao **Meu INSS**, **Gov.br**, **Receita Federal** e **Conecte SUS**.
* **Explicação Prévia**: Antes de enviar o idoso para o site oficial, uma tela explica exatamente o que é aquele serviço e como funciona.
3. **Utilidades (Luz e Água)**:
* Filtro por **Região** (Norte, Sul, Sudeste, etc.).
* Lista das principais concessionárias (Enel, Sabesp, Copel, etc.).
* Instruções de como tirar a 2ª via da conta.
4. **Segurança Digital**:
* Dicas específicas contra golpes no **WhatsApp**, **SMS** e **E-mail**.
* Orientações sobre senhas e verificação de duas etapas.
---
## 🛠️ Tecnologias Utilizadas
* **Java 17**: Linguagem principal.
* **Spring Boot 3.x**: Framework para criação da aplicação web.
* **Thymeleaf**: Motor de templates para gerar as páginas HTML no servidor.
* **CSS3**: Estilização com Flexbox/Grid e Animações (Keyframes).
* **Docker & Docker Compose**: Para empacotar e rodar a aplicação em qualquer lugar.
---
## 📂 Estrutura do Código
A estrutura do projeto segue o padrão MVC (Model-View-Controller) do Spring Boot:
```text
GuiaSeniorWeb/
├── src/main/java/com/example/guiasenior/
│ ├── GuiaSeniorApplication.java # Classe principal que inicia o Spring
│ └── HomeController.java # "Cérebro" do app. Contém os dados dos sites/dicas e as rotas.
├── src/main/resources/templates/ # Telas (HTML)
│ ├── home.html # Tela Inicial (Menu Principal)
│ ├── sites.html # Lista de sites do governo
│ ├── regions.html # Seleção de região (Luz e Água)
│ ├── site_detail.html # Tela de explicação detalhada de um site
│ └── safety_detail.html # Tela com lista de dicas de segurança
├── src/main/resources/static/css/
│ └── style.css # Cores, tamanhos de fonte e animações
├── Dockerfile # Receita para criar a imagem do sistema
└── docker-compose.yml # Comando único para rodar o sistema
```
---
## 🚀 Como Rodar o Projeto
É muito simples rodar o projeto usando Docker. Você não precisa instalar Java no seu computador, apenas o Docker.
1. Abra o terminal na pasta do projeto:
```bash
cd GuiaSeniorWeb
```
2. Execute o comando para construir e rodar:
```bash
docker-compose up --build
```
3. Aguarde aparecer a mensagem de que o aplicativo iniciou.
4. Abra seu navegador (Chrome, Firefox, etc.) e acesse:
👉 **http://localhost:8080**

View File

@@ -0,0 +1,8 @@
version: '3.8'
services:
app:
build: .
ports:
- "8080:8080"
container_name: guiasenior_app
restart: always

45
GuiaSeniorWeb/pom.xml Normal file
View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>guiasenior</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>GuiaSenior</name>
<description>App para idosos</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,13 @@
package com.example.guiasenior;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GuiaSeniorApplication {
public static void main(String[] args) {
SpringApplication.run(GuiaSeniorApplication.class, args);
}
}

View File

@@ -0,0 +1,199 @@
package com.example.guiasenior;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.HashMap;
import java.util.Map;
@Controller
public class HomeController {
private final Map<String, java.util.List<SiteInfo>> utilities = new HashMap<>();
private final Map<String, SiteInfo> allSitesLookup = new HashMap<>();
private final Map<String, SiteInfo> govSitesDisplay = new HashMap<>();
private final Map<String, SafetyInfo> safetyTopics = new HashMap<>();
public HomeController() {
// Initialize Utilities (add to lookup only)
initializeUtilities();
// Initialize Government Sites (add to both display and lookup)
addGovSite("inss", new SiteInfo("Meu INSS", "Serviços da previdência.",
"O Meu INSS é a ferramenta oficial para pedir aposentadoria, salário-maternidade, ver extratos de pagamento e agendar perícias médicas. Você precisa de uma conta Gov.br para entrar.",
"https://meu.inss.gov.br/"));
addGovSite("govbr", new SiteInfo("Gov.br", "Documentos digitais.",
"O Gov.br é sua identidade digital. Com ele você acessa o INSS, assina documentos eletronicamente, vê sua carteira de motorista digital e muito mais.",
"https://www.gov.br/"));
addGovSite("receita", new SiteInfo("Receita Federal", "CPF e Imposto de Renda.",
"Aqui você pode consultar se seu CPF está regular, fazer a declaração do Imposto de Renda e pedir restituição.",
"https://www.gov.br/receitafederal"));
addGovSite("sus", new SiteInfo("Conecte SUS", "Vacinas e Saúde.",
"Visualize sua carteira de vacinação da COVID-19 e outras vacinas, além de ver histórico de medicamentos retirados na Farmácia Popular.",
"https://conectesus-paciente.saude.gov.br/"));
// Safety Data
safetyTopics.put("whatsapp", new SafetyInfo("Segurança no WhatsApp",
new String[]{
"Nunca compartilhe o código de 6 dígitos que chega por SMS.",
"Se um parente pedir dinheiro urgente por mensagem, ligue para ele NO NÚMERO ANTIGO antes de transferir.",
"Ative a 'Confirmação em Duas Etapas' nas configurações do WhatsApp.",
"Desconfie de promoções boas demais para ser verdade.",
"Oculte sua foto de perfil para estranhos nas configurações de Privacidade.",
"Não atenda chamadas de vídeo de números desconhecidos.",
"Se alguém disser que 'trocou de número' e pedir dinheiro, desconfie na hora."
}));
safetyTopics.put("sms", new SafetyInfo("Segurança no SMS",
new String[]{
"Bancos nunca mandam link por SMS pedindo para atualizar cadastro.",
"Não clique em links dizendo que sua conta foi bloqueada.",
"Ignore mensagens de prêmios que você não se lembra de ter concorrido.",
"Cuidado com mensagens sobre 'entregas pendentes' que pedem taxas.",
"O governo não manda SMS pedindo dados pessoais.",
"Se receber um código que não pediu, não faça nada e apague a mensagem."
}));
safetyTopics.put("email", new SafetyInfo("Segurança no E-mail",
new String[]{
"Verifique sempre o remetente. O governo usa e-mails terminados em .gov.br.",
"Não abra arquivos anexados de pessoas desconhecidas.",
"Nunca digite sua senha após clicar em um link de e-mail suspeito.",
"Fique atento a erros de ortografia ou mensagens muito urgentes.",
"Não clique em 'Cancelar Assinatura' de emails que você não conhece.",
"Se o e-mail diz 'Você tem uma dívida', ligue para a empresa oficial para confirmar."
}));
}
private void addGovSite(String id, SiteInfo site) {
site.id = id;
govSitesDisplay.put(id, site);
allSitesLookup.put(id, site);
}
private void initializeUtilities() {
// Sudeste
java.util.List<SiteInfo> sudeste = new java.util.ArrayList<>();
sudeste.add(new SiteInfo("Enel SP", "Energia - São Paulo", "2ª via de conta e serviços da Enel SP.", "https://www.enel.com.br/"));
sudeste.add(new SiteInfo("Sabesp", "Água - São Paulo", "2ª via e agência virtual da Sabesp.", "https://agenciavirtual.sabesp.com.br/"));
sudeste.add(new SiteInfo("Light", "Energia - Rio de Janeiro", "Agência virtual Light RJ.", "https://agenciavirtual.light.com.br/"));
sudeste.add(new SiteInfo("Copasa", "Água - Minas Gerais", "Agência virtual Copasa MG.", "https://copasa.com.br/"));
sudeste.add(new SiteInfo("Cemig", "Energia - Minas Gerais", "Cemig Atende.", "https://atende.cemig.com.br/"));
utilities.put("sudeste", sudeste);
// Sul
java.util.List<SiteInfo> sul = new java.util.ArrayList<>();
sul.add(new SiteInfo("Copel", "Energia - Paraná", "Agência virtual Copel.", "https://www.copel.com/"));
sul.add(new SiteInfo("Sanepar", "Água - Paraná", "Serviços Sanepar.", "https://site.sanepar.com.br/"));
sul.add(new SiteInfo("CEEE Equatorial", "Energia - Rio Grande do Sul", "Agência Digital CEEE.", "https://ceee.equatorialenergia.com.br/"));
sul.add(new SiteInfo("Celesc", "Energia - Santa Catarina", "Agência Web Celesc.", "https://www.celesc.com.br/"));
utilities.put("sul", sul);
// Nordeste
java.util.List<SiteInfo> nordeste = new java.util.ArrayList<>();
nordeste.add(new SiteInfo("Coelba", "Energia - Bahia", "Neoenergia Coelba.", "https://servicos.neoenergiacoelba.com.br/"));
nordeste.add(new SiteInfo("Embasa", "Água - Bahia", "Agência Virtual Embasa.", "https://agenciavirtual.embasa.ba.gov.br/"));
nordeste.add(new SiteInfo("Celpe", "Energia - Pernambuco", "Neoenergia Pernambuco.", "https://servicos.neoenergiapernambuco.com.br/"));
nordeste.add(new SiteInfo("Cagece", "Água - Ceará", "Agência Virtual Cagece.", "https://www.cagece.com.br/"));
utilities.put("nordeste", nordeste);
// Norte
java.util.List<SiteInfo> norte = new java.util.ArrayList<>();
norte.add(new SiteInfo("Equatorial Pará", "Energia - Pará", "Agência Web Equatorial.", "https://pa.equatorialenergia.com.br/"));
norte.add(new SiteInfo("Amazonas Energia", "Energia - Amazonas", "Agência Virtual.", "https://www.amazonasenergia.com/"));
utilities.put("norte", norte);
// Centro-Oeste
java.util.List<SiteInfo> centro = new java.util.ArrayList<>();
centro.add(new SiteInfo("Enel GO", "Energia - Goiás", "Agência Virtual Enel Goiás.", "https://www.enel.com.br/"));
centro.add(new SiteInfo("Saneago", "Água - Goiás", "Agência Virtual Saneago.", "https://www.saneago.com.br/"));
centro.add(new SiteInfo("Energisa MS", "Energia - Mato Grosso do Sul", "Agência Virtual Energisa.", "https://www.energisa.com.br/"));
utilities.put("centro_oeste", centro);
// Add to main lookup map only, NOT to the display map
for (java.util.List<SiteInfo> list : utilities.values()) {
for (SiteInfo s : list) {
// Generate a simple ID
s.id = s.name.toLowerCase().replace(" ", "");
allSitesLookup.put(s.id, s);
}
}
}
// ... existing routes ...
@GetMapping("/regioes")
public String regions() {
return "regions";
}
@GetMapping("/utilidades/{regiao}")
public String utilitiesByRegion(@PathVariable String regiao, Model model) {
java.util.List<SiteInfo> list = utilities.get(regiao);
if (list == null) return "redirect:/regioes";
// Capitalize for display
String regionName = regiao.replace("_", "-").toUpperCase();
model.addAttribute("regionName", regionName);
model.addAttribute("sites", list);
return "region_utilities";
}
@GetMapping("/")
public String home() {
return "home";
}
@GetMapping("/sites")
public String sites(Model model) {
model.addAttribute("sites", govSitesDisplay);
return "sites";
}
@GetMapping("/site/{id}")
public String siteDetail(@PathVariable String id, Model model) {
SiteInfo site = allSitesLookup.get(id);
if (site == null) return "redirect:/sites";
model.addAttribute("site", site);
return "site_detail";
}
@GetMapping("/seguranca/{tipo}")
public String safetyDetail(@PathVariable String tipo, Model model) {
SafetyInfo info = safetyTopics.get(tipo);
if (info == null) return "redirect:/";
model.addAttribute("info", info);
return "safety_detail";
}
// Inner classes for simple data holding
public static class SiteInfo {
public String name;
public String shortDesc;
public String fullDesc;
public String url;
public String id; // helper
public SiteInfo(String name, String shortDesc, String fullDesc, String url) {
this.name = name;
this.shortDesc = shortDesc;
this.fullDesc = fullDesc;
this.url = url;
}
}
public static class SafetyInfo {
public String title;
public String[] tips;
public SafetyInfo(String title, String[] tips) {
this.title = title;
this.tips = tips;
}
}
}

View File

@@ -0,0 +1,154 @@
:root {
--primary-blue: #0052CC;
--primary-dark: #003380;
--accent-green: #008037;
--text-color: #212121;
--bg-color: #FAFAFA;
}
body {
font-family: 'Arial', sans-serif;
/* Simple, readable font */
background-color: var(--bg-color);
color: var(--text-color);
margin: 0;
padding: 0;
text-align: center;
}
header {
background-color: var(--primary-blue);
color: white;
padding: 20px;
font-size: 1.5rem;
}
h1 {
font-size: 2rem;
margin-top: 30px;
}
.container {
padding: 20px;
max-width: 600px;
/* Mobile width focus */
margin: 0 auto;
}
/* Animations */
@keyframes pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.02);
}
100% {
transform: scale(1);
}
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.pulse-anim {
animation: pulse 2s infinite ease-in-out;
}
.fade-in {
animation: fadeIn 0.8s ease-out forwards;
}
/* Updated Buttons with Animation */
.big-button {
display: block;
width: 100%;
padding: 30px;
margin: 20px 0;
font-size: 1.5rem;
font-weight: bold;
color: white;
text-decoration: none;
border-radius: 15px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
transition: transform 0.1s;
/* Apply pulse by default or via class */
}
.big-button:hover {
transform: scale(1.05);
/* Interactive hover */
}
.big-button:active {
transform: scale(0.98);
}
.btn-blue {
background-color: var(--primary-blue);
}
.btn-green {
background-color: var(--accent-green);
}
/* Back Button */
.back-btn {
display: inline-block;
padding: 10px 20px;
background-color: #ddd;
color: black;
text-decoration: none;
border-radius: 5px;
font-size: 1.2rem;
margin-bottom: 20px;
}
/* Cards */
.card {
background: white;
border: 1px solid #ddd;
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
text-align: left;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.card h2 {
margin-top: 0;
color: var(--primary-dark);
}
.card p {
font-size: 1.2rem;
line-height: 1.6;
}
.card .link-btn {
display: inline-block;
margin-top: 10px;
padding: 15px 25px;
background-color: var(--primary-blue);
color: white;
text-decoration: none;
font-size: 1.2rem;
border-radius: 8px;
}
.safety-card {
background-color: #FFF9C4;
/* Yellowish warning color */
border-left: 10px solid #FBC02D;
}

View File

@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dicas de Segurança</title>
<link rel="stylesheet" th:href="@{/css/style.css}">
</head>
<body>
<header style="background-color: var(--accent-green);">
Segurança Digital
</header>
<div class="container">
<a th:href="@{/}" class="back-btn">⬅ Voltar</a>
<div class="card safety-card">
<p>⚠ Nunca forneça senhas, códigos SMS ou números de cartão por telefone ou WhatsApp.</p>
</div>
<div class="card safety-card">
<p>✅ Ao entrar em sites do governo, verifique se o endereço termina em <strong>.gov.br</strong>.</p>
</div>
<div class="card safety-card">
<p>⛔ Não clique em links estranhos enviados por SMS prometendo prêmios.</p>
</div>
<div class="card safety-card">
<p>🔐 Use senhas diferentes para cada serviço e, se possível, anote longe do celular.</p>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GuiaSenior</title>
<link rel="stylesheet" th:href="@{/css/style.css}">
</head>
<body>
<header>
GuiaSenior
</header>
<div class="container">
<h1>Olá! O que você precisa?</h1>
<a th:href="@{/sites}" class="big-button btn-blue">
🔎 Sites do Governo
</a>
<a th:href="@{/regioes}" class="big-button btn-blue" style="background-color: #0277BD;">
💧 Luz e Água
</a>
<!-- New Safety Buttons -->
<a th:href="@{/seguranca/whatsapp}" class="big-button btn-green pulse-anim">
📱 Segurança no WhatsApp
</a>
<a th:href="@{/seguranca/sms}" class="big-button btn-green pulse-anim" style="background-color: #006029;">
📩 Segurança no SMS
</a>
<a th:href="@{/seguranca/email}" class="big-button btn-green pulse-anim" style="background-color: #004D21;">
📧 Segurança no E-mail
</a>
</div>
</body>
</html>

View File

@@ -0,0 +1,52 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Serviços Públicos</title>
<link rel="stylesheet" th:href="@{/css/style.css}">
<style>
.site-card {
cursor: pointer;
transition: background 0.2s;
}
.site-card:hover {
background-color: #f0f0f0;
}
a.card-link {
text-decoration: none;
color: inherit;
display: block;
}
</style>
</head>
<body>
<header style="background-color: #0277BD;">
<span th:text="${regionName}">Região</span>
</header>
<div class="container fade-in">
<a th:href="@{/regioes}" class="back-btn">⬅ Voltar</a>
<p style="font-size: 1.2rem; margin-bottom: 20px;">Selecione empresa para detalhes:</p>
<a th:each="site : ${sites}" th:href="@{'/site/' + ${site.id}}" class="card-link">
<div class="card site-card">
<h2 th:text="${site.name}">Nome Empresa</h2>
<p th:text="${site.shortDesc}">Descrição</p>
<div style="color: #0277BD; font-weight: bold; margin-top: 10px;">
Ver Detalhes ➡
</div>
</div>
</a>
</div>
</body>
</html>

View File

@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Escolha sua Região</title>
<link rel="stylesheet" th:href="@{/css/style.css}">
</head>
<body>
<header>
Onde você mora?
</header>
<div class="container fade-in">
<a th:href="@{/}" class="back-btn">⬅ Voltar</a>
<a th:href="@{/utilidades/sudeste}" class="big-button btn-blue">
Sudeste (SP, RJ, MG, ES)
</a>
<a th:href="@{/utilidades/sul}" class="big-button btn-blue">
Sul (RS, SC, PR)
</a>
<a th:href="@{/utilidades/nordeste}" class="big-button btn-blue">
Nordeste
</a>
<a th:href="@{/utilidades/centro_oeste}" class="big-button btn-blue">
Centro-Oeste
</a>
<a th:href="@{/utilidades/norte}" class="big-button btn-blue">
Norte
</a>
</div>
</body>
</html>

View File

@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title th:text="${info.title}">Dicas de Segurança</title>
<link rel="stylesheet" th:href="@{/css/style.css}">
</head>
<body>
<header th:text="${info.title}" style="background-color: var(--accent-green);">
Segurança
</header>
<div class="container">
<a th:href="@{/}" class="back-btn">⬅ Voltar ao Início</a>
<div class="card safety-card fade-in" th:each="tip : ${info.tips}">
<p style="font-weight: bold; font-size: 1.3rem;">⚠ Atenção:</p>
<p th:text="${tip}">Dica texto...</p>
</div>
<div class="card">
<p>Se tiver dúvida, sempre pergunte a alguém de confiança antes de fazer qualquer coisa!</p>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title th:text="${site.name}">Detalhes do Site</title>
<link rel="stylesheet" th:href="@{/css/style.css}">
</head>
<body>
<header th:text="${site.name}">
Nome Site
</header>
<div class="container">
<a th:href="@{/sites}" class="back-btn">⬅ Voltar para a lista</a>
<div class="card">
<h2>Como funciona?</h2>
<p th:text="${site.fullDesc}" style="font-size: 1.4rem; line-height: 1.8;">
Explicação detalhada...
</p>
</div>
<div style="margin-top: 30px;">
<p>Se você entendeu, clique abaixo para ir ao site oficial:</p>
<a th:href="${site.url}" target="_blank" class="big-button btn-blue">
Ir para o site oficial ➡
</a>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,54 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sites do Governo</title>
<link rel="stylesheet" th:href="@{/css/style.css}">
<style>
.site-card {
cursor: pointer;
transition: background 0.2s;
}
.site-card:hover {
background-color: #f0f0f0;
}
/* Make the whole card clickable via link overlay or JS, easiest is wrapping in <a> or onclick */
a.card-link {
text-decoration: none;
color: inherit;
display: block;
}
</style>
</head>
<body>
<header>
Sites Úteis
</header>
<div class="container">
<a th:href="@{/}" class="back-btn">⬅ Voltar</a>
<p style="font-size: 1.2rem; margin-bottom: 20px;">Toque em um item para ler a explicação.</p>
<!-- Dynamic Loop -->
<a th:each="siteEntry : ${sites}" th:href="@{'/site/' + ${siteEntry.key}}" class="card-link">
<div class="card site-card">
<h2 th:text="${siteEntry.value.name}">Nome do Site</h2>
<p th:text="${siteEntry.value.shortDesc}">Descrição curta</p>
<div style="color: var(--primary-blue); font-weight: bold; margin-top: 10px;">
Toque para saber mais ➡
</div>
</div>
</a>
</div>
</body>
</html>