Compare commits

...

1 Commits

Author SHA1 Message Date
Flatlogic Bot
ce3a3e5ec3 V1 2026-06-26 02:08:52 +00:00
8 changed files with 1000 additions and 558 deletions

108
api/contact.php Normal file
View File

@ -0,0 +1,108 @@
<?php
declare(strict_types=1);
header('Content-Type: application/json; charset=utf-8');
function respond(int $status, array $payload): void {
http_response_code($status);
echo json_encode($payload, JSON_UNESCAPED_UNICODE);
exit;
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
respond(405, ['success' => false, 'message' => 'Método não permitido.']);
}
$name = trim((string)($_POST['name'] ?? ''));
$email = trim((string)($_POST['email'] ?? ''));
$phone = trim((string)($_POST['phone'] ?? ''));
$projectType = trim((string)($_POST['project_type'] ?? ''));
$message = trim((string)($_POST['message'] ?? ''));
$honeypot = trim((string)($_POST['website'] ?? ''));
if ($honeypot !== '') {
respond(200, ['success' => true, 'message' => 'Pedido recebido. Obrigado.']);
}
$errors = [];
if (mb_strlen($name) < 2 || mb_strlen($name) > 120) $errors[] = 'Indica um nome válido.';
if (!filter_var($email, FILTER_VALIDATE_EMAIL) || mb_strlen($email) > 160) $errors[] = 'Indica um email válido.';
if ($phone !== '' && mb_strlen($phone) > 40) $errors[] = 'O telemóvel é demasiado longo.';
if ($projectType === '' || mb_strlen($projectType) > 80) $errors[] = 'Selecciona o tipo de projecto.';
if (mb_strlen($message) < 20 || mb_strlen($message) > 1800) $errors[] = 'A mensagem deve ter entre 20 e 1800 caracteres.';
if ($errors) {
respond(422, ['success' => false, 'message' => implode(' ', $errors)]);
}
$recipient = 'ola@paulogomes.pt';
$siteName = 'pg Web e APP Designer';
$emailSent = false;
$submissionId = null;
try {
require_once __DIR__ . '/../db/config.php';
$pdo = db();
$pdo->exec("CREATE TABLE IF NOT EXISTS contact_requests (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(120) NOT NULL,
email VARCHAR(160) NOT NULL,
phone VARCHAR(40) NULL,
project_type VARCHAR(80) NOT NULL,
message TEXT NOT NULL,
email_sent TINYINT(1) NOT NULL DEFAULT 0,
status VARCHAR(30) NOT NULL DEFAULT 'new',
ip_address VARCHAR(45) NULL,
user_agent VARCHAR(255) NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
$stmt = $pdo->prepare('INSERT INTO contact_requests (name, email, phone, project_type, message, ip_address, user_agent) VALUES (:name, :email, :phone, :project_type, :message, :ip, :user_agent)');
$stmt->execute([
':name' => $name,
':email' => $email,
':phone' => $phone !== '' ? $phone : null,
':project_type' => $projectType,
':message' => $message,
':ip' => substr((string)($_SERVER['REMOTE_ADDR'] ?? ''), 0, 45) ?: null,
':user_agent' => substr((string)($_SERVER['HTTP_USER_AGENT'] ?? ''), 0, 255) ?: null,
]);
$submissionId = (int)$pdo->lastInsertId();
} catch (Throwable $e) {
error_log('Contact DB error: ' . $e->getMessage());
}
try {
require_once __DIR__ . '/../mail/MailService.php';
$safeName = htmlspecialchars($name, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
$safeEmail = htmlspecialchars($email, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
$safePhone = htmlspecialchars($phone !== '' ? $phone : 'Não indicado', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
$safeProject = htmlspecialchars($projectType, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
$safeMessage = nl2br(htmlspecialchars($message, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'));
$html = "<h2>Novo pedido — {$siteName}</h2>"
. "<p><strong>Nome:</strong> {$safeName}</p>"
. "<p><strong>Email:</strong> {$safeEmail}</p>"
. "<p><strong>Telemóvel:</strong> {$safePhone}</p>"
. "<p><strong>Tipo de projecto:</strong> {$safeProject}</p>"
. "<p><strong>Website:</strong> {$siteName}</p>"
. "<hr><p><strong>Detalhe da mensagem:</strong></p><p>{$safeMessage}</p>";
$text = "Novo pedido — {$siteName}\nNome: {$name}\nEmail: {$email}\nTelemóvel: " . ($phone !== '' ? $phone : 'Não indicado') . "\nTipo de projecto: {$projectType}\n\nDetalhe da mensagem:\n{$message}";
$result = MailService::sendMail($recipient, $siteName . ' — novo pedido de contacto', $html, $text, ['reply_to' => $email]);
$emailSent = !empty($result['success']);
if (!$emailSent) error_log('Contact mail error: ' . ($result['error'] ?? 'unknown'));
} catch (Throwable $e) {
error_log('Contact mail exception: ' . $e->getMessage());
}
if ($submissionId) {
try {
$stmt = db()->prepare('UPDATE contact_requests SET email_sent = :email_sent WHERE id = :id');
$stmt->execute([':email_sent' => $emailSent ? 1 : 0, ':id' => $submissionId]);
} catch (Throwable $e) {
error_log('Contact DB update error: ' . $e->getMessage());
}
}
$messageText = $emailSent
? 'Mensagem enviada com sucesso. Obrigado — responderei assim que possível.'
: 'Pedido guardado. Se o envio por email não estiver configurado no servidor, contacta directamente ola@paulogomes.pt.';
respond(200, ['success' => true, 'message' => $messageText]);

View File

@ -1,403 +1,431 @@
:root {
--bg: #080808;
--surface: rgba(14, 14, 14, 0.86);
--surface-strong: #101010;
--text: #f5f5f1;
--muted: #a7a7a0;
--line: rgba(255, 255, 255, 0.12);
--line-strong: rgba(255, 255, 255, 0.22);
--accent: #ffffff;
--accent-text: #050505;
--radius-sm: 8px;
--radius-md: 12px;
--radius-lg: 18px;
--space: 1rem;
}
[data-bs-theme="light"] {
--bg: #ffffff;
--surface: rgba(255, 255, 255, 0.88);
--surface-strong: #f6f6f3;
--text: #050505;
--muted: #60605b;
--line: rgba(0, 0, 0, 0.12);
--line-strong: rgba(0, 0, 0, 0.2);
--accent: #050505;
--accent-text: #ffffff;
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body { body {
background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab); margin: 0;
background-size: 400% 400%; background: var(--bg);
animation: gradient 15s ease infinite; color: var(--text);
color: #212529; font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; text-rendering: optimizeLegibility;
font-size: 14px; }
margin: 0; a { color: inherit; }
min-height: 100vh; .skip-link {
position: absolute;
left: 1rem;
top: -4rem;
z-index: 999;
background: var(--accent);
color: var(--accent-text);
padding: .65rem .85rem;
border-radius: var(--radius-sm);
transition: top .2s ease;
}
.skip-link:focus { top: 1rem; }
.site-shell { position: relative; min-height: 100vh; overflow-x: clip; }
.hero-video {
position: fixed;
top: 300px;
right: 0;
bottom: 0;
left: 0;
width: 100%;
height: calc(100vh - 300px);
object-fit: cover;
opacity: 0;
z-index: 0;
filter: grayscale(1) contrast(1.08) brightness(.62);
pointer-events: none;
}
.video-scrim {
position: fixed;
inset: 0;
z-index: 1;
background: rgba(0, 0, 0, .46);
pointer-events: none;
}
[data-bs-theme="light"] .video-scrim { background: rgba(255, 255, 255, .58); }
.site-header {
z-index: 10;
background: rgba(8, 8, 8, .72);
border-bottom: 1px solid var(--line);
backdrop-filter: blur(18px);
}
[data-bs-theme="light"] .site-header { background: rgba(255,255,255,.74); }
.navbar { --bs-navbar-color: var(--muted); --bs-navbar-hover-color: var(--text); --bs-navbar-active-color: var(--text); }
.brand-mark {
color: var(--text);
font-family: "Instrument Serif", Georgia, serif;
font-size: clamp(1.35rem, 2vw, 1.9rem);
line-height: 1;
letter-spacing: -.045em;
}
.brand-mark sup { font-family: Inter, sans-serif; font-size: .42em; top: -.9em; margin-left: .08rem; }
.nav-link { font-size: .9rem; color: var(--muted); transition: color .2s ease; }
.nav-link:hover, .nav-link:focus, .nav-link.active { color: var(--text); }
.navbar-toggler { border: 1px solid var(--line); border-radius: var(--radius-sm); }
.btn { border-radius: var(--radius-sm); transition: transform .18s ease, background-color .18s ease, border-color .18s ease; }
.btn:hover { transform: translateY(-1px) scale(1.015); }
.btn-primary-dark {
background: var(--accent);
color: var(--accent-text);
border: 1px solid var(--accent);
font-weight: 650;
padding: .72rem 1.15rem;
}
.btn-primary-dark:hover, .btn-primary-dark:focus { background: var(--accent); color: var(--accent-text); border-color: var(--accent); }
.btn-outline-soft { color: var(--text); border: 1px solid var(--line-strong); background: transparent; padding: .72rem 1.15rem; }
.btn-outline-soft:hover { border-color: var(--accent); color: var(--text); }
.btn-ghost { color: var(--muted); border: 1px solid var(--line); background: transparent; }
.btn-ghost:hover { color: var(--text); border-color: var(--line-strong); }
.hero-section {
position: relative;
z-index: 2;
padding-top: calc(8rem - 75px);
padding-bottom: 10rem;
}
.eyebrow {
color: var(--muted);
font-size: .76rem;
font-weight: 700;
letter-spacing: .12em;
text-transform: uppercase;
margin-bottom: 1rem;
}
.hero-title {
font-family: "Instrument Serif", Georgia, serif;
font-size: clamp(3.4rem, 8.4vw, 8.25rem);
font-weight: 400;
line-height: .95;
letter-spacing: -0.045em;
max-width: 1180px;
margin: 0 auto;
}
.hero-copy {
color: var(--muted);
font-size: clamp(1rem, 1.3vw, 1.18rem);
line-height: 1.75;
max-width: 720px;
margin-top: 1.75rem;
}
.hero-actions { margin-top: 2.5rem; }
.stats-panel {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 1px;
max-width: 820px;
margin: 5rem auto 0;
border: 1px solid var(--line);
border-radius: var(--radius-lg);
overflow: hidden;
background: var(--line);
}
.stats-panel div {
background: var(--surface);
padding: 1.35rem 1rem;
backdrop-filter: blur(18px);
}
.stats-panel strong {
display: block;
font-family: "Instrument Serif", Georgia, serif;
font-size: clamp(2.3rem, 5vw, 4.2rem);
font-weight: 400;
line-height: 1;
}
.stats-panel span { display: block; color: var(--muted); font-size: .9rem; margin-top: .35rem; }
.section-pad { position: relative; z-index: 2; padding: 6.5rem 0; }
.section-quiet { background: rgba(255,255,255,.025); border-block: 1px solid var(--line); }
[data-bs-theme="light"] .section-quiet { background: rgba(0,0,0,.025); }
.section-heading { max-width: 760px; margin-bottom: 2.5rem; }
.section-heading.compact { margin-inline: auto; text-align: center; }
.section-heading h2, .section-title, .contact-card h2 {
font-family: "Instrument Serif", Georgia, serif;
font-size: clamp(2.45rem, 5vw, 5.25rem);
line-height: .98;
letter-spacing: -.04em;
font-weight: 400;
}
.section-heading p:not(.eyebrow), .section-copy { color: var(--muted); line-height: 1.75; }
.service-card, .contact-card, .form-card, .timeline > div {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--radius-md);
backdrop-filter: blur(18px);
}
.service-card { padding: 1.35rem; transition: transform .2s ease, border-color .2s ease; }
.service-card:hover { transform: translateY(-4px); border-color: var(--line-strong); }
.service-card span { color: var(--muted); font-size: .78rem; font-weight: 700; letter-spacing: .12em; }
.service-card h3 { margin: 1.4rem 0 .8rem; font-size: 1.03rem; font-weight: 750; }
.service-card p { color: var(--muted); line-height: 1.7; font-size: .94rem; margin: 0; }
.tool-grid { display: flex; flex-wrap: wrap; gap: .65rem; }
.tool-grid span {
border: 1px solid var(--line);
background: var(--surface);
color: var(--text);
border-radius: var(--radius-sm);
padding: .7rem .9rem;
font-size: .92rem;
}
.timeline { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 1rem; margin-top: 2rem; }
.timeline > div { padding: 1.25rem; }
.timeline strong { color: var(--muted); font-size: .78rem; letter-spacing: .12em; }
.timeline h3 { font-size: 1.04rem; margin: 1rem 0 .6rem; }
.timeline p { color: var(--muted); margin: 0; line-height: 1.65; }
.contact-section { padding-bottom: 7rem; }
.contact-card, .form-card { padding: clamp(1.25rem, 3vw, 2rem); }
.contact-list { list-style: none; padding: 0; margin: 2rem 0 0; display: grid; gap: 1rem; }
.contact-list li { border-top: 1px solid var(--line); padding-top: 1rem; }
.contact-list span { display: block; color: var(--muted); font-size: .78rem; text-transform: uppercase; letter-spacing: .11em; margin-bottom: .35rem; }
.contact-list a, .contact-list strong { font-size: 1.06rem; text-decoration: none; font-weight: 650; }
.contact-list small { display: block; color: var(--muted); margin-top: .25rem; }
.form-label { font-size: .86rem; font-weight: 700; color: var(--text); }
.form-label span, .form-text, .muted-note { color: var(--muted); }
.form-control, .form-select {
border: 1px solid var(--line);
border-radius: var(--radius-sm);
background-color: rgba(255,255,255,.04);
color: var(--text);
min-height: 3rem;
}
[data-bs-theme="light"] .form-control, [data-bs-theme="light"] .form-select { background-color: rgba(0,0,0,.035); }
.form-control:focus, .form-select:focus {
color: var(--text);
background-color: transparent;
border-color: var(--line-strong);
box-shadow: 0 0 0 .2rem rgba(128,128,128,.14);
}
.form-control::placeholder { color: color-mix(in srgb, var(--muted) 75%, transparent); }
.secure-pill { border: 1px solid var(--line); border-radius: 999px; padding: .4rem .6rem; color: var(--muted); font-size: .78rem; white-space: nowrap; }
.site-footer { position: relative; z-index: 2; border-top: 1px solid var(--line); color: var(--muted); padding: 1.25rem 0; font-size: .86rem; }
.toast { background: var(--surface-strong); color: var(--text); border: 1px solid var(--line); }
.animate-rise { opacity: 0; transform: translateY(20px); animation: fade-rise .8s ease-out forwards; }
.delay-1 { animation-delay: .2s; }
.delay-2 { animation-delay: .4s; }
@keyframes fade-rise { to { opacity: 1; transform: translateY(0); } }
@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
.animate-rise { animation: none; opacity: 1; transform: none; }
.btn, .service-card { transition: none; }
}
@media (max-width: 991.98px) {
.site-header .navbar-collapse { padding-top: 1rem; }
.timeline { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 575.98px) {
.hero-section { padding-top: 7rem; padding-bottom: 6rem; }
.stats-panel { grid-template-columns: 1fr; margin-top: 3rem; }
.section-pad { padding: 4.5rem 0; }
.timeline { grid-template-columns: 1fr; }
.hero-video { top: 220px; height: calc(100vh - 220px); }
} }
.main-wrapper {
display: flex; .portfolio-section { overflow: hidden; }
align-items: center; .project-featured, .project-card, .testimonial-card, .cookie-banner {
justify-content: center; background: var(--surface);
min-height: 100vh; border: 1px solid var(--line);
width: 100%; border-radius: var(--radius-lg);
padding: 20px; backdrop-filter: blur(18px);
box-sizing: border-box;
position: relative;
z-index: 1;
} }
.project-featured {
@keyframes gradient { display: grid;
0% { grid-template-columns: minmax(0, 1.08fr) minmax(320px, .92fr);
background-position: 0% 50%; gap: clamp(1.5rem, 4vw, 3rem);
} align-items: center;
50% { padding: clamp(1.1rem, 3vw, 2rem);
background-position: 100% 50%; box-shadow: 0 30px 90px rgba(0,0,0,.22);
}
100% {
background-position: 0% 50%;
}
} }
.project-visual, .project-thumb {
.chat-container { position: relative;
width: 100%; overflow: hidden;
max-width: 600px; border: 1px solid var(--line);
background: rgba(255, 255, 255, 0.85); background: #111;
border: 1px solid rgba(255, 255, 255, 0.3); isolation: isolate;
border-radius: 20px;
display: flex;
flex-direction: column;
height: 85vh;
box-shadow: 0 20px 40px rgba(0,0,0,0.2);
backdrop-filter: blur(15px);
-webkit-backdrop-filter: blur(15px);
overflow: hidden;
} }
.project-visual { min-height: 360px; border-radius: var(--radius-lg); }
.chat-header { .project-visual::before, .project-thumb::before {
padding: 1.5rem; content: "";
border-bottom: 1px solid rgba(0, 0, 0, 0.05); position: absolute;
background: rgba(255, 255, 255, 0.5); inset: 0;
font-weight: 700; z-index: -1;
font-size: 1.1rem; background: linear-gradient(135deg, rgba(255,255,255,.16), transparent 45%), radial-gradient(circle at 18% 18%, rgba(255,255,255,.24), transparent 28%);
display: flex;
justify-content: space-between;
align-items: center;
} }
.real-estate-visual { background: radial-gradient(circle at 20% 20%, rgba(255,255,255,.28), transparent 30%), linear-gradient(135deg, #101010, #2f332f 50%, #090909); }
.chat-messages { .visual-orb {
flex: 1; position: absolute;
overflow-y: auto; width: 190px;
padding: 1.5rem; height: 190px;
display: flex; right: 8%;
flex-direction: column; top: 8%;
gap: 1.25rem; border-radius: 50%;
background: radial-gradient(circle at 35% 30%, #fff, #8d9388 45%, #222 74%);
filter: blur(.1px);
opacity: .72;
} }
.mock-browser {
/* Custom Scrollbar */ position: absolute;
::-webkit-scrollbar { left: 8%;
width: 6px; bottom: 10%;
width: min(72%, 420px);
min-height: 250px;
padding: 1rem;
border-radius: 18px;
background: rgba(255,255,255,.09);
border: 1px solid rgba(255,255,255,.18);
box-shadow: 0 25px 80px rgba(0,0,0,.34);
backdrop-filter: blur(16px);
} }
.mock-top { display: flex; gap: .35rem; margin-bottom: 1rem; }
::-webkit-scrollbar-track { .mock-top span { width: .58rem; height: .58rem; border-radius: 50%; background: rgba(255,255,255,.55); }
background: transparent; .mock-hero { height: 92px; border-radius: 14px; background: linear-gradient(135deg, rgba(255,255,255,.72), rgba(255,255,255,.12)); }
.mock-lines { display: grid; gap: .6rem; margin-top: 1rem; }
.mock-lines i { display: block; height: .55rem; border-radius: 999px; background: rgba(255,255,255,.22); }
.mock-lines i:nth-child(2) { width: 72%; }
.mock-lines i:nth-child(3) { width: 54%; }
.project-visual > strong {
position: absolute;
right: 1rem;
bottom: .65rem;
font-family: "Instrument Serif", Georgia, serif;
font-size: clamp(4rem, 10vw, 8rem);
font-weight: 400;
line-height: 1;
letter-spacing: -.06em;
color: rgba(255,255,255,.92);
} }
.project-content { padding: clamp(.25rem, 2vw, 1rem); }
::-webkit-scrollbar-thumb { .project-type {
background: rgba(255, 255, 255, 0.3); display: inline-flex;
border-radius: 10px; align-items: center;
width: fit-content;
color: var(--muted);
border: 1px solid var(--line);
border-radius: 999px;
padding: .42rem .68rem;
font-size: .74rem;
font-weight: 750;
letter-spacing: .11em;
text-transform: uppercase;
} }
.project-content h3, .project-card h3 {
::-webkit-scrollbar-thumb:hover { font-family: "Instrument Serif", Georgia, serif;
background: rgba(255, 255, 255, 0.5); font-weight: 400;
letter-spacing: -.035em;
line-height: 1;
} }
.project-content h3 { font-size: clamp(2.5rem, 5vw, 5rem); margin: 1.3rem 0 1rem; }
.message { .project-card h3 { font-size: clamp(1.9rem, 3vw, 2.55rem); margin: 1rem 0 .75rem; }
max-width: 85%; .project-content p, .project-card p { color: var(--muted); line-height: 1.72; }
padding: 0.85rem 1.1rem; .project-metric {
border-radius: 16px; display: inline-grid;
line-height: 1.5; grid-template-columns: auto 1fr;
font-size: 0.95rem; gap: .7rem;
box-shadow: 0 4px 15px rgba(0,0,0,0.05); align-items: center;
animation: fadeIn 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); margin: .4rem 0 1.4rem;
} }
.project-metric strong { font-family: "Instrument Serif", Georgia, serif; font-size: 3.2rem; font-weight: 400; line-height: 1; }
@keyframes fadeIn { .project-metric span { color: var(--muted); max-width: 170px; line-height: 1.2; }
from { opacity: 0; transform: translateY(20px) scale(0.95); } .project-link {
to { opacity: 1; transform: translateY(0) scale(1); } display: inline-flex;
align-items: center;
gap: .4rem;
color: var(--text);
font-weight: 750;
text-decoration: none;
border-bottom: 1px solid var(--line-strong);
padding-bottom: .2rem;
} }
.project-link::after { content: "↗"; font-size: .86rem; transition: transform .18s ease; }
.message.visitor { .project-link:hover::after { transform: translate(2px, -2px); }
align-self: flex-end; .project-card { padding: 1rem; transition: transform .2s ease, border-color .2s ease; }
background: linear-gradient(135deg, #212529 0%, #343a40 100%); .project-card:hover { transform: translateY(-5px); border-color: var(--line-strong); }
color: #fff; .project-thumb { min-height: 190px; border-radius: var(--radius-md); margin-bottom: 1rem; display: grid; place-items: center; }
border-bottom-right-radius: 4px; .project-thumb span {
position: relative;
z-index: 2;
font-family: "Instrument Serif", Georgia, serif;
font-size: clamp(3rem, 7vw, 5rem);
line-height: 1;
letter-spacing: -.06em;
color: rgba(255,255,255,.94);
} }
.project-thumb i {
.message.bot { position: absolute;
align-self: flex-start; width: 130px;
background: #ffffff; height: 130px;
color: #212529; border-radius: 28px;
border-bottom-left-radius: 4px; background: linear-gradient(145deg, rgba(255,255,255,.7), rgba(255,255,255,.04));
transform: rotate(18deg) translate(28px, 12px);
box-shadow: 0 30px 70px rgba(0,0,0,.26);
} }
.fitflow-visual { background: linear-gradient(135deg, #101010, #24352d 48%, #0d1611); }
.chat-input-area { .community-visual { background: radial-gradient(circle at 25% 20%, rgba(255,255,255,.25), transparent 28%), linear-gradient(135deg, #111, #29323a 55%, #090b0d); }
padding: 1.25rem; .ocr-visual { background: linear-gradient(135deg, #0a0a0a, #263144 52%, #0c0e16); }
background: rgba(255, 255, 255, 0.5); .moto-visual { background: radial-gradient(circle at 75% 18%, rgba(255,255,255,.26), transparent 26%), linear-gradient(135deg, #111, #3a3028 52%, #100d0a); }
border-top: 1px solid rgba(0, 0, 0, 0.05); .testimonial-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1rem; margin-top: 2rem; }
.testimonial-card { padding: clamp(1.25rem, 3vw, 1.75rem); }
.testimonial-card blockquote { margin: 0; color: var(--text); font-size: clamp(1.05rem, 1.5vw, 1.28rem); line-height: 1.65; }
.testimonial-card footer { border-top: 1px solid var(--line); margin-top: 1.4rem; padding-top: 1rem; }
.testimonial-card strong { display: block; font-weight: 750; }
.testimonial-card span { display: block; color: var(--muted); font-size: .9rem; margin-top: .18rem; }
.cookie-banner {
position: fixed;
left: 1rem;
right: 1rem;
bottom: 1rem;
z-index: 1080;
max-width: 980px;
margin: 0 auto;
padding: 1rem;
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
box-shadow: 0 24px 80px rgba(0,0,0,.28);
opacity: 0;
transform: translateY(18px);
pointer-events: none;
transition: opacity .22s ease, transform .22s ease;
} }
.cookie-banner.is-visible { opacity: 1; transform: translateY(0); pointer-events: auto; }
.chat-input-area form { .cookie-banner strong { display: block; margin-bottom: .3rem; }
display: flex; .cookie-banner p { color: var(--muted); margin: 0; line-height: 1.55; font-size: .92rem; }
gap: 0.75rem; .cookie-actions { display: flex; flex-wrap: wrap; gap: .55rem; justify-content: flex-end; min-width: max-content; }
@media (max-width: 991.98px) {
.project-featured { grid-template-columns: 1fr; }
.testimonial-grid { grid-template-columns: 1fr; }
.cookie-banner { align-items: flex-start; flex-direction: column; }
.cookie-actions { width: 100%; justify-content: flex-start; min-width: 0; }
} }
@media (max-width: 575.98px) {
.chat-input-area input { .project-visual { min-height: 280px; }
flex: 1; .mock-browser { width: 78%; min-height: 210px; }
border: 1px solid rgba(0, 0, 0, 0.1); .project-metric { grid-template-columns: 1fr; }
border-radius: 12px; .cookie-actions .btn { width: 100%; }
padding: 0.75rem 1rem;
outline: none;
background: rgba(255, 255, 255, 0.9);
transition: all 0.3s ease;
} }
.chat-input-area input:focus {
border-color: #23a6d5;
box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.2);
}
.chat-input-area button {
background: #212529;
color: #fff;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 12px;
cursor: pointer;
font-weight: 600;
transition: all 0.3s ease;
}
.chat-input-area button:hover {
background: #000;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
}
/* Background Animations */
.bg-animations {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
overflow: hidden;
pointer-events: none;
}
.blob {
position: absolute;
width: 500px;
height: 500px;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
filter: blur(80px);
animation: move 20s infinite alternate cubic-bezier(0.45, 0, 0.55, 1);
}
.blob-1 {
top: -10%;
left: -10%;
background: rgba(238, 119, 82, 0.4);
}
.blob-2 {
bottom: -10%;
right: -10%;
background: rgba(35, 166, 213, 0.4);
animation-delay: -7s;
width: 600px;
height: 600px;
}
.blob-3 {
top: 40%;
left: 30%;
background: rgba(231, 60, 126, 0.3);
animation-delay: -14s;
width: 450px;
height: 450px;
}
@keyframes move {
0% { transform: translate(0, 0) rotate(0deg) scale(1); }
33% { transform: translate(150px, 100px) rotate(120deg) scale(1.1); }
66% { transform: translate(-50px, 200px) rotate(240deg) scale(0.9); }
100% { transform: translate(0, 0) rotate(360deg) scale(1); }
}
.header-link {
font-size: 14px;
color: #fff;
text-decoration: none;
background: rgba(0, 0, 0, 0.2);
padding: 0.5rem 1rem;
border-radius: 8px;
transition: all 0.3s ease;
}
.header-link:hover {
background: rgba(0, 0, 0, 0.4);
text-decoration: none;
}
/* Admin Styles */
.admin-container {
max-width: 900px;
margin: 3rem auto;
padding: 2.5rem;
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-radius: 24px;
box-shadow: 0 20px 50px rgba(0,0,0,0.15);
border: 1px solid rgba(255, 255, 255, 0.4);
position: relative;
z-index: 1;
}
.admin-container h1 {
margin-top: 0;
color: #212529;
font-weight: 800;
}
.table {
width: 100%;
border-collapse: separate;
border-spacing: 0 8px;
margin-top: 1.5rem;
}
.table th {
background: transparent;
border: none;
padding: 1rem;
color: #6c757d;
font-weight: 600;
text-transform: uppercase;
font-size: 0.75rem;
letter-spacing: 1px;
}
.table td {
background: #fff;
padding: 1rem;
border: none;
}
.table tr td:first-child { border-radius: 12px 0 0 12px; }
.table tr td:last-child { border-radius: 0 12px 12px 0; }
.form-group {
margin-bottom: 1.25rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: 600;
font-size: 0.9rem;
}
.form-control {
width: 100%;
padding: 0.75rem 1rem;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 12px;
background: #fff;
transition: all 0.3s ease;
box-sizing: border-box;
}
.form-control:focus {
outline: none;
border-color: #23a6d5;
box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.1);
}
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
}
.header-links {
display: flex;
gap: 1rem;
}
.admin-card {
background: rgba(255, 255, 255, 0.6);
padding: 2rem;
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.5);
margin-bottom: 2.5rem;
box-shadow: 0 10px 30px rgba(0,0,0,0.05);
}
.admin-card h3 {
margin-top: 0;
margin-bottom: 1.5rem;
font-weight: 700;
}
.btn-delete {
background: #dc3545;
color: white;
border: none;
padding: 0.25rem 0.5rem;
border-radius: 4px;
cursor: pointer;
}
.btn-add {
background: #212529;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
margin-top: 1rem;
}
.btn-save {
background: #0088cc;
color: white;
border: none;
padding: 0.8rem 1.5rem;
border-radius: 12px;
cursor: pointer;
font-weight: 600;
width: 100%;
transition: all 0.3s ease;
}
.webhook-url {
font-size: 0.85em;
color: #555;
margin-top: 0.5rem;
}
.history-table-container {
overflow-x: auto;
background: rgba(255, 255, 255, 0.4);
padding: 1rem;
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.3);
}
.history-table {
width: 100%;
}
.history-table-time {
width: 15%;
white-space: nowrap;
font-size: 0.85em;
color: #555;
}
.history-table-user {
width: 35%;
background: rgba(255, 255, 255, 0.3);
border-radius: 8px;
padding: 8px;
}
.history-table-ai {
width: 50%;
background: rgba(255, 255, 255, 0.5);
border-radius: 8px;
padding: 8px;
}
.no-messages {
text-align: center;
color: #777;
}

1
assets/css/fonts.css Normal file
View File

@ -0,0 +1 @@
@import url('https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Inter:wght@400;500;600;700&display=swap');

13
assets/images/favicon.svg Normal file
View File

@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="pg Web e APP Designer">
<defs>
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#ffffff"/>
<stop offset="0.52" stop-color="#b8c3b2"/>
<stop offset="1" stop-color="#30342f"/>
</linearGradient>
</defs>
<rect width="64" height="64" rx="18" fill="#080808"/>
<circle cx="46" cy="18" r="12" fill="url(#g)" opacity="0.92"/>
<path d="M13 43 C19 25 28 17 43 13 C35 24 28 34 25 51 Z" fill="url(#g)" opacity="0.58"/>
<text x="13" y="39" fill="#ffffff" font-family="Georgia, serif" font-size="24" font-weight="400" letter-spacing="-2">pg</text>
</svg>

After

Width:  |  Height:  |  Size: 693 B

View File

@ -1,39 +1,122 @@
document.addEventListener('DOMContentLoaded', () => { (() => {
const chatForm = document.getElementById('chat-form'); const root = document.body;
const chatInput = document.getElementById('chat-input'); const themeToggle = document.getElementById('themeToggle');
const chatMessages = document.getElementById('chat-messages'); const storedTheme = localStorage.getItem('pg-theme');
if (storedTheme === 'light' || storedTheme === 'dark') {
root.setAttribute('data-bs-theme', storedTheme);
}
const syncThemeButton = () => {
if (!themeToggle) return;
themeToggle.textContent = root.getAttribute('data-bs-theme') === 'dark' ? 'Modo claro' : 'Modo escuro';
};
syncThemeButton();
themeToggle?.addEventListener('click', () => {
const next = root.getAttribute('data-bs-theme') === 'dark' ? 'light' : 'dark';
root.setAttribute('data-bs-theme', next);
localStorage.setItem('pg-theme', next);
syncThemeButton();
});
const appendMessage = (text, sender) => { const video = document.getElementById('heroVideo');
const msgDiv = document.createElement('div'); let rafId = null;
msgDiv.classList.add('message', sender); const fadeWindow = 0.5;
msgDiv.textContent = text; const monitorVideo = () => {
chatMessages.appendChild(msgDiv); if (video && Number.isFinite(video.duration) && video.duration > 0) {
chatMessages.scrollTop = chatMessages.scrollHeight; const current = video.currentTime || 0;
}; const duration = video.duration;
let opacity = 1;
chatForm.addEventListener('submit', async (e) => { if (current < fadeWindow) opacity = current / fadeWindow;
e.preventDefault(); if (duration - current < fadeWindow) opacity = Math.max(0, (duration - current) / fadeWindow);
const message = chatInput.value.trim(); video.style.opacity = String(Math.max(0, Math.min(1, opacity * 0.7)));
if (!message) return; }
rafId = requestAnimationFrame(monitorVideo);
appendMessage(message, 'visitor'); };
chatInput.value = ''; if (video) {
video.addEventListener('loadedmetadata', () => {
try { video.currentTime = 0;
const response = await fetch('api/chat.php', { video.play().catch(() => {});
method: 'POST', if (!rafId) monitorVideo();
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message })
});
const data = await response.json();
// Artificial delay for realism
setTimeout(() => {
appendMessage(data.reply, 'bot');
}, 500);
} catch (error) {
console.error('Error:', error);
appendMessage("Sorry, something went wrong. Please try again.", 'bot');
}
}); });
}); video.addEventListener('ended', () => {
video.style.opacity = '0';
window.setTimeout(() => {
video.currentTime = 0;
video.play().catch(() => {});
}, 100);
});
video.load();
}
const parallaxItems = [video, document.querySelector('.stats-panel')].filter(Boolean);
const handleParallax = () => {
const y = window.scrollY || 0;
parallaxItems.forEach((item, idx) => {
const speed = idx === 0 ? 0.08 : -0.025;
item.style.transform = `translate3d(0, ${y * speed}px, 0)`;
});
};
if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
window.addEventListener('scroll', handleParallax, { passive: true });
handleParallax();
}
const form = document.getElementById('contactForm');
const alertBox = document.getElementById('formAlert');
const submitBtn = document.getElementById('submitBtn');
const toastEl = document.getElementById('siteToast');
const toastBody = document.getElementById('toastBody');
const showAlert = (type, message) => {
if (!alertBox) return;
alertBox.className = `alert alert-${type}`;
alertBox.textContent = message;
};
const showToast = (message) => {
if (toastBody) toastBody.textContent = message;
if (toastEl && window.bootstrap) new bootstrap.Toast(toastEl).show();
};
form?.addEventListener('submit', async (event) => {
event.preventDefault();
showAlert('secondary', 'A enviar o teu pedido...');
if (submitBtn) {
submitBtn.disabled = true;
submitBtn.textContent = 'A enviar...';
}
try {
const response = await fetch('api/contact.php', {
method: 'POST',
headers: { 'Accept': 'application/json' },
body: new FormData(form)
});
const data = await response.json().catch(() => ({}));
if (!response.ok || !data.success) {
showAlert('warning', data.message || 'Não foi possível enviar. Revê os campos e tenta novamente.');
return;
}
form.reset();
showAlert('success', data.message || 'Mensagem enviada com sucesso.');
showToast('Pedido recebido. Obrigado!');
} catch (error) {
showAlert('danger', 'Erro de ligação. Tenta novamente dentro de instantes.');
} finally {
if (submitBtn) {
submitBtn.disabled = false;
submitBtn.textContent = 'Enviar mensagem';
}
}
});
const cookieBanner = document.getElementById("cookieBanner");
const cookieAccept = document.getElementById("cookieAccept");
const cookieDecline = document.getElementById("cookieDecline");
const cookieChoice = localStorage.getItem("pg-cookie-choice");
const closeCookieBanner = (choice) => {
localStorage.setItem("pg-cookie-choice", choice);
cookieBanner?.classList.remove("is-visible");
};
if (cookieBanner && !cookieChoice) {
window.setTimeout(() => cookieBanner.classList.add("is-visible"), 650);
}
cookieAccept?.addEventListener("click", () => closeCookieBanner("accepted"));
cookieDecline?.addEventListener("click", () => closeCookieBanner("declined"));
})();

470
index.php
View File

@ -1,150 +1,346 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
@ini_set('display_errors', '1'); @date_default_timezone_set('Europe/Lisbon');
@error_reporting(E_ALL); $projectName = $_SERVER['PROJECT_NAME'] ?? 'pg Web e APP Designer';
@date_default_timezone_set('UTC'); $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Design e desenvolvimento de websites, aplicações web e mobile em Portugal.';
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
$phpVersion = PHP_VERSION;
$now = date('Y-m-d H:i:s'); $now = date('Y-m-d H:i:s');
?> ?>
<!doctype html> <!doctype html>
<html lang="en"> <html lang="pt-PT">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>New Style</title> <title><?= htmlspecialchars($projectName, ENT_QUOTES, 'UTF-8') ?> | Websites e aplicações digitais</title>
<?php <?php if ($projectDescription): ?>
// Read project preview data from environment <meta name="description" content="<?= htmlspecialchars($projectDescription, ENT_QUOTES, 'UTF-8') ?>">
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? ''; <meta property="og:description" content="<?= htmlspecialchars($projectDescription, ENT_QUOTES, 'UTF-8') ?>">
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? ''; <meta property="twitter:description" content="<?= htmlspecialchars($projectDescription, ENT_QUOTES, 'UTF-8') ?>">
?> <?php endif; ?>
<?php if ($projectDescription): ?> <meta property="og:title" content="<?= htmlspecialchars($projectName, ENT_QUOTES, 'UTF-8') ?>">
<!-- Meta description --> <meta property="og:type" content="website">
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' /> <meta name="twitter:card" content="summary_large_image">
<!-- Open Graph meta tags --> <meta name="twitter:title" content="<?= htmlspecialchars($projectName, ENT_QUOTES, 'UTF-8') ?>">
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" /> <?php if ($projectImageUrl): ?>
<!-- Twitter meta tags --> <meta property="og:image" content="<?= htmlspecialchars($projectImageUrl, ENT_QUOTES, 'UTF-8') ?>">
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" /> <meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl, ENT_QUOTES, 'UTF-8') ?>">
<?php endif; ?> <?php endif; ?>
<?php if ($projectImageUrl): ?> <link rel="icon" type="image/svg+xml" href="assets/images/favicon.svg">
<!-- Open Graph image -->
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
<!-- Twitter image -->
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
<?php endif; ?>
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet"> <link rel="stylesheet" href="assets/css/fonts.css?v=2026062603">
<style> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
:root { <link rel="stylesheet" href="assets/css/custom.css?v=2026062603">
--bg-color-start: #6a11cb;
--bg-color-end: #2575fc;
--text-color: #ffffff;
--card-bg-color: rgba(255, 255, 255, 0.01);
--card-border-color: rgba(255, 255, 255, 0.1);
}
body {
margin: 0;
font-family: 'Inter', sans-serif;
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
color: var(--text-color);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
text-align: center;
overflow: hidden;
position: relative;
}
body::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
animation: bg-pan 20s linear infinite;
z-index: -1;
}
@keyframes bg-pan {
0% { background-position: 0% 0%; }
100% { background-position: 100% 100%; }
}
main {
padding: 2rem;
}
.card {
background: var(--card-bg-color);
border: 1px solid var(--card-border-color);
border-radius: 16px;
padding: 2rem;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
}
.loader {
margin: 1.25rem auto 1.25rem;
width: 48px;
height: 48px;
border: 3px solid rgba(255, 255, 255, 0.25);
border-top-color: #fff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.hint {
opacity: 0.9;
}
.sr-only {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap; border: 0;
}
h1 {
font-size: 3rem;
font-weight: 700;
margin: 0 0 1rem;
letter-spacing: -1px;
}
p {
margin: 0.5rem 0;
font-size: 1.1rem;
}
code {
background: rgba(0,0,0,0.2);
padding: 2px 6px;
border-radius: 4px;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
footer {
position: absolute;
bottom: 1rem;
font-size: 0.8rem;
opacity: 0.7;
}
</style>
</head> </head>
<body> <body data-bs-theme="dark">
<main> <a class="skip-link" href="#conteudo">Saltar para o conteúdo</a>
<div class="card">
<h1>Analyzing your requirements and generating your website…</h1> <div class="site-shell">
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes"> <video id="heroVideo" class="hero-video" muted playsinline preload="metadata" aria-hidden="true">
<span class="sr-only">Loading…</span> <source src="https://d8j0ntlcm91z4.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/hf_20260328_083109_283f3553-e28f-428b-a723-d639c617eb2b.mp4" type="video/mp4">
</video>
<div class="video-scrim" aria-hidden="true"></div>
<header class="site-header sticky-top">
<nav class="navbar navbar-expand-lg py-3" aria-label="Navegação principal">
<div class="container-xl px-3 px-lg-4">
<a class="navbar-brand brand-mark" href="#home" aria-label="pg Web e APP Designer início">pg Web e APP Designer<sup>®</sup></a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#mainNav" aria-controls="mainNav" aria-expanded="false" aria-label="Abrir navegação">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="mainNav">
<ul class="navbar-nav mx-auto mb-2 mb-lg-0 gap-lg-2">
<li class="nav-item"><a class="nav-link active" href="#home">Início</a></li>
<li class="nav-item"><a class="nav-link" href="#servicos">Serviços</a></li>
<li class="nav-item"><a class="nav-link" href="#portfolio">Portfólio</a></li>
<li class="nav-item"><a class="nav-link" href="#ferramentas">Ferramentas</a></li>
<li class="nav-item"><a class="nav-link" href="#processo">Processo</a></li>
<li class="nav-item"><a class="nav-link" href="#testemunhos">Testemunhos</a></li>
<li class="nav-item"><a class="nav-link" href="#contacto">Contacto</a></li>
</ul>
<div class="d-flex align-items-center gap-2">
<button id="themeToggle" class="btn btn-ghost btn-sm" type="button" aria-label="Alternar dark mode">Modo claro</button>
<a class="btn btn-primary-dark" href="#contacto">Começar projecto</a>
</div>
</div>
</div>
</nav>
</header>
<main id="conteudo">
<section id="home" class="hero-section min-vh-100 d-flex align-items-center">
<div class="container-xl px-3 px-lg-4 position-relative z-2">
<div class="row justify-content-center text-center">
<div class="col-12 col-xl-11">
<p class="eyebrow animate-rise">Websites · Web Apps · Mobile UI</p>
<h1 class="hero-title animate-rise">Da ideia ao produto digital: websites e apps com presença, clareza e performance.</h1>
<p class="hero-copy animate-rise delay-1 mx-auto">Crio experiências digitais para marcas, freelancers e negócios que querem transformar uma primeira impressão num pedido de contacto real.</p>
<div class="hero-actions animate-rise delay-2 d-flex flex-column flex-sm-row justify-content-center gap-3">
<a class="btn btn-primary-dark btn-lg" href="#contacto">Começar projecto</a>
<a class="btn btn-outline-soft btn-lg" href="#servicos">Ver serviços</a>
</div>
</div>
</div>
<div class="stats-panel animate-rise delay-2" aria-label="Métricas do estúdio">
<div><strong>8+</strong><span>projectos entregues</span></div>
<div><strong>3+</strong><span>anos de experiência</span></div>
<div><strong>5+</strong><span>clientes satisfeitos</span></div>
</div>
</div>
</section>
<section id="servicos" class="section-pad">
<div class="container-xl px-3 px-lg-4">
<div class="section-heading">
<p class="eyebrow">O que eu faço</p>
<h2>Uma entrega completa, pensada para lançar e evoluir o teu produto digital.</h2>
<p>Da estratégia à manutenção, cada fase é tratada com detalhe para que o resultado seja bonito, rápido e fácil de usar.</p>
</div>
<div class="row g-3 g-lg-4">
<?php
$services = [
['01', 'Estratégia Digital', 'Alinhamento de objectivos, público, mensagens principais e prioridades para criar um plano simples de execução.'],
['02', 'Branding de Marca', 'Desde a ideia inicial até ao tom visual: nome, posicionamento, referências, identidade e coerência para o digital.'],
['03', 'UX/UI Design', 'Desenho de fluxos, wireframes e interfaces responsivas com foco em conversão, leitura e experiência.'],
['04', 'Websites OnePage', 'Páginas rápidas e elegantes para apresentar serviços, captar leads e comunicar confiança desde o primeiro scroll.'],
['05', 'Aplicações Web', 'Dashboards, áreas reservadas, formulários e ferramentas à medida com lógica de negócio clara e segura.'],
['06', 'Mobile UI', 'Interfaces para apps mobile com navegação intuitiva, componentes consistentes e adaptação a vários ecrãs.'],
['07', 'SEO Técnico', 'Estrutura semântica, performance, metadados e boas práticas para melhorar a descoberta orgânica.'],
['08', 'Suporte e Manutenção', 'Acompanhamento após entrega: correcções, melhorias, backups, actualizações e evolução contínua.'],
];
foreach ($services as $service): ?>
<div class="col-12 col-md-6 col-xl-3">
<article class="service-card h-100">
<span><?= htmlspecialchars($service[0], ENT_QUOTES, 'UTF-8') ?></span>
<h3><?= htmlspecialchars($service[1], ENT_QUOTES, 'UTF-8') ?></h3>
<p><?= htmlspecialchars($service[2], ENT_QUOTES, 'UTF-8') ?></p>
</article>
</div>
<?php endforeach; ?>
</div>
</div>
</section>
<section id="portfolio" class="section-pad portfolio-section">
<div class="container-xl px-3 px-lg-4">
<div class="section-heading">
<p class="eyebrow">Portfólio</p>
<h2>Projectos digitais com foco em clareza, confiança e resultados.</h2>
<p>Uma selecção curta com exactamente os projectos principais: website, web apps, comunidade e IA. Sem projectos extra.</p>
</div>
<article class="project-featured" aria-labelledby="portfolio-veigas-title">
<div class="project-visual real-estate-visual" aria-hidden="true">
<span class="visual-orb"></span>
<div class="mock-browser">
<div class="mock-top"><span></span><span></span><span></span></div>
<div class="mock-hero"></div>
<div class="mock-lines"><i></i><i></i><i></i></div>
</div>
<strong>+320%</strong>
</div>
<div class="project-content">
<span class="project-type">Web site em destaque</span>
<h3 id="portfolio-veigas-title">Agência Imobiliária da Veigas</h3>
<p>Redesenho completo do web site e da experiência digital para tornar a presença online mais rápida, clara e orientada à captação de contactos.</p>
<div class="project-metric"><strong>+320%</strong><span>aumento de tráfego gerado</span></div>
<a class="project-link" href="https://imoveisdeportugal.pt/" target="_blank" rel="noopener">Ver projecto</a>
</div>
</article>
<div class="row g-3 g-lg-4 mt-4" aria-label="Outros projectos do portfólio">
<div class="col-12 col-md-6 col-xl-3">
<article class="project-card h-100">
<div class="project-thumb fitflow-visual" aria-hidden="true"><span>FF</span><i></i></div>
<span class="project-type">Web App</span>
<h3>FitFlow</h3>
<p>Experiência digital para acompanhar rotinas, progresso e fluxos de treino com uma interface limpa.</p>
<a class="project-link" href="https://fitflow.paulogomes.pt/" target="_blank" rel="noopener">Abrir Web App</a>
</article>
</div>
<div class="col-12 col-md-6 col-xl-3">
<article class="project-card h-100">
<div class="project-thumb community-visual" aria-hidden="true"><span>?</span><i></i></div>
<span class="project-type">Comunidade</span>
<h3>Perdidos e Achados</h3>
<p>Plataforma comunitária para aproximar pessoas, objectos perdidos e informação útil num lugar.</p>
<a class="project-link" href="https://perdidoseachados.paulogomes.pt/" target="_blank" rel="noopener">Ver comunidade</a>
</article>
</div>
<div class="col-12 col-md-6 col-xl-3">
<article class="project-card h-100">
<div class="project-thumb ocr-visual" aria-hidden="true"><span>OCR</span><i></i></div>
<span class="project-type">Web site de IA</span>
<h3>Imagem com texto: OCR to Image</h3>
<p>Interface orientada à conversão de imagens com texto em conteúdo legível, simples e directo.</p>
<a class="project-link" href="https://ocrtoimage.paulogomes.pt/" target="_blank" rel="noopener">Ver web site de IA</a>
</article>
</div>
<div class="col-12 col-md-6 col-xl-3">
<article class="project-card h-100">
<div class="project-thumb moto-visual" aria-hidden="true"><span></span><i></i></div>
<span class="project-type">Web App</span>
<h3>Moto trips - As minhas aventuras sobre rodas</h3>
<p>Web app para organizar viagens, memórias e aventuras em duas rodas com estética editorial.</p>
<a class="project-link" href="https://mototrips.paulogomes.pt/" target="_blank" rel="noopener">Ver Moto trips</a>
</article>
</div>
</div>
</div>
</section>
<section id="ferramentas" class="section-pad section-quiet">
<div class="container-xl px-3 px-lg-4">
<div class="row align-items-end g-4">
<div class="col-lg-6">
<p class="eyebrow">Ferramentas de desenvolvimento</p>
<h2 class="section-title">Stack moderna, leve e preparada para produção.</h2>
</div>
<div class="col-lg-6">
<p class="section-copy">Escolho tecnologia consoante o projecto: protótipos rápidos, websites em alojamento próprio, aplicações React ou integrações com PHP/MySQL.</p>
</div>
</div>
<div class="tool-grid mt-4">
<span>HTML5</span><span>CSS3</span><span>JavaScript</span><span>TypeScript</span><span>React</span><span>Vite</span><span>Tailwind CSS</span><span>Bootstrap</span><span>PHP</span><span>MySQL</span><span>WordPress</span><span>Git</span>
</div>
</div>
</section>
<section id="processo" class="section-pad">
<div class="container-xl px-3 px-lg-4">
<div class="section-heading compact">
<p class="eyebrow">Processo</p>
<h2>Um caminho simples para sair da ideia e chegar ao lançamento.</h2>
</div>
<div class="timeline">
<div><strong>01</strong><h3>Descoberta</h3><p>Entendo o negócio, objectivos, referências e restrições.</p></div>
<div><strong>02</strong><h3>Direcção</h3><p>Defino estrutura, conteúdo, navegação e prioridades visuais.</p></div>
<div><strong>03</strong><h3>Construção</h3><p>Implemento, testo responsividade e preparo a publicação.</p></div>
<div><strong>04</strong><h3>Evolução</h3><p>Recolho feedback e planeio melhorias após o lançamento.</p></div>
</div>
</div>
</section>
<section id="testemunhos" class="section-pad testimonials-section section-quiet">
<div class="container-xl px-3 px-lg-4">
<div class="section-heading compact">
<p class="eyebrow">Testemunhos</p>
<h2>Experiências digitais criadas com proximidade, detalhe e foco no resultado.</h2>
</div>
<div class="testimonial-grid">
<article class="testimonial-card">
<blockquote>"O redesenho tornou a navegação mais simples, a marca mais clara e a comunicação muito mais profissional."</blockquote>
<footer><strong>Cliente de imobiliário</strong><span>Website e experiência digital</span></footer>
</article>
<article class="testimonial-card">
<blockquote>"A página ficou rápida, bonita e fácil de usar. O processo foi directo e sempre bem explicado."</blockquote>
<footer><strong>Fundador de comunidade</strong><span>Produto digital e lançamento</span></footer>
</article>
<article class="testimonial-card">
<blockquote>"Transformou uma ideia pouco estruturada numa web app com identidade, fluxo claro e pronta a evoluir."</blockquote>
<footer><strong>Criador independente</strong><span>Web app à medida</span></footer>
</article>
</div>
</div>
</section>
<section id="contacto" class="section-pad contact-section">
<div class="container-xl px-3 px-lg-4">
<div class="row g-4 g-xl-5 align-items-stretch">
<div class="col-lg-5">
<div class="contact-card h-100">
<p class="eyebrow">Contacto</p>
<h2>Vamos falar sobre o teu próximo projecto?</h2>
<p class="section-copy">Envia uma mensagem com o essencial. Recebes resposta para marcar uma conversa e percebermos o melhor ponto de partida.</p>
<ul class="contact-list">
<li><span>Email</span><a href="mailto:ola@paulogomes.pt">ola@paulogomes.pt</a></li>
<li><span>Telemóvel</span><a href="tel:+351917890864">+351 917 890 864</a><small><em>Chamada para a rede móvel nacional</em></small></li>
<li><span>Localização</span><strong>Almada - Portugal</strong></li>
</ul>
</div>
</div>
<div class="col-lg-7">
<form id="contactForm" class="form-card" novalidate>
<div class="d-flex justify-content-between align-items-start gap-3 mb-4">
<div>
<p class="eyebrow mb-2">Pedido rápido</p>
<h2 class="h3 mb-0">Contacto</h2>
</div>
<span class="secure-pill">Resposta por email</span>
</div>
<div id="formAlert" class="alert d-none" role="alert" aria-live="polite"></div>
<input type="text" name="website" class="visually-hidden" tabindex="-1" autocomplete="off" aria-hidden="true">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label" for="name">Nome</label>
<input class="form-control" id="name" name="name" type="text" autocomplete="name" maxlength="120" required>
</div>
<div class="col-md-6">
<label class="form-label" for="email">Email</label>
<input class="form-control" id="email" name="email" type="email" autocomplete="email" maxlength="160" required>
</div>
<div class="col-md-6">
<label class="form-label" for="phone">Telemóvel <span>opcional</span></label>
<input class="form-control" id="phone" name="phone" type="tel" autocomplete="tel" maxlength="40">
</div>
<div class="col-md-6">
<label class="form-label" for="project_type">Tipo de projecto</label>
<select class="form-select" id="project_type" name="project_type" required>
<option value="">Seleccionar</option>
<option>Website institucional</option>
<option>Landing page</option>
<option>Aplicação web</option>
<option>Aplicação mobile</option>
<option>Branding de Marca</option>
<option>Manutenção</option>
</select>
</div>
<div class="col-12">
<label class="form-label" for="message">Detalhe da mensagem</label>
<textarea class="form-control" id="message" name="message" rows="5" maxlength="1800" placeholder="Conta-me o que queres construir, prazo e objectivo principal." required></textarea>
<div class="form-text">Mínimo 20 caracteres. Não incluas palavras-passe ou dados sensíveis.</div>
</div>
<div class="col-12 d-flex flex-column flex-sm-row gap-3 align-items-sm-center justify-content-between pt-2">
<button id="submitBtn" class="btn btn-primary-dark btn-lg" type="submit">Enviar mensagem</button>
<small class="muted-note">Ao enviar, o pedido é guardado e enviado por email para pg Web e APP Designer.</small>
</div>
</div>
</form>
</div>
</div>
</div>
</section>
</main>
<footer class="site-footer">
<div class="container-xl px-3 px-lg-4 d-flex flex-column flex-md-row justify-content-between gap-2">
<span>© <?= date('Y') ?> pg Web e APP Designer. Todos os direitos reservados.</span>
<span>PHP <?= htmlspecialchars(PHP_VERSION, ENT_QUOTES, 'UTF-8') ?> · Lisboa <?= htmlspecialchars($now, ENT_QUOTES, 'UTF-8') ?></span>
</div>
</footer>
</div>
<div class="toast-container position-fixed bottom-0 end-0 p-3">
<div id="siteToast" class="toast align-items-center" role="status" aria-live="polite" aria-atomic="true">
<div class="d-flex">
<div id="toastBody" class="toast-body">Mensagem enviada.</div>
<button type="button" class="btn-close me-2 m-auto" data-bs-dismiss="toast" aria-label="Fechar"></button>
</div> </div>
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
<p class="hint">This page will update automatically as the plan is implemented.</p>
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
</div> </div>
</main> </div>
<footer>
Page updated: <?= htmlspecialchars($now) ?> (UTC) <div id="cookieBanner" class="cookie-banner" role="dialog" aria-live="polite" aria-label="Aviso de cookies">
</footer> <div>
<strong>Privacidade e cookies</strong>
<p>Este website usa cookies e dados técnicos para compreender como é utilizado, melhorar a experiência, medir desempenho e ajudar a encontrar problemas de navegação. Não vendemos dados pessoais.</p>
</div>
<div class="cookie-actions">
<button id="cookieDecline" class="btn btn-outline-soft btn-sm" type="button">Continuar sem aceitar</button>
<button id="cookieAccept" class="btn btn-primary-dark btn-sm" type="button">Aceitar</button>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js?v=2026062603" defer></script>
</body> </body>
</html> </html>

4
robots.txt Normal file
View File

@ -0,0 +1,4 @@
User-agent: *
Allow: /
Sitemap: https://pg-web-e-app-designer-8ee2.dev.flatlogic.app/sitemap.xml

9
sitemap.xml Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://pg-web-e-app-designer-8ee2.dev.flatlogic.app/</loc>
<lastmod>2026-06-26</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
</urlset>