From 310f5e1f71acfb4a8c7ec6b36028058cf8905ac3 Mon Sep 17 00:00:00 2001 From: Gustavo Date: Thu, 13 Aug 2026 21:53:30 -0300 Subject: [PATCH] feat: sistema de design (tokens claro/escuro) + sidebar responsiva em base.html Co-Authored-By: Claude Sonnet 5 --- app/static/estilo.css | 231 ++++++++++++++++++++++++++++++++++++++++ app/static/sidebar.js | 39 +++++++ app/static/tema.js | 17 +++ app/templates/base.html | 117 +++++++++++++------- 4 files changed, 366 insertions(+), 38 deletions(-) create mode 100644 app/static/estilo.css create mode 100644 app/static/sidebar.js create mode 100644 app/static/tema.js diff --git a/app/static/estilo.css b/app/static/estilo.css new file mode 100644 index 0000000..e3a68a2 --- /dev/null +++ b/app/static/estilo.css @@ -0,0 +1,231 @@ +/* app/static/estilo.css — tokens de cor e componentes compartilhados do painel */ + +:root { + --cor-fundo: #f7f7f9; + --cor-superficie: #ffffff; + --cor-superficie-alt: #f1f1f4; + --cor-texto: #1c1c1f; + --cor-texto-secundario: #6b6b74; + --cor-borda: #e4e4e9; + --cor-acento: #F5821F; + --cor-acento-contraste: #ffffff; + --cor-acento-suave: #fff1e4; + --cor-sucesso: #15803d; + --cor-sucesso-fundo: #dcfce7; + --cor-perigo: #b91c1c; + --cor-perigo-fundo: #fee2e2; + --cor-aviso: #b45309; + --cor-aviso-fundo: #fef3c7; + --sombra-card: 0 1px 2px rgba(0,0,0,0.05), 0 1px 8px rgba(0,0,0,0.04); + --transicao: 0.16s ease; +} + +@media (prefers-color-scheme: dark) { + :root:not([data-tema="claro"]) { + --cor-fundo: #121214; + --cor-superficie: #1c1c1f; + --cor-superficie-alt: #232326; + --cor-texto: #f4f4f5; + --cor-texto-secundario: #9a9aa2; + --cor-borda: #2e2e33; + --cor-acento-contraste: #17171a; + --cor-acento-suave: rgba(245,130,31,0.16); + --cor-sucesso: #4ade80; + --cor-sucesso-fundo: rgba(74,222,128,0.14); + --cor-perigo: #f87171; + --cor-perigo-fundo: rgba(248,113,113,0.14); + --cor-aviso: #fbbf24; + --cor-aviso-fundo: rgba(251,191,36,0.14); + --sombra-card: 0 1px 2px rgba(0,0,0,0.3), 0 1px 8px rgba(0,0,0,0.25); + } +} + +:root[data-tema="escuro"] { + --cor-fundo: #121214; + --cor-superficie: #1c1c1f; + --cor-superficie-alt: #232326; + --cor-texto: #f4f4f5; + --cor-texto-secundario: #9a9aa2; + --cor-borda: #2e2e33; + --cor-acento-contraste: #17171a; + --cor-acento-suave: rgba(245,130,31,0.16); + --cor-sucesso: #4ade80; + --cor-sucesso-fundo: rgba(74,222,128,0.14); + --cor-perigo: #f87171; + --cor-perigo-fundo: rgba(248,113,113,0.14); + --cor-aviso: #fbbf24; + --cor-aviso-fundo: rgba(251,191,36,0.14); + --sombra-card: 0 1px 2px rgba(0,0,0,0.3), 0 1px 8px rgba(0,0,0,0.25); +} + +* { box-sizing: border-box; } + +body { + font-family: system-ui, -apple-system, "Segoe UI", sans-serif; + margin: 0; + background: var(--cor-fundo); + color: var(--cor-texto); + transition: background var(--transicao), color var(--transicao); +} + +h1, h2, h3 { font-weight: 700; line-height: 1.25; } +h1 { font-size: 1.5rem; margin: 0 0 16px; } +h2 { font-size: 1.15rem; margin: 28px 0 12px; } + +a { color: var(--cor-acento); } + +/* ---------- layout raiz: sidebar + área de conteúdo ---------- */ + +.layout { display: flex; min-height: 100vh; } + +.sidebar { + width: 240px; + flex-shrink: 0; + background: var(--cor-superficie); + border-right: 1px solid var(--cor-borda); + display: flex; + flex-direction: column; + transition: width var(--transicao), transform var(--transicao); +} + +.sidebar-topo { + display: flex; + align-items: center; + gap: 10px; + padding: 18px 16px; + border-bottom: 1px solid var(--cor-borda); +} +.sidebar-logo { width: 32px; height: 32px; border-radius: 8px; flex-shrink: 0; } +.sidebar-titulo { font-weight: 700; white-space: nowrap; overflow: hidden; } + +.sidebar-nav { flex: 1; padding: 12px 8px; display: flex; flex-direction: column; gap: 2px; overflow-y: auto; } +.sidebar-nav a { + display: flex; align-items: center; gap: 12px; + padding: 10px 12px; border-radius: 8px; + color: var(--cor-texto); text-decoration: none; white-space: nowrap; + transition: background var(--transicao), color var(--transicao); +} +.sidebar-nav a:hover { background: var(--cor-superficie-alt); } +.sidebar-nav a.ativo { background: var(--cor-acento-suave); color: var(--cor-acento); font-weight: 600; } +.sidebar-nav svg { flex-shrink: 0; width: 20px; height: 20px; } +.sidebar-nav span { overflow: hidden; text-overflow: ellipsis; } + +.sidebar-rodape { + border-top: 1px solid var(--cor-borda); + padding: 12px 8px; + display: flex; flex-direction: column; gap: 8px; +} +.sidebar-usuario { padding: 6px 12px; font-size: 0.85rem; color: var(--cor-texto-secundario); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.sidebar-rodape .form-logout { margin: 0; } +.botao-tema, .botao-recolher, .botao-sair, .topbar-menu-btn { + display: flex; align-items: center; gap: 10px; width: 100%; + padding: 10px 12px; border-radius: 8px; border: none; + background: transparent; color: var(--cor-texto); cursor: pointer; + font: inherit; font-weight: 400; margin: 0; text-align: left; +} +.botao-tema:hover, .botao-recolher:hover, .botao-sair:hover { background: var(--cor-superficie-alt); } +.botao-tema svg, .botao-recolher svg, .botao-sair svg { flex-shrink: 0; width: 20px; height: 20px; } + +/* Sidebar recolhida (desktop): só ícones */ +.sidebar.recolhida { width: 64px; } +.sidebar.recolhida .sidebar-titulo, +.sidebar.recolhida .sidebar-nav span, +.sidebar.recolhida .sidebar-usuario, +.sidebar.recolhida .botao-tema span, +.sidebar.recolhida .botao-recolher span, +.sidebar.recolhida .botao-sair span { display: none; } + +.sidebar-backdrop { + display: none; + position: fixed; inset: 0; background: rgba(0,0,0,0.45); z-index: 40; +} + +.conteudo-area { flex: 1; min-width: 0; display: flex; flex-direction: column; } + +.topbar-mobile { + display: none; + align-items: center; gap: 12px; + padding: 12px 16px; border-bottom: 1px solid var(--cor-borda); + background: var(--cor-superficie); +} +.topbar-menu-btn { width: auto; padding: 8px; } +.topbar-titulo { font-weight: 700; } + +.conteudo { padding: 24px; max-width: 1080px; width: 100%; margin: 0 auto; } + +/* ---------- mobile: sidebar vira gaveta ---------- */ +@media (max-width: 900px) { + .sidebar { + position: fixed; top: 0; bottom: 0; left: 0; z-index: 50; + transform: translateX(-100%); + } + .sidebar.aberta { transform: translateX(0); } + .sidebar.recolhida { width: 240px; } /* recolher não se aplica no mobile */ + .sidebar.recolhida .sidebar-titulo, + .sidebar.recolhida .sidebar-nav span, + .sidebar.recolhida .sidebar-usuario, + .sidebar.recolhida .botao-tema span, + .sidebar.recolhida .botao-recolher span, + .sidebar.recolhida .botao-sair span { display: inline; } + .sidebar-backdrop.aberta { display: block; } + .topbar-mobile { display: flex; } + .conteudo { padding: 16px; } +} + +/* ---------- componentes ---------- */ + +.cartao { + background: var(--cor-superficie); + border: 1px solid var(--cor-borda); + border-radius: 12px; + box-shadow: var(--sombra-card); + padding: 20px; + margin-bottom: 20px; +} + +.tabela-wrap { overflow-x: auto; border: 1px solid var(--cor-borda); border-radius: 12px; background: var(--cor-superficie); box-shadow: var(--sombra-card); } +table { width: 100%; border-collapse: collapse; } +th, td { text-align: left; padding: 10px 14px; border-bottom: 1px solid var(--cor-borda); white-space: nowrap; } +tr:last-child td { border-bottom: none; } +th a { color: inherit; text-decoration: none; } +th a:hover { color: var(--cor-acento); } + +input, select { + padding: 9px 10px; border-radius: 8px; border: 1px solid var(--cor-borda); + background: var(--cor-fundo); color: var(--cor-texto); width: 100%; +} +input:focus-visible, select:focus-visible, a:focus-visible, button:focus-visible { + outline: 2px solid var(--cor-acento); outline-offset: 2px; +} +label { display: block; margin-top: 10px; margin-bottom: 4px; font-size: 0.85rem; color: var(--cor-texto-secundario); } + +button, a.botao { + display: inline-block; margin-top: 14px; padding: 10px 16px; + border: 1px solid transparent; border-radius: 8px; + background: var(--cor-acento); color: var(--cor-acento-contraste); + font-weight: 700; cursor: pointer; text-decoration: none; text-align: center; +} +a.botao { background: transparent; border-color: var(--cor-borda); color: var(--cor-texto); font-weight: 400; margin-right: 6px; } +a.botao:hover { border-color: var(--cor-acento); color: var(--cor-acento); } + +.mensagem { padding: 10px 14px; border-radius: 8px; margin-bottom: 16px; } +.mensagem.success { background: var(--cor-sucesso-fundo); color: var(--cor-sucesso); } +.mensagem.danger { background: var(--cor-perigo-fundo); color: var(--cor-perigo); } +.mensagem.warning { background: var(--cor-aviso-fundo); color: var(--cor-aviso); } + +.badge { display: inline-block; padding: 3px 10px; border-radius: 999px; font-size: 0.78rem; font-weight: 600; } +.badge-acento { background: var(--cor-acento-suave); color: var(--cor-acento); } +.badge-neutro { background: var(--cor-superficie-alt); color: var(--cor-texto-secundario); } +.badge-sucesso { background: var(--cor-sucesso-fundo); color: var(--cor-sucesso); } + +.grid-form { display: grid; grid-template-columns: 1fr 1fr; gap: 0 16px; } +.grid-form-largura-total { grid-column: 1 / -1; } +@media (max-width: 640px) { + .grid-form { grid-template-columns: 1fr; } +} + +.paginacao { display: flex; align-items: center; gap: 12px; justify-content: center; margin-top: 16px; } + +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { transition: none !important; } +} diff --git a/app/static/sidebar.js b/app/static/sidebar.js new file mode 100644 index 0000000..ef8a70e --- /dev/null +++ b/app/static/sidebar.js @@ -0,0 +1,39 @@ +// app/static/sidebar.js — abre/fecha a gaveta da sidebar no mobile e +// recolhe/expande no desktop, persistindo a preferência de recolher. +(function () { + var sidebar = document.getElementById("sidebar"); + var backdrop = document.getElementById("sidebar-backdrop"); + var btnMenuMobile = document.getElementById("btn-menu-mobile"); + var btnRecolher = document.getElementById("botao-recolher"); + if (!sidebar) return; + + function abrirGaveta() { + sidebar.classList.add("aberta"); + if (backdrop) backdrop.classList.add("aberta"); + } + function fecharGaveta() { + sidebar.classList.remove("aberta"); + if (backdrop) backdrop.classList.remove("aberta"); + } + + if (btnMenuMobile) { + btnMenuMobile.addEventListener("click", function () { + if (sidebar.classList.contains("aberta")) fecharGaveta(); + else abrirGaveta(); + }); + } + if (backdrop) backdrop.addEventListener("click", fecharGaveta); + sidebar.querySelectorAll("a").forEach(function (link) { + link.addEventListener("click", fecharGaveta); + }); + + if (btnRecolher) { + if (localStorage.getItem("sidebarRecolhida") === "1") { + sidebar.classList.add("recolhida"); + } + btnRecolher.addEventListener("click", function () { + var recolhida = sidebar.classList.toggle("recolhida"); + localStorage.setItem("sidebarRecolhida", recolhida ? "1" : "0"); + }); + } +})(); diff --git a/app/static/tema.js b/app/static/tema.js new file mode 100644 index 0000000..eca36fb --- /dev/null +++ b/app/static/tema.js @@ -0,0 +1,17 @@ +// app/static/tema.js — alterna e persiste o tema claro/escuro do painel. +(function () { + var botao = document.getElementById("botao-tema"); + if (!botao) return; + + function temaAtual() { + var salvo = localStorage.getItem("tema"); + if (salvo === "claro" || salvo === "escuro") return salvo; + return window.matchMedia("(prefers-color-scheme: dark)").matches ? "escuro" : "claro"; + } + + botao.addEventListener("click", function () { + var novo = temaAtual() === "escuro" ? "claro" : "escuro"; + document.documentElement.setAttribute("data-tema", novo); + localStorage.setItem("tema", novo); + }); +})(); diff --git a/app/templates/base.html b/app/templates/base.html index 00ad7e1..00e3d53 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -3,46 +3,87 @@ + {% block titulo %}Painel de Clientes{% endblock %} - + + - -
- {% if mensagem %}
{{ mensagem }}
{% endif %} - {% block conteudo %}{% endblock %} -
+
+ + +
+
+ + {{ self.titulo() }} +
+
+ {% if mensagem %}
{{ mensagem }}
{% endif %} + {% block conteudo %}{% endblock %} +
+
+
+ +