feat(ui): moderniza home/geral com estilo glass e tabela moderna
This commit is contained in:
parent
b66eb96879
commit
a4fb34146d
|
|
@ -3,10 +3,11 @@ import { Home } from './pages/home/home';
|
|||
import { Register } from './pages/register/register';
|
||||
import { LoginComponent } from './pages/login/login';
|
||||
import { Geral } from './pages/geral/geral';
|
||||
import { authGuard } from './guards/auth.guard';
|
||||
|
||||
export const routes: Routes = [
|
||||
{ path: '', component: Home },
|
||||
{ path: "register", component: Register },
|
||||
{ path: "login", component: LoginComponent },
|
||||
{ path: "geral", component: Geral },
|
||||
{ path: 'geral', component: Geral, canActivate: [authGuard] },
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,21 +1,31 @@
|
|||
<div class="feature-card-container">
|
||||
<!-- Use [ngStyle] para aplicar o alinhamento a todo o bloco do card -->
|
||||
<div class="feature-card"
|
||||
(mouseenter)="isHovered = true"
|
||||
(mouseleave)="isHovered = false"
|
||||
[ngClass]="{'hover-state': isHovered}"
|
||||
[ngStyle]="{'text-align': textAlign}"> <!-- 🎯 APLICA O ALINHAMENTO AQUI -->
|
||||
|
||||
<!-- Ícone e Título Principal (em rosa) -->
|
||||
<h3 class="card-title">
|
||||
<i class="card-icon {{ iconClass }}"></i>
|
||||
{{ title }}
|
||||
</h3>
|
||||
<div
|
||||
class="feature-card"
|
||||
(mouseenter)="isHovered = true"
|
||||
(mouseleave)="isHovered = false"
|
||||
[ngClass]="{ 'hover-state': isHovered }"
|
||||
[ngStyle]="{ 'text-align': textAlign }"
|
||||
>
|
||||
<!-- brilho sutil -->
|
||||
<span class="shine" aria-hidden="true"></span>
|
||||
|
||||
<!-- Conteúdo/Descrição do Card -->
|
||||
<!-- header -->
|
||||
<div class="card-head">
|
||||
<div class="icon-wrap" aria-hidden="true">
|
||||
<i class="card-icon {{ iconClass }}"></i>
|
||||
</div>
|
||||
|
||||
<h3 class="card-title">
|
||||
{{ title }}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<!-- descrição -->
|
||||
<p class="card-description">
|
||||
<span [innerHTML]="description"></span>
|
||||
</p>
|
||||
|
||||
<!-- linha decorativa -->
|
||||
<div class="card-accent" aria-hidden="true"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,136 +1,250 @@
|
|||
.feature-card-container {
|
||||
padding: 10px;
|
||||
:host {
|
||||
--brand: #E33DCF;
|
||||
--text: #111214;
|
||||
--muted: rgba(17, 18, 20, 0.70);
|
||||
--radius-xl: 22px;
|
||||
--radius-lg: 16px;
|
||||
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* CARD BASE — mantém o design exato do Figma em desktops */
|
||||
/* ✅ CARD PRINCIPAL */
|
||||
.feature-card {
|
||||
border: 1px solid #000000;
|
||||
background-color: #FFFFFF;
|
||||
padding: 30px;
|
||||
border-radius: 5px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 365px;
|
||||
min-height: 175px;
|
||||
padding: 18px 18px 16px 18px;
|
||||
border-radius: 18px;
|
||||
cursor: default;
|
||||
|
||||
/* DESKTOP (layout original) */
|
||||
width: 365px;
|
||||
height: 169px;
|
||||
background: rgba(255, 255, 255, 0.82);
|
||||
border: 1px solid rgba(227, 61, 207, 0.18);
|
||||
backdrop-filter: blur(10px);
|
||||
box-shadow: 0 18px 38px rgba(17, 18, 20, 0.10);
|
||||
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
gap: 10px;
|
||||
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
transform: translateZ(0);
|
||||
transition:
|
||||
transform 180ms ease,
|
||||
border-color 180ms ease,
|
||||
box-shadow 180ms ease,
|
||||
background 180ms ease;
|
||||
|
||||
/* borda interna sutil */
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 1px;
|
||||
border-radius: 17px;
|
||||
pointer-events: none;
|
||||
border: 1px solid rgba(255, 255, 255, 0.65);
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
/* efeito “radial” suave no fundo */
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: -80px;
|
||||
pointer-events: none;
|
||||
background:
|
||||
radial-gradient(380px 200px at 20% 10%, rgba(227, 61, 207, 0.16), transparent 60%),
|
||||
radial-gradient(320px 180px at 85% 70%, rgba(227, 61, 207, 0.10), transparent 60%);
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
/* ================================
|
||||
📱 RESPONSIVIDADE
|
||||
================================ */
|
||||
|
||||
@media (min-width: 1200px) and (max-width: 1399.98px) {
|
||||
width: 330px;
|
||||
height: auto;
|
||||
padding: 24px;
|
||||
min-height: 170px;
|
||||
}
|
||||
|
||||
|
||||
/* Telas MUITO pequenas (≤ 360px) */
|
||||
@media (max-width: 360px) {
|
||||
width: 95%;
|
||||
height: auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* Celulares (≤ 480px) */
|
||||
@media (max-width: 480px) {
|
||||
width: 95%;
|
||||
height: auto; /* Permite texto quebrar */
|
||||
}
|
||||
|
||||
/* Tablets pequenos (≤ 768px) */
|
||||
@media (max-width: 768px) {
|
||||
width: 300px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/* Tablets grandes e monitores pequenos (≤ 1024px) */
|
||||
@media (max-width: 1024px) {
|
||||
width: 330px;
|
||||
height: auto;
|
||||
min-height: 170px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
width: 300px;
|
||||
min-height: 170px;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
width: 95%;
|
||||
min-height: 170px;
|
||||
}
|
||||
|
||||
@media (max-width: 360px) {
|
||||
width: 95%;
|
||||
min-height: 165px;
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
/* HOVER */
|
||||
/* ✅ HOVER PREMIUM (sem “cinza chapado”) */
|
||||
.feature-card.hover-state {
|
||||
background-color: #E1E1E1;
|
||||
transform: translateY(-4px);
|
||||
border-color: rgba(227, 61, 207, 0.32);
|
||||
box-shadow: 0 26px 52px rgba(17, 18, 20, 0.14);
|
||||
background: rgba(255, 255, 255, 0.90);
|
||||
}
|
||||
|
||||
/* -------------------------
|
||||
TÍTULO DO CARD
|
||||
-------------------------- */
|
||||
.card-title {
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: #E33DCF;
|
||||
/* brilho animado (fica bem SaaS) */
|
||||
.shine {
|
||||
position: absolute;
|
||||
inset: -40%;
|
||||
pointer-events: none;
|
||||
transform: translateX(-120%) rotate(12deg);
|
||||
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.55), transparent);
|
||||
opacity: 0.0;
|
||||
transition: opacity 200ms ease;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
margin-top: -15px;
|
||||
margin-bottom: 10px;
|
||||
.feature-card.hover-state .shine {
|
||||
opacity: 1;
|
||||
animation: shine 1.25s ease-in-out 1;
|
||||
}
|
||||
|
||||
/* header */
|
||||
.card-head {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
@media (min-width: 1200px) and (max-width: 1399.98px) {
|
||||
font-size: 18px;
|
||||
margin-top: -10px;
|
||||
}
|
||||
|
||||
|
||||
/* Mobile */
|
||||
@media (max-width: 480px) {
|
||||
font-size: 18px;
|
||||
margin-top: -5px;
|
||||
}
|
||||
gap: 12px;
|
||||
justify-content: center; /* seu layout atual é centralizado */
|
||||
}
|
||||
|
||||
/* ÍCONE */
|
||||
/* ícone no estilo do mock */
|
||||
.icon-wrap {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 14px;
|
||||
|
||||
display: grid;
|
||||
place-items: center;
|
||||
|
||||
background: rgba(227, 61, 207, 0.10);
|
||||
border: 1px solid rgba(227, 61, 207, 0.22);
|
||||
|
||||
transition: transform 180ms ease, box-shadow 180ms ease, background 180ms ease;
|
||||
}
|
||||
|
||||
.feature-card.hover-state .icon-wrap {
|
||||
transform: translateY(-1px) scale(1.03);
|
||||
box-shadow: 0 12px 24px rgba(227, 61, 207, 0.18);
|
||||
background: rgba(227, 61, 207, 0.14);
|
||||
}
|
||||
|
||||
/* ícone */
|
||||
.card-icon {
|
||||
margin-right: 10px;
|
||||
font-size: 24px;
|
||||
|
||||
@media (min-width: 1200px) and (max-width: 1399.98px) {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
|
||||
/* Mobile */
|
||||
@media (max-width: 480px) {
|
||||
font-size: 20px;
|
||||
}
|
||||
font-size: 18px;
|
||||
color: var(--brand);
|
||||
}
|
||||
|
||||
/* -------------------------
|
||||
DESCRIÇÃO DO CARD
|
||||
-------------------------- */
|
||||
.card-description {
|
||||
/* título */
|
||||
.card-title {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
|
||||
margin: 0;
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-size: 17px;
|
||||
color: #000000;
|
||||
line-height: 1;
|
||||
padding-left: 15px;
|
||||
font-size: 18px;
|
||||
font-weight: 950;
|
||||
color: var(--text);
|
||||
letter-spacing: -0.2px;
|
||||
|
||||
@media (min-width: 1200px) and (max-width: 1399.98px) {
|
||||
font-size: 16px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
transition: transform 180ms ease, color 180ms ease;
|
||||
}
|
||||
|
||||
.feature-card.hover-state .card-title {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* Mobile */
|
||||
/* descrição */
|
||||
.card-description {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
|
||||
margin: 0;
|
||||
padding: 0 6px;
|
||||
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 1.35;
|
||||
color: var(--muted);
|
||||
|
||||
/* deixa mais “SaaS” e menos apertado */
|
||||
@media (max-width: 480px) {
|
||||
font-size: 15px;
|
||||
padding-left: 8px;
|
||||
font-size: 13.5px;
|
||||
padding: 0 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.card-description strong {
|
||||
font-weight: 700;
|
||||
font-weight: 900;
|
||||
color: #111214;
|
||||
}
|
||||
|
||||
/* linha decorativa inferior */
|
||||
.card-accent {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
|
||||
margin-top: auto;
|
||||
height: 6px;
|
||||
border-radius: 999px;
|
||||
background: rgba(227, 61, 207, 0.12);
|
||||
border: 1px solid rgba(227, 61, 207, 0.18);
|
||||
overflow: hidden;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
transform: translateX(-60%);
|
||||
background: linear-gradient(90deg, transparent, rgba(227, 61, 207, 0.28), transparent);
|
||||
opacity: 0.0;
|
||||
transition: opacity 180ms ease;
|
||||
}
|
||||
}
|
||||
|
||||
.feature-card.hover-state .card-accent::after {
|
||||
opacity: 1;
|
||||
animation: sweep 1.2s ease-in-out 1;
|
||||
}
|
||||
|
||||
/* acessibilidade: reduz animações */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.feature-card,
|
||||
.icon-wrap,
|
||||
.card-title {
|
||||
transition: none !important;
|
||||
}
|
||||
.feature-card.hover-state {
|
||||
transform: none;
|
||||
}
|
||||
.feature-card.hover-state .shine,
|
||||
.feature-card.hover-state .card-accent::after {
|
||||
animation: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* animações */
|
||||
@keyframes shine {
|
||||
0% { transform: translateX(-120%) rotate(12deg); }
|
||||
100% { transform: translateX(120%) rotate(12deg); }
|
||||
}
|
||||
|
||||
@keyframes sweep {
|
||||
0% { transform: translateX(-60%); }
|
||||
100% { transform: translateX(60%); }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,23 @@
|
|||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common'; // <-- Importe aqui!
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'app-feature-card',
|
||||
standalone: true, // <-- DEVE ser standalone
|
||||
imports: [CommonModule], // <-- PRECISA do CommonModule para usar [ngClass]
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
templateUrl: './feature-card.html',
|
||||
styleUrls: ['./feature-card.scss']
|
||||
})
|
||||
export class FeatureCardComponent implements OnInit {
|
||||
|
||||
// ... o restante da lógica ...
|
||||
|
||||
@Input() title: string = '';
|
||||
@Input() description: string = '';
|
||||
@Input() iconClass: string = '';
|
||||
@Input() iconClass: string = '';
|
||||
@Input() textAlign: 'left' | 'center' = 'left';
|
||||
|
||||
isHovered: boolean = false;
|
||||
isHovered: boolean = false;
|
||||
|
||||
constructor() { }
|
||||
constructor() {}
|
||||
|
||||
ngOnInit(): void { }
|
||||
}
|
||||
ngOnInit(): void {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,52 +1,115 @@
|
|||
<header
|
||||
class="header-container"
|
||||
[class.header-scrolled]="isScrolled">
|
||||
|
||||
[class.header-scrolled]="isScrolled"
|
||||
>
|
||||
<div class="header-top">
|
||||
<!-- ESQUERDA: HAMBURGUER (só no /geral) + LOGO -->
|
||||
<div class="left-area">
|
||||
<button
|
||||
*ngIf="isLoggedHeader"
|
||||
type="button"
|
||||
class="hamburger-btn"
|
||||
aria-label="Abrir menu"
|
||||
(click)="toggleMenu()"
|
||||
>
|
||||
<i class="bi bi-list"></i>
|
||||
</button>
|
||||
|
||||
<!-- LOGO + TÍTULO (CLICÁVEIS) -->
|
||||
<a class="logo-area" routerLink="/"> <!-- ⬅️ AGORA É UM LINK ANGULAR -->
|
||||
<img src="logo.png" alt="Logo" class="logo">
|
||||
<!-- ✅ Logo some apenas quando menu estiver aberto (no /geral) -->
|
||||
<a class="logo-area" routerLink="/" *ngIf="!menuOpen">
|
||||
<img src="logo.png" alt="Logo" class="logo" />
|
||||
<div class="logo-text ms-2">
|
||||
<span class="line">Line</span><span class="gestao">Gestão</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="logo-text ms-2">
|
||||
<span class="line">Line</span><span class="gestao">Gestão</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<!-- MENU -->
|
||||
<nav class="menu">
|
||||
<!-- ✅ MENU HOME: só aparece fora do /geral -->
|
||||
<nav class="menu" *ngIf="!isLoggedHeader">
|
||||
<a href="https://www.linemovel.com.br/sobrenos" class="menu-item" target="_blank">O que é a Line Móvel?</a>
|
||||
<a href="https://www.linemovel.com.br/empresas" class="menu-item" target="_blank">Para sua empresa</a>
|
||||
<a href="https://www.linemovel.com.br/proposta" class="menu-item" target="_blank">Solicite sua Proposta</a>
|
||||
<a href="https://www.linemovel.com.br/indique" class="menu-item" target="_blank">Indique um amigo</a>
|
||||
</nav>
|
||||
|
||||
<!-- BOTÕES -->
|
||||
<div class="btn-area">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-cadastrar"
|
||||
[routerLink]="['/register']">
|
||||
<!-- ✅ BOTÕES: só aparecem fora do /geral -->
|
||||
<div class="btn-area" *ngIf="!isLoggedHeader">
|
||||
<button type="button" class="btn btn-cadastrar" [routerLink]="['/register']">
|
||||
Cadastre-se
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-login"
|
||||
[routerLink]="['/login']">
|
||||
<button type="button" class="btn btn-login" [routerLink]="['/login']">
|
||||
Login
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<!-- FAIXA AZUL – SÓ NA HOME -->
|
||||
<!-- FAIXA AZUL – SÓ NA HOME -->
|
||||
<div class="header-bar" *ngIf="isHome">
|
||||
<span class="header-bar-text">
|
||||
Somos a escolha certa para estar sempre conectado!
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
</header>
|
||||
|
||||
<!-- OVERLAY -->
|
||||
<div class="menu-overlay" *ngIf="menuOpen" (click)="closeMenu()"></div>
|
||||
|
||||
<!-- MENU LATERAL -->
|
||||
<aside class="side-menu" [class.open]="menuOpen" (click)="$event.stopPropagation()">
|
||||
<div class="side-menu-header">
|
||||
<a class="logo-area" routerLink="/" (click)="closeMenu()">
|
||||
<img src="logo.png" alt="Logo" class="logo" />
|
||||
<div class="logo-text ms-2">
|
||||
<span class="line">Line</span><span class="gestao">Gestão</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="close-btn"
|
||||
aria-label="Fechar menu"
|
||||
(click)="closeMenu()"
|
||||
>
|
||||
<i class="bi bi-x-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="side-menu-body">
|
||||
<!-- ✅ Opções do Geral (mantém) -->
|
||||
<a routerLink="/geral" class="side-item" (click)="closeMenu()">
|
||||
<i class="bi bi-clipboard-data me-2"></i> Controle de Contratos
|
||||
</a>
|
||||
|
||||
<a routerLink="/geral" class="side-item" (click)="closeMenu()">
|
||||
<i class="bi bi-sim me-2"></i> Gerenciar Linhas
|
||||
</a>
|
||||
|
||||
<a routerLink="/geral" class="side-item" (click)="closeMenu()">
|
||||
<i class="bi bi-people me-2"></i> Gerenciar Clientes
|
||||
</a>
|
||||
|
||||
<a routerLink="/geral" class="side-item" (click)="closeMenu()">
|
||||
<i class="bi bi-bar-chart me-2"></i> Relatórios
|
||||
</a>
|
||||
|
||||
<hr class="my-2" />
|
||||
|
||||
<!-- ✅ Links da home só aqui dentro (no /geral) -->
|
||||
<a href="https://www.linemovel.com.br/sobrenos" target="_blank" class="side-item" (click)="closeMenu()">
|
||||
<i class="bi bi-info-circle me-2"></i> O que é a Line Móvel?
|
||||
</a>
|
||||
|
||||
<a href="https://www.linemovel.com.br/empresas" target="_blank" class="side-item" (click)="closeMenu()">
|
||||
<i class="bi bi-building me-2"></i> Para sua empresa
|
||||
</a>
|
||||
|
||||
<a href="https://www.linemovel.com.br/proposta" target="_blank" class="side-item" (click)="closeMenu()">
|
||||
<i class="bi bi-file-earmark-text me-2"></i> Solicite sua Proposta
|
||||
</a>
|
||||
|
||||
<a href="https://www.linemovel.com.br/indique" target="_blank" class="side-item" (click)="closeMenu()">
|
||||
<i class="bi bi-megaphone me-2"></i> Indique um amigo
|
||||
</a>
|
||||
</div>
|
||||
</aside>
|
||||
|
|
|
|||
|
|
@ -1,380 +1,420 @@
|
|||
/* ===================== */
|
||||
/* HEADER PRINCIPAL */
|
||||
/* ===================== */
|
||||
.header-container {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
:host {
|
||||
--brand: #E33DCF;
|
||||
--brand-2: rgba(227, 61, 207, 0.18);
|
||||
--brand-3: rgba(227, 61, 207, 0.10);
|
||||
|
||||
width: 100%;
|
||||
/* mais transparente mesmo no topo */
|
||||
background: rgba(255, 255, 255, 0.75);
|
||||
font-family: 'Inter', sans-serif;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
--text: #0b0b0f;
|
||||
--muted: rgba(0, 0, 0, 0.66);
|
||||
|
||||
backdrop-filter: blur(6px);
|
||||
-webkit-backdrop-filter: blur(6px);
|
||||
--border: rgba(0, 0, 0, 0.08);
|
||||
|
||||
transition:
|
||||
background-color 0.25s ease,
|
||||
backdrop-filter 0.25s ease,
|
||||
-webkit-backdrop-filter 0.25s ease,
|
||||
box-shadow 0.25s ease,
|
||||
border-color 0.25s ease;
|
||||
--shadow: 0 18px 45px rgba(0, 0, 0, 0.10);
|
||||
--shadow-soft: 0 10px 26px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
/* HEADER “FOSCO” QUANDO SCROLLADO – BEM TRANSPARENTE */
|
||||
.header-container.header-scrolled {
|
||||
background: rgba(255, 255, 255, 0.22); /* quase vidro puro */
|
||||
backdrop-filter: blur(14px);
|
||||
-webkit-backdrop-filter: blur(14px);
|
||||
/* =============================== */
|
||||
/* PÁGINA / FUNDO GLOBAL */
|
||||
/* =============================== */
|
||||
.home-page {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
background: #efefef;
|
||||
|
||||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.12);
|
||||
border-bottom: 1px solid rgba(201, 30, 181, 0.3);
|
||||
/* 🔑 evita o background “atrapalhar” o conteúdo */
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
/* BACKGROUND GLOBAL (viewport inteiro) */
|
||||
.page-bg {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: -1; /* 🔑 sempre atrás */
|
||||
pointer-events: none;
|
||||
|
||||
.grid {
|
||||
position: absolute;
|
||||
inset: -40px;
|
||||
opacity: 0.30;
|
||||
background:
|
||||
radial-gradient(circle at 25% 25%, var(--brand-2), transparent 45%),
|
||||
radial-gradient(circle at 80% 30%, var(--brand-3), transparent 55%),
|
||||
radial-gradient(circle at 45% 90%, var(--brand-2), transparent 50%),
|
||||
repeating-linear-gradient(
|
||||
90deg,
|
||||
rgba(0, 0, 0, 0.05) 0px,
|
||||
rgba(0, 0, 0, 0.05) 1px,
|
||||
transparent 1px,
|
||||
transparent 46px
|
||||
),
|
||||
repeating-linear-gradient(
|
||||
0deg,
|
||||
rgba(0, 0, 0, 0.04) 0px,
|
||||
rgba(0, 0, 0, 0.04) 1px,
|
||||
transparent 1px,
|
||||
transparent 46px
|
||||
);
|
||||
/* ❌ SEM blur */
|
||||
transform: translateZ(0);
|
||||
animation: gridMove 12s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.blob {
|
||||
position: absolute;
|
||||
width: 520px;
|
||||
height: 520px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle at 30% 30%, var(--brand), transparent 62%);
|
||||
opacity: 0.16; /* um pouco mais leve sem blur */
|
||||
/* ❌ SEM blur */
|
||||
transform: translateZ(0);
|
||||
animation: floaty 9s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* ===================== */
|
||||
/* TOP AREA (LOGO + MENU + BOTÕES) */
|
||||
/* ===================== */
|
||||
.header-top {
|
||||
width: 100%;
|
||||
height: 72px;
|
||||
padding: 0 32px;
|
||||
.blob-1 {
|
||||
top: -180px;
|
||||
left: -180px;
|
||||
}
|
||||
|
||||
display: flex;
|
||||
.blob-2 {
|
||||
bottom: -220px;
|
||||
right: -220px;
|
||||
opacity: 0.12;
|
||||
animation-duration: 11s;
|
||||
}
|
||||
}
|
||||
|
||||
/* =============================== */
|
||||
/* HERO */
|
||||
/* =============================== */
|
||||
.hero {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
padding: clamp(48px, 5vw, 70px) 0 20px;
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
text-align: center;
|
||||
max-width: 980px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* =============================== */
|
||||
/* KICKER */
|
||||
/* =============================== */
|
||||
.kicker {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: space-between; /* Alinha logo / menu / botões */
|
||||
gap: 40px;
|
||||
gap: 10px;
|
||||
|
||||
@media (max-width: 900px) {
|
||||
gap: 20px;
|
||||
}
|
||||
padding: 10px 14px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.65);
|
||||
border: 1px solid var(--border);
|
||||
box-shadow: var(--shadow-soft);
|
||||
|
||||
@media (max-width: 768px) {
|
||||
padding: 0 20px;
|
||||
}
|
||||
color: var(--muted);
|
||||
font-family: 'Poppins', sans-serif;
|
||||
font-size: 14px;
|
||||
|
||||
@media (max-width: 600px) {
|
||||
flex-direction: column;
|
||||
height: auto;
|
||||
gap: 12px;
|
||||
padding: 12px 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===================== */
|
||||
/* LOGO */
|
||||
/* ===================== */
|
||||
|
||||
.logo {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
|
||||
/* NOTEBOOKS 1200–1399px */
|
||||
@media (min-width: 1200px) and (max-width: 1399.98px) {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
}
|
||||
|
||||
@media (max-width: 1280px) {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
}
|
||||
}
|
||||
|
||||
.logo-area {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
text-decoration: none; /* ⬅️ tira sublinhado */
|
||||
color: inherit; /* ⬅️ usa as cores definidas nos spans */
|
||||
cursor: pointer; /* ⬅️ deixa com “carinha” de botão/link */
|
||||
}
|
||||
|
||||
/* TEXTO DA LOGO */
|
||||
.logo-text .line,
|
||||
.logo-text .gestao {
|
||||
font-weight: 600;
|
||||
font-size: 32px; /* desktop grande */
|
||||
|
||||
/* NOTEBOOKS 1200–1399px */
|
||||
@media (min-width: 1200px) and (max-width: 1399.98px) {
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
font-size: 19px;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
i {
|
||||
color: var(--brand);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.logo-text .line { color: #030FAA; }
|
||||
.logo-text .gestao { color: #000000; }
|
||||
/* =============================== */
|
||||
/* TÍTULO PRINCIPAL */
|
||||
/* =============================== */
|
||||
.main-title {
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-size: clamp(28px, 4.6vw, 54px);
|
||||
line-height: 1.08;
|
||||
margin: 22px auto 22px;
|
||||
|
||||
/* ===================== */
|
||||
/* MENU */
|
||||
/* ===================== */
|
||||
.menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
width: fit-content;
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
gap: 18px;
|
||||
.first-line,
|
||||
.second-line {
|
||||
color: var(--brand);
|
||||
font-weight: 600;
|
||||
display: block;
|
||||
text-align: center;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
gap: 14px;
|
||||
.second-line strong {
|
||||
background: linear-gradient(90deg, var(--brand), rgba(227, 61, 207, 0.55));
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
.second-line strong::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: -6px;
|
||||
right: -6px;
|
||||
bottom: -6px;
|
||||
height: 10px;
|
||||
border-radius: 999px;
|
||||
background: rgba(227, 61, 207, 0.18);
|
||||
z-index: -1;
|
||||
transform: scaleX(0);
|
||||
transform-origin: left;
|
||||
animation: underlineGrow 900ms ease forwards;
|
||||
animation-delay: 700ms;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #000 !important;
|
||||
text-decoration: none !important;
|
||||
|
||||
/* NOTEBOOKS 1200–1399px */
|
||||
@media (min-width: 1200px) and (max-width: 1399.98px) {
|
||||
font-size: 14.5px;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
font-size: 13.5px;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
font-size: 12.5px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
font-size: 12px;
|
||||
}
|
||||
/* deslocamento em telas grandes */
|
||||
@media (min-width: 1200px) {
|
||||
.main-title .first-line { transform: translateX(-70px); }
|
||||
.main-title .second-line { transform: translateX(100px) translateY(-6px); }
|
||||
}
|
||||
|
||||
.menu-item:hover {
|
||||
color: #030FAA !important;
|
||||
@media (max-width: 1199.98px) {
|
||||
.main-title .first-line,
|
||||
.main-title .second-line { transform: none; }
|
||||
}
|
||||
|
||||
/* ===================== */
|
||||
/* BOTÕES */
|
||||
/* ===================== */
|
||||
.btn-area {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-left: 0 !important;
|
||||
/* =============================== */
|
||||
/* PARÁGRAFO PRINCIPAL */
|
||||
/* =============================== */
|
||||
.main-paragraph {
|
||||
width: min(980px, 92%);
|
||||
margin: 0 auto 26px;
|
||||
|
||||
@media (max-width: 900px) {
|
||||
gap: 10px;
|
||||
}
|
||||
font-family: 'Poppins', sans-serif;
|
||||
font-size: clamp(15px, 1.55vw, 20px);
|
||||
color: var(--text);
|
||||
line-height: 1.45;
|
||||
font-weight: 400;
|
||||
text-align: center;
|
||||
|
||||
@media (max-width: 600px) {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
strong { font-weight: 700; }
|
||||
}
|
||||
|
||||
/* --- Botão Cadastre-se --- */
|
||||
.btn-cadastrar {
|
||||
width: 164px;
|
||||
height: 41px;
|
||||
background: #E1E1E1;
|
||||
border-radius: 8px;
|
||||
color: #000;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s ease, transform 0.08s ease;
|
||||
.brand-name { color: var(--brand); }
|
||||
|
||||
@media (min-width: 1200px) and (max-width: 1399.98px) {
|
||||
width: 140px;
|
||||
height: 36px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
width: 140px;
|
||||
height: 36px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
width: 135px;
|
||||
height: 36px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
width: 120px;
|
||||
height: 34px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
width: 45%;
|
||||
}
|
||||
.highlight {
|
||||
color: var(--text);
|
||||
position: relative;
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
.btn-cadastrar:hover {
|
||||
background: #d7d7d7;
|
||||
.highlight::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: -2px;
|
||||
right: -2px;
|
||||
bottom: 2px;
|
||||
height: 10px;
|
||||
background: rgba(227, 61, 207, 0.12);
|
||||
border-radius: 999px;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.btn-cadastrar:active {
|
||||
background: rgba(225, 225, 225, 0.7) !important;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
/* --- Botão Login --- */
|
||||
.btn-login {
|
||||
width: 164px;
|
||||
height: 41px;
|
||||
background: #E33DCF;
|
||||
border-radius: 8px;
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s ease, transform 0.08s ease;
|
||||
|
||||
@media (min-width: 1200px) and (max-width: 1399.98px) {
|
||||
width: 140px;
|
||||
height: 36px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
width: 150px;
|
||||
height: 38px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
width: 135px;
|
||||
height: 36px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
width: 120px;
|
||||
height: 34px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
width: 45%;
|
||||
}
|
||||
}
|
||||
|
||||
/* garante que o texto SEMPRE fique branco */
|
||||
.btn-login,
|
||||
.btn-login:hover,
|
||||
.btn-login:active,
|
||||
.btn-login:focus {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.btn-login:hover {
|
||||
background: #d72bd0;
|
||||
}
|
||||
|
||||
.btn-login:active {
|
||||
background: rgba(227, 61, 207, 0.8) !important;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
/* ===================== */
|
||||
/* FAIXA AZUL INFERIOR */
|
||||
/* ===================== */
|
||||
.header-bar {
|
||||
width: 100%;
|
||||
height: 34px; /* um pouco mais baixa */
|
||||
background: linear-gradient(90deg, #030FAA 0%, #6066FF 45%, #C91EB5 100%);
|
||||
/* =============================== */
|
||||
/* AÇÕES / LINKS */
|
||||
/* =============================== */
|
||||
.hero-actions {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.25);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18);
|
||||
|
||||
@media (max-width: 480px) {
|
||||
height: 30px;
|
||||
}
|
||||
gap: 14px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.header-bar-text {
|
||||
color: #ffffff;
|
||||
font-size: 13px;
|
||||
.secondary-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
color: rgba(0, 0, 0, 0.72);
|
||||
font-family: 'Poppins', sans-serif;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
text-decoration: none;
|
||||
|
||||
/* “pill” para destacar o texto */
|
||||
padding: 4px 14px;
|
||||
padding: 12px 14px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--border);
|
||||
background: rgba(255, 255, 255, 0.55);
|
||||
box-shadow: var(--shadow-soft);
|
||||
|
||||
transition: transform 0.22s ease, box-shadow 0.22s ease;
|
||||
}
|
||||
|
||||
.secondary-link:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.hero-badges {
|
||||
margin-top: 18px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.badge-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
padding: 9px 12px;
|
||||
border-radius: 999px;
|
||||
background: rgba(0, 0, 0, 0.12);
|
||||
backdrop-filter: blur(4px);
|
||||
-webkit-backdrop-filter: blur(4px);
|
||||
|
||||
@media (max-width: 480px) {
|
||||
font-size: 11.5px;
|
||||
padding: 3px 10px;
|
||||
letter-spacing: 0.06em;
|
||||
text-align: center;
|
||||
background: rgba(255, 255, 255, 0.70);
|
||||
border: 1px solid var(--border);
|
||||
box-shadow: var(--shadow-soft);
|
||||
|
||||
font-family: 'Poppins', sans-serif;
|
||||
font-size: 13px;
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
|
||||
i { color: var(--brand); }
|
||||
}
|
||||
|
||||
/* =============================== */
|
||||
/* BENEFÍCIOS */
|
||||
/* =============================== */
|
||||
.hero-benefits {
|
||||
margin: 20px auto 0;
|
||||
width: min(820px, 96%);
|
||||
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
|
||||
.benefit {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: flex-start;
|
||||
|
||||
padding: 12px 14px;
|
||||
border-radius: 16px;
|
||||
|
||||
background: rgba(255, 255, 255, 0.58);
|
||||
border: 1px solid var(--border);
|
||||
box-shadow: var(--shadow-soft);
|
||||
|
||||
i {
|
||||
color: var(--brand);
|
||||
font-size: 18px;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-family: 'Poppins', sans-serif;
|
||||
color: rgba(0, 0, 0, 0.78);
|
||||
font-size: 14px;
|
||||
line-height: 1.35;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 820px) {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* =============================== */
|
||||
/* FEATURES */
|
||||
/* =============================== */
|
||||
.section-head {
|
||||
text-align: center;
|
||||
margin: 0 auto 24px;
|
||||
max-width: 820px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-size: clamp(20px, 2.4vw, 30px);
|
||||
color: rgba(0, 0, 0, 0.88);
|
||||
margin-bottom: 8px;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.section-subtitle {
|
||||
font-family: 'Poppins', sans-serif;
|
||||
color: rgba(0, 0, 0, 0.64);
|
||||
font-size: 15px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.feature-cards-row { margin-top: 18px; }
|
||||
|
||||
.feature-item {
|
||||
border-radius: 18px;
|
||||
padding: 10px;
|
||||
background: transparent;
|
||||
transition: transform 0.25s ease, filter 0.25s ease;
|
||||
}
|
||||
|
||||
.feature-item:hover {
|
||||
transform: translateY(-6px);
|
||||
filter: drop-shadow(0 14px 22px rgba(0, 0, 0, 0.10));
|
||||
}
|
||||
|
||||
.button-section {
|
||||
margin-top: 18px;
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
|
||||
/* =============================== */
|
||||
/* REVEAL (scroll) */
|
||||
/* =============================== */
|
||||
.reveal {
|
||||
opacity: 0;
|
||||
transform: translateY(18px) scale(0.98);
|
||||
transition:
|
||||
opacity 650ms ease,
|
||||
transform 650ms ease;
|
||||
transition-delay: var(--delay, 0ms);
|
||||
}
|
||||
|
||||
.reveal.is-visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
|
||||
/* =============================== */
|
||||
/* ANIMAÇÕES */
|
||||
/* =============================== */
|
||||
@keyframes floaty {
|
||||
0%, 100% { transform: translate(0, 0) scale(1); }
|
||||
50% { transform: translate(14px, 18px) scale(1.03); }
|
||||
}
|
||||
|
||||
@keyframes gridMove {
|
||||
0%, 100% { transform: translate(0, 0); }
|
||||
50% { transform: translate(-10px, 10px); }
|
||||
}
|
||||
|
||||
@keyframes underlineGrow {
|
||||
from { transform: scaleX(0); }
|
||||
to { transform: scaleX(1); }
|
||||
}
|
||||
|
||||
/* =============================== */
|
||||
/* Acessibilidade */
|
||||
/* =============================== */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.reveal,
|
||||
.page-bg .grid,
|
||||
.page-bg .blob,
|
||||
.main-title .second-line strong::after {
|
||||
animation: none !important;
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
.reveal {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +1,61 @@
|
|||
import { Component, HostListener } from '@angular/core';
|
||||
import { Component, HostListener, Inject } from '@angular/core';
|
||||
import { RouterLink, Router, NavigationEnd } from '@angular/router';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CommonModule, isPlatformBrowser } from '@angular/common';
|
||||
import { PLATFORM_ID } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-header',
|
||||
standalone: true,
|
||||
imports: [RouterLink, CommonModule], // ⬅️ CommonModule para usar *ngIf
|
||||
imports: [RouterLink, CommonModule],
|
||||
templateUrl: './header.html',
|
||||
styleUrls: ['./header.scss'],
|
||||
})
|
||||
export class Header {
|
||||
isScrolled = false;
|
||||
isHome = true; // valor inicial (ao abrir normalmente cai na home)
|
||||
isHome = true;
|
||||
|
||||
constructor(private router: Router) {
|
||||
// escuta mudanças de rota para saber se está na home
|
||||
// ✅ menu hamburguer
|
||||
menuOpen = false;
|
||||
|
||||
// ✅ define quando mostrar header “logado”
|
||||
isLoggedHeader = false;
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
@Inject(PLATFORM_ID) private platformId: object
|
||||
) {
|
||||
this.router.events.subscribe((event) => {
|
||||
if (event instanceof NavigationEnd) {
|
||||
const url = event.urlAfterRedirects || event.url;
|
||||
|
||||
this.isHome = (url === '/' || url === '');
|
||||
|
||||
// ✅ considera header logado quando está em /geral
|
||||
this.isLoggedHeader = url.startsWith('/geral');
|
||||
|
||||
// ✅ ao trocar de rota, fecha o menu
|
||||
this.menuOpen = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
toggleMenu() {
|
||||
this.menuOpen = !this.menuOpen;
|
||||
}
|
||||
|
||||
closeMenu() {
|
||||
this.menuOpen = false;
|
||||
}
|
||||
|
||||
@HostListener('window:scroll', [])
|
||||
onWindowScroll() {
|
||||
this.isScrolled = window.scrollY > 10; // passou 10px de scroll, ativa o “fosco”
|
||||
if (!isPlatformBrowser(this.platformId)) return;
|
||||
this.isScrolled = window.scrollY > 10;
|
||||
}
|
||||
|
||||
@HostListener('document:keydown.escape', [])
|
||||
onEsc() {
|
||||
if (!isPlatformBrowser(this.platformId)) return;
|
||||
this.closeMenu();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
import { inject, PLATFORM_ID } from '@angular/core';
|
||||
import { CanActivateFn, Router } from '@angular/router';
|
||||
import { isPlatformBrowser } from '@angular/common';
|
||||
|
||||
export const authGuard: CanActivateFn = () => {
|
||||
const router = inject(Router);
|
||||
const platformId = inject(PLATFORM_ID);
|
||||
|
||||
// SSR: não existe localStorage. Bloqueia e manda pro login.
|
||||
if (!isPlatformBrowser(platformId)) {
|
||||
return router.parseUrl('/login');
|
||||
}
|
||||
|
||||
const token = localStorage.getItem('token');
|
||||
|
||||
if (!token) {
|
||||
return router.parseUrl('/login');
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
|
@ -1 +1,265 @@
|
|||
<p>geral works!</p>
|
||||
<!-- Toast (Sucesso Login) -->
|
||||
<div class="toast-container position-fixed top-0 end-0 p-3" style="z-index: 2000;">
|
||||
<div
|
||||
#successToast
|
||||
class="toast text-bg-success"
|
||||
role="alert"
|
||||
aria-live="assertive"
|
||||
aria-atomic="true"
|
||||
>
|
||||
<div class="toast-header">
|
||||
<strong class="me-auto">LineGestão</strong>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Fechar"></button>
|
||||
</div>
|
||||
<div class="toast-body">
|
||||
{{ toastMessage }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="geral-page">
|
||||
|
||||
<!-- background "home-like" -->
|
||||
<span class="page-blob blob-1" aria-hidden="true"></span>
|
||||
<span class="page-blob blob-2" aria-hidden="true"></span>
|
||||
<span class="page-blob blob-3" aria-hidden="true"></span>
|
||||
<span class="page-blob blob-4" aria-hidden="true"></span>
|
||||
|
||||
<div class="container-geral">
|
||||
|
||||
<div class="geral-card" data-animate>
|
||||
|
||||
<!-- HEADER -->
|
||||
<div class="geral-header">
|
||||
<div class="header-row-top">
|
||||
|
||||
<div class="header-title" data-animate>
|
||||
<div class="title-badge">
|
||||
<i class="bi bi-grid-1x2"></i>
|
||||
Gestão centralizada
|
||||
</div>
|
||||
|
||||
<h5 class="title mb-0">Geral</h5>
|
||||
<small class="subtitle">Tabela de linhas e dados de telefonia</small>
|
||||
</div>
|
||||
|
||||
<div class="header-actions d-flex gap-2" data-animate>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-primary btn-sm btn-glass"
|
||||
(click)="onImportExcel()"
|
||||
title="Importar dados de planilha"
|
||||
>
|
||||
<i class="bi bi-file-earmark-excel me-1"></i>
|
||||
Importar Excel
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary btn-sm btn-brand"
|
||||
(click)="onCadastrarLinha()"
|
||||
title="Cadastrar uma nova linha"
|
||||
>
|
||||
<i class="bi bi-plus-circle me-1"></i>
|
||||
Cadastrar Linha
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CONTROLES -->
|
||||
<div class="controls mt-3" data-animate>
|
||||
|
||||
<div class="input-group input-group-sm search-group">
|
||||
<span class="input-group-text" title="Buscar">
|
||||
<i class="bi bi-search"></i>
|
||||
</span>
|
||||
|
||||
<input
|
||||
class="form-control"
|
||||
placeholder="Pesquisar..."
|
||||
[(ngModel)]="searchTerm"
|
||||
(ngModelChange)="onSearch()"
|
||||
/>
|
||||
|
||||
<button
|
||||
class="btn btn-outline-secondary btn-clear"
|
||||
type="button"
|
||||
(click)="clearSearch()"
|
||||
*ngIf="searchTerm"
|
||||
title="Limpar busca"
|
||||
>
|
||||
<i class="bi bi-x-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="page-size">
|
||||
<span class="text-muted small">Itens por página</span>
|
||||
<select
|
||||
class="form-select form-select-sm w-auto select-glass"
|
||||
[(ngModel)]="pageSize"
|
||||
(change)="onPageSizeChange()"
|
||||
>
|
||||
<option [ngValue]="10">10</option>
|
||||
<option [ngValue]="20">20</option>
|
||||
<option [ngValue]="50">50</option>
|
||||
<option [ngValue]="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TABELA -->
|
||||
<div class="geral-body">
|
||||
<div class="table-wrap">
|
||||
<div class="table-responsive">
|
||||
|
||||
<table class="table table-hover align-middle mb-0 table-geral">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="sortable" (click)="setSort('item')">
|
||||
ITÉM
|
||||
<span class="sort-caret">
|
||||
{{ sortKey==='item' ? (sortDir==='asc' ? '▲' : '▼') : '' }}
|
||||
</span>
|
||||
</th>
|
||||
|
||||
<th class="sortable" (click)="setSort('conta')">
|
||||
CONTA
|
||||
<span class="sort-caret">
|
||||
{{ sortKey==='conta' ? (sortDir==='asc' ? '▲' : '▼') : '' }}
|
||||
</span>
|
||||
</th>
|
||||
|
||||
<th class="sortable" (click)="setSort('linha')">
|
||||
LINHA
|
||||
<span class="sort-caret">
|
||||
{{ sortKey==='linha' ? (sortDir==='asc' ? '▲' : '▼') : '' }}
|
||||
</span>
|
||||
</th>
|
||||
|
||||
<th class="sortable" (click)="setSort('chip')">
|
||||
CHIP
|
||||
<span class="sort-caret">
|
||||
{{ sortKey==='chip' ? (sortDir==='asc' ? '▲' : '▼') : '' }}
|
||||
</span>
|
||||
</th>
|
||||
|
||||
<th class="sortable" (click)="setSort('cliente')">
|
||||
CLIENTE
|
||||
<span class="sort-caret">
|
||||
{{ sortKey==='cliente' ? (sortDir==='asc' ? '▲' : '▼') : '' }}
|
||||
</span>
|
||||
</th>
|
||||
|
||||
<th class="sortable" (click)="setSort('usuario')">
|
||||
USUÁRIO
|
||||
<span class="sort-caret">
|
||||
{{ sortKey==='usuario' ? (sortDir==='asc' ? '▲' : '▼') : '' }}
|
||||
</span>
|
||||
</th>
|
||||
|
||||
<th class="sortable" (click)="setSort('plano')">
|
||||
PLANO
|
||||
<span class="sort-caret">
|
||||
{{ sortKey==='plano' ? (sortDir==='asc' ? '▲' : '▼') : '' }}
|
||||
</span>
|
||||
</th>
|
||||
|
||||
<th class="sortable" (click)="setSort('contrato')">
|
||||
CONTRATO
|
||||
<span class="sort-caret">
|
||||
{{ sortKey==='contrato' ? (sortDir==='asc' ? '▲' : '▼') : '' }}
|
||||
</span>
|
||||
</th>
|
||||
|
||||
<th class="text-end">AÇÕES</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr *ngFor="let r of pagedRows; trackBy: trackById" data-row>
|
||||
<td class="text-muted">{{ r.item }}</td>
|
||||
<td>{{ r.conta }}</td>
|
||||
<td class="fw-semibold td-highlight">{{ r.linha }}</td>
|
||||
<td>{{ r.chip }}</td>
|
||||
<td>{{ r.cliente }}</td>
|
||||
<td>{{ r.usuario }}</td>
|
||||
<td>{{ r.plano }}</td>
|
||||
<td>{{ r.contrato }}</td>
|
||||
|
||||
<td class="text-end actions">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline-secondary btn-icon"
|
||||
(click)="onDetalhes(r)"
|
||||
title="Detalhes"
|
||||
>
|
||||
<i class="bi bi-eye"></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline-success btn-icon ms-1"
|
||||
(click)="onFinanceiro(r)"
|
||||
title="Financeiro"
|
||||
>
|
||||
<i class="bi bi-cash-coin"></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline-danger btn-icon ms-1"
|
||||
(click)="onRemover(r)"
|
||||
title="Remover"
|
||||
>
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr *ngIf="pagedRows.length === 0">
|
||||
<td colspan="9" class="text-center py-5 text-muted empty-state">
|
||||
<i class="bi bi-inbox me-2"></i>
|
||||
Nenhum registro encontrado.
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FOOTER / PAGINAÇÃO -->
|
||||
<div class="geral-footer">
|
||||
<div class="small text-muted">
|
||||
Mostrando {{ pageStart }}–{{ pageEnd }} de {{ filteredCount }} registros
|
||||
</div>
|
||||
|
||||
<nav aria-label="Paginação">
|
||||
<ul class="pagination pagination-sm mb-0 pagination-modern">
|
||||
<li class="page-item" [class.disabled]="page === 1">
|
||||
<button class="page-link" (click)="goToPage(page - 1)">
|
||||
<i class="bi bi-chevron-left"></i>
|
||||
Anterior
|
||||
</button>
|
||||
</li>
|
||||
|
||||
<li class="page-item" *ngFor="let p of pageNumbers" [class.active]="p === page">
|
||||
<button class="page-link" (click)="goToPage(p)">{{ p }}</button>
|
||||
</li>
|
||||
|
||||
<li class="page-item" [class.disabled]="page === totalPages">
|
||||
<button class="page-link" (click)="goToPage(page + 1)">
|
||||
Próxima
|
||||
<i class="bi bi-chevron-right"></i>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,400 @@
|
|||
:host {
|
||||
--brand: #E33DCF;
|
||||
--blue: #030FAA;
|
||||
|
||||
--text: #111214;
|
||||
--muted: rgba(17, 18, 20, 0.70);
|
||||
|
||||
--radius-xl: 22px;
|
||||
--radius-lg: 16px;
|
||||
|
||||
display: block;
|
||||
}
|
||||
|
||||
.geral-page {
|
||||
min-height: calc(100vh - 69.2px);
|
||||
padding: 24px 12px 90px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
font-family: 'Inter', sans-serif;
|
||||
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
background:
|
||||
radial-gradient(900px 420px at 20% 10%, rgba(227, 61, 207, 0.14), transparent 60%),
|
||||
radial-gradient(820px 380px at 80% 30%, rgba(227, 61, 207, 0.08), transparent 60%),
|
||||
linear-gradient(180deg, #ffffff 0%, #f5f5f7 70%);
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
}
|
||||
|
||||
/* blobs fixos */
|
||||
.page-blob {
|
||||
position: fixed;
|
||||
pointer-events: none;
|
||||
border-radius: 999px;
|
||||
filter: blur(34px);
|
||||
opacity: 0.55;
|
||||
z-index: 0;
|
||||
|
||||
background: radial-gradient(circle at 30% 30%, rgba(227,61,207,0.55), rgba(227,61,207,0.06));
|
||||
animation: floaty 10s ease-in-out infinite;
|
||||
|
||||
&.blob-1 { width: 420px; height: 420px; top: -140px; left: -140px; }
|
||||
&.blob-2 { width: 520px; height: 520px; top: -220px; right: -240px; animation-duration: 12s; }
|
||||
&.blob-3 { width: 360px; height: 360px; bottom: -180px; left: 25%; animation-duration: 14s; }
|
||||
&.blob-4 { width: 520px; height: 520px; bottom: -260px; right: -260px; animation-duration: 16s; opacity: .45; }
|
||||
|
||||
@media (max-width: 992px) { opacity: 0.35; }
|
||||
}
|
||||
|
||||
.container-geral {
|
||||
width: 100%;
|
||||
max-width: 1180px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* CARD GLASS */
|
||||
.geral-card {
|
||||
border-radius: var(--radius-xl);
|
||||
overflow: hidden;
|
||||
|
||||
background: rgba(255, 255, 255, 0.82);
|
||||
border: 1px solid rgba(227, 61, 207, 0.16);
|
||||
backdrop-filter: blur(12px);
|
||||
box-shadow: 0 22px 46px rgba(17, 18, 20, 0.10);
|
||||
|
||||
/* borda interna */
|
||||
position: relative;
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 1px;
|
||||
border-radius: calc(var(--radius-xl) - 1px);
|
||||
pointer-events: none;
|
||||
border: 1px solid rgba(255, 255, 255, 0.65);
|
||||
opacity: 0.75;
|
||||
}
|
||||
}
|
||||
|
||||
/* HEADER */
|
||||
.geral-header {
|
||||
padding: 16px 16px 14px 16px;
|
||||
border-bottom: 1px solid rgba(17, 18, 20, 0.06);
|
||||
background: linear-gradient(180deg, rgba(227,61,207,0.08), rgba(255,255,255,0.10));
|
||||
}
|
||||
|
||||
.header-row-top {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
grid-template-columns: 1fr;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.header-title {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.title-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
||||
padding: 8px 12px;
|
||||
border-radius: 999px;
|
||||
|
||||
background: rgba(255, 255, 255, 0.78);
|
||||
border: 1px solid rgba(227, 61, 207, 0.22);
|
||||
backdrop-filter: blur(10px);
|
||||
|
||||
color: var(--text);
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
|
||||
i { color: var(--brand); }
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 26px;
|
||||
font-weight: 950;
|
||||
letter-spacing: -0.3px;
|
||||
color: var(--text);
|
||||
margin-top: 10px;
|
||||
|
||||
@media (max-width: 768px) { font-size: 24px; }
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: rgba(17, 18, 20, 0.65);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
justify-content: flex-end;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
/* BOTÕES */
|
||||
.btn-brand {
|
||||
background-color: var(--brand);
|
||||
border-color: var(--brand);
|
||||
font-weight: 900;
|
||||
border-radius: 12px;
|
||||
|
||||
transition: transform 180ms ease, box-shadow 180ms ease, filter 180ms ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 14px 24px rgba(227, 61, 207, 0.22);
|
||||
filter: brightness(0.97);
|
||||
}
|
||||
}
|
||||
|
||||
.btn-glass {
|
||||
border-radius: 12px;
|
||||
font-weight: 900;
|
||||
|
||||
background: rgba(255, 255, 255, 0.72);
|
||||
border: 1px solid rgba(3, 15, 170, 0.25);
|
||||
color: var(--blue);
|
||||
|
||||
transition: transform 180ms ease, box-shadow 180ms ease, border-color 180ms ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 14px 24px rgba(17, 18, 20, 0.10);
|
||||
border-color: rgba(227, 61, 207, 0.30);
|
||||
}
|
||||
}
|
||||
|
||||
/* CONTROLES */
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
/* Busca */
|
||||
.search-group {
|
||||
max-width: 340px;
|
||||
border-radius: 14px;
|
||||
overflow: hidden;
|
||||
|
||||
.input-group-text {
|
||||
background: rgba(255, 255, 255, 0.78);
|
||||
border: 1px solid rgba(17, 18, 20, 0.10);
|
||||
color: var(--brand);
|
||||
}
|
||||
|
||||
.form-control {
|
||||
border: 1px solid rgba(17, 18, 20, 0.10);
|
||||
background: rgba(255, 255, 255, 0.78);
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
border-color: rgba(227, 61, 207, 0.35);
|
||||
box-shadow: 0 0 0 0.2rem rgba(227, 61, 207, 0.12);
|
||||
}
|
||||
|
||||
.btn-clear {
|
||||
border: 1px solid rgba(17, 18, 20, 0.10);
|
||||
background: rgba(255, 255, 255, 0.78);
|
||||
}
|
||||
}
|
||||
|
||||
/* select */
|
||||
.select-glass {
|
||||
border-radius: 12px;
|
||||
background: rgba(255, 255, 255, 0.78);
|
||||
border: 1px solid rgba(17, 18, 20, 0.10);
|
||||
}
|
||||
|
||||
/* BODY */
|
||||
.geral-body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Wrapper com altura e scroll */
|
||||
.table-wrap {
|
||||
max-height: 58vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* TABELA MODERNA */
|
||||
.table-geral {
|
||||
font-size: 0.875rem;
|
||||
|
||||
th, td {
|
||||
white-space: nowrap;
|
||||
padding: 10px 12px;
|
||||
color: var(--text);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
thead th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
backdrop-filter: blur(10px);
|
||||
|
||||
border-bottom: 2px solid rgba(227, 61, 207, 0.15);
|
||||
|
||||
color: rgba(17, 18, 20, 0.75);
|
||||
font-weight: 950;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
th.sortable {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: color 180ms ease;
|
||||
|
||||
&:hover { color: var(--brand); }
|
||||
}
|
||||
|
||||
.sort-caret {
|
||||
font-size: 11px;
|
||||
margin-left: 6px;
|
||||
opacity: 0.75;
|
||||
color: rgba(17, 18, 20, 0.60);
|
||||
}
|
||||
|
||||
/* destaque da linha */
|
||||
.td-highlight {
|
||||
color: var(--blue);
|
||||
font-weight: 950;
|
||||
}
|
||||
|
||||
/* linhas mais “clean” */
|
||||
tbody tr {
|
||||
border-top: 1px solid rgba(17, 18, 20, 0.06);
|
||||
transition: background-color 180ms ease, transform 180ms ease;
|
||||
}
|
||||
|
||||
tbody tr:hover {
|
||||
background-color: rgba(227, 61, 207, 0.06);
|
||||
}
|
||||
|
||||
td.actions {
|
||||
min-width: 160px;
|
||||
}
|
||||
}
|
||||
|
||||
/* botões ícone */
|
||||
.btn-icon {
|
||||
padding: 6px 10px;
|
||||
border-radius: 12px;
|
||||
transition: transform 150ms ease, box-shadow 150ms ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 12px 22px rgba(17, 18, 20, 0.10);
|
||||
}
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
background: rgba(255, 255, 255, 0.35);
|
||||
}
|
||||
|
||||
/* FOOTER */
|
||||
.geral-footer {
|
||||
padding: 14px 16px;
|
||||
border-top: 1px solid rgba(17, 18, 20, 0.06);
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
/* Paginação moderna */
|
||||
.pagination-modern .page-link {
|
||||
color: var(--blue);
|
||||
font-weight: 900;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(17, 18, 20, 0.10);
|
||||
background: rgba(255, 255, 255, 0.78);
|
||||
transition: transform 150ms ease, border-color 150ms ease, box-shadow 150ms ease, color 150ms ease;
|
||||
|
||||
i { font-size: 12px; }
|
||||
}
|
||||
|
||||
.pagination-modern .page-link:hover {
|
||||
transform: translateY(-1px);
|
||||
border-color: rgba(227, 61, 207, 0.28);
|
||||
color: var(--brand);
|
||||
box-shadow: 0 12px 22px rgba(17, 18, 20, 0.08);
|
||||
}
|
||||
|
||||
.pagination-modern .page-item.active .page-link {
|
||||
background-color: rgba(3, 15, 170, 0.92);
|
||||
border-color: rgba(3, 15, 170, 0.92);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* =============================== */
|
||||
/* ✅ ANIMAÇÕES SSR-SAFE */
|
||||
/* =============================== */
|
||||
[data-animate] { opacity: 1; transform: none; }
|
||||
|
||||
.js-animate [data-animate] {
|
||||
opacity: 0;
|
||||
transform: translateY(14px);
|
||||
transition: opacity 600ms ease, transform 600ms ease;
|
||||
will-change: opacity, transform;
|
||||
}
|
||||
|
||||
.js-animate [data-animate].is-visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.page-blob { animation: none; }
|
||||
.js-animate [data-animate] { transition: none; transform: none; opacity: 1; }
|
||||
}
|
||||
|
||||
/* animações */
|
||||
@keyframes floaty {
|
||||
0% { transform: translate(0, 0) scale(1); }
|
||||
50% { transform: translate(18px, 10px) scale(1.03); }
|
||||
100% { transform: translate(0, 0) scale(1); }
|
||||
}
|
||||
|
|
@ -1,11 +1,188 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { Component, ElementRef, ViewChild, Inject, PLATFORM_ID, AfterViewInit } from '@angular/core';
|
||||
import { isPlatformBrowser, CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
type SortDir = 'asc' | 'desc';
|
||||
|
||||
type LineRow = {
|
||||
id: number;
|
||||
item: string;
|
||||
conta: string;
|
||||
linha: string;
|
||||
chip: string;
|
||||
cliente: string;
|
||||
usuario: string;
|
||||
plano: string;
|
||||
contrato: string;
|
||||
};
|
||||
|
||||
@Component({
|
||||
selector: 'app-geral',
|
||||
imports: [],
|
||||
standalone: true,
|
||||
imports: [CommonModule, FormsModule],
|
||||
templateUrl: './geral.html',
|
||||
styleUrl: './geral.scss',
|
||||
styleUrls: ['./geral.scss']
|
||||
})
|
||||
export class Geral {
|
||||
export class Geral implements AfterViewInit {
|
||||
toastMessage = '';
|
||||
@ViewChild('successToast') successToast!: ElementRef;
|
||||
|
||||
constructor(@Inject(PLATFORM_ID) private platformId: object) {}
|
||||
|
||||
async ngAfterViewInit() {
|
||||
if (!isPlatformBrowser(this.platformId)) return;
|
||||
|
||||
// ✅ animações (igual Home)
|
||||
this.initAnimations();
|
||||
|
||||
const msg = (history.state && history.state.toastMessage) ? String(history.state.toastMessage) : '';
|
||||
if (!msg) return;
|
||||
|
||||
this.toastMessage = msg;
|
||||
await this.showToast(msg);
|
||||
|
||||
const newState = { ...history.state };
|
||||
delete newState.toastMessage;
|
||||
history.replaceState(newState, '', location.href);
|
||||
}
|
||||
|
||||
private initAnimations() {
|
||||
document.documentElement.classList.add('js-animate');
|
||||
|
||||
setTimeout(() => {
|
||||
const items = Array.from(document.querySelectorAll<HTMLElement>('[data-animate]'));
|
||||
if (!items.length) return;
|
||||
|
||||
const reduceMotion =
|
||||
window.matchMedia &&
|
||||
window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
|
||||
if (reduceMotion || !('IntersectionObserver' in window)) {
|
||||
items.forEach(i => i.classList.add('is-visible'));
|
||||
return;
|
||||
}
|
||||
|
||||
const io = new IntersectionObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
if (entry.isIntersecting) {
|
||||
(entry.target as HTMLElement).classList.add('is-visible');
|
||||
io.unobserve(entry.target);
|
||||
}
|
||||
}
|
||||
}, { threshold: 0.12 });
|
||||
|
||||
items.forEach(el => io.observe(el));
|
||||
}, 0);
|
||||
}
|
||||
|
||||
private async showToast(message: string) {
|
||||
this.toastMessage = message;
|
||||
const bs = await import('bootstrap');
|
||||
const toast = new bs.Toast(this.successToast.nativeElement, { autohide: true, delay: 1500 });
|
||||
toast.show();
|
||||
}
|
||||
|
||||
// ========= TABELA (mock por enquanto) =========
|
||||
rows: LineRow[] = [
|
||||
{ id: 1, item: '001', conta: 'Empresa A', linha: '71999990001', chip: 'ICCID-0001', cliente: 'Cliente A', usuario: 'João', plano: 'Pós 30GB', contrato: 'CT-2025-001' },
|
||||
{ id: 2, item: '002', conta: 'Empresa B', linha: '71999990002', chip: 'ICCID-0002', cliente: 'Cliente B', usuario: 'Maria', plano: 'Pós Ilimitado', contrato: 'CT-2025-002' },
|
||||
{ id: 3, item: '003', conta: 'Empresa A', linha: '71999990003', chip: 'ICCID-0003', cliente: 'Cliente A', usuario: 'Carlos', plano: 'Controle 20GB', contrato: 'CT-2025-003' },
|
||||
];
|
||||
|
||||
searchTerm = '';
|
||||
sortKey: keyof LineRow = 'item';
|
||||
sortDir: SortDir = 'asc';
|
||||
|
||||
page = 1;
|
||||
pageSize = 10;
|
||||
|
||||
onSearch() { this.page = 1; }
|
||||
clearSearch() { this.searchTerm = ''; this.page = 1; }
|
||||
|
||||
setSort(key: keyof LineRow) {
|
||||
if (this.sortKey === key) {
|
||||
this.sortDir = this.sortDir === 'asc' ? 'desc' : 'asc';
|
||||
return;
|
||||
}
|
||||
this.sortKey = key;
|
||||
this.sortDir = 'asc';
|
||||
}
|
||||
|
||||
onPageSizeChange() { this.page = 1; }
|
||||
|
||||
goToPage(p: number) {
|
||||
const target = Math.max(1, Math.min(this.totalPages, p));
|
||||
this.page = target;
|
||||
}
|
||||
|
||||
trackById(_: number, row: LineRow) { return row.id; }
|
||||
|
||||
get filteredRows(): LineRow[] {
|
||||
const term = this.searchTerm.trim().toLowerCase();
|
||||
if (!term) return this.rows;
|
||||
|
||||
return this.rows.filter(r => {
|
||||
const blob = [r.item, r.conta, r.linha, r.chip, r.cliente, r.usuario, r.plano, r.contrato].join(' ').toLowerCase();
|
||||
return blob.includes(term);
|
||||
});
|
||||
}
|
||||
|
||||
get sortedRows(): LineRow[] {
|
||||
const copy = [...this.filteredRows];
|
||||
const key = this.sortKey;
|
||||
const dir = this.sortDir;
|
||||
|
||||
copy.sort((a, b) => {
|
||||
const av = String(a[key] ?? '').toLowerCase();
|
||||
const bv = String(b[key] ?? '').toLowerCase();
|
||||
if (av < bv) return dir === 'asc' ? -1 : 1;
|
||||
if (av > bv) return dir === 'asc' ? 1 : -1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
get totalPages(): number {
|
||||
const total = Math.ceil(this.sortedRows.length / this.pageSize);
|
||||
return total > 0 ? total : 1;
|
||||
}
|
||||
|
||||
get pagedRows(): LineRow[] {
|
||||
const start = (this.page - 1) * this.pageSize;
|
||||
const end = start + this.pageSize;
|
||||
return this.sortedRows.slice(start, end);
|
||||
}
|
||||
|
||||
get filteredCount(): number { return this.sortedRows.length; }
|
||||
|
||||
get pageStart(): number {
|
||||
if (this.filteredCount === 0) return 0;
|
||||
return (this.page - 1) * this.pageSize + 1;
|
||||
}
|
||||
|
||||
get pageEnd(): number {
|
||||
if (this.filteredCount === 0) return 0;
|
||||
return Math.min(this.page * this.pageSize, this.filteredCount);
|
||||
}
|
||||
|
||||
get pageNumbers(): number[] {
|
||||
const total = this.totalPages;
|
||||
const current = this.page;
|
||||
|
||||
const max = 5;
|
||||
let start = Math.max(1, current - 2);
|
||||
let end = Math.min(total, start + (max - 1));
|
||||
start = Math.max(1, end - (max - 1));
|
||||
|
||||
const pages: number[] = [];
|
||||
for (let i = start; i <= end; i++) pages.push(i);
|
||||
return pages;
|
||||
}
|
||||
|
||||
// ========= BOTÕES (placeholders) =========
|
||||
async onImportExcel() { await this.showToast('Importação via Excel será implementada.'); }
|
||||
async onCadastrarLinha() { await this.showToast('Cadastro de linha será implementado.'); }
|
||||
async onDetalhes(row: LineRow) { await this.showToast(`Detalhes: linha ${row.linha}`); }
|
||||
async onFinanceiro(row: LineRow) { await this.showToast(`Financeiro: linha ${row.linha} (em implementação)`); }
|
||||
async onRemover(row: LineRow) { await this.showToast(`Remover: linha ${row.linha} (em implementação)`); }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,62 +1,201 @@
|
|||
<section class="hero-text-section">
|
||||
<h1 class="main-title">
|
||||
<span class="first-line">Gerencie suas linhas móveis</span>
|
||||
<span class="second-line">com <strong>inteligência e praticidade</strong></span>
|
||||
</h1>
|
||||
<section class="home-page">
|
||||
|
||||
<p class="main-paragraph">
|
||||
<strong class="brand-name">LineGestão</strong> é a solução completa para empresas que
|
||||
<strong class="highlight">desejam controlar suas linhas móveis com eficiência e segurança</strong>.
|
||||
Com recursos como <strong class="highlight">gerenciamento de clientes</strong>,
|
||||
<strong class="highlight">importação de dados via Excel</strong> e
|
||||
<strong class="highlight">monitoramento estratégico de contratos e linhas</strong>,
|
||||
você <strong class="highlight">simplifica processos</strong>,
|
||||
<strong class="highlight">reduz erros</strong> e
|
||||
<strong class="highlight">ganha mais controle sobre seus recursos corporativos</strong>.
|
||||
</p>
|
||||
</section>
|
||||
<!-- BACKGROUND GLOBAL -->
|
||||
<span class="page-blob blob-1" aria-hidden="true"></span>
|
||||
<span class="page-blob blob-2" aria-hidden="true"></span>
|
||||
<span class="page-blob blob-3" aria-hidden="true"></span>
|
||||
<span class="page-blob blob-4" aria-hidden="true"></span>
|
||||
|
||||
<div class="container my-5">
|
||||
<div class="row justify-content-center feature-cards-row">
|
||||
|
||||
<div class="col-auto mb-4">
|
||||
<app-feature-card
|
||||
title="Monitoramento Completo"
|
||||
[textAlign]="'center'"
|
||||
iconClass="bi bi-laptop"
|
||||
description="<strong>Acompanhe contratos, valores e suas linhas</strong> com visão estratégica, garantindo controle total sobre seus recursos móveis."
|
||||
></app-feature-card>
|
||||
</div>
|
||||
<!-- HERO -->
|
||||
<section class="hero">
|
||||
<div class="container">
|
||||
|
||||
<div class="col-auto mb-4">
|
||||
<app-feature-card
|
||||
title="Gerenciamento de Clientes"
|
||||
[textAlign]="'center'"
|
||||
iconClass="bi bi-people"
|
||||
description="<strong>Organize e acompanhe seus clientes</strong> com praticidade e segurança, garantindo uma gestão eficiente."
|
||||
></app-feature-card>
|
||||
</div>
|
||||
<div class="hero-inner">
|
||||
|
||||
<div class="col-auto mb-4">
|
||||
<app-feature-card
|
||||
title="Importação via Excel"
|
||||
[textAlign]="'center'"
|
||||
iconClass="bi bi-table"
|
||||
description="<strong>Integre dados rapidamente</strong> sem esforço manual, substituindo planilhas por uma solução moderna e automatizada."
|
||||
></app-feature-card>
|
||||
</div>
|
||||
<!-- COLUNA TEXTO (CENTRALIZADA) -->
|
||||
<div class="hero-copy">
|
||||
|
||||
</div>
|
||||
<div class="hero-badge" data-animate>
|
||||
<i class="bi bi-stars"></i>
|
||||
SaaS para gestão de linhas corporativas
|
||||
</div>
|
||||
|
||||
<section class="hero-text-section">
|
||||
<h1 class="main-title" data-animate>
|
||||
<span class="first-line">Gerencie suas linhas móveis</span>
|
||||
<span class="second-line">com <strong>inteligência e praticidade</strong></span>
|
||||
</h1>
|
||||
|
||||
<p class="main-paragraph" data-animate>
|
||||
<strong class="brand-name">LineGestão</strong> é a solução completa para empresas que
|
||||
<strong class="highlight">desejam controlar suas linhas móveis com eficiência e segurança</strong>.
|
||||
Com recursos como <strong class="highlight">gerenciamento de clientes</strong>,
|
||||
<strong class="highlight">importação de dados via Excel</strong> e
|
||||
<strong class="highlight">monitoramento estratégico de contratos e linhas</strong>,
|
||||
você <strong class="highlight">simplifica processos</strong>,
|
||||
<strong class="highlight">reduz erros</strong> e
|
||||
<strong class="highlight">ganha mais controle sobre seus recursos corporativos</strong>.
|
||||
</p>
|
||||
|
||||
<div class="hero-actions" data-animate>
|
||||
<app-cta-button
|
||||
label="COMEÇAR AGORA"
|
||||
(clicked)="iniciar()">
|
||||
</app-cta-button>
|
||||
|
||||
<button type="button" class="cta-secondary" (click)="scrollToFeatures()">
|
||||
<i class="bi bi-arrow-down-circle"></i>
|
||||
Ver recursos
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- CARD/MOCK (fica na direita) -->
|
||||
<div class="hero-mock" data-animate aria-label="Prévia visual do painel">
|
||||
<div class="mock-card">
|
||||
<div class="mock-top">
|
||||
<span class="dot"></span><span class="dot"></span><span class="dot"></span>
|
||||
<span class="mock-title">Visão Geral</span>
|
||||
</div>
|
||||
|
||||
<div class="mock-grid">
|
||||
<div class="mock-kpi">
|
||||
<span class="kpi-label">Linhas ativas</span>
|
||||
<span class="kpi-value">128</span>
|
||||
<span class="kpi-tag"><i class="bi bi-graph-up"></i> controle</span>
|
||||
</div>
|
||||
|
||||
<div class="mock-kpi">
|
||||
<span class="kpi-label">Contratos</span>
|
||||
<span class="kpi-value">12</span>
|
||||
<span class="kpi-tag"><i class="bi bi-file-earmark-text"></i> organizado</span>
|
||||
</div>
|
||||
|
||||
<div class="mock-kpi">
|
||||
<span class="kpi-label">Clientes</span>
|
||||
<span class="kpi-value">34</span>
|
||||
<span class="kpi-tag"><i class="bi bi-people"></i> centralizado</span>
|
||||
</div>
|
||||
|
||||
<div class="mock-line">
|
||||
<div class="line-icon"><i class="bi bi-sim"></i></div>
|
||||
<div class="line-info">
|
||||
<div class="line-title">Linha 55XX9XXXXXXXX</div>
|
||||
<div class="line-sub">Status: Ativa • Operadora: Vivo</div>
|
||||
</div>
|
||||
<div class="line-pill">OK</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row justify-content-center button-section">
|
||||
<div class="col-auto">
|
||||
<app-cta-button
|
||||
label="COMEÇAR AGORA"
|
||||
(clicked)="iniciar()">
|
||||
</app-cta-button>
|
||||
</div>
|
||||
|
||||
<!-- NOVO: CENTRALIZAR OS CARDS NO MEIO -->
|
||||
<div class="hero-metrics-wide" data-animate>
|
||||
<div class="hero-metrics">
|
||||
<div class="metric">
|
||||
<i class="bi bi-lightning-charge"></i>
|
||||
<div>
|
||||
<span class="metric-title">Setup rápido</span>
|
||||
<span class="metric-sub">comece em minutos</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="metric">
|
||||
<i class="bi bi-file-earmark-spreadsheet"></i>
|
||||
<div>
|
||||
<span class="metric-title">Excel → Sistema</span>
|
||||
<span class="metric-sub">importação inteligente</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="metric">
|
||||
<i class="bi bi-shield-check"></i>
|
||||
<div>
|
||||
<span class="metric-title">Mais segurança</span>
|
||||
<span class="metric-sub">menos erro manual</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
</div>
|
||||
<!-- FEATURES -->
|
||||
<section id="features" class="features-section">
|
||||
<div class="container my-5">
|
||||
|
||||
<div class="section-head" data-animate>
|
||||
<h2 class="section-title">
|
||||
Tudo o que você precisa para <span class="brand">gestão de linhas</span>
|
||||
</h2>
|
||||
<p class="section-subtitle">
|
||||
Um painel simples, bonito e direto ao ponto — feito para empresa que quer controle.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- NOVO: Centralizar os cards -->
|
||||
<div class="row justify-content-center feature-cards-row">
|
||||
|
||||
<div class="col-auto mb-4 feature-item" data-animate>
|
||||
<app-feature-card
|
||||
title="Monitoramento Completo"
|
||||
[textAlign]="'center'"
|
||||
iconClass="bi bi-laptop"
|
||||
description="<strong>Acompanhe contratos, valores e suas linhas</strong> com visão estratégica, garantindo controle total sobre seus recursos móveis."
|
||||
></app-feature-card>
|
||||
</div>
|
||||
|
||||
<div class="col-auto mb-4 feature-item" data-animate>
|
||||
<app-feature-card
|
||||
title="Gerenciamento de Clientes"
|
||||
[textAlign]="'center'"
|
||||
iconClass="bi bi-people"
|
||||
description="<strong>Organize e acompanhe seus clientes</strong> com praticidade e segurança, garantindo uma gestão eficiente."
|
||||
></app-feature-card>
|
||||
</div>
|
||||
|
||||
<div class="col-auto mb-4 feature-item" data-animate>
|
||||
<app-feature-card
|
||||
title="Importação via Excel"
|
||||
[textAlign]="'center'"
|
||||
iconClass="bi bi-table"
|
||||
description="<strong>Integre dados rapidamente</strong> sem esforço manual, substituindo planilhas por uma solução moderna e automatizada."
|
||||
></app-feature-card>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row justify-content-center button-section" data-animate>
|
||||
<div class="col-auto">
|
||||
<app-cta-button
|
||||
label="COMEÇAR AGORA"
|
||||
(clicked)="iniciar()">
|
||||
</app-cta-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="value-strip" data-animate>
|
||||
<div class="value">
|
||||
<i class="bi bi-check2-circle"></i>
|
||||
<span><strong>Menos planilha</strong>, mais controle.</span>
|
||||
</div>
|
||||
<div class="value">
|
||||
<i class="bi bi-check2-circle"></i>
|
||||
<span><strong>Mais agilidade</strong> no dia a dia.</span>
|
||||
</div>
|
||||
<div class="value">
|
||||
<i class="bi bi-check2-circle"></i>
|
||||
<span><strong>Mais segurança</strong> na gestão.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -1,136 +1,544 @@
|
|||
/* =============================== */
|
||||
/* CONTAINER PRINCIPAL */
|
||||
/* =============================== */
|
||||
.hero-text-section {
|
||||
text-align: center;
|
||||
padding-top: 50px;
|
||||
margin: 0 auto;
|
||||
:host {
|
||||
--brand: #E33DCF;
|
||||
--brand-soft: rgba(227, 61, 207, 0.14);
|
||||
--brand-soft-2: rgba(227, 61, 207, 0.08);
|
||||
|
||||
--text: #111214;
|
||||
--muted: rgba(17, 18, 20, 0.70);
|
||||
|
||||
--radius-xl: 22px;
|
||||
--radius-lg: 16px;
|
||||
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* ✅ FUNDO GLOBAL (vale para a Home TODA até o footer) */
|
||||
.home-page {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
overflow: hidden;
|
||||
|
||||
background:
|
||||
radial-gradient(900px 420px at 20% 10%, var(--brand-soft), transparent 60%),
|
||||
radial-gradient(820px 380px at 80% 30%, var(--brand-soft-2), transparent 60%),
|
||||
linear-gradient(180deg, #ffffff 0%, #f5f5f7 70%);
|
||||
|
||||
/* névoa suave pra dar sensação de blur */
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
}
|
||||
|
||||
/* ✅ BLOBS FIXOS (continuam no scroll) */
|
||||
.page-blob {
|
||||
position: fixed;
|
||||
pointer-events: none;
|
||||
border-radius: 999px;
|
||||
filter: blur(34px);
|
||||
opacity: 0.55;
|
||||
z-index: 0;
|
||||
|
||||
background: radial-gradient(circle at 30% 30%, rgba(227,61,207,0.55), rgba(227,61,207,0.06));
|
||||
animation: floaty 10s ease-in-out infinite;
|
||||
|
||||
&.blob-1 { width: 420px; height: 420px; top: -140px; left: -140px; }
|
||||
&.blob-2 { width: 520px; height: 520px; top: -220px; right: -240px; animation-duration: 12s; }
|
||||
&.blob-3 { width: 360px; height: 360px; bottom: -180px; left: 25%; animation-duration: 14s; }
|
||||
&.blob-4 { width: 520px; height: 520px; bottom: -260px; right: -260px; animation-duration: 16s; opacity: .45; }
|
||||
|
||||
@media (max-width: 992px) {
|
||||
opacity: 0.35;
|
||||
}
|
||||
}
|
||||
|
||||
/* ✅ garante que o conteúdo fique acima do fundo */
|
||||
.hero,
|
||||
.features-section,
|
||||
.container {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* =============================== */
|
||||
/* TÍTULO PRINCIPAL */
|
||||
/* HERO */
|
||||
/* =============================== */
|
||||
.hero {
|
||||
padding: 56px 0 18px 0;
|
||||
}
|
||||
|
||||
.hero-inner {
|
||||
display: grid;
|
||||
grid-template-columns: 1.1fr 0.9fr;
|
||||
gap: 36px;
|
||||
align-items: center;
|
||||
|
||||
@media (max-width: 992px) {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 26px;
|
||||
}
|
||||
}
|
||||
|
||||
/* texto centralizado */
|
||||
.hero-copy {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* badge */
|
||||
.hero-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
||||
width: fit-content;
|
||||
padding: 10px 14px;
|
||||
border-radius: 999px;
|
||||
|
||||
background: rgba(255, 255, 255, 0.78);
|
||||
border: 1px solid rgba(227, 61, 207, 0.22);
|
||||
backdrop-filter: blur(10px);
|
||||
|
||||
color: var(--text);
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
|
||||
i { color: var(--brand); font-size: 16px; }
|
||||
}
|
||||
|
||||
.hero-text-section {
|
||||
width: 100%;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
/* título */
|
||||
.main-title {
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-size: 48px;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 30px;
|
||||
font-size: 52px;
|
||||
line-height: 1.05;
|
||||
margin: 18px 0 18px 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
width: fit-content;
|
||||
margin: 0 auto 80px auto;
|
||||
|
||||
/* ========== RESPONSIVO ========== */
|
||||
|
||||
/* até 1400px diminui um pouco */
|
||||
@media (max-width: 1400px) {
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
/* tablets e notebooks pequenos */
|
||||
@media (max-width: 1024px) {
|
||||
font-size: 36px;
|
||||
}
|
||||
|
||||
/* tablets menores */
|
||||
@media (max-width: 768px) {
|
||||
font-size: 32px;
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
/* celulares */
|
||||
@media (max-width: 480px) {
|
||||
font-size: 28px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
@media (max-width: 1400px) { font-size: 44px; }
|
||||
@media (max-width: 1024px) { font-size: 38px; }
|
||||
@media (max-width: 768px) { font-size: 32px; }
|
||||
@media (max-width: 480px) { font-size: 28px; }
|
||||
}
|
||||
|
||||
/* Primeira linha */
|
||||
.main-title .first-line {
|
||||
color: #E33DCF;
|
||||
font-weight: 500;
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Segunda linha */
|
||||
.main-title .first-line,
|
||||
.main-title .second-line {
|
||||
color: #E33DCF;
|
||||
font-weight: 500;
|
||||
font-weight: 650;
|
||||
display: block;
|
||||
color: var(--text);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* -------- Efeito deslocado só em telas grandes -------- */
|
||||
@media (min-width: 1200px) {
|
||||
.main-title .first-line {
|
||||
transform: translateX(-70px);
|
||||
}
|
||||
|
||||
.main-title .second-line {
|
||||
transform: translateX(100px) translateY(-6px);
|
||||
}
|
||||
.main-title strong {
|
||||
color: var(--brand);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Em telas menores que 1200px o título fica plenamente centralizado */
|
||||
@media (max-width: 1199.98px) {
|
||||
.main-title .first-line,
|
||||
.main-title .second-line {
|
||||
transform: none; /* garante que não herda nenhum translate */
|
||||
}
|
||||
.main-title strong::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 6px;
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
border-radius: 999px;
|
||||
background: rgba(227, 61, 207, 0.18);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
/* =============================== */
|
||||
/* PARÁGRAFO PRINCIPAL */
|
||||
/* =============================== */
|
||||
/* parágrafo */
|
||||
.main-paragraph {
|
||||
width: 985px;
|
||||
height: 132px;
|
||||
margin: 0 auto 50px auto;
|
||||
width: min(980px, 100%);
|
||||
margin: 0 auto 16px auto;
|
||||
|
||||
font-family: 'Poppins', sans-serif;
|
||||
font-size: 24px;
|
||||
color: #000000;
|
||||
line-height: 1;
|
||||
font-weight: 400;
|
||||
font-size: 20px;
|
||||
color: var(--muted);
|
||||
line-height: 1.45;
|
||||
|
||||
@media (max-width: 1400px) { font-size: 19px; }
|
||||
@media (max-width: 1024px) { font-size: 18px; }
|
||||
@media (max-width: 768px) { font-size: 16px; }
|
||||
}
|
||||
|
||||
.main-paragraph .brand-name { color: var(--text); }
|
||||
.main-paragraph .highlight { color: var(--text); font-weight: 800; }
|
||||
.main-paragraph strong { font-weight: 800; }
|
||||
|
||||
/* botões */
|
||||
.hero-actions {
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.cta-secondary {
|
||||
height: 44px;
|
||||
padding: 0 14px;
|
||||
border-radius: 12px;
|
||||
background: rgba(255, 255, 255, 0.72);
|
||||
border: 1px solid rgba(17, 18, 20, 0.10);
|
||||
color: var(--text);
|
||||
font-weight: 800;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
transition: transform 180ms ease, border-color 180ms ease, box-shadow 180ms ease;
|
||||
|
||||
i { color: var(--brand); }
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
border-color: rgba(227, 61, 207, 0.28);
|
||||
box-shadow: 0 12px 24px rgba(17, 18, 20, 0.10);
|
||||
}
|
||||
}
|
||||
|
||||
/* =============================== */
|
||||
/* MOCK (direita) */
|
||||
/* =============================== */
|
||||
.hero-mock {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
@media (max-width: 992px) {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.mock-card {
|
||||
width: min(460px, 100%);
|
||||
border-radius: var(--radius-xl);
|
||||
background: rgba(255, 255, 255, 0.78);
|
||||
border: 1px solid rgba(227, 61, 207, 0.14);
|
||||
backdrop-filter: blur(12px);
|
||||
box-shadow: 0 22px 46px rgba(17, 18, 20, 0.10);
|
||||
overflow: hidden;
|
||||
|
||||
transform: perspective(900px) rotateY(-6deg) rotateX(2deg);
|
||||
transition: transform 200ms ease;
|
||||
|
||||
&:hover {
|
||||
transform: perspective(900px) rotateY(-2deg) rotateX(1deg) translateY(-2px);
|
||||
}
|
||||
}
|
||||
|
||||
.mock-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 14px 16px;
|
||||
background: linear-gradient(180deg, rgba(227, 61, 207, 0.10), rgba(255, 255, 255, 0.20));
|
||||
|
||||
.dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 999px;
|
||||
background: rgba(17, 18, 20, 0.12);
|
||||
}
|
||||
|
||||
.mock-title {
|
||||
margin-left: 6px;
|
||||
font-weight: 950;
|
||||
font-family: 'Inter', sans-serif;
|
||||
color: var(--text);
|
||||
}
|
||||
}
|
||||
|
||||
.mock-grid {
|
||||
padding: 16px;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px;
|
||||
|
||||
@media (max-width: 380px) { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
.mock-kpi {
|
||||
border-radius: var(--radius-lg);
|
||||
background: #fff;
|
||||
border: 1px solid rgba(17, 18, 20, 0.08);
|
||||
padding: 12px;
|
||||
|
||||
.kpi-label {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: rgba(17, 18, 20, 0.65);
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.kpi-value {
|
||||
display: block;
|
||||
font-size: 26px;
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-weight: 950;
|
||||
color: var(--text);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.kpi-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-top: 8px;
|
||||
padding: 6px 10px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(227, 61, 207, 0.20);
|
||||
background: rgba(227, 61, 207, 0.07);
|
||||
color: var(--text);
|
||||
font-size: 12px;
|
||||
font-weight: 900;
|
||||
|
||||
i { color: var(--brand); }
|
||||
}
|
||||
}
|
||||
|
||||
.mock-line {
|
||||
grid-column: 1 / -1;
|
||||
border-radius: var(--radius-lg);
|
||||
background: #fff;
|
||||
border: 1px solid rgba(17, 18, 20, 0.08);
|
||||
padding: 12px;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
|
||||
.line-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 12px;
|
||||
background: rgba(227, 61, 207, 0.10);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
|
||||
i { color: var(--brand); font-size: 18px; }
|
||||
}
|
||||
|
||||
.line-title {
|
||||
font-weight: 950;
|
||||
font-family: 'Inter', sans-serif;
|
||||
color: var(--text);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.line-sub {
|
||||
color: rgba(17, 18, 20, 0.65);
|
||||
font-size: 12px;
|
||||
margin-top: 2px;
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
.line-pill {
|
||||
padding: 8px 10px;
|
||||
border-radius: 999px;
|
||||
font-weight: 950;
|
||||
font-size: 12px;
|
||||
background: rgba(227, 61, 207, 0.10);
|
||||
border: 1px solid rgba(227, 61, 207, 0.22);
|
||||
color: var(--text);
|
||||
}
|
||||
}
|
||||
|
||||
/* =============================== */
|
||||
/* MÉTRICAS CENTRALIZADAS (MEIO) */
|
||||
/* =============================== */
|
||||
.hero-metrics-wide {
|
||||
margin-top: 18px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.hero-metrics {
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.metric {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 14px;
|
||||
border-radius: 14px;
|
||||
background: rgba(255, 255, 255, 0.78);
|
||||
border: 1px solid rgba(227, 61, 207, 0.16);
|
||||
backdrop-filter: blur(10px);
|
||||
|
||||
i { color: var(--brand); font-size: 18px; }
|
||||
|
||||
.metric-title {
|
||||
display: block;
|
||||
font-weight: 900;
|
||||
color: var(--text);
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.metric-sub {
|
||||
display: block;
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-size: 12px;
|
||||
color: rgba(17, 18, 20, 0.65);
|
||||
}
|
||||
}
|
||||
|
||||
/* =============================== */
|
||||
/* FEATURES */
|
||||
/* =============================== */
|
||||
.features-section {
|
||||
padding: 18px 0 60px 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.section-head {
|
||||
text-align: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
/* ========== RESPONSIVO ========== */
|
||||
.section-title {
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-weight: 950;
|
||||
color: var(--text);
|
||||
font-size: 30px;
|
||||
|
||||
@media (max-width: 1400px) {
|
||||
width: 65%;
|
||||
height: auto;
|
||||
font-size: 20px;
|
||||
@media (max-width: 768px) { font-size: 24px; }
|
||||
}
|
||||
|
||||
.section-title .brand { color: var(--brand); }
|
||||
|
||||
.section-subtitle {
|
||||
margin-top: 10px;
|
||||
color: var(--muted);
|
||||
font-family: 'Poppins', sans-serif;
|
||||
}
|
||||
|
||||
/* ✅ AQUI: 3 CARDS CENTRALIZADOS LADO A LADO NO NOTEBOOK */
|
||||
.feature-cards-row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: stretch;
|
||||
gap: 26px;
|
||||
flex-wrap: nowrap; /* ✅ força ficar em 1 linha */
|
||||
|
||||
/* notebook / telas médias */
|
||||
@media (max-width: 1199.98px) {
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
/* Tablets */
|
||||
@media (max-width: 1024px) {
|
||||
width: 70%;
|
||||
height: auto;
|
||||
font-size: 20px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* Tablets pequenos */
|
||||
@media (max-width: 768px) {
|
||||
width: 80%;
|
||||
font-size: 18px;
|
||||
line-height: 1.25;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
/* Celulares */
|
||||
@media (max-width: 480px) {
|
||||
width: 90%;
|
||||
font-size: 16px;
|
||||
line-height: 1.3;
|
||||
margin-bottom: 30px;
|
||||
/* quando a tela ficar pequena de verdade, aí quebra */
|
||||
@media (max-width: 992px) {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.main-paragraph strong {
|
||||
font-weight: 700;
|
||||
/* Garante que o col-auto do bootstrap não atrapalhe */
|
||||
.feature-item {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: stretch;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
/* melhora o respiro no botão */
|
||||
.button-section {
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
/* faixa de valores */
|
||||
.value-strip {
|
||||
margin-top: 26px;
|
||||
padding: 14px 16px;
|
||||
border-radius: var(--radius-xl);
|
||||
background: rgba(255, 255, 255, 0.80);
|
||||
border: 1px solid rgba(227, 61, 207, 0.14);
|
||||
backdrop-filter: blur(10px);
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 18px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.value {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
||||
i { color: var(--brand); font-size: 18px; }
|
||||
span { color: var(--text); font-family: 'Inter', sans-serif; }
|
||||
}
|
||||
}
|
||||
|
||||
/* =============================== */
|
||||
/* ✅ ANIMAÇÕES SSR-SAFE */
|
||||
/* =============================== */
|
||||
[data-animate] { opacity: 1; transform: none; }
|
||||
|
||||
.js-animate [data-animate] {
|
||||
opacity: 0;
|
||||
transform: translateY(14px);
|
||||
transition: opacity 600ms ease, transform 600ms ease;
|
||||
will-change: opacity, transform;
|
||||
}
|
||||
|
||||
.js-animate [data-animate].is-visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* brilho suave no botão */
|
||||
:host ::ng-deep app-cta-button .btn,
|
||||
:host ::ng-deep app-cta-button button {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
:host ::ng-deep app-cta-button .btn::after,
|
||||
:host ::ng-deep app-cta-button button::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
transform: translateX(-120%) rotate(12deg);
|
||||
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.55), transparent);
|
||||
animation: shine 3.4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.page-blob { animation: none; }
|
||||
.js-animate [data-animate] { transition: none; transform: none; opacity: 1; }
|
||||
:host ::ng-deep app-cta-button .btn::after,
|
||||
:host ::ng-deep app-cta-button button::after { animation: none; }
|
||||
}
|
||||
|
||||
@keyframes floaty {
|
||||
0% { transform: translate(0, 0) scale(1); }
|
||||
50% { transform: translate(18px, 10px) scale(1.03); }
|
||||
100% { transform: translate(0, 0) scale(1); }
|
||||
}
|
||||
|
||||
@keyframes shine {
|
||||
0%, 65% { transform: translateX(-120%) rotate(12deg); opacity: 0.0; }
|
||||
75% { opacity: 1; }
|
||||
100% { transform: translateX(120%) rotate(12deg); opacity: 0.0; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,68 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, AfterViewInit, Inject, PLATFORM_ID } from '@angular/core';
|
||||
import { CommonModule, isPlatformBrowser } from '@angular/common';
|
||||
import { FeatureCardComponent } from '../../components/feature-card/feature-card';
|
||||
import { CtaButtonComponent } from '../../components/cta-button/cta-button';
|
||||
import { Router } from '@angular/router'; // ⬅️ IMPORT CERTO (ANGULAR)
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-home',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
FeatureCardComponent,
|
||||
CtaButtonComponent
|
||||
],
|
||||
imports: [CommonModule, FeatureCardComponent, CtaButtonComponent],
|
||||
templateUrl: './home.html',
|
||||
styleUrls: ['./home.scss'], // ⬅️ styleUrls (array)
|
||||
styleUrls: ['./home.scss'],
|
||||
})
|
||||
export class Home {
|
||||
export class Home implements AfterViewInit {
|
||||
private readonly isBrowser: boolean;
|
||||
|
||||
// ⬅️ Aqui o Angular injeta o Router e guarda em this.router
|
||||
constructor(private router: Router) {}
|
||||
constructor(
|
||||
private router: Router,
|
||||
@Inject(PLATFORM_ID) platformId: Object
|
||||
) {
|
||||
this.isBrowser = isPlatformBrowser(platformId);
|
||||
}
|
||||
|
||||
iniciar(): void {
|
||||
// só pra debug, se quiser:
|
||||
// console.log('Botão COMEÇAR AGORA clicado! Redirecionando para /register...');
|
||||
this.router.navigate(['/register']);
|
||||
}
|
||||
|
||||
this.router.navigate(['/register']); // ⬅️ agora this.router não é mais undefined
|
||||
scrollToFeatures(): void {
|
||||
if (!this.isBrowser) return;
|
||||
document.getElementById('features')?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
if (!this.isBrowser) return;
|
||||
|
||||
document.documentElement.classList.add('js-animate');
|
||||
|
||||
setTimeout(() => {
|
||||
const items = Array.from(document.querySelectorAll<HTMLElement>('[data-animate]'));
|
||||
if (!items.length) return;
|
||||
|
||||
const reduceMotion =
|
||||
window.matchMedia &&
|
||||
window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
|
||||
if (reduceMotion) {
|
||||
items.forEach(i => i.classList.add('is-visible'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!('IntersectionObserver' in window)) {
|
||||
items.forEach(i => i.classList.add('is-visible'));
|
||||
return;
|
||||
}
|
||||
|
||||
const io = new IntersectionObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
if (entry.isIntersecting) {
|
||||
(entry.target as HTMLElement).classList.add('is-visible');
|
||||
io.unobserve(entry.target);
|
||||
}
|
||||
}
|
||||
}, { threshold: 0.12 });
|
||||
|
||||
items.forEach(el => io.observe(el));
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,15 +77,15 @@ export class LoginComponent {
|
|||
};
|
||||
|
||||
this.authService.login(payload).subscribe({
|
||||
next: async (res) => {
|
||||
next: (res) => {
|
||||
this.isSubmitting = false;
|
||||
|
||||
const nome = this.getNameFromToken(res.token);
|
||||
await this.showToast(`Bem-vindo, ${nome}!`);
|
||||
|
||||
setTimeout(() => {
|
||||
this.router.navigate(['/geral']);
|
||||
}, 900);
|
||||
// ✅ Vai para /geral já levando a mensagem do toast
|
||||
this.router.navigate(['/geral'], {
|
||||
state: { toastMessage: `Bem-vindo, ${nome}!` }
|
||||
});
|
||||
},
|
||||
error: (err) => {
|
||||
this.isSubmitting = false;
|
||||
|
|
|
|||
Loading…
Reference in New Issue