Nesse tutorial vamos criar a página do seu portfólio para apresentar seus projetos:
1- Acesse a sua hospedagem Infinityfree e clique em “Manager”
2- Acesse “Flie Manager”
3- Dentro da pasta raiz “Htdocs” crie as pastas img e avatar, deixe essas pastas vazias serão usadas para guardas as imagens.
4- Ainda dentro de Htdocs, cries os outros arquivos conforme a estrutura abaixo:
htdocs/ ├── avatar/ ← (nova pasta)Foto de perfil do aluno ├── img/ ← (nova pasta) Imagens dos projetos ├── index.php ← (arquivo) Portfólio público ├── style.css ← (arquivo) Estilo dinâmico (usa as cores salvas) ├── config.php ← (arquivo) Configuração + funções ├── dados.json ← (arquivo) Dados do aluno + projetos (gerado) ├── login.php ← (arquivo) Login do dashboard ├── logout.php ← (arquivo) Sair ├── dashboard.php ← (arquivo) Painel principal ├── projeto_form.php ← (arquivo) Adicionar/editar projeto ├── projeto_excluir.php ← (arquivo) Excluir projeto ├── perfil.php ← (arquivo) Editar dados do aluno ├── admin.css ← (arquivo) Estilo do dashboard
Definir a senha de acesso
No arquivo config.php contém a senha. Padrão: admin123 (pode trocar).
Arquivo 1: config.php
<?php
// config.php - Configuração geral + funções
// ==========================================================
// SENHA DO DASHBOARD (altere para a sua)
// ==========================================================
define('SENHA_DASHBOARD', 'admin123');
// Caminhos
define('ARQ_DADOS', __DIR__ . '/dados.json');
define('UPLOAD_IMG_DIR', __DIR__ . '/img/');
define('UPLOAD_AVATAR_DIR', __DIR__ . '/avatar/');
define('UPLOAD_IMG_URL', 'img/');
define('UPLOAD_AVATAR_URL', 'avatar/');
// Inicia sessão
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// ==========================================================
// CARREGA DADOS
// ==========================================================
function carregarDados() {
if (!file_exists(ARQ_DADOS)) {
// Dados padrão na primeira execução
return [
'perfil' => [
'nome' => 'Seu Nome Aqui',
'turma' => '2º TEC 01',
'bio' => 'Estudante do curso Técnico em Desenvolvimento de Sistemas.',
'avatar' => '',
'email' => '',
'github' => '',
'linkedin' => ''
],
'tema' => [
'cor_primaria' => '#0f2b46',
'cor_secundaria'=> '#c9a227',
'cor_fundo' => '#f4f6f9'
],
'projetos' => []
];
}
$conteudo = file_get_contents(ARQ_DADOS);
$dados = json_decode($conteudo, true);
// Se der erro no JSON, retorna padrão
if (!is_array($dados)) {
return [
'perfil' => ['nome' => 'Aluno', 'turma' => '', 'bio' => '', 'avatar' => '', 'email' => '', 'github' => '', 'linkedin' => ''],
'tema' => ['cor_primaria' => '#0f2b46', 'cor_secundaria' => '#c9a227', 'cor_fundo' => '#f4f6f9'],
'projetos' => []
];
}
// Garante que as chaves existem
$dados['perfil'] = $dados['perfil'] ?? [];
$dados['tema'] = $dados['tema'] ?? ['cor_primaria' => '#0f2b46', 'cor_secundaria' => '#c9a227', 'cor_fundo' => '#f4f6f9'];
$dados['projetos'] = $dados['projetos'] ?? [];
return $dados;
}
function salvarDados($dados) {
file_put_contents(
ARQ_DADOS,
json_encode($dados, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE),
LOCK_EX
);
}
// ==========================================================
// AUTENTICAÇÃO
// ==========================================================
function estaLogado() {
return isset($_SESSION['admin_logado']) && $_SESSION['admin_logado'] === true;
}
function exigirLogin() {
if (!estaLogado()) {
header('Location: login.php');
exit;
}
}
// ==========================================================
// UPLOAD
// ==========================================================
function fazerUpload($arquivo, $dir, $prefixo) {
if (empty($arquivo['name']) || $arquivo['error'] !== UPLOAD_ERR_OK) {
return ['ok' => false, 'msg' => 'Nenhum arquivo enviado'];
}
$ext = strtolower(pathinfo($arquivo['name'], PATHINFO_EXTENSION));
$permitidas = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'];
if (!in_array($ext, $permitidas)) {
return ['ok' => false, 'msg' => 'Formato inválido. Use JPG, PNG, GIF, WEBP ou SVG.'];
}
if ($arquivo['size'] > 5 * 1024 * 1024) {
return ['ok' => false, 'msg' => 'Imagem muito grande (máx. 5MB).'];
}
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
$nome = uniqid($prefixo) . '.' . $ext;
$caminho = $dir . $nome;
if (!move_uploaded_file($arquivo['tmp_name'], $caminho)) {
return ['ok' => false, 'msg' => 'Erro ao salvar arquivo.'];
}
return ['ok' => true, 'nome' => $nome];
}
// ==========================================================
// GERA SLUG (nome de pasta amigável)
// ==========================================================
function gerarSlug($texto) {
$texto = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $texto);
$texto = strtolower(trim($texto));
$texto = preg_replace('/[^a-z0-9]+/', '-', $texto);
return trim($texto, '-');
}
?>
Arquivo 2: login.php
<?php
require_once 'config.php';
if (estaLogado()) {
header('Location: dashboard.php');
exit;
}
$erro = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$senha = $_POST['senha'] ?? '';
if ($senha === SENHA_DASHBOARD) {
$_SESSION['admin_logado'] = true;
header('Location: dashboard.php');
exit;
} else {
$erro = 'Senha incorreta';
}
}
?>
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<title>Login - Gerenciar Portfólio</title>
<link rel="stylesheet" href="admin.css">
</head>
<body class="login-body">
<div class="login-card">
<div class="login-icon">🔐</div>
<h1>Acesso Restrito</h1>
<p class="login-sub">Área de gerenciamento do portfólio</p>
<?php if ($erro): ?>
<div class="alert alert-error">⚠️ <?php echo htmlspecialchars($erro); ?></div>
<?php endif; ?>
<form method="POST">
<div class="form-group">
<label>Senha</label>
<input type="password" name="senha" required autofocus>
</div>
<button type="submit" class="btn-full">🔓 Entrar</button>
</form>
<a href="index.php" class="login-back">← Voltar ao portfólio</a>
</div>
</body>
</html>
Arquivo 3: logout.php
<?php
require_once 'config.php';
session_destroy();
header('Location: index.php');
exit;
?>
Arquivo 4: dashboard.php
<?php
require_once 'config.php';
exigirLogin();
$dados = carregarDados();
$projetos = $dados['projetos'];
$perfil = $dados['perfil'];
$tema = $dados['tema'];
$msg = $_GET['msg'] ?? '';
?>
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard - Meu Portfólio</title>
<link rel="stylesheet" href="admin.css">
</head>
<body>
<header class="topbar">
<div class="topbar-content">
<div class="topbar-brand">
<span>🎨</span>
<div>
<strong>Meu Portfólio</strong>
<small>Gerenciamento</small>
</div>
</div>
<nav class="topbar-nav">
<a href="index.php" target="_blank">👁️ Ver Portfólio</a>
<a href="perfil.php">👤 Perfil</a>
<a href="logout.php" class="btn-sair">🚪 Sair</a>
</nav>
</div>
</header>
<main class="container">
<?php if ($msg === 'add'): ?>
<div class="alert alert-success">✅ Projeto adicionado com sucesso!</div>
<?php elseif ($msg === 'edit'): ?>
<div class="alert alert-success">✅ Projeto atualizado!</div>
<?php elseif ($msg === 'del'): ?>
<div class="alert alert-success">🗑️ Projeto excluído.</div>
<?php elseif ($msg === 'perfil'): ?>
<div class="alert alert-success">✅ Perfil atualizado!</div>
<?php endif; ?>
<!-- Info do perfil -->
<section class="perfil-resumo">
<div class="perfil-avatar">
<?php if (!empty($perfil['avatar']) && file_exists(UPLOAD_AVATAR_DIR . $perfil['avatar'])): ?>
<img src="<?php echo UPLOAD_AVATAR_URL . htmlspecialchars($perfil['avatar']); ?>" alt="Avatar">
<?php else: ?>
<span><?php echo strtoupper(substr($perfil['nome'] ?? 'A', 0, 1)); ?></span>
<?php endif; ?>
</div>
<div class="perfil-info">
<h2><?php echo htmlspecialchars($perfil['nome'] ?? 'Seu Nome'); ?></h2>
<p><?php echo htmlspecialchars($perfil['turma'] ?? ''); ?></p>
</div>
<a href="perfil.php" class="btn-perfil">✏️ Editar Perfil</a>
</section>
<!-- Cabeçalho da lista -->
<div class="lista-header">
<div>
<h1>Meus Projetos</h1>
<p class="lista-sub"><?php echo count($projetos); ?> projeto(s) cadastrado(s)</p>
</div>
<a href="projeto_form.php" class="btn-novo">➕ Adicionar Projeto</a>
</div>
<!-- Lista -->
<?php if (empty($projetos)): ?>
<div class="empty-state">
<span class="empty-icon">📭</span>
<h2>Você ainda não tem projetos</h2>
<p>Adicione seu primeiro projeto para exibir no portfólio.</p>
<a href="projeto_form.php" class="btn-novo">➕ Adicionar Projeto</a>
</div>
<?php else: ?>
<div class="grid-projetos">
<?php foreach ($projetos as $p): ?>
<div class="card-admin">
<div class="card-img">
<?php if (!empty($p['imagem']) && file_exists(UPLOAD_IMG_DIR . $p['imagem'])): ?>
<img src="<?php echo UPLOAD_IMG_URL . htmlspecialchars($p['imagem']); ?>" alt="">
<?php else: ?>
<div class="card-noimg">💻</div>
<?php endif; ?>
</div>
<div class="card-body">
<h3><?php echo htmlspecialchars($p['titulo']); ?></h3>
<?php if (!empty($p['descricao'])): ?>
<p class="card-desc">
<?php echo htmlspecialchars(mb_strimwidth($p['descricao'], 0, 100, '...')); ?>
</p>
<?php endif; ?>
<div class="card-actions">
<a href="projeto_form.php?id=<?php echo $p['id']; ?>" class="btn-mini btn-edit">✏️</a>
<a href="projeto_excluir.php?id=<?php echo $p['id']; ?>"
class="btn-mini btn-del"
onclick="return confirm('Excluir este projeto?')">🗑️</a>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</main>
</body>
</html>
Arquivo 5: projeto_form.php (Adicionar/Editar)
<?php
require_once 'config.php';
exigirLogin();
$dados = carregarDados();
$erros = [];
$editando = false;
$id = isset($_GET['id']) ? trim($_GET['id']) : '';
// Dados iniciais do formulário
$projeto = [
'id' => '',
'titulo' => '',
'descricao' => '',
'imagem' => '',
'link' => '',
'tecnologias' => '',
'pasta' => ''
];
// Se está editando, carrega o projeto
if ($id !== '') {
foreach ($dados['projetos'] as $p) {
if ($p['id'] === $id) {
$projeto = $p;
$editando = true;
break;
}
}
if (!$editando) {
header('Location: dashboard.php');
exit;
}
}
// Processa o formulário
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$projeto['titulo'] = trim($_POST['titulo'] ?? '');
$projeto['descricao'] = trim($_POST['descricao'] ?? '');
$projeto['link'] = trim($_POST['link'] ?? '');
$projeto['tecnologias'] = trim($_POST['tecnologias'] ?? '');
$projeto['pasta'] = trim($_POST['pasta'] ?? '');
if (strlen($projeto['titulo']) < 3) $erros[] = 'Título muito curto';
if (empty($projeto['link']) || !filter_var($projeto['link'], FILTER_VALIDATE_URL)) {
$erros[] = 'Link inválido (comece com http:// ou https://)';
}
if (empty($projeto['pasta'])) {
$erros[] = 'Informe a pasta do projeto (ex: conversor)';
} elseif (!preg_match('/^[a-zA-Z0-9_\-]+$/', $projeto['pasta'])) {
$erros[] = 'Nome da pasta só pode ter letras, números, _ e -';
}
// Upload de imagem (opcional)
$nova_imagem = null;
if (!empty($_FILES['imagem']['name'])) {
$resultado = fazerUpload($_FILES['imagem'], UPLOAD_IMG_DIR, 'proj_');
if ($resultado['ok']) {
$nova_imagem = $resultado['nome'];
} else {
$erros[] = $resultado['msg'];
}
}
if (empty($erros)) {
// Remove imagem antiga se trocou
if ($nova_imagem && !empty($projeto['imagem']) && file_exists(UPLOAD_IMG_DIR . $projeto['imagem'])) {
unlink(UPLOAD_IMG_DIR . $projeto['imagem']);
}
$imagem_final = $nova_imagem ?: $projeto['imagem'];
if ($editando) {
// Atualiza o projeto existente (comparação por string)
foreach ($dados['projetos'] as &$p) {
if ($p['id'] === $id) {
$p['titulo'] = $projeto['titulo'];
$p['descricao'] = $projeto['descricao'];
$p['link'] = $projeto['link'];
$p['tecnologias'] = $projeto['tecnologias'];
$p['pasta'] = $projeto['pasta'];
$p['imagem'] = $imagem_final;
break;
}
}
unset($p);
salvarDados($dados);
header('Location: dashboard.php?msg=edit');
exit;
} else {
// Adiciona novo projeto
$dados['projetos'][] = [
'id' => uniqid(),
'titulo' => $projeto['titulo'],
'descricao' => $projeto['descricao'],
'link' => $projeto['link'],
'tecnologias' => $projeto['tecnologias'],
'pasta' => $projeto['pasta'],
'imagem' => $imagem_final
];
salvarDados($dados);
header('Location: dashboard.php?msg=add');
exit;
}
}
}
?>
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<title><?php echo $editando ? 'Editar' : 'Novo'; ?> Projeto</title>
<link rel="stylesheet" href="admin.css">
</head>
<body>
<header class="topbar">
<div class="topbar-content">
<div class="topbar-brand">
<span><?php echo $editando ? '✏️' : '➕'; ?></span>
<div>
<strong><?php echo $editando ? 'Editar' : 'Novo'; ?> Projeto</strong>
<small>Gerenciar portfólio</small>
</div>
</div>
<nav class="topbar-nav">
<a href="dashboard.php">← Voltar</a>
</nav>
</div>
</header>
<main class="container container-narrow">
<?php if (!empty($erros)): ?>
<div class="alert alert-error">
<?php foreach ($erros as $e): ?>
<div>⚠️ <?php echo htmlspecialchars($e); ?></div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<form method="POST" enctype="multipart/form-data" class="form-card">
<div class="form-group">
<label>📌 Título do projeto *</label>
<input type="text" name="titulo" required maxlength="150"
value="<?php echo htmlspecialchars($projeto['titulo']); ?>"
placeholder="Ex: Sistema de Conversor de Moedas">
</div>
<div class="form-group">
<label>📁 Nome da pasta no servidor *</label>
<input type="text" name="pasta" required maxlength="80"
value="<?php echo htmlspecialchars($projeto['pasta']); ?>"
placeholder="Ex: conversor">
<small>O nome exato da pasta onde está o projeto. Sem barras, sem espaços.</small>
</div>
<div class="form-group">
<label>📝 Descrição</label>
<textarea name="descricao" rows="4"
placeholder="Descreva brevemente o projeto..."><?php echo htmlspecialchars($projeto['descricao']); ?></textarea>
</div>
<div class="form-group">
<label>🖼️ Imagem (print do sistema)</label>
<?php if ($editando && !empty($projeto['imagem']) && file_exists(UPLOAD_IMG_DIR . $projeto['imagem'])): ?>
<img src="<?php echo UPLOAD_IMG_URL . htmlspecialchars($projeto['imagem']); ?>"
class="preview-img">
<?php endif; ?>
<input type="file" name="imagem" accept="image/*">
<small>Formatos: JPG, PNG, GIF, WEBP. Máximo 5MB.</small>
</div>
<div class="form-group">
<label>🔗 Link do projeto *</label>
<input type="url" name="link" required
value="<?php echo htmlspecialchars($projeto['link']); ?>"
placeholder="https://seu-site.com/conversor/">
<small>Cole a URL completa. Você pode usar sua própria hospedagem ou a URL do InfinityFree.</small>
</div>
<div class="form-group">
<label>💻 Tecnologias usadas</label>
<input type="text" name="tecnologias"
value="<?php echo htmlspecialchars($projeto['tecnologias']); ?>"
placeholder="Ex: PHP, MySQL, JavaScript">
<small>Separe por vírgula.</small>
</div>
<div class="form-actions">
<a href="dashboard.php" class="btn-cancelar">Cancelar</a>
<button type="submit" class="btn-salvar">💾 Salvar</button>
</div>
</form>
</main>
</body>
</html>
Arquivo 6: projeto_excluir.php
<?php
require_once 'config.php';
exigirLogin();
$id = isset($_GET['id']) ? trim($_GET['id']) : '';
$dados = carregarDados();
$encontrado = false;
foreach ($dados['projetos'] as $key => $p) {
if ($p['id'] === $id) {
// Remove a imagem
if (!empty($p['imagem']) && file_exists(UPLOAD_IMG_DIR . $p['imagem'])) {
unlink(UPLOAD_IMG_DIR . $p['imagem']);
}
// Remove do array
unset($dados['projetos'][$key]);
$encontrado = true;
break;
}
}
// Reindexa o array
$dados['projetos'] = array_values($dados['projetos']);
salvarDados($dados);
header('Location: dashboard.php?msg=del');
exit;
?>
Arquivo 7: perfil.php (Personalização)
<?php
require_once 'config.php';
exigirLogin();
$dados = carregarDados();
$perfil = $dados['perfil'];
$tema = $dados['tema'];
$erros = [];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Dados básicos
$perfil['nome'] = trim($_POST['nome'] ?? '');
$perfil['turma'] = trim($_POST['turma'] ?? '');
$perfil['bio'] = trim($_POST['bio'] ?? '');
$perfil['email'] = trim($_POST['email'] ?? '');
$perfil['github'] = trim($_POST['github'] ?? '');
$perfil['linkedin'] = trim($_POST['linkedin'] ?? '');
if (strlen($perfil['nome']) < 2) $erros[] = 'Nome muito curto';
// Upload do avatar
if (!empty($_FILES['avatar']['name'])) {
$resultado = fazerUpload($_FILES['avatar'], UPLOAD_AVATAR_DIR, 'avatar_');
if ($resultado['ok']) {
// Remove o antigo
if (!empty($perfil['avatar']) && file_exists(UPLOAD_AVATAR_DIR . $perfil['avatar'])) {
unlink(UPLOAD_AVATAR_DIR . $perfil['avatar']);
}
$perfil['avatar'] = $resultado['nome'];
} else {
$erros[] = $resultado['msg'];
}
}
// Tema (cores)
$tema['cor_primaria'] = $_POST['cor_primaria'] ?? '#0f2b46';
$tema['cor_secundaria'] = $_POST['cor_secundaria'] ?? '#c9a227';
$tema['cor_fundo'] = $_POST['cor_fundo'] ?? '#f4f6f9';
// Validação básica de hex
foreach (['cor_primaria', 'cor_secundaria', 'cor_fundo'] as $k) {
if (!preg_match('/^#[0-9a-fA-F]{6}$/', $tema[$k])) {
$erros[] = "Cor inválida em $k";
}
}
if (empty($erros)) {
$dados['perfil'] = $perfil;
$dados['tema'] = $tema;
salvarDados($dados);
header('Location: dashboard.php?msg=perfil');
exit;
}
}
// Temas prontos (paletas)
$paletas = [
['nome' => 'Institucional (Azul)', 'primaria' => '#0f2b46', 'secundaria' => '#c9a227', 'fundo' => '#f4f6f9'],
['nome' => 'Moderno (Roxo)', 'primaria' => '#6c5ce7', 'secundaria' => '#fd79a8', 'fundo' => '#f8f9fc'],
['nome' => 'Verde (Natureza)', 'primaria' => '#1f7a3f', 'secundaria' => '#f39c12', 'fundo' => '#f5f9f5'],
['nome' => 'Vermelho (Vibrante)', 'primaria' => '#c0392b', 'secundaria' => '#f1c40f', 'fundo' => '#fdf5f4'],
['nome' => 'Cinza (Minimalista)', 'primaria' => '#2c3e50', 'secundaria' => '#7f8c8d', 'fundo' => '#f7f7f7'],
['nome' => 'Rosa (Criativo)', 'primaria' => '#e84393', 'secundaria' => '#fdcb6e', 'fundo' => '#fdf6f9'],
['nome' => 'Azul Céu (Clean)', 'primaria' => '#0984e3', 'secundaria' => '#00b894', 'fundo' => '#f0f8ff'],
['nome' => 'Preto (Dark)', 'primaria' => '#1a1a1a', 'secundaria' => '#ffb800', 'fundo' => '#f0f0f0']
];
?>
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<title>Perfil e Personalização</title>
<link rel="stylesheet" href="admin.css">
</head>
<body>
<header class="topbar">
<div class="topbar-content">
<div class="topbar-brand">
<span>👤</span>
<div>
<strong>Perfil e Personalização</strong>
<small>Suas informações e tema</small>
</div>
</div>
<nav class="topbar-nav">
<a href="dashboard.php">← Voltar</a>
</nav>
</div>
</header>
<main class="container container-narrow">
<?php if (!empty($erros)): ?>
<div class="alert alert-error">
<?php foreach ($erros as $e): ?>
<div>⚠️ <?php echo htmlspecialchars($e); ?></div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<form method="POST" enctype="multipart/form-data">
<!-- SEÇÃO: DADOS PESSOAIS -->
<section class="form-card">
<h2 class="section-title">👤 Meus Dados</h2>
<div class="perfil-upload">
<div class="perfil-avatar-grande">
<?php if (!empty($perfil['avatar']) && file_exists(UPLOAD_AVATAR_DIR . $perfil['avatar'])): ?>
<img src="<?php echo UPLOAD_AVATAR_URL . htmlspecialchars($perfil['avatar']); ?>" alt="">
<?php else: ?>
<span><?php echo strtoupper(substr($perfil['nome'] ?? 'A', 0, 1)); ?></span>
<?php endif; ?>
</div>
<div class="perfil-upload-info">
<label>Sua foto de perfil</label>
<input type="file" name="avatar" accept="image/*">
<small>Aparecerá no seu portfólio público.</small>
</div>
</div>
<div class="form-group">
<label>Nome completo *</label>
<input type="text" name="nome" required
value="<?php echo htmlspecialchars($perfil['nome'] ?? ''); ?>">
</div>
<div class="form-group">
<label>Turma</label>
<input type="text" name="turma"
value="<?php echo htmlspecialchars($perfil['turma'] ?? ''); ?>"
placeholder="Ex: 2º TEC 01">
</div>
<div class="form-group">
<label>Bio (sobre você)</label>
<textarea name="bio" rows="3"
placeholder="Fale um pouco sobre você..."><?php echo htmlspecialchars($perfil['bio'] ?? ''); ?></textarea>
</div>
<div class="form-group">
<label>E-mail (opcional)</label>
<input type="email" name="email"
value="<?php echo htmlspecialchars($perfil['email'] ?? ''); ?>">
</div>
<div class="grid-2">
<div class="form-group">
<label>GitHub (opcional)</label>
<input type="text" name="github"
value="<?php echo htmlspecialchars($perfil['github'] ?? ''); ?>"
placeholder="https://github.com/usuario">
</div>
<div class="form-group">
<label>LinkedIn (opcional)</label>
<input type="text" name="linkedin"
value="<?php echo htmlspecialchars($perfil['linkedin'] ?? ''); ?>"
placeholder="https://linkedin.com/in/usuario">
</div>
</div>
</section>
<!-- SEÇÃO: TEMA -->
<section class="form-card">
<h2 class="section-title">🎨 Personalização de Cores</h2>
<p class="section-sub">Escolha um tema pronto ou personalize as cores:</p>
<div class="paletas-grid">
<?php foreach ($paletas as $idx => $pal): ?>
<button type="button"
class="paleta <?php echo ($tema['cor_primaria'] === $pal['primaria']) ? 'paleta-ativa' : ''; ?>"
onclick="aplicarPaleta('<?php echo $pal['primaria']; ?>', '<?php echo $pal['secundaria']; ?>', '<?php echo $pal['fundo']; ?>', this)">
<div class="paleta-cores">
<span style="background: <?php echo $pal['primaria']; ?>"></span>
<span style="background: <?php echo $pal['secundaria']; ?>"></span>
<span style="background: <?php echo $pal['fundo']; ?>"></span>
</div>
<span class="paleta-nome"><?php echo $pal['nome']; ?></span>
</button>
<?php endforeach; ?>
</div>
<div class="grid-3">
<div class="form-group">
<label>Cor primária</label>
<input type="color" name="cor_primaria" id="cor_primaria"
value="<?php echo htmlspecialchars($tema['cor_primaria'] ?? '#0f2b46'); ?>"
onchange="atualizarPreview()">
</div>
<div class="form-group">
<label>Cor secundária</label>
<input type="color" name="cor_secundaria" id="cor_secundaria"
value="<?php echo htmlspecialchars($tema['cor_secundaria'] ?? '#c9a227'); ?>"
onchange="atualizarPreview()">
</div>
<div class="form-group">
<label>Cor de fundo</label>
<input type="color" name="cor_fundo" id="cor_fundo"
value="<?php echo htmlspecialchars($tema['cor_fundo'] ?? '#f4f6f9'); ?>"
onchange="atualizarPreview()">
</div>
</div>
</section>
<div class="form-actions-fixed">
<a href="dashboard.php" class="btn-cancelar">Cancelar</a>
<button type="submit" class="btn-salvar">💾 Salvar Alterações</button>
</div>
</form>
</main>
<script>
function aplicarPaleta(primaria, secundaria, fundo, el) {
document.getElementById('cor_primaria').value = primaria;
document.getElementById('cor_secundaria').value = secundaria;
document.getElementById('cor_fundo').value = fundo;
// Atualiza visual
document.querySelectorAll('.paleta').forEach(p => p.classList.remove('paleta-ativa'));
el.classList.add('paleta-ativa');
}
function atualizarPreview() {
// Remove destaque de paleta pronta quando o usuário muda a cor manualmente
document.querySelectorAll('.paleta').forEach(p => p.classList.remove('paleta-ativa'));
}
</script>
</body>
</html>
Arquivo 8: index.php (Portfólio Público com tema dinâmico)
<?php
require_once 'config.php';
$dados = carregarDados();
$perfil = $dados['perfil'];
$tema = $dados['tema'];
$projetos = $dados['projetos'];
// Ordem: mais recentes primeiro
$projetos = array_reverse($projetos);
?>
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Portfólio de <?php echo htmlspecialchars($perfil['nome'] ?? 'Aluno'); ?></title>
<link rel="stylesheet" href="style.css?v=<?php echo time(); ?>">
<style>
:root {
--cor-primaria: <?php echo htmlspecialchars($tema['cor_primaria'] ?? '#0f2b46'); ?>;
--cor-secundaria: <?php echo htmlspecialchars($tema['cor_secundaria'] ?? '#c9a227'); ?>;
--cor-fundo: <?php echo htmlspecialchars($tema['cor_fundo'] ?? '#f4f6f9'); ?>;
}
</style>
</head>
<body>
<!-- HEADER / HERO (horizontal) -->
<header class="hero">
<div class="container">
<div class="hero-content">
<!-- Avatar à esquerda -->
<div class="hero-avatar">
<?php if (!empty($perfil['avatar']) && file_exists(UPLOAD_AVATAR_DIR . $perfil['avatar'])): ?>
<img src="<?php echo UPLOAD_AVATAR_URL . htmlspecialchars($perfil['avatar']); ?>" alt="Foto de perfil">
<?php else: ?>
<span><?php echo strtoupper(substr($perfil['nome'] ?? 'A', 0, 1)); ?></span>
<?php endif; ?>
</div>
<!-- Informações no meio -->
<div class="hero-info">
<h1><?php echo htmlspecialchars($perfil['nome'] ?? 'Meu Portfólio'); ?></h1>
<?php if (!empty($perfil['turma'])): ?>
<p class="hero-turma">🎓 <?php echo htmlspecialchars($perfil['turma']); ?></p>
<?php endif; ?>
<?php if (!empty($perfil['bio'])): ?>
<p class="hero-sub"><?php echo htmlspecialchars($perfil['bio']); ?></p>
<?php endif; ?>
<?php if (!empty($perfil['github']) || !empty($perfil['linkedin']) || !empty($perfil['email'])): ?>
<div class="hero-links">
<?php if (!empty($perfil['github'])): ?>
<a href="<?php echo htmlspecialchars($perfil['github']); ?>" target="_blank" rel="noopener">💻 GitHub</a>
<?php endif; ?>
<?php if (!empty($perfil['linkedin'])): ?>
<a href="<?php echo htmlspecialchars($perfil['linkedin']); ?>" target="_blank" rel="noopener">💼 LinkedIn</a>
<?php endif; ?>
<?php if (!empty($perfil['email'])): ?>
<a href="mailto:<?php echo htmlspecialchars($perfil['email']); ?>">✉️ E-mail</a>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
<!-- Estatísticas à direita -->
<div class="hero-stats">
<div class="hero-stat">
<strong><?php echo count($projetos); ?></strong>
<span>Projetos</span>
</div>
<div class="hero-stat">
<strong><?php echo date('Y'); ?></strong>
<span>Ano</span>
</div>
</div>
</div>
</div>
</header>
<!-- PROJETOS -->
<main class="container">
<?php if (empty($projetos)): ?>
<div class="empty-state">
<span class="empty-icon">📭</span>
<h2>Nenhum projeto publicado</h2>
<p>Os projetos aparecerão aqui quando forem cadastrados.</p>
</div>
<?php else: ?>
<h2 class="section-title">Meus Projetos</h2>
<div class="grid-projetos">
<?php foreach ($projetos as $p): ?>
<article class="card-projeto">
<div class="card-img">
<?php if (!empty($p['imagem']) && file_exists(UPLOAD_IMG_DIR . $p['imagem'])): ?>
<img src="<?php echo UPLOAD_IMG_URL . htmlspecialchars($p['imagem']); ?>"
alt="<?php echo htmlspecialchars($p['titulo']); ?>"
loading="lazy">
<?php else: ?>
<div class="card-noimg"><span>💻</span></div>
<?php endif; ?>
</div>
<div class="card-body">
<h3><?php echo htmlspecialchars($p['titulo']); ?></h3>
<?php if (!empty($p['descricao'])): ?>
<p class="card-desc"><?php echo htmlspecialchars($p['descricao']); ?></p>
<?php endif; ?>
<?php if (!empty($p['tecnologias'])): ?>
<div class="card-techs">
<?php foreach (explode(',', $p['tecnologias']) as $t):
$t = trim($t);
if ($t === '') continue;
?>
<span class="tech-tag"><?php echo htmlspecialchars($t); ?></span>
<?php endforeach; ?>
</div>
<?php endif; ?>
<a href="<?php echo htmlspecialchars($p['link']); ?>"
class="btn-acessar"
target="_blank"
rel="noopener">
🚀 Acessar Projeto
</a>
</div>
</article>
<?php endforeach; ?>
</div>
<?php endif; ?>
</main>
<!-- FOOTER -->
<footer class="footer">
<p>Portfólio de <strong><?php echo htmlspecialchars($perfil['nome'] ?? 'Aluno'); ?></strong></p>
<?php if (!empty($perfil['turma'])): ?>
<p class="footer-turma"><?php echo htmlspecialchars($perfil['turma']); ?> • <?php echo date('Y'); ?></p>
<?php endif; ?>
<a href="login.php" class="footer-admin">🔐 Gerenciar Portfólio</a>
</footer>
</body>
</html>
Arquivo 9: style.css
/* ==========================================================
style.css - Portfólio Público
Usa variáveis CSS dinâmicas:
--cor-primaria, --cor-secundaria, --cor-fundo
========================================================== */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', 'Helvetica', sans-serif;
background: var(--cor-fundo, #f4f6f9);
color: #2d3748;
min-height: 100vh;
line-height: 1.6;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 30px;
}
/* ==========================================================
HERO (cabeçalho do portfólio) - Horizontal
========================================================== */
.hero {
background: linear-gradient(135deg, var(--cor-primaria, #0f2b46) 0%, #0a1f34 100%);
color: white;
padding: 30px 0;
border-bottom: 4px solid var(--cor-secundaria, #c9a227);
position: relative;
overflow: hidden;
}
.hero::before {
content: '';
position: absolute;
top: -50%;
right: -10%;
width: 400px;
height: 400px;
background: radial-gradient(circle, rgba(201, 162, 39, 0.12) 0%, transparent 70%);
border-radius: 50%;
pointer-events: none;
}
.hero-content {
position: relative;
z-index: 1;
display: flex;
align-items: center;
gap: 25px;
flex-wrap: wrap;
}
/* Avatar compacto */
.hero-avatar {
width: 90px;
height: 90px;
background: linear-gradient(135deg, var(--cor-secundaria, #c9a227), #b8901d);
color: var(--cor-primaria, #0f2b46);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 2.4rem;
font-weight: 800;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
border: 3px solid white;
overflow: hidden;
flex-shrink: 0;
}
.hero-avatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
/* Bloco de informações (nome, turma, bio, links) */
.hero-info {
flex: 1;
min-width: 220px;
}
.hero-info h1 {
font-size: 1.7rem;
margin-bottom: 4px;
letter-spacing: -0.3px;
font-weight: 800;
line-height: 1.2;
}
.hero-turma {
color: var(--cor-secundaria, #c9a227);
font-size: 0.88rem;
font-weight: 600;
letter-spacing: 0.5px;
margin-bottom: 6px;
}
.hero-sub {
color: rgba(255, 255, 255, 0.7);
font-size: 0.88rem;
margin-bottom: 10px;
line-height: 1.5;
}
.hero-links {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.hero-links a {
color: rgba(255, 255, 255, 0.85);
text-decoration: none;
padding: 5px 14px;
border: 1px solid rgba(255, 255, 255, 0.25);
border-radius: 16px;
font-size: 0.78rem;
font-weight: 600;
transition: all 0.2s ease;
}
.hero-links a:hover {
background: rgba(255, 255, 255, 0.15);
border-color: var(--cor-secundaria, #c9a227);
color: var(--cor-secundaria, #c9a227);
}
/* Estatísticas compactas à direita */
.hero-stats {
display: flex;
gap: 25px;
flex-shrink: 0;
margin-left: auto;
padding-left: 25px;
border-left: 1px solid rgba(255, 255, 255, 0.15);
}
.hero-stat {
display: flex;
flex-direction: column;
align-items: center;
}
.hero-stat strong {
font-size: 1.6rem;
font-weight: 800;
color: var(--cor-secundaria, #c9a227);
line-height: 1;
}
.hero-stat span {
font-size: 0.68rem;
color: rgba(255, 255, 255, 0.6);
text-transform: uppercase;
letter-spacing: 1.2px;
margin-top: 4px;
}
/* ==========================================================
PROJETOS
========================================================== */
main {
padding: 40px 0;
}
.section-title {
color: var(--cor-primaria, #0f2b46);
font-size: 1.5rem;
margin-bottom: 25px;
padding-bottom: 12px;
border-bottom: 2px solid rgba(0, 0, 0, 0.08);
font-weight: 700;
}
.grid-projetos {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 30px;
}
.card-projeto {
background: white;
border-radius: 14px;
overflow: hidden;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
display: flex;
flex-direction: column;
}
.card-projeto:hover {
transform: translateY(-8px);
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.15);
}
.card-img {
height: 200px;
background: linear-gradient(135deg, var(--cor-primaria, #0f2b46) 0%, #0a1f34 100%);
overflow: hidden;
position: relative;
}
.card-img img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.4s ease;
display: block;
}
.card-projeto:hover .card-img img {
transform: scale(1.08);
}
.card-noimg {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
font-size: 4rem;
color: rgba(255, 255, 255, 0.35);
}
.card-body {
padding: 25px;
display: flex;
flex-direction: column;
gap: 14px;
flex: 1;
}
.card-body h3 {
color: var(--cor-primaria, #0f2b46);
font-size: 1.2rem;
font-weight: 800;
line-height: 1.3;
}
.card-desc {
color: #64748b;
font-size: 0.92rem;
line-height: 1.6;
flex: 1;
}
.card-techs {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.tech-tag {
background: var(--cor-fundo, #f4f6f9);
color: #2d3748;
padding: 4px 12px;
border-radius: 6px;
font-size: 0.72rem;
font-weight: 600;
border: 1px solid rgba(0, 0, 0, 0.08);
}
.btn-acessar {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 14px 24px;
background: linear-gradient(135deg, var(--cor-primaria, #0f2b46), #1e5b96);
color: white;
text-decoration: none;
border-radius: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1px;
font-size: 0.85rem;
transition: all 0.25s ease;
margin-top: 8px;
border: none;
cursor: pointer;
}
.btn-acessar:hover {
background: linear-gradient(135deg, #0a1f34, var(--cor-primaria, #0f2b46));
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.25);
}
/* ==========================================================
EMPTY STATE
========================================================== */
.empty-state {
text-align: center;
padding: 100px 30px;
background: white;
border-radius: 14px;
border: 2px dashed rgba(0, 0, 0, 0.1);
}
.empty-icon {
font-size: 5rem;
display: block;
margin-bottom: 20px;
}
.empty-state h2 {
color: var(--cor-primaria, #0f2b46);
margin-bottom: 10px;
font-size: 1.4rem;
}
.empty-state p {
color: #64748b;
}
/* ==========================================================
FOOTER
========================================================== */
.footer {
background: #0a1f34;
color: rgba(255, 255, 255, 0.7);
padding: 35px 20px;
text-align: center;
border-top: 4px solid var(--cor-secundaria, #c9a227);
margin-top: 50px;
}
.footer p {
font-size: 0.92rem;
margin-bottom: 6px;
}
.footer strong {
color: var(--cor-secundaria, #c9a227);
}
.footer-turma {
font-size: 0.82rem !important;
color: rgba(255, 255, 255, 0.5) !important;
letter-spacing: 1px;
}
.footer-admin {
display: inline-block;
margin-top: 15px;
color: var(--cor-secundaria, #c9a227);
text-decoration: none;
font-size: 0.85rem;
font-weight: 600;
padding: 8px 18px;
border: 1px solid var(--cor-secundaria, #c9a227);
border-radius: 20px;
transition: all 0.2s ease;
}
.footer-admin:hover {
background: var(--cor-secundaria, #c9a227);
color: var(--cor-primaria, #0f2b46);
}
/* ==========================================================
RESPONSIVO
========================================================== */
@media (max-width: 900px) {
.hero-info h1 {
font-size: 1.5rem;
}
.grid-projetos {
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 20px;
}
}
@media (max-width: 700px) {
/* Mobile: hero volta a ser vertical e centralizado */
.hero {
padding: 35px 0 30px;
}
.hero-content {
flex-direction: column;
text-align: center;
gap: 15px;
}
.hero-avatar {
width: 80px;
height: 80px;
font-size: 2rem;
}
.hero-info h1 {
font-size: 1.4rem;
}
.hero-turma {
font-size: 0.85rem;
}
.hero-sub {
font-size: 0.85rem;
max-width: 100%;
}
.hero-links {
justify-content: center;
}
.hero-stats {
margin-left: 0;
padding-left: 0;
border-left: none;
padding-top: 15px;
border-top: 1px solid rgba(255, 255, 255, 0.15);
justify-content: center;
width: 100%;
gap: 40px;
}
.hero-stat strong {
font-size: 1.5rem;
}
}
@media (max-width: 600px) {
.container {
padding: 0 18px;
}
.grid-projetos {
grid-template-columns: 1fr;
}
main {
padding: 30px 0;
}
.section-title {
font-size: 1.3rem;
}
.card-body {
padding: 20px;
}
.empty-state {
padding: 60px 20px;
}
}
Arquivo 10: admin.css
/* admin.css - Estilo do dashboard */
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--navy: #0f2b46;
--navy-soft: #1e5b96;
--navy-dark: #0a1f34;
--gold: #c9a227;
--bg: #f4f6f9;
--white: #ffffff;
--border: #d9e0e8;
--text: #2d3748;
--text-soft: #64748b;
--success: #1f9d55;
--danger: #c53030;
}
body {
font-family: 'Segoe UI', sans-serif;
background: var(--bg);
color: var(--text);
min-height: 100vh;
line-height: 1.6;
}
/* ==========================================================
LOGIN
========================================================== */
body.login-body {
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, var(--navy) 0%, var(--navy-dark) 100%);
padding: 20px;
}
.login-card {
background: white;
padding: 50px 40px;
border-radius: 16px;
max-width: 400px;
width: 100%;
text-align: center;
box-shadow: 0 25px 60px rgba(0, 0, 0, 0.4);
border-top: 5px solid var(--gold);
}
.login-icon { font-size: 3rem; margin-bottom: 15px; }
.login-card h1 {
color: var(--navy);
font-size: 1.5rem;
margin-bottom: 6px;
}
.login-sub {
color: var(--text-soft);
font-size: 0.9rem;
margin-bottom: 30px;
}
.login-back {
display: inline-block;
margin-top: 20px;
color: var(--text-soft);
text-decoration: none;
font-size: 0.85rem;
}
.login-back:hover { color: var(--navy-soft); }
/* ==========================================================
TOPBAR
========================================================== */
.topbar {
background: var(--navy);
color: white;
border-bottom: 3px solid var(--gold);
position: sticky;
top: 0;
z-index: 100;
}
.topbar-content {
max-width: 1200px;
margin: 0 auto;
padding: 16px 30px;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 15px;
}
.topbar-brand {
display: flex;
align-items: center;
gap: 12px;
}
.topbar-brand span { font-size: 1.8rem; }
.topbar-brand strong { display: block; font-size: 1.05rem; }
.topbar-brand small {
display: block;
color: rgba(255, 255, 255, 0.6);
font-size: 0.75rem;
}
.topbar-nav {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.topbar-nav a {
color: rgba(255, 255, 255, 0.85);
text-decoration: none;
padding: 8px 16px;
border-radius: 6px;
font-size: 0.85rem;
font-weight: 500;
transition: all 0.2s;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.topbar-nav a:hover {
background: rgba(255, 255, 255, 0.1);
color: white;
}
.btn-sair {
background: rgba(197, 48, 48, 0.3);
border-color: #ffb3b3 !important;
color: #ffb3b3 !important;
}
.btn-sair:hover {
background: var(--danger) !important;
color: white !important;
}
/* ==========================================================
CONTAINER
========================================================== */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 40px 30px;
}
.container-narrow { max-width: 800px; }
/* ==========================================================
PERFIL RESUMO (no dashboard)
========================================================== */
.perfil-resumo {
background: white;
padding: 25px;
border-radius: 12px;
display: flex;
align-items: center;
gap: 20px;
margin-bottom: 30px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
border-left: 5px solid var(--gold);
}
.perfil-avatar {
width: 70px;
height: 70px;
border-radius: 50%;
background: linear-gradient(135deg, var(--navy), var(--navy-soft));
color: var(--gold);
display: flex;
align-items: center;
justify-content: center;
font-size: 2rem;
font-weight: 800;
overflow: hidden;
flex-shrink: 0;
}
.perfil-avatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
.perfil-info { flex: 1; }
.perfil-info h2 { color: var(--navy); font-size: 1.3rem; margin-bottom: 4px; }
.perfil-info p { color: var(--text-soft); font-size: 0.9rem; }
.btn-perfil {
padding: 10px 20px;
background: var(--bg);
color: var(--navy);
text-decoration: none;
border-radius: 8px;
font-size: 0.85rem;
font-weight: 700;
border: 2px solid var(--border);
transition: all 0.2s;
}
.btn-perfil:hover {
background: var(--navy);
color: white;
border-color: var(--navy);
}
/* ==========================================================
LISTA HEADER
========================================================== */
.lista-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
flex-wrap: wrap;
gap: 15px;
}
.lista-header h1 { color: var(--navy); font-size: 1.6rem; margin-bottom: 4px; }
.lista-sub { color: var(--text-soft); font-size: 0.9rem; }
.btn-novo {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 12px 24px;
background: var(--navy);
color: white;
text-decoration: none;
border-radius: 8px;
font-weight: 700;
font-size: 0.85rem;
text-transform: uppercase;
letter-spacing: 1px;
transition: all 0.2s;
}
.btn-novo:hover {
background: var(--navy-dark);
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(15, 43, 70, 0.25);
}
/* ==========================================================
GRID ADMIN
========================================================== */
.grid-projetos {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 20px;
}
.card-admin {
background: white;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.06);
transition: all 0.25s;
display: flex;
flex-direction: column;
}
.card-admin:hover {
transform: translateY(-4px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.12);
}
.card-img {
height: 160px;
background: linear-gradient(135deg, var(--navy), var(--navy-dark));
overflow: hidden;
}
.card-img img {
width: 100%;
height: 100%;
object-fit: cover;
}
.card-noimg {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
font-size: 3rem;
color: rgba(255, 255, 255, 0.35);
}
.card-body {
padding: 18px;
display: flex;
flex-direction: column;
gap: 10px;
flex: 1;
}
.card-body h3 {
color: var(--navy);
font-size: 1.05rem;
line-height: 1.3;
}
.card-desc {
color: var(--text-soft);
font-size: 0.85rem;
line-height: 1.5;
flex: 1;
}
.card-actions {
display: flex;
gap: 8px;
padding-top: 12px;
border-top: 1px solid var(--border);
}
.btn-mini {
width: 40px;
height: 40px;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 8px;
text-decoration: none;
font-size: 1rem;
transition: all 0.2s;
}
.btn-edit { background: #eef4fb; color: var(--navy-soft); }
.btn-edit:hover { background: var(--navy-soft); color: white; }
.btn-del { background: #fdecea; color: var(--danger); }
.btn-del:hover { background: var(--danger); color: white; }
/* ==========================================================
FORMULÁRIOS
========================================================== */
.form-card {
background: white;
padding: 30px;
border-radius: 12px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
margin-bottom: 25px;
}
.section-title {
color: var(--navy);
font-size: 1.2rem;
margin-bottom: 20px;
padding-bottom: 12px;
border-bottom: 2px solid var(--border);
}
.section-sub {
color: var(--text-soft);
font-size: 0.9rem;
margin-bottom: 20px;
}
.form-group {
display: flex;
flex-direction: column;
margin-bottom: 20px;
}
.form-group label {
font-size: 0.85rem;
font-weight: 700;
color: var(--navy);
margin-bottom: 6px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.form-group input,
.form-group textarea {
padding: 12px 16px;
border: 2px solid var(--border);
border-radius: 8px;
font-size: 0.95rem;
font-family: inherit;
transition: all 0.2s;
background: white;
color: var(--text);
width: 100%;
}
.form-group input:focus,
.form-group textarea:focus {
outline: none;
border-color: var(--navy-soft);
box-shadow: 0 0 0 3px rgba(30, 91, 150, 0.1);
}
.form-group input[type="color"] {
height: 50px;
padding: 4px;
cursor: pointer;
}
.form-group small {
margin-top: 6px;
color: var(--text-soft);
font-size: 0.78rem;
font-style: italic;
}
.form-group textarea {
resize: vertical;
min-height: 100px;
}
.grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
.grid-3 {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
}
/* Upload do perfil */
.perfil-upload {
display: flex;
align-items: center;
gap: 25px;
padding: 20px;
background: var(--bg);
border-radius: 12px;
margin-bottom: 25px;
}
.perfil-avatar-grande {
width: 100px;
height: 100px;
border-radius: 50%;
background: linear-gradient(135deg, var(--navy), var(--navy-soft));
color: var(--gold);
display: flex;
align-items: center;
justify-content: center;
font-size: 2.8rem;
font-weight: 800;
overflow: hidden;
flex-shrink: 0;
border: 4px solid white;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.perfil-avatar-grande img {
width: 100%;
height: 100%;
object-fit: cover;
}
.perfil-upload-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 6px;
}
.perfil-upload-info label {
font-size: 0.85rem;
font-weight: 700;
color: var(--navy);
text-transform: uppercase;
}
.perfil-upload-info small {
color: var(--text-soft);
font-size: 0.78rem;
}
/* Preview da imagem */
.preview-img {
max-width: 200px;
border-radius: 8px;
border: 2px solid var(--border);
margin: 10px 0;
}
/* Paletas de cores */
.paletas-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
gap: 12px;
margin-bottom: 25px;
}
.paleta {
background: white;
border: 2px solid var(--border);
border-radius: 10px;
padding: 12px;
cursor: pointer;
transition: all 0.2s;
font-family: inherit;
text-align: left;
}
.paleta:hover {
border-color: var(--navy-soft);
transform: translateY(-2px);
}
.paleta-ativa {
border-color: var(--navy);
box-shadow: 0 0 0 2px var(--navy);
}
.paleta-cores {
display: flex;
gap: 4px;
margin-bottom: 8px;
}
.paleta-cores span {
flex: 1;
height: 30px;
border-radius: 6px;
border: 1px solid rgba(0,0,0,0.05);
}
.paleta-nome {
font-size: 0.78rem;
font-weight: 700;
color: var(--navy);
display: block;
}
/* Ações do formulário */
.form-actions {
display: flex;
justify-content: flex-end;
gap: 12px;
padding-top: 20px;
margin-top: 20px;
border-top: 1px solid var(--border);
}
.form-actions-fixed {
display: flex;
justify-content: flex-end;
gap: 12px;
padding: 20px 0;
position: sticky;
bottom: 0;
background: var(--bg);
}
.btn-cancelar,
.btn-salvar {
padding: 13px 28px;
border-radius: 8px;
font-weight: 700;
font-size: 0.85rem;
text-transform: uppercase;
letter-spacing: 1px;
text-decoration: none;
cursor: pointer;
font-family: inherit;
border: none;
transition: all 0.2s;
display: inline-flex;
align-items: center;
gap: 6px;
}
.btn-cancelar {
background: var(--bg);
color: var(--text);
border: 1px solid var(--border);
}
.btn-cancelar:hover { background: #e6ecf3; }
.btn-salvar {
background: var(--navy);
color: white;
}
.btn-salvar:hover {
background: var(--navy-dark);
transform: translateY(-1px);
box-shadow: 0 6px 15px rgba(15, 43, 70, 0.2);
}
.btn-full {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
padding: 14px;
background: var(--navy);
color: white;
border: none;
border-radius: 8px;
font-size: 0.95rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1.5px;
cursor: pointer;
font-family: inherit;
transition: all 0.2s;
margin-top: 10px;
}
.btn-full:hover {
background: var(--navy-dark);
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(15, 43, 70, 0.3);
}
/* ==========================================================
ALERTAS
========================================================== */
.alert {
padding: 14px 20px;
border-radius: 8px;
margin-bottom: 20px;
border-left: 5px solid;
font-size: 0.9rem;
line-height: 1.6;
}
.alert-success {
background: #e6f4ea;
border-color: var(--success);
color: #1e4620;
}
.alert-error {
background: #fdecea;
border-color: var(--danger);
color: #5c1a1a;
}
/* ==========================================================
EMPTY
========================================================== */
.empty-state {
text-align: center;
padding: 70px 30px;
background: white;
border-radius: 12px;
border: 2px dashed var(--border);
}
.empty-icon { font-size: 4rem; display: block; margin-bottom: 15px; }
.empty-state h2 { color: var(--navy); margin-bottom: 8px; }
.empty-state p { color: var(--text-soft); margin-bottom: 20px; }
/* ==========================================================
RESPONSIVO
========================================================== */
@media (max-width: 700px) {
.container { padding: 25px 18px; }
.topbar-content { flex-direction: column; align-items: stretch; }
.topbar-nav { justify-content: center; }
.topbar-nav a { flex: 1; text-align: center; }
.lista-header { flex-direction: column; align-items: stretch; }
.btn-novo { justify-content: center; }
.grid-projetos { grid-template-columns: 1fr; }
.perfil-resumo { flex-direction: column; text-align: center; }
.grid-2, .grid-3 { grid-template-columns: 1fr; }
.paletas-grid { grid-template-columns: 1fr 1fr; }
.form-card { padding: 20px; }
.form-actions { flex-direction: column-reverse; }
.btn-cancelar, .btn-salvar { width: 100%; justify-content: center; }
.perfil-upload { flex-direction: column; text-align: center; }
}

