Evidência de código

Código e Evidências de API

Esta página exibe o código real do plugin e dos MU plugins criados para este portfólio WordPress, com comentários em inglês e um exemplo seguro de integração com a API do Google Calendar usando variáveis de ambiente.

Toolkit CPTs + campos bilíngues
MU Runtime Segurança + performance
MU Cache Cache para visitantes anônimos
Exemplo de API Agendador FusionCore

API real do agendador

AgendadorController: slots, booking, aprovação, recusa e cancelamento

O bloco abaixo imprime o controller real do agendador em app.fusioncore.com.br. Ele concentra os endpoints do fluxo de agendamento, enquanto disponibilidade, persistência e Google Calendar/Meet ficam em arquivos auxiliares listados abaixo. Segredos continuam mascarados.

<?php
namespace App\Controllers;

use App\Core\Controller;
use App\Models\EmailSender;
use App\Models\MeetingScheduler;
use App\Services\GoogleCalendarMeetService;

class AgendadorController extends Controller
{
    private array $config;

    public function __construct()
    {
        parent::__construct();
        $this->config = require __DIR__ . '/../../config/meeting_scheduler.php';
    }

    public function index(): void
    {
        $lang = $this->requestLang();
        $dados = [
            'titulo' => $lang === 'en' ? 'Schedule a call' : 'Agendar call',
            'pageLang' => $lang,
            'config' => $this->config,
            'canonicalUrl' => base_url('agendador'),
        ];
        $this->view('agendador/index', $dados, 'layouts/public');
    }

    public function slots(): void
    {
        $date = $_GET['date'] ?? date('Y-m-d');
        if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
            $this->respondJson(['success' => false, 'message' => 'Data inválida.'], 422);
        }

        $slots = $this->buildSlotsForDate($date);
        $this->respondJson(['success' => true, 'date' => $date, 'slots' => $slots]);
    }

    public function booking(): void
    {
        if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
            $this->respondJson(['success' => false, 'message' => 'Método inválido.'], 405);
        }

        $input = $this->getJsonInput();
        if (!$input) {
            $input = $_POST;
        }

        $name = trim((string)($input['name'] ?? ''));
        $email = trim((string)($input['email'] ?? ''));
        $phone = trim((string)($input['phone'] ?? ''));
        $company = trim((string)($input['company'] ?? ''));
        $subject = trim((string)($input['subject'] ?? ''));
        $source = trim((string)($input['source'] ?? ($_GET['source'] ?? '')));
        $notes = trim((string)($input['notes'] ?? ''));
        $slot = trim((string)($input['slot'] ?? ''));
        $lang = $this->requestLang($input);

        if ($name === '' || !filter_var($email, FILTER_VALIDATE_EMAIL) || $slot === '') {
            $this->respondJson(['success' => false, 'message' => $lang === 'en' ? 'Enter your name, a valid email and a time slot.' : 'Informe nome, e-mail válido e horário.'], 422);
        }

        $tz = new \DateTimeZone($this->config['timezone']);
        try {
            $start = new \DateTimeImmutable($slot, $tz);
        } catch (\Throwable $e) {
            $this->respondJson(['success' => false, 'message' => $lang === 'en' ? 'Invalid time slot.' : 'Horário inválido.'], 422);
        }

        $now = new \DateTimeImmutable('now', $tz);
        $minStart = $now->modify('+' . (int)$this->config['min_notice_hours'] . ' hours');
        if ($start < $minStart || !$this->isBusinessTime($start)) {
            $this->respondJson(['success' => false, 'message' => $lang === 'en' ? 'This time slot is no longer available.' : 'Esse horário não está mais disponível.'], 409);
        }

        $end = $start->modify('+' . (int)$this->config['meeting_minutes'] . ' minutes');
        if (!$this->isRangeAvailable($start, $end)) {
            $this->respondJson(['success' => false, 'message' => $lang === 'en' ? 'This time slot was just booked. Choose another one.' : 'Esse horário acabou de ser reservado. Escolha outro.'], 409);
        }

        $model = new MeetingScheduler();
        $startSql = $start->format('Y-m-d H:i:s');
        $endSql = $end->format('Y-m-d H:i:s');
        $token = bin2hex(random_bytes(20));
        $uid = $token . '@app.fusioncore.com.br';
        $meetingUrl = $this->generateMeetingUrl($token, $start);
        $calendarUrl = $this->googleCalendarUrl($start, $end, $name, $email, $company, $notes, $meetingUrl);

        try {
            $bookingId = $model->createBooking([
                'token' => $token,
                'client_name' => $name,
                'client_email' => $email,
                'client_phone' => $phone !== '' ? $phone : null,
                'company' => $company !== '' ? $company : null,
                'subject' => $subject !== '' ? $subject : null,
                'source' => $source !== '' ? $source : null,
                'notes' => $notes !== '' ? $notes : null,
                'starts_at' => $startSql,
                'ends_at' => $endSql,
                'timezone' => $this->config['timezone'],
                'status' => 'pending',
                'meeting_url' => $meetingUrl,
                'google_calendar_url' => $calendarUrl,
                'ics_uid' => $uid,
            ]);
        } catch (\Throwable $e) {
            $this->respondJson(['success' => false, 'message' => $lang === 'en' ? 'This time slot was just booked. Choose another one.' : 'Esse horário acabou de ser reservado. Escolha outro.'], 409);
        }

        $booking = $model->find((int)$bookingId) ?: [];
        $booking['google_calendar_url'] = $calendarUrl;
        $emailResult = $this->sendApprovalRequest($booking, $start, $end);

        $this->respondJson([
            'success' => true,
            'message' => $lang === 'en' ? 'Request sent successfully.' : 'Solicitação enviada com sucesso.',
            'booking' => [
                'token' => $token,
                'date' => $start->format('d/m/Y'),
                'time' => $start->format('H:i'),
                'confirmation_url' => base_url('agendador/confirmacao/' . $token) . '?lang=' . $lang,
            ],
            'email_sent' => (bool)($emailResult['success'] ?? false),
            'email_message' => (string)($emailResult['message'] ?? ''),
        ]);
    }

    public function revisar($token = ''): void
    {
        $model = new MeetingScheduler();
        $booking = $token ? $model->findByToken((string)$token) : null;
        if (!$booking) {
            http_response_code(404);
        }

        $dados = [
            'titulo' => $booking ? 'Revisar agendamento' : 'Agendamento não encontrado',
            'config' => $this->config,
            'booking' => $booking,
            'canonicalUrl' => base_url('agendador/revisar/' . $token),
        ];
        $this->view('agendador/revisar', $dados, 'layouts/public');
    }

    public function aprovar($token = ''): void
    {
        if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
            $this->redirect(base_url('agendador/revisar/' . $token));
        }

        $model = new MeetingScheduler();
        $booking = $token ? $model->findByToken((string)$token) : null;
        if (!$booking || (string)$booking['status'] !== 'pending') {
            $this->redirect(base_url('agendador/revisar/' . $token));
        }

        $start = new \DateTimeImmutable((string)$booking['starts_at'], new \DateTimeZone($this->config['timezone']));
        $durationMinutes = $this->durationFromRequest($booking);
        $end = $start->modify('+' . $durationMinutes . ' minutes');
        $startSql = $start->format('Y-m-d H:i:s');
        $endSql = $end->format('Y-m-d H:i:s');
        if (!$this->isBusinessRange($start, $end)) {
            $_SESSION['error'] = 'A duracao escolhida ultrapassa o horario de atendimento.';
            $this->redirect(base_url('agendador/revisar/' . $token));
        }
        if (!$this->isRangeAvailable($start, $end, (string)$token)) {
            $_SESSION['error'] = 'A duracao escolhida entra em conflito com outro agendamento.';
            $this->redirect(base_url('agendador/revisar/' . $token));
        }

        $meetingUrl = trim((string)($booking['meeting_url'] ?? ''));
        $calendarUrl = '';
        $googleEventId = null;

        try {
            $google = new GoogleCalendarMeetService($this->config);
            if ($google->isConfigured() && $google->hasToken()) {
                $googleEvent = $google->createMeetEvent($booking, $start, $end);
                $meetingUrl = (string)($googleEvent['meet_link'] ?? $meetingUrl);
                $calendarUrl = (string)($googleEvent['html_link'] ?? '');
                $googleEventId = (string)($googleEvent['event_id'] ?? '');
            }
        } catch (\Throwable $e) {
            try { \App\Services\AppLogger::warning('google_meet_create_failed', ['error' => $e->getMessage(), 'token' => $token]); } catch (\Throwable $logError) {}
        }

        if ($meetingUrl === '') {
            $meetingUrl = $this->generateMeetingUrl((string)$token, $start);
        }

        if ($calendarUrl === '') {
            $calendarUrl = $this->googleCalendarUrl(
                $start,
                $end,
                (string)$booking['client_name'],
                (string)$booking['client_email'],
                (string)($booking['company'] ?? ''),
                (string)($booking['notes'] ?? ''),
                $meetingUrl
            );
        }

        $approved = $model->approve((string)$token, $meetingUrl, $calendarUrl, $googleEventId, $endSql);
        if ($approved) {
            $ics = $this->buildIcs($approved, $start, $end, (string)$approved['ics_uid']);
            $this->sendConfirmation($approved, $start, $end, $ics);
        }

        $this->redirect(base_url('agendador/admin'));
    }

    public function recusar($token = ''): void
    {
        if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
            $this->redirect(base_url('agendador/revisar/' . $token));
        }

        $model = new MeetingScheduler();
        $declined = $token ? $model->decline((string)$token) : null;
        if ($declined) {
            $this->sendDeclinedEmail($declined);
        }

        $this->redirect(base_url('agendador/admin'));
    }

    public function cancelar($token = ''): void
    {
        $this->requireAuth();
        if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
            $this->redirect(base_url('agendador/admin'));
        }

        $model = new MeetingScheduler();
        $booking = $token ? $model->findByToken((string)$token) : null;
        if (!$booking || in_array((string)$booking['status'], ['cancelled', 'declined'], true)) {
            $_SESSION['error'] = 'Agendamento nao encontrado ou ja encerrado.';
            $this->redirect(base_url('agendador/admin'));
        }

        $eventId = trim((string)($booking['google_calendar_event_id'] ?? ''));
        if ($eventId !== '') {
            try {
                $google = new GoogleCalendarMeetService($this->config);
                if ($google->isConfigured() && $google->hasToken()) {
                    $google->deleteEvent($eventId);
                }
            } catch (\Throwable $e) {
                try { \App\Services\AppLogger::warning('google_meet_cancel_failed', ['error' => $e->getMessage(), 'token' => $token]); } catch (\Throwable $logError) {}
            }
        }

        $cancelled = $model->cancel((string)$token);
        if ($cancelled) {
            $this->sendCancellationEmail($cancelled);
            $_SESSION['success'] = 'Reuniao cancelada e e-mail enviado ao lead.';
        } else {
            $_SESSION['error'] = 'Nao foi possivel cancelar este agendamento.';
        }

        $this->redirect(base_url('agendador/admin'));
    }

    public function confirmacao($token = ''): void
    {
        $lang = $this->requestLang();
        $model = new MeetingScheduler();
        $booking = $token ? $model->findByToken((string)$token) : null;
        if (!$booking) {
            http_response_code(404);
        }

        $dados = [
            'titulo' => $booking ? ($lang === 'en' ? 'Meeting request' : 'Reunião confirmada') : ($lang === 'en' ? 'Booking not found' : 'Agendamento não encontrado'),
            'pageLang' => $lang,
            'config' => $this->config,
            'booking' => $booking,
            'canonicalUrl' => base_url('agendador/confirmacao/' . $token),
        ];
        $this->view('agendador/confirmacao', $dados, 'layouts/public');
    }

    public function admin(): void
    {
        $this->requireAuth();
        $model = new MeetingScheduler();
        $googleStatus = (new GoogleCalendarMeetService($this->config))->getStatus();
        $dados = [
            'titulo' => t('scheduler.title', [], 'Meeting scheduler'),
            'user' => $this->getUserData(),
            'config' => $this->config,
            'bookings' => $model->latest(30),
            'schedulerLink' => base_url('agendador'),
            'googleStatus' => $googleStatus,
        ];
        $dados['content'] = $this->renderPartial('agendador/admin', $dados);
        $this->view('layouts/main', $dados);
    }

    public function enviar_convite(): void
    {
        $this->requireAuth();
        if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
            $this->redirect(base_url('agendador/admin'));
        }

        $recipient = trim((string)($_POST['recipient_email'] ?? ''));
        $subject = trim((string)($_POST['invite_subject'] ?? ''));

        if (!filter_var($recipient, FILTER_VALIDATE_EMAIL)) {
            $_SESSION['error'] = 'Informe um destinatario valido para enviar o convite.';
            $this->redirect(base_url('agendador/admin'));
        }

        if ($subject === '') {
            $subject = 'Diagnostico FusionCore';
        }

        $schedulerUrl = base_url('agendador') . '?' . http_build_query([
            'source' => 'convite_manual',
            'subject' => $subject,
        ]);

        $html = $this->meetingInviteEmailHtml($subject, $schedulerUrl);
        $sender = new EmailSender();
        $result = $sender->sendEmail(
            $recipient,
            'Agende seu diagnostico com a FusionCore',
            $html,
            strip_tags(str_replace(['<br>', '<br/>', '<br />'], "\n", $html)),
            null,
            false
        );

        if (!empty($result['success'])) {
            $_SESSION['success'] = 'Convite de agendamento enviado para ' . $recipient . '.';
        } else {
            $_SESSION['error'] = 'Nao foi possivel enviar o convite: ' . (string)($result['message'] ?? 'erro desconhecido');
        }

        $this->redirect(base_url('agendador/admin'));
    }

    private function requestLang(array $input = []): string
    {
        $lang = (string)($input['lang'] ?? $_GET['lang'] ?? 'pt');
        return $lang === 'en' ? 'en' : 'pt';
    }

    public function googleConnect(): void
    {
        $this->requireAuth();
        $google = new GoogleCalendarMeetService($this->config);
        if (!$google->isConfigured()) {
            $_SESSION['error'] = 'Configure GOOGLE_CALENDAR_CLIENT_ID e GOOGLE_CALENDAR_CLIENT_SECRET antes de conectar.';
            $this->redirect(base_url('agendador/admin'));
        }

        $state = bin2hex(random_bytes(16));
        $_SESSION['google_calendar_oauth_state'] = $state;
        $this->redirect($google->authUrl($state));
    }

    public function google_connect(): void
    {
        $this->googleConnect();
    }

    public function googleCallback(): void
    {
        $state = (string)($_GET['state'] ?? '');
        $expectedState = (string)($_SESSION['google_calendar_oauth_state'] ?? '');
        if ($expectedState === '' || !hash_equals($expectedState, $state)) {
            $_SESSION['error'] = 'Falha na validacao do OAuth Google.';
            $this->redirect(base_url('agendador/admin'));
        }

        unset($_SESSION['google_calendar_oauth_state']);
        $code = (string)($_GET['code'] ?? '');
        if ($code === '') {
            $_SESSION['error'] = 'Google nao retornou codigo de autorizacao.';
            $this->redirect(base_url('agendador/admin'));
        }

        try {
            $result = (new GoogleCalendarMeetService($this->config))->handleCallback($code);
            $_SESSION['success'] = 'Conta Google conectada: ' . ($result['email'] ?: 'autorizada');
        } catch (\Throwable $e) {
            $_SESSION['error'] = 'Erro ao conectar Google Calendar: ' . $e->getMessage();
        }

        $this->redirect(base_url('agendador/admin'));
    }

    public function google_callback(): void
    {
        $this->googleCallback();
    }

    private function buildSlotsForDate(string $date): array
    {
        $tz = new \DateTimeZone($this->config['timezone']);
        $day = new \DateTimeImmutable($date . ' 00:00:00', $tz);
        $window = $this->businessWindowForDate($day);
        if ($window === null) {
            return [];
        }

        $start = $window['start'];
        $close = $window['end'];
        $now = new \DateTimeImmutable('now', $tz);
        $minStart = $now->modify('+' . (int)$this->config['min_notice_hours'] . ' hours');
        $lastDay = $now->setTime(0, 0)->modify('+' . (int)$this->config['days_ahead'] . ' days');
        if ($day > $lastDay) {
            return [];
        }

        $busy = $this->busyRangesBetween($start, $close);
        $slots = [];
        $cursor = $start;
        while ($cursor->modify('+' . (int)$this->config['meeting_minutes'] . ' minutes') <= $close) {
            $slotEnd = $cursor->modify('+' . (int)$this->config['meeting_minutes'] . ' minutes');
            $available = $cursor >= $minStart && !$this->overlapsBusy($cursor, $slotEnd, $busy);
            $slots[] = [
                'iso' => $cursor->format(DATE_ATOM),
                'label' => $cursor->format('H:i'),
                'available' => $available,
            ];
            $cursor = $cursor->modify('+' . (int)$this->config['slot_minutes'] . ' minutes');
        }

        return $slots;
    }

    private function isBusinessTime(\DateTimeImmutable $start): bool
    {
        $window = $this->businessWindowForDate($start);
        if ($window === null) {
            return false;
        }
        $end = $start->modify('+' . (int)$this->config['meeting_minutes'] . ' minutes');
        return $start >= $window['start'] && $end <= $window['end'];
    }

    private function isBusinessRange(\DateTimeImmutable $start, \DateTimeImmutable $end): bool
    {
        $window = $this->businessWindowForDate($start);
        if ($window === null) {
            return false;
        }
        return $start >= $window['start'] && $end <= $window['end'] && $end > $start;
    }

    private function businessWindowForDate(\DateTimeImmutable $date): ?array
    {
        $tz = new \DateTimeZone($this->config['timezone']);
        $dateKey = $date->setTimezone($tz)->format('Y-m-d');
        $overrides = (array)($this->config['date_overrides'] ?? []);

        if (isset($overrides[$dateKey]) && is_array($overrides[$dateKey])) {
            $override = $overrides[$dateKey];
            $start = preg_match('/^\d{2}:\d{2}$/', (string)($override['start'] ?? '')) ? (string)$override['start'] : null;
            $end = preg_match('/^\d{2}:\d{2}$/', (string)($override['end'] ?? '')) ? (string)$override['end'] : null;
            if ($start && $end) {
                return [
                    'start' => new \DateTimeImmutable($dateKey . ' ' . $start . ':00', $tz),
                    'end' => new \DateTimeImmutable($dateKey . ' ' . $end . ':00', $tz),
                ];
            }
        }

        if ((int)$date->format('N') > 5) {
            return null;
        }

        return [
            'start' => new \DateTimeImmutable($dateKey . ' ' . $this->config['weekday_start'] . ':00', $tz),
            'end' => new \DateTimeImmutable($dateKey . ' ' . $this->config['weekday_end'] . ':00', $tz),
        ];
    }

    private function durationFromRequest(array $booking): int
    {
        $currentStart = new \DateTimeImmutable((string)$booking['starts_at'], new \DateTimeZone($this->config['timezone']));
        $currentEnd = new \DateTimeImmutable((string)$booking['ends_at'], new \DateTimeZone($this->config['timezone']));
        $currentMinutes = max(1, (int)(($currentEnd->getTimestamp() - $currentStart->getTimestamp()) / 60));
        $requested = (int)($_POST['duration_minutes'] ?? $currentMinutes);
        $options = array_map('intval', (array)($this->config['duration_options_minutes'] ?? []));
        if (empty($options)) {
            $options = [(int)$this->config['meeting_minutes']];
        }
        if (!in_array($requested, $options, true)) {
            return in_array($currentMinutes, $options, true) ? $currentMinutes : (int)$this->config['meeting_minutes'];
        }
        return $requested;
    }

    private function overlapsBusy(\DateTimeImmutable $start, \DateTimeImmutable $end, array $busy): bool
    {
        foreach ($busy as $item) {
            $busyStart = new \DateTimeImmutable((string)$item['starts_at'], new \DateTimeZone($this->config['timezone']));
            $busyEnd = new \DateTimeImmutable((string)$item['ends_at'], new \DateTimeZone($this->config['timezone']));
            if ($start < $busyEnd && $end > $busyStart) {
                return true;
            }
        }
        return false;
    }

    private function busyRangesBetween(\DateTimeImmutable $start, \DateTimeImmutable $end): array
    {
        $model = new MeetingScheduler();
        $busy = $model->busyBetween($start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s'));

        try {
            $googleBusy = (new GoogleCalendarMeetService($this->config))->busyBetween($start, $end);
            if (!empty($googleBusy)) {
                $busy = array_merge($busy, $googleBusy);
            }
        } catch (\Throwable $e) {
            try { \App\Services\AppLogger::warning('google_calendar_busy_failed', ['error' => $e->getMessage()]); } catch (\Throwable $logError) {}
        }

        return $busy;
    }

    private function isRangeAvailable(\DateTimeImmutable $start, \DateTimeImmutable $end, ?string $excludeToken = null): bool
    {
        $model = new MeetingScheduler();
        $startSql = $start->format('Y-m-d H:i:s');
        $endSql = $end->format('Y-m-d H:i:s');

        $internalAvailable = $excludeToken === null
            ? $model->isAvailable($startSql, $endSql)
            : $model->isAvailableExcludingToken($startSql, $endSql, $excludeToken);

        if (!$internalAvailable) {
            return false;
        }

        $busy = $this->busyRangesBetween($start, $end);

        if ($excludeToken !== null) {
            $booking = $model->findByToken($excludeToken);
            if ($booking) {
                $busy = array_values(array_filter($busy, function (array $item) use ($booking): bool {
                    return !(
                        (string)($item['starts_at'] ?? '') === (string)($booking['starts_at'] ?? '')
                        && (string)($item['ends_at'] ?? '') === (string)($booking['ends_at'] ?? '')
                    );
                }));
            }
        }

        return !$this->overlapsBusy($start, $end, $busy);
    }

    private function googleCalendarUrl(\DateTimeImmutable $start, \DateTimeImmutable $end, string $name, string $email, string $company, string $notes, string $meetingUrl): string
    {
        $startUtc = $start->setTimezone(new \DateTimeZone('UTC'))->format('Ymd\THis\Z');
        $endUtc = $end->setTimezone(new \DateTimeZone('UTC'))->format('Ymd\THis\Z');
        $details = "Reunião agendada por {$name} ({$email}).";
        if ($company !== '') {
            $details .= "\nEmpresa: {$company}.";
        }
        if ($notes !== '') {
            $details .= "\n\nObservações:\n{$notes}";
        }
        if ($meetingUrl !== '') {
            $details .= "\n\nLink da reunião: " . $meetingUrl;
        }

        return 'https://calendar.google.com/calendar/render?' . http_build_query([
            'action' => 'TEMPLATE',
            'text' => $this->config['title'],
            'dates' => $startUtc . '/' . $endUtc,
            'details' => $details,
            'location' => $meetingUrl !== '' ? $meetingUrl : $this->config['location'],
            'add' => $this->config['owner_email'] . ',' . $email,
        ]);
    }

    private function generateMeetingUrl(string $token, \DateTimeImmutable $start): string
    {
        $configuredUrl = trim((string)($this->config['default_meet_url'] ?? ''));
        if ($configuredUrl !== '') {
            return $configuredUrl;
        }

        $baseUrl = rtrim((string)($this->config['meeting_base_url'] ?? 'https://meet.jit.si'), '/');
        $room = 'fusioncore-' . $start->format('Ymd-Hi') . '-' . substr($token, 0, 10);
        $room = strtolower(preg_replace('/[^a-zA-Z0-9-]/', '-', $room));

        return $baseUrl . '/' . $room;
    }

    private function buildIcs(array $booking, \DateTimeImmutable $start, \DateTimeImmutable $end, string $uid): string
    {
        $nowUtc = (new \DateTimeImmutable('now', new \DateTimeZone('UTC')))->format('Ymd\THis\Z');
        $startUtc = $start->setTimezone(new \DateTimeZone('UTC'))->format('Ymd\THis\Z');
        $endUtc = $end->setTimezone(new \DateTimeZone('UTC'))->format('Ymd\THis\Z');
        $summary = $this->icsEscape($this->config['title']);
        $meetingUrl = (string)($booking['meeting_url'] ?? '');
        $descriptionText = 'Call agendada via FusionCore com ' . ($booking['client_name'] ?? '') . ' (' . ($booking['client_email'] ?? '') . ').';
        if ($meetingUrl !== '') {
            $descriptionText .= "\nLink da call: " . $meetingUrl;
        }
        $description = $this->icsEscape($descriptionText);
        $location = $this->icsEscape($meetingUrl !== '' ? $meetingUrl : $this->config['location']);
        $organizerName = $this->icsEscape($this->config['owner_name']);
        $clientName = $this->icsEscape((string)($booking['client_name'] ?? 'Cliente'));

        return "BEGIN:VCALENDAR\r\n" .
            "VERSION:2.0\r\n" .
            "PRODID:-//FusionCore//Meeting Scheduler//PT-BR\r\n" .
            "CALSCALE:GREGORIAN\r\n" .
            "METHOD:REQUEST\r\n" .
            "BEGIN:VEVENT\r\n" .
            "UID:{$uid}\r\n" .
            "DTSTAMP:{$nowUtc}\r\n" .
            "DTSTART:{$startUtc}\r\n" .
            "DTEND:{$endUtc}\r\n" .
            "SUMMARY:{$summary}\r\n" .
            "DESCRIPTION:{$description}\r\n" .
            "LOCATION:{$location}\r\n" .
            "STATUS:CONFIRMED\r\n" .
            "SEQUENCE:0\r\n" .
            "ORGANIZER;CN={$organizerName}:mailto:{$this->config['owner_email']}\r\n" .
            "ATTENDEE;CN={$clientName};ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:{$booking['client_email']}\r\n" .
            "ATTENDEE;CN={$organizerName};ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;RSVP=FALSE:mailto:{$this->config['owner_email']}\r\n" .
            "END:VEVENT\r\n" .
            "END:VCALENDAR\r\n";
    }

    private function sendApprovalRequest(array $booking, \DateTimeImmutable $start, \DateTimeImmutable $end): array
    {
        $reviewUrl = base_url('agendador/revisar/' . $booking['token']);
        $html = $this->approvalEmailHtml($booking, $start, $end, $reviewUrl);
        $sender = new EmailSender();
        return $sender->sendEmail(
            $this->config['approval_email'],
            'FusionCore | Agendamento aguardando sua confirmação - ' . $start->format('d/m/Y H:i'),
            $html,
            strip_tags(str_replace(['<br>', '<br/>', '<br />'], "\n", $html)),
            null,
            false
        );
    }

    private function approvalEmailHtml(array $booking, \DateTimeImmutable $start, \DateTimeImmutable $end, string $reviewUrl): string
    {
        $client = htmlspecialchars((string)$booking['client_name'], ENT_QUOTES, 'UTF-8');
        $email = htmlspecialchars((string)$booking['client_email'], ENT_QUOTES, 'UTF-8');
        $phone = htmlspecialchars((string)($booking['client_phone'] ?? ''), ENT_QUOTES, 'UTF-8');
        $company = htmlspecialchars((string)($booking['company'] ?? ''), ENT_QUOTES, 'UTF-8');
        $notes = nl2br(htmlspecialchars((string)($booking['notes'] ?? ''), ENT_QUOTES, 'UTF-8'));
        $url = htmlspecialchars($reviewUrl, ENT_QUOTES, 'UTF-8');

        return '<div style="font-family:Inter,Arial,sans-serif;background:#f6f7fb;padding:28px;color:#172033">' .
            '<div style="max-width:680px;margin:0 auto;background:#fff;border:1px solid #e4e7ee;border-radius:14px;overflow:hidden">' .
            '<div style="background:#14213d;color:#fff;padding:26px 30px"><h1 style="margin:0;font-size:24px">Agendamento FusionCore aguardando analise</h1><p style="margin:8px 0 0;color:#dbe7ff">Revise os dados e confirme o melhor proximo passo com o lead.</p></div>' .
            '<div style="padding:30px">' .
            '<p>Um novo lead demonstrou interesse em conversar com a FusionCore. Avalie o horario solicitado para manter a experiencia comercial organizada e profissional.</p>' .
            '<p><strong>Cliente:</strong> ' . $client . '<br><strong>E-mail:</strong> ' . $email . '<br><strong>Telefone:</strong> ' . ($phone ?: '-') . '<br><strong>Empresa:</strong> ' . ($company ?: '-') . '</p>' .
            '<p><strong>Data:</strong> ' . htmlspecialchars($start->format('d/m/Y'), ENT_QUOTES, 'UTF-8') . '<br><strong>Horário:</strong> ' . htmlspecialchars($start->format('H:i') . ' - ' . $end->format('H:i'), ENT_QUOTES, 'UTF-8') . '</p>' .
            ($notes ? '<p><strong>Observações:</strong><br>' . $notes . '</p>' : '') .
            '<p style="margin:28px 0"><a href="' . $url . '" style="background:#1f7a5a;color:#fff;text-decoration:none;padding:13px 18px;border-radius:8px;display:inline-block;font-weight:700">Confirmar ou solicitar reagendamento</a></p>' .
            '<p style="color:#596579;font-size:14px">O lead só receberá o e-mail final depois da confirmação ou recusa.</p>' .
            '</div></div></div>';
    }

    private function sendConfirmation(array $booking, \DateTimeImmutable $start, \DateTimeImmutable $end, string $ics): array
    {
        $calendarUrl = (string)($booking['google_calendar_url'] ?? '');
        $html = $this->confirmationEmailHtml($booking, $start, $end, $calendarUrl);
        $text = strip_tags(str_replace(['<br>', '<br/>', '<br />'], "\n", $html));
        $sender = new EmailSender();
        return $sender->sendEmail(
            [
                (string)$booking['client_email'] => (string)$booking['client_name'],
                $this->config['owner_email'] => $this->config['owner_name'],
            ],
            'Seu agendamento com a FusionCore esta confirmado',
            $html,
            $text,
            null,
            false,
            [[
                'content' => $ics,
                'name' => 'convite-fusioncore.ics',
                'type' => 'text/calendar; method=REQUEST; charset=UTF-8',
            ]]
        );
    }

    private function meetingInviteEmailHtml(string $subject, string $schedulerUrl): string
    {
        $subjectEsc = htmlspecialchars($subject, ENT_QUOTES, 'UTF-8');
        $url = htmlspecialchars($schedulerUrl, ENT_QUOTES, 'UTF-8');
        $logoUrl = 'https://app.fusioncore.com.br/assets/images/fusioncore-email-logo.png';
        $calendarIcon = 'https://www.gstatic.com/images/branding/product/1x/calendar_2020q4_48dp.png';

        return '<div style="font-family:Arial,Helvetica,sans-serif;background:#e8edf3;padding:30px;color:#0f172a">' .
            '<div style="max-width:680px;margin:0 auto;background:#ffffff;border:1px solid #cbd5e1;border-radius:14px;overflow:hidden;color:#0f172a;box-shadow:0 12px 30px rgba(15,23,42,0.10)">' .
            '<div style="background:#0b1220;color:#ffffff;padding:26px 30px 28px">' .
            '<div style="margin-bottom:22px"><img src="' . htmlspecialchars($logoUrl, ENT_QUOTES, 'UTF-8') . '" width="148" alt="FusionCore" style="display:block;max-width:148px;height:auto"></div>' .
            '<h1 style="margin:0;font-size:26px;line-height:1.25;color:#ffffff">Vamos agendar seu diagnostico FusionCore?</h1>' .
            '<p style="margin:12px 0 0;color:#e2e8f0;font-size:16px;line-height:1.55">Escolha o melhor horario para uma conversa objetiva sobre tecnologia, automacao e IA aplicada ao seu cenario.</p>' .
            '</div>' .
            '<div style="padding:30px;color:#0f172a;background:#ffffff">' .
            '<div style="background:#fff7ed;border:1px solid #fed7aa;border-radius:12px;padding:18px;margin:0 0 22px;color:#0f172a"><div style="font-size:13px;font-weight:800;color:#9a3412;text-transform:uppercase;letter-spacing:.04em;margin-bottom:5px">Assunto sugerido</div><div style="font-size:17px;font-weight:800;line-height:1.45;color:#0f172a">' . $subjectEsc . '</div></div>' .
            '<p style="margin:0 0 18px;color:#0f172a;font-size:16px;line-height:1.65">Nossa equipe preparou um canal direto para voce escolher um horario disponivel. Depois da solicitacao, validamos internamente e enviamos a confirmacao com o link oficial da call.</p>' .
            '<p style="margin:28px 0"><a href="' . $url . '" style="background:#166534;color:#ffffff;text-decoration:none;padding:14px 18px;border-radius:8px;display:inline-block;font-weight:800"><img src="' . $calendarIcon . '" width="18" height="18" alt="" style="vertical-align:-4px;margin-right:6px">Agendar meu diagnostico</a></p>' .
            '<p style="color:#334155;font-size:14px;line-height:1.6;margin:16px 0 0">A conversa e online, com horarios de segunda a sexta, das ' . htmlspecialchars($this->config['weekday_start'], ENT_QUOTES, 'UTF-8') . ' as ' . htmlspecialchars($this->config['weekday_end'], ENT_QUOTES, 'UTF-8') . '.</p>' .
            '</div></div></div>';
    }

    private function confirmationEmailHtml(array $booking, \DateTimeImmutable $start, \DateTimeImmutable $end, string $calendarUrl): string
    {
        $name = htmlspecialchars((string)$booking['client_name'], ENT_QUOTES, 'UTF-8');
        $date = htmlspecialchars($start->format('d/m/Y'), ENT_QUOTES, 'UTF-8');
        $time = htmlspecialchars($start->format('H:i') . ' - ' . $end->format('H:i'), ENT_QUOTES, 'UTF-8');
        $url = htmlspecialchars($calendarUrl, ENT_QUOTES, 'UTF-8');
        $meetingUrl = htmlspecialchars((string)($booking['meeting_url'] ?? ''), ENT_QUOTES, 'UTF-8');
        $subjectText = trim((string)($booking['subject'] ?? ''));
        $source = strtolower(trim((string)($booking['source'] ?? '')));
        $isDiagnostic = in_array($source, ['diagnostico', 'diagnosticos', 'diagnostic', 'site_diagnostico', 'fusioncore-site-diagnostico'], true);
        $introHtml = '';
        if ($subjectText !== '') {
            $introHtml = '<div style="background:#fff7ed;border:1px solid #fed7aa;border-radius:12px;padding:18px;margin:0 0 22px;color:#0f172a"><div style="font-size:13px;font-weight:800;color:#9a3412;text-transform:uppercase;letter-spacing:.04em;margin-bottom:5px">Assunto da conversa</div><div style="font-size:17px;font-weight:800;line-height:1.45;color:#0f172a">' . htmlspecialchars($subjectText, ENT_QUOTES, 'UTF-8') . '</div></div>';
        } elseif ($isDiagnostic) {
            $introHtml = '<p style="margin:0 0 22px;color:#0f172a;font-size:16px;line-height:1.65">Sua conversa com a FusionCore foi confirmada. Vamos entender seu cenario, mapear oportunidades praticas e apresentar caminhos para melhorar sua operacao com solucoes digitais, automacao e IA.</p>';
        }
        $logoUrl = 'https://app.fusioncore.com.br/assets/images/fusioncore-email-logo.png';
        $meetIcon = 'https://www.gstatic.com/images/branding/product/1x/meet_2020q4_48dp.png';
        $calendarIcon = 'https://www.gstatic.com/images/branding/product/1x/calendar_2020q4_48dp.png';
        return '<div style="font-family:Arial,Helvetica,sans-serif;background:#e8edf3;padding:30px;color:#0f172a">' .
            '<div style="max-width:680px;margin:0 auto;background:#ffffff;border:1px solid #cbd5e1;border-radius:14px;overflow:hidden;color:#0f172a;box-shadow:0 12px 30px rgba(15,23,42,0.10)">' .
            '<div style="background:#0b1220;color:#ffffff;padding:26px 30px 28px">' .
            '<div style="margin-bottom:22px"><img src="' . htmlspecialchars($logoUrl, ENT_QUOTES, 'UTF-8') . '" width="148" alt="FusionCore" style="display:block;max-width:148px;height:auto"></div>' .
            '<h1 style="margin:0;font-size:26px;line-height:1.25;color:#ffffff">Seu agendamento com a FusionCore esta confirmado</h1>' .
            '<p style="margin:12px 0 0;color:#e2e8f0;font-size:16px;line-height:1.55">Vamos conversar sobre tecnologia, automacao e IA para acelerar resultados com clareza e foco comercial.</p>' .
            '</div>' .
            '<div style="padding:30px;color:#0f172a;background:#ffffff">' .
            '<p style="margin:0 0 14px;color:#0f172a;font-size:16px;line-height:1.65">Olá, ' . $name . '.</p>' .
            $introHtml .
            '<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="background:#f8fafc;border:1px solid #cbd5e1;border-radius:12px;margin:0 0 24px;color:#0f172a"><tr>' .
            '<td style="padding:18px;vertical-align:top;width:50%"><div style="font-size:13px;font-weight:700;color:#475569;text-transform:uppercase;letter-spacing:.04em">Data</div><div style="font-size:18px;font-weight:800;color:#0f172a;margin-top:4px">' . $date . '</div></td>' .
            '<td style="padding:18px;vertical-align:top;width:50%"><div style="font-size:13px;font-weight:700;color:#475569;text-transform:uppercase;letter-spacing:.04em">Horario</div><div style="font-size:18px;font-weight:800;color:#0f172a;margin-top:4px">' . $time . '</div></td>' .
            '</tr></table>' .
            '<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="margin:0 0 22px"><tr>' .
            '<td style="background:#eef6ff;border:1px solid #bfdbfe;border-radius:12px;padding:18px;color:#0f172a">' .
            '<table role="presentation" cellspacing="0" cellpadding="0"><tr><td style="padding-right:12px;vertical-align:middle"><img src="' . $meetIcon . '" width="38" height="38" alt="Google Meet" style="display:block"></td><td style="vertical-align:middle"><div style="font-size:13px;font-weight:700;color:#1d4ed8;text-transform:uppercase;letter-spacing:.04em">Google Meet</div><a href="' . $meetingUrl . '" style="color:#0057d8;font-size:16px;font-weight:800;text-decoration:none">' . $meetingUrl . '</a></td></tr></table>' .
            '</td></tr></table>' .
            '<p style="margin:0 0 18px"><a href="' . $meetingUrl . '" style="background:#166534;color:#ffffff;text-decoration:none;padding:14px 18px;border-radius:8px;display:inline-block;font-weight:800;margin:0 10px 10px 0">Entrar no Google Meet</a><a href="' . $url . '" style="background:#0f172a;color:#ffffff;text-decoration:none;padding:14px 18px;border-radius:8px;display:inline-block;font-weight:800;margin:0 0 10px 0"><img src="' . $calendarIcon . '" width="18" height="18" alt="" style="vertical-align:-4px;margin-right:6px">Abrir no Google Calendar</a></p>' .
            '<p style="color:#334155;font-size:14px;line-height:1.6;margin:16px 0 0">Recomendamos entrar alguns minutos antes. Se precisar remarcar, responda este e-mail e nossa equipe ajuda com um novo horario.</p>' .
            '</div></div></div>';
    }

    private function sendDeclinedEmail(array $booking): array
    {
        $schedulerUrl = base_url('agendador');
        $name = htmlspecialchars((string)$booking['client_name'], ENT_QUOTES, 'UTF-8');
        $url = htmlspecialchars($schedulerUrl, ENT_QUOTES, 'UTF-8');
        $html = '<div style="font-family:Inter,Arial,sans-serif;background:#f6f7fb;padding:28px;color:#172033">' .
            '<div style="max-width:640px;margin:0 auto;background:#fff;border:1px solid #e4e7ee;border-radius:14px;overflow:hidden">' .
            '<div style="background:#14213d;color:#fff;padding:28px 30px"><h1 style="margin:0;font-size:25px">Vamos encontrar um horario melhor para sua conversa com a FusionCore</h1><p style="margin:8px 0 0;color:#dbe7ff">Queremos garantir uma reuniao produtiva e com a atencao certa para o seu projeto.</p></div>' .
            '<div style="padding:30px"><p>Olá, ' . $name . '.</p><p>O horario solicitado nao pôde ser confirmado pela nossa equipe, mas queremos seguir com a conversa.</p>' .
            '<p>Escolha uma nova disponibilidade para falarmos sobre suas necessidades, oportunidades de automacao e como a FusionCore pode apoiar sua operacao.</p>' .
            '<p style="margin:28px 0"><a href="' . $url . '" style="background:#1f7a5a;color:#fff;text-decoration:none;padding:13px 18px;border-radius:8px;display:inline-block;font-weight:700">Escolher novo horario</a></p>' .
            '</div></div></div>';
        $sender = new EmailSender();
        return $sender->sendEmail(
            (string)$booking['client_email'],
            'Vamos reagendar sua conversa com a FusionCore',
            $html,
            strip_tags(str_replace(['<br>', '<br/>', '<br />'], "\n", $html)),
            null,
            false
        );
    }

    private function sendCancellationEmail(array $booking): array
    {
        $tz = new \DateTimeZone((string)($booking['timezone'] ?? $this->config['timezone']));
        $start = new \DateTimeImmutable((string)$booking['starts_at'], $tz);
        $end = new \DateTimeImmutable((string)$booking['ends_at'], $tz);
        $schedulerUrl = base_url('agendador');
        $name = htmlspecialchars((string)$booking['client_name'], ENT_QUOTES, 'UTF-8');
        $date = htmlspecialchars($start->format('d/m/Y'), ENT_QUOTES, 'UTF-8');
        $time = htmlspecialchars($start->format('H:i') . ' - ' . $end->format('H:i'), ENT_QUOTES, 'UTF-8');
        $url = htmlspecialchars($schedulerUrl, ENT_QUOTES, 'UTF-8');
        $logoUrl = 'https://app.fusioncore.com.br/assets/images/fusioncore-email-logo.png';
        $calendarIcon = 'https://www.gstatic.com/images/branding/product/1x/calendar_2020q4_48dp.png';

        $html = '<div style="font-family:Arial,Helvetica,sans-serif;background:#e8edf3;padding:30px;color:#0f172a">' .
            '<div style="max-width:680px;margin:0 auto;background:#ffffff;border:1px solid #cbd5e1;border-radius:14px;overflow:hidden;color:#0f172a;box-shadow:0 12px 30px rgba(15,23,42,0.10)">' .
            '<div style="background:#0b1220;color:#ffffff;padding:26px 30px 28px">' .
            '<div style="margin-bottom:22px"><img src="' . htmlspecialchars($logoUrl, ENT_QUOTES, 'UTF-8') . '" width="148" alt="FusionCore" style="display:block;max-width:148px;height:auto"></div>' .
            '<h1 style="margin:0;font-size:26px;line-height:1.25;color:#ffffff">Sua reunião com a FusionCore foi cancelada</h1>' .
            '<p style="margin:12px 0 0;color:#e2e8f0;font-size:16px;line-height:1.55">Estamos avisando para evitar qualquer desencontro de agenda.</p>' .
            '</div>' .
            '<div style="padding:30px;color:#0f172a;background:#ffffff">' .
            '<p style="margin:0 0 18px;color:#0f172a;font-size:16px;line-height:1.65">Olá, ' . $name . '.</p>' .
            '<p style="margin:0 0 22px;color:#0f172a;font-size:16px;line-height:1.65">A reunião que estava prevista com a FusionCore foi cancelada pela nossa equipe.</p>' .
            '<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="background:#f8fafc;border:1px solid #cbd5e1;border-radius:12px;margin:0 0 24px;color:#0f172a"><tr>' .
            '<td style="padding:18px;vertical-align:top;width:50%"><div style="font-size:13px;font-weight:700;color:#475569;text-transform:uppercase;letter-spacing:.04em">Data cancelada</div><div style="font-size:18px;font-weight:800;color:#0f172a;margin-top:4px">' . $date . '</div></td>' .
            '<td style="padding:18px;vertical-align:top;width:50%"><div style="font-size:13px;font-weight:700;color:#475569;text-transform:uppercase;letter-spacing:.04em">Horario</div><div style="font-size:18px;font-weight:800;color:#0f172a;margin-top:4px">' . $time . '</div></td>' .
            '</tr></table>' .
            '<p style="margin:28px 0"><a href="' . $url . '" style="background:#0f172a;color:#ffffff;text-decoration:none;padding:14px 18px;border-radius:8px;display:inline-block;font-weight:800"><img src="' . $calendarIcon . '" width="18" height="18" alt="" style="vertical-align:-4px;margin-right:6px">Escolher novo horario</a></p>' .
            '<p style="color:#334155;font-size:14px;line-height:1.6;margin:16px 0 0">Se essa conversa ainda fizer sentido, escolha uma nova disponibilidade pelo link acima ou responda este e-mail.</p>' .
            '</div></div></div>';

        $sender = new EmailSender();
        return $sender->sendEmail(
            (string)$booking['client_email'],
            'Sua reuniao com a FusionCore foi cancelada',
            $html,
            strip_tags(str_replace(['<br>', '<br/>', '<br />'], "\n", $html)),
            null,
            false
        );
    }

    private function icsEscape(string $value): string
    {
        return str_replace(["\\", "\n", "\r", ",", ";"], ["\\\\", "\\n", '', "\\,", "\\;"], $value);
    }
}

plugins/lucas-portfolio-toolkit/portfolio-toolkit.php

Plugin Portfolio Toolkit

Registra CPTs, campos estruturados bilíngues, grupos de campos para currículo e assessment, visuais de assessment, páginas de admin, metaboxes específicas de template e o modelo de conteúdo usado no portfólio.

<?php
/**
 * Plugin Name: Lucas Portfolio Toolkit
 * Description: Custom post types, structured fields, bilingual content helpers and admin tooling for the Lucas Portfolio subdomain.
 * Version: 1.2.0
 * Author: FusionCore
 */

declare(strict_types=1);

if (!defined('ABSPATH')) {
	exit;
}

const LP_TOOLKIT_VERSION = '1.2.0';

// Force the classic editor so reviewers can see a traditional WordPress editing workflow.
add_filter('use_block_editor_for_post', '__return_false', 100);
add_filter('use_block_editor_for_post_type', '__return_false', 100);

// Central registry for the project custom post types shown under the Lucas Portfolio admin menu.
function lp_project_types(): array {
	return [
		'lp_delivery' => [
			'singular' => 'Delivery Project',
			'plural'   => 'Delivery Projects',
			'slug'     => 'delivery',
			'icon'     => 'dashicons-store',
			'accent'   => '#f97316',
		],
		'lp_app' => [
			'singular' => 'App Project',
			'plural'   => 'App Projects',
			'slug'     => 'apps',
			'icon'     => 'dashicons-smartphone',
			'accent'   => '#0ea5e9',
		],
		'lp_pms' => [
			'singular' => 'PMS Project',
			'plural'   => 'PMS Projects',
			'slug'     => 'pms',
			'icon'     => 'dashicons-building',
			'accent'   => '#14b8a6',
		],
		'lp_crm' => [
			'singular' => 'CRM Project',
			'plural'   => 'CRM Projects',
			'slug'     => 'crm',
			'icon'     => 'dashicons-groups',
			'accent'   => '#6366f1',
		],
		'lp_fusion_ai' => [
			'singular' => 'Fusion AI Project',
			'plural'   => 'Fusion AI Projects',
			'slug'     => 'fusion-ai',
			'icon'     => 'dashicons-superhero',
			'accent'   => '#111827',
		],
	];
}

// Served brands are intentionally separated from project case studies.
function lp_brand_type(): array {
	return [
		'lp_brand' => [
			'singular' => 'Served Brand',
			'plural'   => 'Served Brands',
			'slug'     => 'marcas',
			'icon'     => 'dashicons-awards',
			'accent'   => '#525252',
		],
	];
}

function lp_assessment_visual_type(): array {
	return [
		'lp_assessment_visual' => [
			'singular' => 'Assessment Visual',
			'plural'   => 'Assessment Visuals',
			'slug'     => 'assessment-visuals',
			'icon'     => 'dashicons-format-gallery',
			'accent'   => '#0f766e',
		],
	];
}

// Register all portfolio content models with REST support, archives and dashboard icons.
add_action('init', function (): void {
	foreach (lp_project_types() as $post_type => $config) {
		register_post_type($post_type, [
			'labels' => [
				'name'               => $config['plural'],
				'singular_name'      => $config['singular'],
				'add_new_item'       => 'Add ' . $config['singular'],
				'edit_item'          => 'Edit ' . $config['singular'],
				'new_item'           => 'New ' . $config['singular'],
				'view_item'          => 'View ' . $config['singular'],
				'search_items'       => 'Search ' . $config['plural'],
				'not_found'          => 'No projects found',
				'menu_name'          => $config['plural'],
			],
			'public'       => true,
			'has_archive'  => true,
			'rewrite'      => ['slug' => $config['slug']],
			'show_in_menu' => 'lucas-portfolio',
			'menu_icon'    => $config['icon'],
			'show_in_rest' => true,
			'supports'     => ['title', 'editor', 'excerpt', 'thumbnail', 'revisions'],
			'taxonomies'   => ['post_tag'],
		]);
	}

	foreach (lp_brand_type() as $post_type => $config) {
		register_post_type($post_type, [
			'labels' => [
				'name'               => $config['plural'],
				'singular_name'      => $config['singular'],
				'add_new_item'       => 'Add ' . $config['singular'],
				'edit_item'          => 'Edit ' . $config['singular'],
				'new_item'           => 'New ' . $config['singular'],
				'view_item'          => 'View ' . $config['singular'],
				'search_items'       => 'Search ' . $config['plural'],
				'not_found'          => 'No brands found',
				'menu_name'          => $config['plural'],
			],
			'public'       => true,
			'has_archive'  => true,
			'rewrite'      => ['slug' => $config['slug']],
			'show_in_menu' => 'lucas-portfolio',
			'menu_icon'    => $config['icon'],
			'show_in_rest' => true,
			'supports'     => ['title', 'editor', 'excerpt', 'thumbnail', 'revisions'],
		]);
	}

	foreach (lp_assessment_visual_type() as $post_type => $config) {
		register_post_type($post_type, [
			'labels' => [
				'name'               => $config['plural'],
				'singular_name'      => $config['singular'],
				'add_new_item'       => 'Add ' . $config['singular'],
				'edit_item'          => 'Edit ' . $config['singular'],
				'new_item'           => 'New ' . $config['singular'],
				'view_item'          => 'View ' . $config['singular'],
				'search_items'       => 'Search ' . $config['plural'],
				'not_found'          => 'No assessment visuals found',
				'menu_name'          => $config['plural'],
			],
			'public'       => false,
			'show_ui'      => true,
			'show_in_menu' => 'lucas-portfolio',
			'menu_icon'    => $config['icon'],
			'show_in_rest' => true,
			'supports'     => ['title', 'thumbnail', 'page-attributes', 'revisions'],
		]);
	}
}, 20);

// Project fields mimic the ACF workflow without requiring ACF as a dependency.
function lp_acf_like_fields(): array {
	return [
		'lp_project_summary' => ['label' => 'Project summary', 'type' => 'textarea'],
		'lp_client_type'     => ['label' => 'Client / business type', 'type' => 'text'],
		'lp_stack'           => ['label' => 'Stack and tools', 'type' => 'text'],
		'lp_stack_used'      => ['label' => 'Technology stack (one per line)', 'type' => 'textarea'],
		'lp_integrated_apis' => ['label' => 'Integrated APIs (one per line)', 'type' => 'textarea'],
		'lp_integrations'    => ['label' => 'Integrations / automations', 'type' => 'textarea'],
		'lp_automation_flow' => ['label' => 'Automation flow', 'type' => 'textarea'],
		'lp_admin_features'  => ['label' => 'Admin / dashboard features', 'type' => 'textarea'],
		'lp_code_evidence'   => ['label' => 'Server code evidence', 'type' => 'textarea'],
		'lp_source_path'     => ['label' => 'Source path / server project', 'type' => 'text'],
		'lp_project_url'     => ['label' => 'Project URL', 'type' => 'url'],
		'lp_access_url'      => ['label' => 'Access / login URL', 'type' => 'url'],
		'lp_demo_user'       => ['label' => 'Temporary user', 'type' => 'text'],
		'lp_demo_password'   => ['label' => 'Temporary password', 'type' => 'text'],
		'lp_access_notes'    => ['label' => 'Temporary access notes', 'type' => 'textarea'],
		'lp_results'         => ['label' => 'Results / business outcome', 'type' => 'textarea'],
		'lp_demo_url'        => ['label' => 'Demo or case-study URL', 'type' => 'url'],
		'lp_icon'            => ['label' => 'Card icon', 'type' => 'text'],
		'lp_flexible_json'   => ['label' => 'Flexible sections JSON', 'type' => 'textarea'],
	];
}

// Brand fields keep client/brand evidence editable from the WordPress admin.
function lp_brand_fields(): array {
	return [
		'lp_project_summary' => ['label' => 'Brand summary', 'type' => 'textarea'],
		'lp_brand_url'       => ['label' => 'Brand website', 'type' => 'url'],
		'lp_brand_scope'     => ['label' => 'Scope / work type', 'type' => 'textarea'],
		'lp_brand_stack'     => ['label' => 'Stack / tools', 'type' => 'textarea'],
		'lp_client_type'     => ['label' => 'Client / business type', 'type' => 'text'],
		'lp_source_path'     => ['label' => 'Source path / server project', 'type' => 'text'],
		'lp_demo_url'        => ['label' => 'Demo or case-study URL', 'type' => 'url'],
		'lp_project_url'     => ['label' => 'Project URL', 'type' => 'url'],
		'lp_access_url'      => ['label' => 'Access / login URL', 'type' => 'url'],
		'lp_demo_user'       => ['label' => 'Temporary user', 'type' => 'text'],
		'lp_demo_password'   => ['label' => 'Temporary password', 'type' => 'text'],
		'lp_access_notes'    => ['label' => 'Temporary access notes', 'type' => 'textarea'],
		'lp_stack_used'      => ['label' => 'Technology stack (one per line)', 'type' => 'textarea'],
		'lp_integrated_apis' => ['label' => 'Integrated APIs (one per line)', 'type' => 'textarea'],
		'lp_admin_features'  => ['label' => 'Admin / dashboard features', 'type' => 'textarea'],
		'lp_code_evidence'   => ['label' => 'Server code evidence', 'type' => 'textarea'],
		'lp_integrations'    => ['label' => 'Integrations / automations', 'type' => 'textarea'],
		'lp_automation_flow' => ['label' => 'Automation flow', 'type' => 'textarea'],
		'lp_results'         => ['label' => 'Results / business outcome', 'type' => 'textarea'],
		'lp_flexible_json'   => ['label' => 'Flexible sections JSON', 'type' => 'textarea'],
	];
}

// Homepage fields keep the public layout in the theme while letting copy stay editable in WordPress.
function lp_home_fields(): array {
	return [
		'lp_home_meta_description_pt' => ['label' => 'Homepage meta description (PT)', 'type' => 'textarea'],
		'lp_home_meta_description_en' => ['label' => 'Homepage meta description (EN)', 'type' => 'textarea'],
		'lp_home_hero_enabled' => ['label' => 'Enable hero section', 'type' => 'checkbox'],
		'lp_home_hero_eyebrow_pt' => ['label' => 'Hero eyebrow (PT)', 'type' => 'text'],
		'lp_home_hero_eyebrow_en' => ['label' => 'Hero eyebrow (EN)', 'type' => 'text'],
		'lp_home_hero_title_pt' => ['label' => 'Hero title (PT)', 'type' => 'textarea'],
		'lp_home_hero_title_en' => ['label' => 'Hero title (EN)', 'type' => 'textarea'],
		'lp_home_hero_lede_pt' => ['label' => 'Hero intro text (PT)', 'type' => 'textarea'],
		'lp_home_hero_lede_en' => ['label' => 'Hero intro text (EN)', 'type' => 'textarea'],
		'lp_home_hero_primary_label_pt' => ['label' => 'Hero primary label (PT)', 'type' => 'text'],
		'lp_home_hero_primary_label_en' => ['label' => 'Hero primary label (EN)', 'type' => 'text'],
		'lp_home_hero_primary_url' => ['label' => 'Hero primary URL', 'type' => 'url'],
		'lp_home_hero_secondary_label_pt' => ['label' => 'Hero secondary label (PT)', 'type' => 'text'],
		'lp_home_hero_secondary_label_en' => ['label' => 'Hero secondary label (EN)', 'type' => 'text'],
		'lp_home_hero_secondary_url' => ['label' => 'Hero secondary URL', 'type' => 'url'],
		'lp_home_hero_highlights_json' => ['label' => 'Hero highlights JSON', 'type' => 'textarea'],
		'lp_home_hero_panel_label_pt' => ['label' => 'Hero panel label (PT)', 'type' => 'text'],
		'lp_home_hero_panel_label_en' => ['label' => 'Hero panel label (EN)', 'type' => 'text'],
		'lp_home_hero_panel_title_pt' => ['label' => 'Hero panel title (PT)', 'type' => 'text'],
		'lp_home_hero_panel_title_en' => ['label' => 'Hero panel title (EN)', 'type' => 'text'],
		'lp_home_hero_panel_body_pt' => ['label' => 'Hero panel body (PT)', 'type' => 'textarea'],
		'lp_home_hero_panel_body_en' => ['label' => 'Hero panel body (EN)', 'type' => 'textarea'],
		'lp_home_hero_panel_badge_pt' => ['label' => 'Hero panel badge (PT)', 'type' => 'text'],
		'lp_home_hero_panel_badge_en' => ['label' => 'Hero panel badge (EN)', 'type' => 'text'],
		'lp_home_quick_enabled' => ['label' => 'Enable quick signals section', 'type' => 'checkbox'],
		'lp_home_quick_eyebrow_pt' => ['label' => 'Quick signals eyebrow (PT)', 'type' => 'text'],
		'lp_home_quick_eyebrow_en' => ['label' => 'Quick signals eyebrow (EN)', 'type' => 'text'],
		'lp_home_quick_title_pt' => ['label' => 'Quick signals title (PT)', 'type' => 'textarea'],
		'lp_home_quick_title_en' => ['label' => 'Quick signals title (EN)', 'type' => 'textarea'],
		'lp_home_quick_items_json' => ['label' => 'Quick signals JSON', 'type' => 'textarea'],
		'lp_home_skills_enabled' => ['label' => 'Enable core skills section', 'type' => 'checkbox'],
		'lp_home_skills_eyebrow_pt' => ['label' => 'Core skills eyebrow (PT)', 'type' => 'text'],
		'lp_home_skills_eyebrow_en' => ['label' => 'Core skills eyebrow (EN)', 'type' => 'text'],
		'lp_home_skills_title_pt' => ['label' => 'Core skills title (PT)', 'type' => 'textarea'],
		'lp_home_skills_title_en' => ['label' => 'Core skills title (EN)', 'type' => 'textarea'],
		'lp_home_skills_items_json' => ['label' => 'Core skills JSON', 'type' => 'textarea'],
		'lp_home_proof_enabled' => ['label' => 'Enable technical proof section', 'type' => 'checkbox'],
		'lp_home_proof_eyebrow_pt' => ['label' => 'Technical proof eyebrow (PT)', 'type' => 'text'],
		'lp_home_proof_eyebrow_en' => ['label' => 'Technical proof eyebrow (EN)', 'type' => 'text'],
		'lp_home_proof_title_pt' => ['label' => 'Technical proof title (PT)', 'type' => 'textarea'],
		'lp_home_proof_title_en' => ['label' => 'Technical proof title (EN)', 'type' => 'textarea'],
		'lp_home_proof_items_json' => ['label' => 'Technical proof JSON', 'type' => 'textarea'],
		'lp_home_proof_media_json' => ['label' => 'Technical proof media JSON', 'type' => 'textarea'],
		'lp_home_review_enabled' => ['label' => 'Enable reviewer guide section', 'type' => 'checkbox'],
		'lp_home_review_eyebrow_pt' => ['label' => 'Reviewer guide eyebrow (PT)', 'type' => 'text'],
		'lp_home_review_eyebrow_en' => ['label' => 'Reviewer guide eyebrow (EN)', 'type' => 'text'],
		'lp_home_review_title_pt' => ['label' => 'Reviewer guide title (PT)', 'type' => 'textarea'],
		'lp_home_review_title_en' => ['label' => 'Reviewer guide title (EN)', 'type' => 'textarea'],
		'lp_home_review_items_json' => ['label' => 'Reviewer guide JSON', 'type' => 'textarea'],
		'lp_home_featured_enabled' => ['label' => 'Enable featured work section', 'type' => 'checkbox'],
		'lp_home_featured_eyebrow_pt' => ['label' => 'Featured work eyebrow (PT)', 'type' => 'text'],
		'lp_home_featured_eyebrow_en' => ['label' => 'Featured work eyebrow (EN)', 'type' => 'text'],
		'lp_home_featured_title_pt' => ['label' => 'Featured work title (PT)', 'type' => 'textarea'],
		'lp_home_featured_title_en' => ['label' => 'Featured work title (EN)', 'type' => 'textarea'],
		'lp_home_featured_button_label_pt' => ['label' => 'Featured work button label (PT)', 'type' => 'text'],
		'lp_home_featured_button_label_en' => ['label' => 'Featured work button label (EN)', 'type' => 'text'],
		'lp_home_related_enabled' => ['label' => 'Enable related systems section', 'type' => 'checkbox'],
		'lp_home_related_eyebrow_pt' => ['label' => 'Related systems eyebrow (PT)', 'type' => 'text'],
		'lp_home_related_eyebrow_en' => ['label' => 'Related systems eyebrow (EN)', 'type' => 'text'],
		'lp_home_related_title_pt' => ['label' => 'Related systems title (PT)', 'type' => 'textarea'],
		'lp_home_related_title_en' => ['label' => 'Related systems title (EN)', 'type' => 'textarea'],
		'lp_home_related_items_json' => ['label' => 'Related systems JSON', 'type' => 'textarea'],
		'lp_home_trust_enabled' => ['label' => 'Enable trust section', 'type' => 'checkbox'],
		'lp_home_trust_eyebrow_pt' => ['label' => 'Trust section eyebrow (PT)', 'type' => 'text'],
		'lp_home_trust_eyebrow_en' => ['label' => 'Trust section eyebrow (EN)', 'type' => 'text'],
		'lp_home_trust_title_pt' => ['label' => 'Trust section title (PT)', 'type' => 'textarea'],
		'lp_home_trust_title_en' => ['label' => 'Trust section title (EN)', 'type' => 'textarea'],
		'lp_home_trust_body_pt' => ['label' => 'Trust section body (PT)', 'type' => 'textarea'],
		'lp_home_trust_body_en' => ['label' => 'Trust section body (EN)', 'type' => 'textarea'],
		'lp_home_trust_button_label_pt' => ['label' => 'Trust section button label (PT)', 'type' => 'text'],
		'lp_home_trust_button_label_en' => ['label' => 'Trust section button label (EN)', 'type' => 'text'],
		'lp_home_cta_enabled' => ['label' => 'Enable final CTA section', 'type' => 'checkbox'],
		'lp_home_cta_eyebrow_pt' => ['label' => 'Final CTA eyebrow (PT)', 'type' => 'text'],
		'lp_home_cta_eyebrow_en' => ['label' => 'Final CTA eyebrow (EN)', 'type' => 'text'],
		'lp_home_cta_title_pt' => ['label' => 'Final CTA title (PT)', 'type' => 'textarea'],
		'lp_home_cta_title_en' => ['label' => 'Final CTA title (EN)', 'type' => 'textarea'],
		'lp_home_cta_body_pt' => ['label' => 'Final CTA body (PT)', 'type' => 'textarea'],
		'lp_home_cta_body_en' => ['label' => 'Final CTA body (EN)', 'type' => 'textarea'],
		'lp_home_cta_button_label_pt' => ['label' => 'Final CTA button label (PT)', 'type' => 'text'],
		'lp_home_cta_button_label_en' => ['label' => 'Final CTA button label (EN)', 'type' => 'text'],
		'lp_home_cta_button_url' => ['label' => 'Final CTA button URL', 'type' => 'url'],
	];
}

// Rollout page fields keep the custom migration panel editable from the classic page editor.
function lp_rollout_fields(): array {
	return [
		'lp_rollout_hero_eyebrow_pt' => ['label' => 'Hero eyebrow (PT)', 'type' => 'text'],
		'lp_rollout_hero_eyebrow_en' => ['label' => 'Hero eyebrow (EN)', 'type' => 'text'],
		'lp_rollout_hero_lede_pt' => ['label' => 'Hero intro text (PT)', 'type' => 'textarea'],
		'lp_rollout_hero_lede_en' => ['label' => 'Hero intro text (EN)', 'type' => 'textarea'],
		'lp_rollout_primary_label_pt' => ['label' => 'Primary button label (PT)', 'type' => 'text'],
		'lp_rollout_primary_label_en' => ['label' => 'Primary button label (EN)', 'type' => 'text'],
		'lp_rollout_primary_url' => ['label' => 'Primary button URL', 'type' => 'text'],
		'lp_rollout_secondary_label_pt' => ['label' => 'Secondary button label (PT)', 'type' => 'text'],
		'lp_rollout_secondary_label_en' => ['label' => 'Secondary button label (EN)', 'type' => 'text'],
		'lp_rollout_secondary_url' => ['label' => 'Secondary button URL', 'type' => 'url'],
		'lp_rollout_stats_json' => ['label' => 'Hero stats JSON', 'type' => 'textarea'],
		'lp_rollout_hero_card_label_pt' => ['label' => 'Hero card eyebrow (PT)', 'type' => 'text'],
		'lp_rollout_hero_card_label_en' => ['label' => 'Hero card eyebrow (EN)', 'type' => 'text'],
		'lp_rollout_hero_card_title_pt' => ['label' => 'Hero card title (PT)', 'type' => 'textarea'],
		'lp_rollout_hero_card_title_en' => ['label' => 'Hero card title (EN)', 'type' => 'textarea'],
		'lp_rollout_hero_card_body_pt' => ['label' => 'Hero card body (PT)', 'type' => 'textarea'],
		'lp_rollout_hero_card_body_en' => ['label' => 'Hero card body (EN)', 'type' => 'textarea'],
		'lp_rollout_proof_eyebrow_pt' => ['label' => 'Proof section eyebrow (PT)', 'type' => 'text'],
		'lp_rollout_proof_eyebrow_en' => ['label' => 'Proof section eyebrow (EN)', 'type' => 'text'],
		'lp_rollout_proof_title_pt' => ['label' => 'Proof section title (PT)', 'type' => 'textarea'],
		'lp_rollout_proof_title_en' => ['label' => 'Proof section title (EN)', 'type' => 'textarea'],
		'lp_rollout_proof_blocks' => ['label' => 'Proof blocks JSON', 'type' => 'textarea'],
		'lp_rollout_story_eyebrow_pt' => ['label' => 'Story eyebrow (PT)', 'type' => 'text'],
		'lp_rollout_story_eyebrow_en' => ['label' => 'Story eyebrow (EN)', 'type' => 'text'],
		'lp_rollout_story_title_pt' => ['label' => 'Story title (PT)', 'type' => 'textarea'],
		'lp_rollout_story_title_en' => ['label' => 'Story title (EN)', 'type' => 'textarea'],
		'lp_rollout_story_body_pt' => ['label' => 'Story body paragraph 1 (PT)', 'type' => 'textarea'],
		'lp_rollout_story_body_en' => ['label' => 'Story body paragraph 1 (EN)', 'type' => 'textarea'],
		'lp_rollout_story_body_2_pt' => ['label' => 'Story body paragraph 2 (PT)', 'type' => 'textarea'],
		'lp_rollout_story_body_2_en' => ['label' => 'Story body paragraph 2 (EN)', 'type' => 'textarea'],
		'lp_rollout_timeline_eyebrow_pt' => ['label' => 'Timeline eyebrow (PT)', 'type' => 'text'],
		'lp_rollout_timeline_eyebrow_en' => ['label' => 'Timeline eyebrow (EN)', 'type' => 'text'],
		'lp_rollout_timeline_steps' => ['label' => 'Timeline steps JSON', 'type' => 'textarea'],
		'lp_rollout_band_eyebrow_pt' => ['label' => 'Band eyebrow (PT)', 'type' => 'text'],
		'lp_rollout_band_eyebrow_en' => ['label' => 'Band eyebrow (EN)', 'type' => 'text'],
		'lp_rollout_band_title_pt' => ['label' => 'Band title (PT)', 'type' => 'textarea'],
		'lp_rollout_band_title_en' => ['label' => 'Band title (EN)', 'type' => 'textarea'],
		'lp_rollout_band_body_pt' => ['label' => 'Band body (PT)', 'type' => 'textarea'],
		'lp_rollout_band_body_en' => ['label' => 'Band body (EN)', 'type' => 'textarea'],
		'lp_rollout_screens_eyebrow_pt' => ['label' => 'Screens eyebrow (PT)', 'type' => 'text'],
		'lp_rollout_screens_eyebrow_en' => ['label' => 'Screens eyebrow (EN)', 'type' => 'text'],
		'lp_rollout_screens_title_pt' => ['label' => 'Screens title (PT)', 'type' => 'textarea'],
		'lp_rollout_screens_title_en' => ['label' => 'Screens title (EN)', 'type' => 'textarea'],
		'lp_rollout_screens_json' => ['label' => 'Screens JSON', 'type' => 'textarea'],
	];
}

// Plugin code page fields keep code-page labels, summaries and file registry editable.
function lp_plugin_code_fields(): array {
	return [
		'lp_plugin_code_hero_eyebrow'   => ['label' => 'Hero eyebrow', 'type' => 'text'],
		'lp_plugin_code_summary_cards' => ['label' => 'Summary cards JSON', 'type' => 'textarea'],
		'lp_plugin_code_api_eyebrow'   => ['label' => 'API section eyebrow', 'type' => 'text'],
		'lp_plugin_code_api_title'     => ['label' => 'API section title', 'type' => 'text'],
		'lp_plugin_code_api_body'      => ['label' => 'API section body', 'type' => 'textarea'],
		'lp_plugin_code_api_code'      => ['label' => 'API example code', 'type' => 'textarea'],
		'lp_plugin_code_paths'         => ['label' => 'Code file paths (one per line)', 'type' => 'textarea'],
		'lp_plugin_code_files'         => ['label' => 'Code panels JSON', 'type' => 'textarea'],
	];
}

function lp_plugin_code_default_paths(): string {
	return implode("\n", [
		'/var/www/portfolio.fusioncore.com.br/public/wp-content/plugins/lucas-portfolio-toolkit/portfolio-toolkit.php',
		'/var/www/portfolio.fusioncore.com.br/public/wp-content/mu-plugins/portfolio-runtime-optimization.php',
		'/var/www/portfolio.fusioncore.com.br/public/wp-content/mu-plugins/portfolio-page-cache.php',
		'/var/www/portfolio.fusioncore.com.br/public/wp-content/themes/lago-process/functions.php',
		'/var/www/portfolio.fusioncore.com.br/public/wp-content/themes/lago-process/front-page.php',
		'/var/www/portfolio.fusioncore.com.br/public/wp-content/themes/lago-process/single.php',
		'/var/www/portfolio.fusioncore.com.br/public/wp-content/themes/lago-process/page-documentation.php',
		'/var/www/portfolio.fusioncore.com.br/public/wp-content/themes/lago-process/header.php',
		'/var/www/portfolio.fusioncore.com.br/public/wp-content/themes/lago-process/footer.php',
		'/var/www/portfolio.fusioncore.com.br/public/wp-content/themes/lago-process/assets/css/screen.css',
	]);
}

function lp_verdian_take_home_fields(): array {
	return [
		'lp_verdian_hero_eyebrow_pt' => ['label' => 'Hero eyebrow (PT)', 'type' => 'text'],
		'lp_verdian_hero_eyebrow_en' => ['label' => 'Hero eyebrow (EN)', 'type' => 'text'],
		'lp_verdian_hero_title_pt' => ['label' => 'Hero title (PT)', 'type' => 'text'],
		'lp_verdian_hero_title_en' => ['label' => 'Hero title (EN)', 'type' => 'text'],
		'lp_verdian_hero_subtitle_pt' => ['label' => 'Hero subtitle (PT)', 'type' => 'textarea'],
		'lp_verdian_hero_subtitle_en' => ['label' => 'Hero subtitle (EN)', 'type' => 'textarea'],
		'lp_verdian_hero_body_pt' => ['label' => 'Hero intro body (PT)', 'type' => 'editor'],
		'lp_verdian_hero_body_en' => ['label' => 'Hero intro body (EN)', 'type' => 'editor'],
		'lp_verdian_hero_cta_label_pt' => ['label' => 'Hero CTA label (PT)', 'type' => 'text'],
		'lp_verdian_hero_cta_label_en' => ['label' => 'Hero CTA label (EN)', 'type' => 'text'],
		'lp_verdian_hero_cta_url' => ['label' => 'Hero CTA URL', 'type' => 'url'],
		'lp_verdian_hero_images' => ['label' => 'Hero images', 'type' => 'image_cards'],
		'lp_verdian_competency_section_title_pt' => ['label' => 'Competency section title (PT)', 'type' => 'text'],
		'lp_verdian_competency_section_title_en' => ['label' => 'Competency section title (EN)', 'type' => 'text'],
		'lp_verdian_competency_intro_pt' => ['label' => 'Competency intro (PT)', 'type' => 'editor'],
		'lp_verdian_competency_intro_en' => ['label' => 'Competency intro (EN)', 'type' => 'editor'],
		'lp_verdian_competency_items' => ['label' => 'Competency items', 'type' => 'competency_items'],
		'lp_verdian_analytics_section_title_pt' => ['label' => 'Analytics section title (PT)', 'type' => 'text'],
		'lp_verdian_analytics_section_title_en' => ['label' => 'Analytics section title (EN)', 'type' => 'text'],
		'lp_verdian_analytics_intro_title_pt' => ['label' => 'Analytics intro title (PT)', 'type' => 'text'],
		'lp_verdian_analytics_intro_title_en' => ['label' => 'Analytics intro title (EN)', 'type' => 'text'],
		'lp_verdian_analytics_intro_pt' => ['label' => 'Analytics intro (PT)', 'type' => 'editor'],
		'lp_verdian_analytics_intro_en' => ['label' => 'Analytics intro (EN)', 'type' => 'editor'],
		'lp_verdian_analytics_fix_first_title_pt' => ['label' => 'What I would fix first title (PT)', 'type' => 'text'],
		'lp_verdian_analytics_fix_first_title_en' => ['label' => 'What I would fix first title (EN)', 'type' => 'text'],
		'lp_verdian_analytics_fix_first_body_pt' => ['label' => 'What I would fix first body (PT)', 'type' => 'editor'],
		'lp_verdian_analytics_fix_first_body_en' => ['label' => 'What I would fix first body (EN)', 'type' => 'editor'],
		'lp_verdian_analytics_plan_title_pt' => ['label' => '30-day plan title (PT)', 'type' => 'text'],
		'lp_verdian_analytics_plan_title_en' => ['label' => '30-day plan title (EN)', 'type' => 'text'],
		'lp_verdian_analytics_plan_body_pt' => ['label' => '30-day plan body (PT)', 'type' => 'editor'],
		'lp_verdian_analytics_plan_body_en' => ['label' => '30-day plan body (EN)', 'type' => 'editor'],
		'lp_verdian_analytics_ceo_title_pt' => ['label' => 'CEO explanation title (PT)', 'type' => 'text'],
		'lp_verdian_analytics_ceo_title_en' => ['label' => 'CEO explanation title (EN)', 'type' => 'text'],
		'lp_verdian_analytics_ceo_body_pt' => ['label' => 'CEO explanation body (PT)', 'type' => 'editor'],
		'lp_verdian_analytics_ceo_body_en' => ['label' => 'CEO explanation body (EN)', 'type' => 'editor'],
		'lp_verdian_analytics_doesnt_add_up_title_pt' => ['label' => 'What does not add up title (PT)', 'type' => 'text'],
		'lp_verdian_analytics_doesnt_add_up_title_en' => ['label' => 'What does not add up title (EN)', 'type' => 'text'],
		'lp_verdian_analytics_doesnt_add_up_body_pt' => ['label' => 'What does not add up body (PT)', 'type' => 'editor'],
		'lp_verdian_analytics_doesnt_add_up_body_en' => ['label' => 'What does not add up body (EN)', 'type' => 'editor'],
		'lp_verdian_analytics_images' => ['label' => 'Analytics section images', 'type' => 'image_cards'],
		'lp_verdian_architecture_section_title_pt' => ['label' => 'Architecture section title (PT)', 'type' => 'text'],
		'lp_verdian_architecture_section_title_en' => ['label' => 'Architecture section title (EN)', 'type' => 'text'],
		'lp_verdian_architecture_intro_pt' => ['label' => 'Architecture intro (PT)', 'type' => 'editor'],
		'lp_verdian_architecture_intro_en' => ['label' => 'Architecture intro (EN)', 'type' => 'editor'],
		'lp_verdian_architecture_overview_title_pt' => ['label' => 'Architecture overview title (PT)', 'type' => 'text'],
		'lp_verdian_architecture_overview_title_en' => ['label' => 'Architecture overview title (EN)', 'type' => 'text'],
		'lp_verdian_architecture_overview_body_pt' => ['label' => 'Architecture overview body (PT)', 'type' => 'editor'],
		'lp_verdian_architecture_overview_body_en' => ['label' => 'Architecture overview body (EN)', 'type' => 'editor'],
		'lp_verdian_architecture_flow_title_pt' => ['label' => 'Data flow title (PT)', 'type' => 'text'],
		'lp_verdian_architecture_flow_title_en' => ['label' => 'Data flow title (EN)', 'type' => 'text'],
		'lp_verdian_architecture_flow_body_pt' => ['label' => 'Data flow body (PT)', 'type' => 'editor'],
		'lp_verdian_architecture_flow_body_en' => ['label' => 'Data flow body (EN)', 'type' => 'editor'],
		'lp_verdian_architecture_roles_title_pt' => ['label' => 'Roles / permissions title (PT)', 'type' => 'text'],
		'lp_verdian_architecture_roles_title_en' => ['label' => 'Roles / permissions title (EN)', 'type' => 'text'],
		'lp_verdian_architecture_roles_body_pt' => ['label' => 'Roles / permissions body (PT)', 'type' => 'editor'],
		'lp_verdian_architecture_roles_body_en' => ['label' => 'Roles / permissions body (EN)', 'type' => 'editor'],
		'lp_verdian_architecture_notes_title_pt' => ['label' => 'Notes / constraints title (PT)', 'type' => 'text'],
		'lp_verdian_architecture_notes_title_en' => ['label' => 'Notes / constraints title (EN)', 'type' => 'text'],
		'lp_verdian_architecture_notes_body_pt' => ['label' => 'Notes / constraints body (PT)', 'type' => 'editor'],
		'lp_verdian_architecture_notes_body_en' => ['label' => 'Notes / constraints body (EN)', 'type' => 'editor'],
		'lp_verdian_architecture_diagram_title_pt' => ['label' => 'Architecture diagram title (PT)', 'type' => 'text'],
		'lp_verdian_architecture_diagram_title_en' => ['label' => 'Architecture diagram title (EN)', 'type' => 'text'],
		'lp_verdian_architecture_diagram_body_pt' => ['label' => 'Architecture diagram body (PT)', 'type' => 'editor'],
		'lp_verdian_architecture_diagram_body_en' => ['label' => 'Architecture diagram body (EN)', 'type' => 'editor'],
		'lp_verdian_architecture_diagram_image' => ['label' => 'Architecture diagram image', 'type' => 'image'],
		'lp_verdian_architecture_diagram_caption_pt' => ['label' => 'Architecture diagram caption (PT)', 'type' => 'textarea'],
		'lp_verdian_architecture_diagram_caption_en' => ['label' => 'Architecture diagram caption (EN)', 'type' => 'textarea'],
		'lp_verdian_architecture_diagram_notes_pt' => ['label' => 'Architecture diagram notes (PT)', 'type' => 'textarea'],
		'lp_verdian_architecture_diagram_notes_en' => ['label' => 'Architecture diagram notes (EN)', 'type' => 'textarea'],
		'lp_verdian_architecture_system_flow_title_pt' => ['label' => 'System flow title (PT)', 'type' => 'text'],
		'lp_verdian_architecture_system_flow_title_en' => ['label' => 'System flow title (EN)', 'type' => 'text'],
		'lp_verdian_architecture_system_flow_body_pt' => ['label' => 'System flow body (PT)', 'type' => 'editor'],
		'lp_verdian_architecture_system_flow_body_en' => ['label' => 'System flow body (EN)', 'type' => 'editor'],
		'lp_verdian_architecture_images' => ['label' => 'Architecture section images', 'type' => 'image_cards'],
		'lp_verdian_growth_section_title_pt' => ['label' => 'Growth section title (PT)', 'type' => 'text'],
		'lp_verdian_growth_section_title_en' => ['label' => 'Growth section title (EN)', 'type' => 'text'],
		'lp_verdian_growth_question_areas_title_pt' => ['label' => 'Growth areas question title (PT)', 'type' => 'text'],
		'lp_verdian_growth_question_areas_title_en' => ['label' => 'Growth areas question title (EN)', 'type' => 'text'],
		'lp_verdian_growth_question_areas_body_pt' => ['label' => 'Growth areas question body (PT)', 'type' => 'editor'],
		'lp_verdian_growth_question_areas_body_en' => ['label' => 'Growth areas question body (EN)', 'type' => 'editor'],
		'lp_verdian_growth_question_enjoy_title_pt' => ['label' => 'Enjoy question title (PT)', 'type' => 'text'],
		'lp_verdian_growth_question_enjoy_title_en' => ['label' => 'Enjoy question title (EN)', 'type' => 'text'],
		'lp_verdian_growth_question_enjoy_body_pt' => ['label' => 'Enjoy question body (PT)', 'type' => 'editor'],
		'lp_verdian_growth_question_enjoy_body_en' => ['label' => 'Enjoy question body (EN)', 'type' => 'editor'],
		'lp_verdian_visuals_section_title_pt' => ['label' => 'Visual references title (PT)', 'type' => 'text'],
		'lp_verdian_visuals_section_title_en' => ['label' => 'Visual references title (EN)', 'type' => 'text'],
		'lp_verdian_visuals_intro_pt' => ['label' => 'Visual references intro (PT)', 'type' => 'editor'],
		'lp_verdian_visuals_intro_en' => ['label' => 'Visual references intro (EN)', 'type' => 'editor'],
		'lp_verdian_visual_reference_items' => ['label' => 'Visual reference items', 'type' => 'image_cards'],
		'lp_verdian_ai_section_title_pt' => ['label' => 'AI section title (PT)', 'type' => 'text'],
		'lp_verdian_ai_section_title_en' => ['label' => 'AI section title (EN)', 'type' => 'text'],
		'lp_verdian_ai_tools_used_pt' => ['label' => 'Tools used (PT)', 'type' => 'editor'],
		'lp_verdian_ai_tools_used_en' => ['label' => 'Tools used (EN)', 'type' => 'editor'],
		'lp_verdian_ai_prompt_examples_pt' => ['label' => 'Prompt examples (PT)', 'type' => 'editor'],
		'lp_verdian_ai_prompt_examples_en' => ['label' => 'Prompt examples (EN)', 'type' => 'editor'],
		'lp_verdian_ai_prompt_2_pt' => ['label' => 'Prompt 2 (PT)', 'type' => 'editor'],
		'lp_verdian_ai_prompt_2_en' => ['label' => 'Prompt 2 (EN)', 'type' => 'editor'],
		'lp_verdian_ai_prompt_3_pt' => ['label' => 'Prompt 3 (PT)', 'type' => 'editor'],
		'lp_verdian_ai_prompt_3_en' => ['label' => 'Prompt 3 (EN)', 'type' => 'editor'],
		'lp_verdian_ai_adjusted_pt' => ['label' => 'What was adjusted (PT)', 'type' => 'editor'],
		'lp_verdian_ai_adjusted_en' => ['label' => 'What was adjusted (EN)', 'type' => 'editor'],
		'lp_verdian_ai_without_ai_pt' => ['label' => 'What was not done with AI (PT)', 'type' => 'editor'],
		'lp_verdian_ai_without_ai_en' => ['label' => 'What was not done with AI (EN)', 'type' => 'editor'],
		'lp_verdian_ai_notes_pt' => ['label' => 'AI notes (PT)', 'type' => 'editor'],
		'lp_verdian_ai_notes_en' => ['label' => 'AI notes (EN)', 'type' => 'editor'],
		'lp_verdian_ai_images' => ['label' => 'AI section images', 'type' => 'image_cards'],
		'lp_verdian_closing_section_title_pt' => ['label' => 'Closing title (PT)', 'type' => 'text'],
		'lp_verdian_closing_section_title_en' => ['label' => 'Closing title (EN)', 'type' => 'text'],
		'lp_verdian_closing_body_pt' => ['label' => 'Closing body (PT)', 'type' => 'editor'],
		'lp_verdian_closing_body_en' => ['label' => 'Closing body (EN)', 'type' => 'editor'],
		'lp_verdian_closing_cta_label_pt' => ['label' => 'Closing CTA label (PT)', 'type' => 'text'],
		'lp_verdian_closing_cta_label_en' => ['label' => 'Closing CTA label (EN)', 'type' => 'text'],
		'lp_verdian_closing_cta_url' => ['label' => 'Closing CTA URL', 'type' => 'url'],
		'lp_verdian_closing_images' => ['label' => 'Closing section images', 'type' => 'image_cards'],
	];
}

function lp_verdian_default_competency_items(): array {
	return [
		[
			'skill_pt' => 'WordPress — core',
			'skill_en' => 'WordPress — core',
			'rating'   => 5,
			'notes_pt' => 'Base principal do meu trabalho. Tenho experiência prática com tema customizado, plugin próprio, modelagem de campos, debugging, produção e manutenção contínua.',
			'notes_en' => 'Core part of my day-to-day work. Strong hands-on experience with custom themes, plugins, field modeling, debugging, production ownership, and ongoing maintenance.',
		],
		[
			'skill_pt' => 'WordPress — Newspack',
			'skill_en' => 'WordPress — Newspack',
			'rating'   => 3,
			'notes_pt' => 'Entendo bem a proposta editorial e as restrições do ecossistema. Ainda quero aprofundar mais execução direta em projetos Newspack de longa duração.',
			'notes_en' => 'I understand the editorial model and the constraints of the ecosystem well. I still want deeper long-term delivery exposure on active Newspack implementations.',
		],
		[
			'skill_pt' => 'Front-end',
			'skill_en' => 'Front-end',
			'rating'   => 4,
			'notes_pt' => 'Consigo implementar interfaces sólidas, responsivas e conectadas a regras de negócio. Meu foco costuma ser clareza, performance e integração com backend.',
			'notes_en' => 'I can build solid, responsive interfaces tied to business rules. My focus is usually clarity, performance, and tight backend integration.',
		],
		[
			'skill_pt' => 'PHP',
			'skill_en' => 'PHP',
			'rating'   => 4,
			'notes_pt' => 'Uso PHP diariamente em WordPress, automações e integrações. Tenho conforto com lógica de aplicação, APIs, sanitização, metadados e manutenção de código legado.',
			'notes_en' => 'I use PHP daily across WordPress, automations, and integrations. I am comfortable with application logic, APIs, sanitization, metadata, and legacy maintenance.',
		],
		[
			'skill_pt' => 'API integrations',
			'skill_en' => 'API integrations',
			'rating'   => 5,
			'notes_pt' => 'Ponto forte. Já conectei WordPress, CRMs, gateways, ERPs, PMS, webhooks e middleware, com preocupação prática com autenticação, retries e observabilidade.',
			'notes_en' => 'Strong area. I have connected WordPress with CRMs, gateways, ERPs, PMSs, webhooks, and middleware, with practical attention to auth, retries, and observability.',
		],
		[
			'skill_pt' => 'DNS & email authentication',
			'skill_en' => 'DNS & email authentication',
			'rating'   => 4,
			'notes_pt' => 'Tenho experiência real com DNS, SPF, DKIM, DMARC, roteamento e troubleshooting de entrega. Não trato isso como área separada do ownership técnico.',
			'notes_en' => 'Real experience with DNS, SPF, DKIM, DMARC, routing, and delivery troubleshooting. I treat this as part of technical ownership, not as a separate silo.',
		],
		[
			'skill_pt' => 'SEO & on-page optimization',
			'skill_en' => 'SEO & on-page optimization',
			'rating'   => 4,
			'notes_pt' => 'Tenho boa base em estrutura on-page, indexação, schema, metadados, links internos e higiene técnica. Costumo trabalhar isso de forma integrada ao conteúdo e à performance.',
			'notes_en' => 'Strong grounding in on-page structure, indexation, schema, metadata, internal linking, and technical hygiene. I usually handle it alongside content and performance work.',
		],
		[
			'skill_pt' => 'Analytics & tracking',
			'skill_en' => 'Analytics & tracking',
			'rating'   => 4,
			'notes_pt' => 'Tenho boa prática com GA4, GTM, UTMs, eventos e validação. Meu viés é menos “dashboard bonito” e mais garantir que os dados sirvam para decisão.',
			'notes_en' => 'Solid practical experience with GA4, GTM, UTMs, events, and QA. My bias is less toward pretty dashboards and more toward making the data decision-grade.',
		],
		[
			'skill_pt' => 'Performance optimization',
			'skill_en' => 'Performance optimization',
			'rating'   => 4,
			'notes_pt' => 'Atuo bem em cache, peso de página, assets, consultas e higiene de runtime. Normalmente penso performance junto com estabilidade operacional.',
			'notes_en' => 'Strong with caching, page weight, assets, queries, and runtime hygiene. I usually treat performance together with operational stability.',
		],
		[
			'skill_pt' => 'Front-end to CRM',
			'skill_en' => 'Front-end to CRM',
			'rating'   => 5,
			'notes_pt' => 'Uma das áreas em que mais agrego valor. Gosto de desenhar o caminho completo entre formulário, validação, middleware, CRM e retorno para a operação.',
			'notes_en' => 'One of the areas where I add the most value. I like designing the full path between form, validation, middleware, CRM, and the operational handoff.',
		],
		[
			'skill_pt' => 'Project scoping & docs',
			'skill_en' => 'Project scoping & docs',
			'rating'   => 5,
			'notes_pt' => 'Costumo traduzir necessidade de negócio em escopo executável, com documentação clara, trade-offs e expectativa realista de entrega.',
			'notes_en' => 'I regularly translate business needs into executable scope, with clear documentation, explicit trade-offs, and realistic delivery framing.',
		],
	];
}

function lp_assessment_visual_fields(): array {
	return [
		'lp_assessment_visual_section' => [
			'label'   => 'Page section',
			'type'    => 'select',
			'options' => [
				'hero'         => 'Hero / Intro',
				'analytics'    => 'Analytics Triage',
				'architecture' => 'Member Portal Architecture',
				'visuals'      => 'Visual References',
				'ai'           => 'AI Usage',
				'closing'      => 'Closing Note',
			],
		],
		'lp_assessment_visual_label' => ['label' => 'Public label', 'type' => 'text'],
		'lp_assessment_visual_caption' => ['label' => 'Caption / description', 'type' => 'textarea'],
		'lp_assessment_visual_alt' => ['label' => 'Image alt text override', 'type' => 'text'],
		'lp_assessment_visual_external_url' => ['label' => 'External image URL (optional)', 'type' => 'url'],
	];
}

function lp_verdian_take_home_seed_meta(): array {
	return [
		'lp_verdian_hero_eyebrow_pt' => 'Assessment response',
		'lp_verdian_hero_eyebrow_en' => 'Assessment response',
		'lp_verdian_hero_title_pt' => 'Verdian Insights - Take Home Assessment',
		'lp_verdian_hero_title_en' => 'Verdian Insights - Take Home Assessment',
		'lp_verdian_hero_subtitle_pt' => 'Uma abordagem estruturada para recuperar analytics, esclarecer atribuição e desenhar uma arquitetura escalável para portal de membros.',
		'lp_verdian_hero_subtitle_en' => 'A structured approach to analytics recovery, attribution clarity, and scalable member portal architecture.',
		'lp_verdian_hero_body_pt' => '<p>Esta página reúne minha resposta completa ao take-home da Verdian Insights.</p><p>Organizei a entrega da mesma forma que costumo trabalhar em contexto real: primeiro alinhando leitura de senioridade e escopo, depois atacando o problema de analytics, em seguida desenhando a arquitetura do portal e, por fim, documentando claramente o uso de IA.</p>',
		'lp_verdian_hero_body_en' => '<p>This page is my complete response to the Verdian Insights take-home exercise.</p><p>I structured it the same way I usually work in real delivery: first making seniority and scope visible, then addressing the analytics problem, then outlining the portal architecture, and finally documenting AI usage clearly.</p>',
		'lp_verdian_hero_cta_label_pt' => 'Ir para autoavaliação',
		'lp_verdian_hero_cta_label_en' => 'Jump to self-assessment',
		'lp_verdian_hero_cta_url' => '#competency-self-assessment',
		'lp_verdian_hero_images' => '[]',
		'lp_verdian_competency_section_title_pt' => 'Competency Self-Assessment',
		'lp_verdian_competency_section_title_en' => 'Competency Self-Assessment',
		'lp_verdian_competency_intro_pt' => '<p>Minha leitura abaixo tenta ser honesta e útil: mais alta onde já tenho profundidade real de execução, mais conservadora onde ainda quero ampliar repertório direto em contexto editorial específico.</p><p>Essa autoavaliação ajuda a contextualizar as respostas mais abertas logo em seguida.</p>',
		'lp_verdian_competency_intro_en' => '<p>The assessment below is meant to be honest and useful: higher where I already have real delivery depth, more conservative where I still want broader direct exposure in a specific editorial context.</p><p>It also sets up the short open questions that follow.</p>',
		'lp_verdian_competency_items' => '[{"skill_pt":"WordPress — core","skill_en":"WordPress — core","rating":5,"notes_pt":"Base principal do meu trabalho. Tenho experiência prática com tema customizado, plugin próprio, modelagem de campos, debugging, produção e manutenção contínua.","notes_en":"Core part of my day-to-day work. Strong hands-on experience with custom themes, plugins, field modeling, debugging, production ownership, and ongoing maintenance."},{"skill_pt":"WordPress — Newspack","skill_en":"WordPress — Newspack","rating":3,"notes_pt":"Entendo bem a proposta editorial e as restrições do ecossistema. Ainda quero aprofundar mais execução direta em projetos Newspack de longa duração.","notes_en":"I understand the editorial model and the constraints of the ecosystem well. I still want deeper long-term delivery exposure on active Newspack implementations."},{"skill_pt":"Front-end","skill_en":"Front-end","rating":4,"notes_pt":"Consigo implementar interfaces sólidas, responsivas e conectadas a regras de negócio. Meu foco costuma ser clareza, performance e integração com backend.","notes_en":"I can build solid, responsive interfaces tied to business rules. My focus is usually clarity, performance, and tight backend integration."},{"skill_pt":"PHP","skill_en":"PHP","rating":4,"notes_pt":"Uso PHP diariamente em WordPress, automações e integrações. Tenho conforto com lógica de aplicação, APIs, sanitização, metadados e manutenção de código legado.","notes_en":"I use PHP daily across WordPress, automations, and integrations. I am comfortable with application logic, APIs, sanitization, metadata, and legacy maintenance."},{"skill_pt":"API integrations","skill_en":"API integrations","rating":5,"notes_pt":"Ponto forte. Já conectei WordPress, CRMs, gateways, ERPs, PMS, webhooks e middleware, com preocupação prática com autenticação, retries e observabilidade.","notes_en":"Strong area. I have connected WordPress with CRMs, gateways, ERPs, PMSs, webhooks, and middleware, with practical attention to auth, retries, and observability."},{"skill_pt":"DNS & email authentication","skill_en":"DNS & email authentication","rating":4,"notes_pt":"Tenho experiência real com DNS, SPF, DKIM, DMARC, roteamento e troubleshooting de entrega. Não trato isso como área separada do ownership técnico.","notes_en":"Real experience with DNS, SPF, DKIM, DMARC, routing, and delivery troubleshooting. I treat this as part of technical ownership, not as a separate silo."},{"skill_pt":"SEO & on-page optimization","skill_en":"SEO & on-page optimization","rating":4,"notes_pt":"Tenho boa base em estrutura on-page, indexação, schema, metadados, links internos e higiene técnica. Costumo trabalhar isso de forma integrada ao conteúdo e à performance.","notes_en":"Strong grounding in on-page structure, indexation, schema, metadata, internal linking, and technical hygiene. I usually handle it alongside content and performance work."},{"skill_pt":"Analytics & tracking","skill_en":"Analytics & tracking","rating":4,"notes_pt":"Tenho boa prática com GA4, GTM, UTMs, eventos e validação. Meu viés é menos “dashboard bonito” e mais garantir que os dados sirvam para decisão.","notes_en":"Solid practical experience with GA4, GTM, UTMs, events, and QA. My bias is less toward pretty dashboards and more toward making the data decision-grade."},{"skill_pt":"Performance optimization","skill_en":"Performance optimization","rating":4,"notes_pt":"Atuo bem em cache, peso de página, assets, consultas e higiene de runtime. Normalmente penso performance junto com estabilidade operacional.","notes_en":"Strong with caching, page weight, assets, queries, and runtime hygiene. I usually treat performance together with operational stability."},{"skill_pt":"Front-end to CRM","skill_en":"Front-end to CRM","rating":5,"notes_pt":"Uma das áreas em que mais agrego valor. Gosto de desenhar o caminho completo entre formulário, validação, middleware, CRM e retorno para a operação.","notes_en":"One of the areas where I add the most value. I like designing the full path between form, validation, middleware, CRM, and the operational handoff."},{"skill_pt":"Project scoping & docs","skill_en":"Project scoping & docs","rating":5,"notes_pt":"Costumo traduzir necessidade de negócio em escopo executável, com documentação clara, trade-offs e expectativa realista de entrega.","notes_en":"I regularly translate business needs into executable scope, with clear documentation, explicit trade-offs, and realistic delivery framing."}]',
		'lp_verdian_growth_section_title_pt' => 'Open Questions',
		'lp_verdian_growth_section_title_en' => 'Open Questions',
		'lp_verdian_growth_question_areas_title_pt' => 'Quais 2 áreas você mais quer desenvolver, e por quê?',
		'lp_verdian_growth_question_areas_title_en' => 'Which 2 areas do you most want to grow into, and why?',
		'lp_verdian_growth_question_areas_body_pt' => '<p>As duas áreas em que mais quero crescer são <strong>WordPress — Newspack</strong> e <strong>Analytics & tracking</strong>.</p><p>No caso de Newspack, porque me interessa bastante o contexto editorial, membership e operações de newsroom com produto WordPress mais opinionado. No caso de analytics, porque é uma área em que impacto técnico e impacto de negócio se encontram diretamente: quando a medição fica confiável, a empresa passa a decidir melhor.</p>',
		'lp_verdian_growth_question_areas_body_en' => '<p>The two areas I most want to grow into are <strong>WordPress — Newspack</strong> and <strong>Analytics & tracking</strong>.</p><p>For Newspack, that is because I am very interested in editorial, membership, and newsroom operations inside a more opinionated WordPress product context. For analytics, it is because that is where technical work and business impact meet very directly: once measurement becomes trustworthy, the company makes better decisions.</p>',
		'lp_verdian_growth_question_enjoy_title_pt' => 'Qual 1 área você mais gosta de fazer no dia a dia?',
		'lp_verdian_growth_question_enjoy_title_en' => 'Which 1 area do you most enjoy doing day-to-day?',
		'lp_verdian_growth_question_enjoy_body_pt' => '<p>A área que eu mais gosto no dia a dia é <strong>Front-end to CRM</strong>.</p><p>Ela normalmente exige entender o processo inteiro, não só a interface: origem do lead, validação, qualidade do dado, middleware, CRM e uso operacional depois. Gosto desse tipo de problema porque ele junta produto, operação e engenharia de forma muito concreta.</p><p>Esse mesmo viés aparece na análise de tracking abaixo: primeiro fazer a medição voltar a sustentar decisão.</p>',
		'lp_verdian_growth_question_enjoy_body_en' => '<p>The area I most enjoy day to day is <strong>Front-end to CRM</strong>.</p><p>It usually requires understanding the whole process, not just the interface: lead source, validation, data quality, middleware, CRM, and downstream operational use. I like that kind of problem because it combines product, operations, and engineering in a very concrete way.</p><p>The same bias shows up in the tracking analysis below: restore measurement first so decisions can be trusted again.</p>',
		'lp_verdian_analytics_section_title_pt' => 'Analytics Triage',
		'lp_verdian_analytics_section_title_en' => 'Analytics Triage',
		'lp_verdian_analytics_intro_title_pt' => 'Por que começar por aqui',
		'lp_verdian_analytics_intro_title_en' => 'Why Start Here',
		'lp_verdian_analytics_intro_pt' => '<p>Eu começaria restaurando a capacidade de confiar nos dados antes de qualquer tentativa de otimização mais ampla. Sem conversões confiáveis e sem uma camada mínima de higiene de tracking, qualquer leitura de CAC, ROI ou canal vira opinião com verniz de dashboard.</p><p>Uma vez estabilizada a medição, a próxima pergunta passa a ser como estruturar o portal de benefícios com fronteiras claras entre experiência, middleware e sistema de registro.</p>',
		'lp_verdian_analytics_intro_en' => '<p>I would start by restoring the team\'s ability to trust the data before trying to optimize anything else. Without reliable conversions and a minimum level of tracking hygiene, any CAC, ROI, or channel readout becomes opinion wearing dashboard clothing.</p><p>Once measurement is stable again, the next question becomes how to structure the benefits portal with clear boundaries between experience, middleware, and system of record.</p>',
		'lp_verdian_analytics_fix_first_title_pt' => 'O que eu corrigiria primeiro',
		'lp_verdian_analytics_fix_first_title_en' => 'What I would fix first',
		'lp_verdian_analytics_fix_first_body_pt' => '<p>A primeira frente que eu corrigiria é o rastreamento de conversões. Hoje o GA4 mostra tráfego, mas não mostra resultado, então o negócio não consegue entender com clareza quais canais, campanhas ou ações do usuário estão gerando receita.</p><p>Sem essa visibilidade, a otimização de marketing vira tentativa e erro. Quando o tracking de conversão volta a funcionar, a empresa recupera a capacidade de medir ROI, comparar fontes de aquisição e priorizar melhorias com base em resultado real.</p>',
		'lp_verdian_analytics_fix_first_body_en' => '<p>The first issue I would fix is missing conversion tracking. Right now GA4 shows traffic, but not outcomes, which means the business cannot clearly see what channels, campaigns, or user actions are generating revenue.</p><p>Without that visibility, marketing optimization becomes guesswork. Fixing conversion tracking restores the ability to measure ROI, compare acquisition sources, and prioritize improvements based on actual business results.</p>',
		'lp_verdian_analytics_plan_title_pt' => 'Plano de 30 dias para recuperar a atribuição',
		'lp_verdian_analytics_plan_title_en' => '30-day plan to restore attribution',
		'lp_verdian_analytics_plan_body_pt' => '<h4>Semana 1 — Auditoria</h4><p>Auditar a implementação atual de GA4 e GTM, revisar a cobertura de eventos e identificar lacunas de tracking em formulários, assinaturas e compras.</p><h4>Semana 2 — Definição</h4><p>Definir as conversões que realmente importam para o negócio, padronizar convenções de nomenclatura e corrigir a disciplina de UTM entre campanhas, e-mails, PDFs e canais próprios.</p><h4>Semana 3 — Implementação</h4><p>Implementar ou reparar o rastreamento de conversão via GTM, validar o disparo dos eventos e garantir que os parâmetros corretos cheguem ao GA4 de forma consistente.</p><h4>Semana 4 — Validação</h4><p>Validar o setup com jornadas reais, filtrar tráfego claramente automatizado, comparar GA4 com backend ou CRM e montar uma camada simples de reporting para dar visibilidade ao negócio.</p>',
		'lp_verdian_analytics_plan_body_en' => '<h4>Week 1 — Audit</h4><p>Audit the current GA4 and GTM implementation, review event coverage, and identify tracking gaps across forms, subscriptions, and purchases.</p><h4>Week 2 — Definition</h4><p>Define the key business conversions, standardize naming conventions, and clean up UTM discipline across campaigns, email, PDFs, and owned channels.</p><h4>Week 3 — Implementation</h4><p>Implement or repair conversion tracking through GTM, validate event firing, and ensure the correct parameters reach GA4 consistently.</p><h4>Week 4 — Validation</h4><p>QA the setup using real journeys, filter obvious bot traffic, compare GA4 with backend or CRM reality, and create a simple reporting layer for business visibility.</p>',
		'lp_verdian_analytics_ceo_title_pt' => 'Como eu explicaria “0 conversões rastreadas” para o CEO',
		'lp_verdian_analytics_ceo_title_en' => 'How I would explain “0 conversions tracked” to the CEO',
		'lp_verdian_analytics_ceo_body_pt' => '<p>Estamos medindo visitas, mas não estamos medindo resultado. O GA4 mostrar zero conversões não significa que o negócio está gerando zero valor; significa que a plataforma não está capturando as ações que realmente importam.</p><p>Com isso, não dá para conectar tráfego ou campanhas à receita com confiança. O custo de não corrigir isso é continuar tomando decisão de marketing no escuro, com atribuição frágil e mais dificuldade para justificar investimento em crescimento.</p>',
		'lp_verdian_analytics_ceo_body_en' => '<p>We are measuring visits, but not results. GA4 showing zero conversions does not mean the business is generating zero value — it means the platform is not capturing the actions that matter.</p><p>Because of that, we cannot reliably connect traffic or campaigns to revenue. The cost of doing nothing is that marketing decisions stay blind, attribution stays unreliable, and growth investments are harder to justify.</p>',
		'lp_verdian_analytics_doesnt_add_up_title_pt' => 'O que eu verificaria antes de confiar nos dados',
		'lp_verdian_analytics_doesnt_add_up_title_en' => 'What I would verify before trusting the data',
		'lp_verdian_analytics_doesnt_add_up_body_pt' => '<p>Antes de confiar nos números, eu validaria pelo menos quatro pontos: se os 60% de “Direct” são tráfego direto real ou perda de atribuição por UTM/cross-domain; se o tráfego do Vietnã é ruído de bot ou referral spam; se início e conclusão de formulário estão sendo disparados com a mesma lógica em todas as páginas; e se os números do GA4 batem minimamente com CRM, backend ou outra fonte transacional.</p><p>Também verificaria consent mode, exclusões internas, deduplicação de eventos, filtros de tráfego inválido e se houve alguma quebra recente em GTM/publicação do site. Hoje eu trataria os dados como um sinal inicial, não como base confiável para decisão executiva.</p>',
		'lp_verdian_analytics_doesnt_add_up_body_en' => '<p>Before trusting the numbers, I would validate at least four things: whether the 60% “Direct” bucket is truly direct or attribution loss caused by UTM/cross-domain issues; whether the Vietnam traffic is bot noise or referral spam; whether form starts and completions fire with the same logic across pages; and whether GA4 roughly reconciles with the CRM, backend, or another transactional source.</p><p>I would also verify consent mode, internal traffic exclusions, event deduplication, invalid-traffic filtering, and whether something recently broke in GTM or site publishing. Right now I would treat the data as an initial signal, not as an executive-grade source of truth.</p>',
		'lp_verdian_analytics_images' => '[]',
		'lp_verdian_architecture_section_title_pt' => 'Member Portal Architecture',
		'lp_verdian_architecture_section_title_en' => 'Member Portal Architecture',
		'lp_verdian_architecture_intro_pt' => '<p>Minha abordagem aqui é manter fronteiras claras: Salesforce como fonte da verdade, WordPress/Newspack como camada de experiência autenticada e um middleware responsável por autenticação de serviço, transformação, cache e observabilidade. Isso reduz acoplamento e deixa o portal evoluível sem expor a complexidade do CRM no frontend.</p><p>O diagrama e o fluxo abaixo tornam esse desenho explícito em leitura rápida.</p>',
		'lp_verdian_architecture_intro_en' => '<p>My approach here is to keep boundaries clear: Salesforce as the source of truth, WordPress/Newspack as the authenticated experience layer, and middleware responsible for service auth, transformation, caching, and observability. That reduces coupling and keeps the portal evolvable without exposing CRM complexity to the frontend.</p><p>The diagram and system flow below make that design explicit at a quick scan.</p>',
		'lp_verdian_architecture_overview_title_pt' => 'Architecture Overview',
		'lp_verdian_architecture_overview_title_en' => 'Architecture Overview',
		'lp_verdian_architecture_overview_body_pt' => '<ul><li>WordPress/Newspack para login, experiência do membro e páginas administráveis</li><li>Middleware/API para autenticação entre sistemas, normalização de payload e regras de negócio leves</li><li>Salesforce como sistema de registro para benefícios, elegibilidade e dados de conta</li><li>Cache curto e invalidação por evento para aliviar consultas repetidas sem criar verdade paralela</li><li>Logs e alertas para erro de integração, timeout e falha de permissão</li></ul>',
		'lp_verdian_architecture_overview_body_en' => '<ul><li>WordPress/Newspack for login, member experience, and admin-manageable pages</li><li>Middleware/API for inter-system auth, payload normalization, and lightweight business rules</li><li>Salesforce as the system of record for benefits, eligibility, and account data</li><li>Short-lived caching plus event-based invalidation to reduce repeated lookups without creating a shadow source of truth</li><li>Logging and alerting for integration errors, timeouts, and permission failures</li></ul>',
		'lp_verdian_architecture_flow_title_pt' => 'Data Flow',
		'lp_verdian_architecture_flow_title_en' => 'Data Flow',
		'lp_verdian_architecture_flow_body_pt' => '<ol><li>O membro autentica no WordPress/Newspack</li><li>O portal solicita ao middleware apenas os dados necessários para aquela sessão/tela</li><li>O middleware valida permissões, consulta o Salesforce e transforma o payload para um formato estável de frontend</li><li>Quando fizer sentido, uma camada de cache curta responde leituras repetidas; eventos críticos seguem vindo da fonte primária</li><li>O WordPress renderiza a experiência e registra telemetria operacional sem depender de chamadas diretas do navegador ao Salesforce</li></ol>',
		'lp_verdian_architecture_flow_body_en' => '<ol><li>The member authenticates in WordPress/Newspack</li><li>The portal requests only the data needed for that session or screen from the middleware</li><li>The middleware validates permissions, queries Salesforce, and transforms the payload into a stable frontend shape</li><li>Where appropriate, short-lived cache serves repeated reads; critical events still come from the primary source</li><li>WordPress renders the experience and records operational telemetry without requiring direct browser calls to Salesforce</li></ol>',
		'lp_verdian_architecture_roles_title_pt' => 'Roles & Permissions',
		'lp_verdian_architecture_roles_title_en' => 'Roles & Permissions',
		'lp_verdian_architecture_roles_body_pt' => '<ul><li>Membro final: vê apenas seus próprios benefícios, elegibilidade e documentos permitidos</li><li>Admin do cliente: pode gerenciar membros relacionados à sua conta, convites e uso, sem ganhar acesso bruto ao CRM</li><li>Equipe interna Verdian: visão ampliada para suporte, troubleshooting e auditoria</li><li>Segmentação por plano, marca, grupo ou contrato deve ser resolvida na camada de dados/middleware, não por lógica espalhada no tema</li></ul>',
		'lp_verdian_architecture_roles_body_en' => '<ul><li>End member: sees only their own benefits, eligibility, and permitted documents</li><li>Client admin: can manage account-related members, invites, and usage without gaining raw CRM access</li><li>Internal Verdian team: broader visibility for support, troubleshooting, and audit</li><li>Plan, brand, group, or contract segmentation should be resolved in the data layer or middleware, not scattered through the theme</li></ul>',
		'lp_verdian_architecture_notes_title_pt' => 'Notes & Constraints',
		'lp_verdian_architecture_notes_title_en' => 'Notes & Constraints',
		'lp_verdian_architecture_notes_body_pt' => '<ul><li>Sem expor credenciais, schema ou endpoints sensíveis do Salesforce no browser</li><li>Preferir componentes modulares e payloads previsíveis para reduzir dependência de tema/plugin em detalhes do CRM</li><li>Registrar logs, retries e alertas porque erro de integração vira problema de suporte rapidamente</li><li>Planejar degradar com elegância: se a API falhar, o usuário precisa receber estado claro e o time precisa saber onde investigar</li><li>Estrutura pronta para evoluir para webhooks, filas ou sync assíncrono se a complexidade crescer</li></ul>',
		'lp_verdian_architecture_notes_body_en' => '<ul><li>No Salesforce credentials, schema details, or sensitive endpoints exposed in the browser</li><li>Prefer modular components and predictable payloads so the theme or plugin does not depend on CRM internals</li><li>Log, retry, and alert because integration failures quickly become support problems</li><li>Plan graceful degradation: if the API fails, the user needs a clear state and the team needs to know where to investigate</li><li>Keep the structure ready to evolve into webhooks, queues, or async sync if complexity grows</li></ul>',
		'lp_verdian_architecture_diagram_title_pt' => 'Diagrama de Arquitetura',
		'lp_verdian_architecture_diagram_title_en' => 'Architecture Diagram',
		'lp_verdian_architecture_diagram_body_pt' => '<p>Este diagrama representa o fluxo completo do Member Benefits Portal.</p><p>Os usuários, tanto membros individuais quanto admins corporativos, acessam uma experiência autenticada única em WordPress/Newspack, onde dashboards e benefícios disponíveis são apresentados.</p><p>Toda a lógica de negócio e a orquestração dos dados ficam fora do frontend, em uma camada de middleware (n8n), que se comunica com o Salesforce como fonte da verdade.</p><p>O Salesforce define quais benefícios cada usuário pode acessar, e o middleware traduz isso em respostas estruturadas consumidas pela aplicação em WordPress.</p>',
		'lp_verdian_architecture_diagram_body_en' => '<p>This diagram represents the full flow of the Member Benefits Portal.</p><p>Users (both individual members and corporate admins) access a single authenticated experience in WordPress/Newspack, where dashboards and entitlements are presented.</p><p>All business logic and data orchestration are handled outside the frontend through a middleware layer (n8n), which communicates with Salesforce as the source of truth.</p><p>Salesforce defines which benefits each user can access, and the middleware translates that into structured responses consumed by the WordPress application.</p>',
		'lp_verdian_architecture_diagram_image' => '',
		'lp_verdian_architecture_diagram_caption_pt' => 'Fluxo end-to-end entre Member User e Corporate Admin, WordPress/Newspack, middleware (n8n), Salesforce e as plataformas downstream Thinkific/LPEC, CW News, Pheedloop, HQP e Circle.',
		'lp_verdian_architecture_diagram_caption_en' => 'End-to-end flow between Member User and Corporate Admin, WordPress/Newspack, middleware (n8n), Salesforce, and the downstream platforms Thinkific/LPEC, CW News, Pheedloop, HQP, and Circle.',
		'lp_verdian_architecture_diagram_notes_pt' => "WordPress/Newspack cuida apenas da apresentação e autenticação.\nn8n cuida da orquestração e transformação dos dados.\nSalesforce define regras de acesso e entitlements.\nCredenciais do CRM e lógica de negócio não ficam expostas no frontend.\nO sistema permanece modular e escalável para múltiplas marcas e múltiplas plataformas de benefício.",
		'lp_verdian_architecture_diagram_notes_en' => "WordPress/Newspack handles only presentation and authentication.\nn8n handles orchestration and data transformation.\nSalesforce defines access rules and entitlements.\nNo CRM credentials or business logic are exposed to the frontend.\nThe system remains modular and scalable across multiple brands and benefit platforms.",
		'lp_verdian_architecture_system_flow_title_pt' => 'Fluxo do Sistema',
		'lp_verdian_architecture_system_flow_title_en' => 'System Flow',
		'lp_verdian_architecture_system_flow_body_pt' => '<ol><li>O usuário, membro individual ou admin corporativo, faz login no portal</li><li>WordPress/Newspack autentica a sessão e carrega o dashboard</li><li>A aplicação solicita os dados do usuário através da camada de middleware</li><li>O n8n orquestra as requisições e busca no Salesforce os dados de entitlement</li><li>Os dados são normalizados e devolvidos ao WordPress</li><li>O dashboard renderiza os benefícios disponíveis, como LPEC/Thinkific, CW News, Events/Pheedloop, HQP e Connect/Circle, com base nos entitlements</li></ol>',
		'lp_verdian_architecture_system_flow_body_en' => '<ol><li>User (Member or Corporate Admin) logs into the portal</li><li>WordPress/Newspack handles authentication and loads the dashboard</li><li>The application requests user data through the middleware layer</li><li>n8n orchestrates requests and retrieves entitlement data from Salesforce</li><li>Data is normalized and returned to WordPress</li><li>The dashboard renders available benefits (LPEC, CW News, Events, HQP, Connect) based on entitlements</li></ol>',
		'lp_verdian_architecture_images' => '[]',
		'lp_verdian_growth_section_title_pt' => 'Open Questions',
		'lp_verdian_growth_section_title_en' => 'Open Questions',
		'lp_verdian_visuals_section_title_pt' => 'Referências Visuais',
		'lp_verdian_visuals_section_title_en' => 'Visual References',
		'lp_verdian_visuals_intro_pt' => '<p>As imagens abaixo funcionam como apoio visual para o desenho acima. Elas não substituem a arquitetura proposta, mas ajudam a mostrar como essa experiência pode aparecer para o usuário final.</p>',
		'lp_verdian_visuals_intro_en' => '<p>The visuals below act as visual support for the architecture above. They do not replace the proposed system design, but they help show how that experience could surface for the end user.</p>',
		'lp_verdian_visual_reference_items' => '[]',
		'lp_verdian_ai_section_title_pt' => 'Uso de IA',
		'lp_verdian_ai_section_title_en' => 'AI Usage',
		'lp_verdian_ai_tools_used_pt' => '<p><strong>Ferramenta usada:</strong> ChatGPT.</p><p>Usei como apoio de drafting e estruturação, principalmente para testar enquadramentos do plano de analytics, revisar wording do trecho para CEO e organizar a seção de disclosure de IA. A arquitetura final, as notas de senioridade e a implementação em WordPress/PHP foram fechadas manualmente por mim.</p><p>Deixo essa parte por último porque, para mim, IA é apoio de processo, não o centro da entrega.</p>',
		'lp_verdian_ai_tools_used_en' => '<p><strong>Tool used:</strong> ChatGPT.</p><p>I used it as a drafting and structuring assistant, mainly to test framing options for the analytics plan, refine wording for the CEO explanation, and organize the AI disclosure section. The final architecture, seniority assessment, and WordPress/PHP implementation were completed manually by me.</p><p>I keep this section last intentionally because, to me, AI is process support, not the center of the submission.</p>',
		'lp_verdian_ai_prompt_examples_pt' => '<p><strong>Prompt 1:</strong> "Draft a concise 30-day recovery plan for a GA4/GTM setup where traffic is visible but conversions are missing, with actions grouped by audit, definition, implementation, and validation."</p>',
		'lp_verdian_ai_prompt_examples_en' => '<p><strong>Prompt 1:</strong> "Draft a concise 30-day recovery plan for a GA4/GTM setup where traffic is visible but conversions are missing, with actions grouped by audit, definition, implementation, and validation."</p>',
		'lp_verdian_ai_prompt_2_pt' => '<p><strong>Prompt 2:</strong> "Outline a WordPress/Newspack member benefits portal architecture where Salesforce is the source of truth and a middleware layer handles data access, transformation, and permissions."</p>',
		'lp_verdian_ai_prompt_2_en' => '<p><strong>Prompt 2:</strong> "Outline a WordPress/Newspack member benefits portal architecture where Salesforce is the source of truth and a middleware layer handles data access, transformation, and permissions."</p>',
		'lp_verdian_ai_prompt_3_pt' => '<p><strong>Prompt 3:</strong> "Give me three executive-safe ways to explain why GA4 can show zero conversions even when the business is still generating leads or revenue."</p>',
		'lp_verdian_ai_prompt_3_en' => '<p><strong>Prompt 3:</strong> "Give me three executive-safe ways to explain why GA4 can show zero conversions even when the business is still generating leads or revenue."</p>',
		'lp_verdian_ai_adjusted_pt' => '<p>Um exemplo claro do que eu reescrevi: a IA sugeriu uma resposta de arquitetura mais genérica, muito centrada em “dashboard” e pouco centrada em governança de dados e fronteiras entre WordPress, middleware e Salesforce. Eu descartei esse caminho porque ele soava correto no papel, mas não refletia o tipo de cuidado operacional que o problema realmente pede.</p>',
		'lp_verdian_ai_adjusted_en' => '<p>One clear example of something I rewrote: the AI suggested a more generic architecture answer that was too “dashboard”-centric and not focused enough on data governance and the boundaries between WordPress, middleware, and Salesforce. I rejected that direction because it sounded plausible on paper, but did not reflect the operational care the problem actually requires.</p>',
		'lp_verdian_ai_without_ai_pt' => '<p>Não usei IA para definir as notas da autoavaliação nem para a implementação em código desta entrega. Fiz isso manualmente porque os dois pontos dependem mais de julgamento pessoal, contexto de experiência real e coerência de execução do que de geração de texto.</p>',
		'lp_verdian_ai_without_ai_en' => '<p>I did not use AI to determine the self-assessment ratings or to implement this submission in code. I handled both manually because they depend more on personal judgment, real delivery context, and execution consistency than on text generation.</p>',
		'lp_verdian_ai_notes_pt' => '<p>Em resumo: usei IA como apoio de estrutura e wording, não como substituta de decisão técnica. Tudo o que entrou aqui passou por revisão, cortes e reescrita manual para caber no contexto real do brief.</p>',
		'lp_verdian_ai_notes_en' => '<p>In short: I used AI as support for structure and wording, not as a substitute for technical judgment. Everything included here was reviewed, cut down, and manually rewritten to fit the real brief.</p>',
		'lp_verdian_ai_images' => '[]',
		'lp_verdian_closing_section_title_pt' => 'Nota final',
		'lp_verdian_closing_section_title_en' => 'Final Note',
		'lp_verdian_closing_body_pt' => '<p>Essa entrega tenta mostrar não só resposta, mas forma de trabalho: leitura honesta de senioridade, priorização orientada ao negócio, arquitetura pragmática e documentação suficiente para execução.</p><p>O bonus continua ligado como prova complementar do ambiente WordPress usado na entrega, mas deixei o assessment principal completo por conta própria, sem depender dele para cobrir itens obrigatórios do brief.</p>',
		'lp_verdian_closing_body_en' => '<p>This submission is meant to show not just answers, but working style: honest seniority assessment, business-aware prioritization, pragmatic architecture, and enough documentation to support execution.</p><p>The bonus page remains connected as supporting proof of the WordPress environment used for the delivery, but I kept the main assessment complete on its own instead of relying on the bonus to cover required parts of the brief.</p>',
		'lp_verdian_closing_cta_label_pt' => '',
		'lp_verdian_closing_cta_label_en' => '',
		'lp_verdian_closing_cta_url' => '',
		'lp_verdian_closing_images' => '[]',
	];
}

function lp_bonus_assessment_fields(): array {
	return [
		'lp_bonus_assessment_eyebrow_pt' => ['label' => 'Hero eyebrow (PT)', 'type' => 'text'],
		'lp_bonus_assessment_eyebrow_en' => ['label' => 'Hero eyebrow (EN)', 'type' => 'text'],
		'lp_bonus_assessment_title_pt' => ['label' => 'Page title (PT)', 'type' => 'text'],
		'lp_bonus_assessment_title_en' => ['label' => 'Page title (EN)', 'type' => 'text'],
		'lp_bonus_assessment_intro_pt' => ['label' => 'Intro body (PT)', 'type' => 'editor'],
		'lp_bonus_assessment_intro_en' => ['label' => 'Intro body (EN)', 'type' => 'editor'],
		'lp_bonus_assessment_environment_title_pt' => ['label' => 'Environment title (PT)', 'type' => 'text'],
		'lp_bonus_assessment_environment_title_en' => ['label' => 'Environment title (EN)', 'type' => 'text'],
		'lp_bonus_assessment_environment_body_pt' => ['label' => 'Environment body (PT)', 'type' => 'editor'],
		'lp_bonus_assessment_environment_body_en' => ['label' => 'Environment body (EN)', 'type' => 'editor'],
		'lp_bonus_assessment_stack_title_pt' => ['label' => 'Implementation title (PT)', 'type' => 'text'],
		'lp_bonus_assessment_stack_title_en' => ['label' => 'Implementation title (EN)', 'type' => 'text'],
		'lp_bonus_assessment_stack_body_pt' => ['label' => 'Implementation body (PT)', 'type' => 'editor'],
		'lp_bonus_assessment_stack_body_en' => ['label' => 'Implementation body (EN)', 'type' => 'editor'],
		'lp_bonus_assessment_plugin_title_pt' => ['label' => 'Plugin title (PT)', 'type' => 'text'],
		'lp_bonus_assessment_plugin_title_en' => ['label' => 'Plugin title (EN)', 'type' => 'text'],
		'lp_bonus_assessment_plugin_body_pt' => ['label' => 'Plugin body (PT)', 'type' => 'editor'],
		'lp_bonus_assessment_plugin_body_en' => ['label' => 'Plugin body (EN)', 'type' => 'editor'],
		'lp_bonus_assessment_closing_pt' => ['label' => 'Closing body (PT)', 'type' => 'editor'],
		'lp_bonus_assessment_closing_en' => ['label' => 'Closing body (EN)', 'type' => 'editor'],
	];
}

function lp_bonus_assessment_seed_meta(): array {
	return [
		'lp_bonus_assessment_eyebrow_pt' => 'Bonus Assessment',
		'lp_bonus_assessment_eyebrow_en' => 'Bonus Assessment',
		'lp_bonus_assessment_title_pt' => 'Ambiente WordPress usado nesta entrega',
		'lp_bonus_assessment_title_en' => 'WordPress environment used in this delivery',
		'lp_bonus_assessment_intro_pt' => '<p>Como complemento ao assessment, esta página documenta que toda a entrega foi organizada dentro de um ambiente WordPress real, seguindo a mesma lógica estrutural que uso nos meus projetos.</p>',
		'lp_bonus_assessment_intro_en' => '<p>As a bonus assessment note, this page documents that the entire delivery was organized inside a real WordPress environment, following the same structural approach I use in my projects.</p>',
		'lp_bonus_assessment_environment_title_pt' => 'Tudo rodando em WordPress',
		'lp_bonus_assessment_environment_title_en' => 'Everything running inside WordPress',
		'lp_bonus_assessment_environment_body_pt' => '<p>Esta apresentação não foi montada em slides estáticos nem em um mockup isolado. Tudo está dentro de um ambiente WordPress funcional, com tema customizado, páginas especiais, persistência em admin e renderização real no frontend.</p>',
		'lp_bonus_assessment_environment_body_en' => '<p>This presentation was not assembled as static slides or an isolated mockup. Everything lives inside a functional WordPress environment, with a custom theme, special pages, admin persistence, and real frontend rendering.</p>',
		'lp_bonus_assessment_stack_title_pt' => 'ACF-like e CPT-like sem dependência externa',
		'lp_bonus_assessment_stack_title_en' => 'ACF-like and CPT-like without external dependency',
		'lp_bonus_assessment_stack_body_pt' => '<p>A modelagem foi tratada com uma camada ACF-like e estruturas CPT-like, mas implementadas de forma customizada. Isso permite campos organizados, edição controlada e separação clara entre conteúdo, layout e lógica, sem depender de page builder ou ACF.</p>',
		'lp_bonus_assessment_stack_body_en' => '<p>The modeling was handled through an ACF-like layer and CPT-like structures, but implemented in a custom way. That keeps fields organized, editing controlled, and content, layout, and logic clearly separated, without relying on a page builder or ACF.</p>',
		'lp_bonus_assessment_plugin_title_pt' => 'Plugin desenvolvido por mim',
		'lp_bonus_assessment_plugin_title_en' => 'Plugin developed by me',
		'lp_bonus_assessment_plugin_body_pt' => '<p>O toolkit que sustenta esse fluxo administrativo foi desenvolvido por mim. Ele registra os tipos de conteúdo, define os campos customizados, organiza a experiência no wp-admin e sustenta a renderização das páginas especiais deste portfólio.</p>',
		'lp_bonus_assessment_plugin_body_en' => '<p>The toolkit behind this admin workflow was developed by me. It registers the content structures, defines the custom fields, organizes the wp-admin experience, and supports the rendering of the special pages in this portfolio.</p>',
		'lp_bonus_assessment_closing_pt' => '<p>Em outras palavras: além da resposta em si, o próprio ambiente onde ela está publicada também funciona como prova de implementação.</p>',
		'lp_bonus_assessment_closing_en' => '<p>In other words: beyond the response itself, the environment where it is published also serves as implementation proof.</p>',
	];
}

function lp_verdian_architecture_diagram_attachment_id(): int {
	$existing = get_posts([
		'post_type'      => 'attachment',
		'post_status'    => 'inherit',
		'posts_per_page' => 1,
		'meta_key'       => '_wp_attached_file',
		'meta_value'     => '2026/04/verdian/webp/arch2.webp',
		'fields'         => 'ids',
	]);

	if (!empty($existing[0])) {
		return (int) $existing[0];
	}

	$relative_path = '2026/04/verdian/webp/arch2.webp';
	$file_path = trailingslashit(wp_get_upload_dir()['basedir']) . $relative_path;

	if (!file_exists($file_path)) {
		return 0;
	}

	$filetype = wp_check_filetype(basename($file_path), null);
	$attachment_id = wp_insert_attachment([
		'post_mime_type' => $filetype['type'] ?? 'image/webp',
		'post_title'     => 'Verdian Architecture Diagram 2',
		'post_content'   => '',
		'post_status'    => 'inherit',
	], $file_path);

	if (is_wp_error($attachment_id) || $attachment_id <= 0) {
		return 0;
	}

	require_once ABSPATH . 'wp-admin/includes/image.php';
	$metadata = wp_generate_attachment_metadata($attachment_id, $file_path);
	if (!is_wp_error($metadata) && is_array($metadata)) {
		wp_update_attachment_metadata($attachment_id, $metadata);
	}

	update_post_meta($attachment_id, '_wp_attachment_image_alt', 'Architecture diagram showing WordPress, middleware, and external systems interaction');

	return (int) $attachment_id;
}

function lp_seed_verdian_take_home_page(): void {
	$page = get_page_by_path('verdian-take-home', OBJECT, 'page');
	$page_id = $page instanceof WP_Post ? (int) $page->ID : 0;
	$content_version = 'verdian-brief-complete-v5';

	if ($page_id <= 0) {
		$page_id = wp_insert_post([
			'post_type'    => 'page',
			'post_status'  => 'publish',
			'post_title'   => 'Verdian Take Home',
			'post_name'    => 'verdian-take-home',
			'post_content' => '',
		], true);

		if (is_wp_error($page_id)) {
			return;
		}
	}

	if (get_the_title($page_id) === '') {
		wp_update_post([
			'ID'         => $page_id,
			'post_title' => 'Verdian Take Home',
		]);
	}

	$stored_version = (string) get_post_meta($page_id, 'lp_verdian_content_version', true);
	foreach (lp_verdian_take_home_seed_meta() as $key => $value) {
		$existing = get_post_meta($page_id, $key, true);
		if ($stored_version !== $content_version || $existing === '' || $existing === null) {
			update_post_meta($page_id, $key, $value);
		}
	}

	update_post_meta($page_id, 'lp_verdian_content_version', $content_version);

	$diagram_image = get_post_meta($page_id, 'lp_verdian_architecture_diagram_image', true);
	if ($diagram_image === '' || $diagram_image === null) {
		$attachment_id = lp_verdian_architecture_diagram_attachment_id();
		if ($attachment_id > 0) {
			update_post_meta($page_id, 'lp_verdian_architecture_diagram_image', (string) $attachment_id);
		}
	}
}

add_action('init', 'lp_seed_verdian_take_home_page', 30);

function lp_seed_bonus_assessment_page(): void {
	$page = get_page_by_path('bonus-assessment', OBJECT, 'page');
	$page_id = $page instanceof WP_Post ? (int) $page->ID : 0;

	if ($page_id <= 0) {
		$page_id = wp_insert_post([
			'post_type'    => 'page',
			'post_status'  => 'publish',
			'post_title'   => 'Bonus Assessment',
			'post_name'    => 'bonus-assessment',
			'post_content' => '',
		], true);

		if (is_wp_error($page_id)) {
			return;
		}
	}

	if (get_the_title($page_id) === '') {
		wp_update_post([
			'ID'         => $page_id,
			'post_title' => 'Bonus Assessment',
		]);
	}

	foreach (lp_bonus_assessment_seed_meta() as $key => $value) {
		$existing = get_post_meta($page_id, $key, true);
		if ($existing === '' || $existing === null) {
			update_post_meta($page_id, $key, $value);
		}
	}
}

add_action('init', 'lp_seed_bonus_assessment_page', 30);

function lp_global_fields(): array {
	return [
		'lp_brand_mark'                  => ['label' => 'Header brand mark', 'type' => 'text'],
		'lp_brand_subtitle_pt'           => ['label' => 'Header subtitle (PT)', 'type' => 'text'],
		'lp_brand_subtitle_en'           => ['label' => 'Header subtitle (EN)', 'type' => 'text'],
		'lp_header_cta_label_pt'         => ['label' => 'Header CTA label (PT)', 'type' => 'text'],
		'lp_header_cta_label_en'         => ['label' => 'Header CTA label (EN)', 'type' => 'text'],
		'lp_header_cta_url'              => ['label' => 'Header CTA URL', 'type' => 'url'],
		'lp_schedule_global_eyebrow_pt'  => ['label' => 'Global schedule eyebrow (PT)', 'type' => 'text'],
		'lp_schedule_global_eyebrow_en'  => ['label' => 'Global schedule eyebrow (EN)', 'type' => 'text'],
		'lp_schedule_global_title_pt'    => ['label' => 'Global schedule title (PT)', 'type' => 'text'],
		'lp_schedule_global_title_en'    => ['label' => 'Global schedule title (EN)', 'type' => 'text'],
		'lp_schedule_global_body_pt'     => ['label' => 'Global schedule body (PT)', 'type' => 'textarea'],
		'lp_schedule_global_body_en'     => ['label' => 'Global schedule body (EN)', 'type' => 'textarea'],
		'lp_schedule_global_button_label_pt' => ['label' => 'Global schedule button label (PT)', 'type' => 'text'],
		'lp_schedule_global_button_label_en' => ['label' => 'Global schedule button label (EN)', 'type' => 'text'],
		'lp_schedule_global_button_url'  => ['label' => 'Global schedule button URL', 'type' => 'url'],
		'lp_footer_text_pt'              => ['label' => 'Footer text (PT)', 'type' => 'textarea'],
		'lp_footer_text_en'              => ['label' => 'Footer text (EN)', 'type' => 'textarea'],
		'lp_footer_link_label'           => ['label' => 'Footer link label', 'type' => 'text'],
		'lp_default_seo_description_pt'  => ['label' => 'Default SEO description (PT)', 'type' => 'textarea'],
		'lp_default_seo_description_en'  => ['label' => 'Default SEO description (EN)', 'type' => 'textarea'],
		'lp_default_social_image_url'    => ['label' => 'Default social image URL', 'type' => 'url'],
		'lp_schema_person_name'          => ['label' => 'Schema person name', 'type' => 'text'],
		'lp_schema_job_title_pt'         => ['label' => 'Schema job title (PT)', 'type' => 'text'],
		'lp_schema_job_title_en'         => ['label' => 'Schema job title (EN)', 'type' => 'text'],
		'lp_schema_knows_about_json'     => ['label' => 'Schema knowsAbout JSON', 'type' => 'textarea'],
		'lp_archive_latest_label'        => ['label' => 'Archive fallback eyebrow', 'type' => 'text'],
		'lp_archive_empty_message'       => ['label' => 'Archive empty message', 'type' => 'text'],
		'lp_single_client_label'         => ['label' => 'Single project client label', 'type' => 'text'],
		'lp_single_source_label'         => ['label' => 'Single project source label', 'type' => 'text'],
		'lp_single_demo_label'           => ['label' => 'Single project demo label', 'type' => 'text'],
		'lp_single_demo_link_label'      => ['label' => 'Single project demo link label', 'type' => 'text'],
		'lp_single_private_label'        => ['label' => 'Single project private demo label', 'type' => 'text'],
		'lp_single_access_eyebrow'       => ['label' => 'Access section eyebrow', 'type' => 'text'],
		'lp_single_access_title'         => ['label' => 'Access section title', 'type' => 'text'],
		'lp_single_access_body'          => ['label' => 'Access section body', 'type' => 'textarea'],
		'lp_single_project_label'        => ['label' => 'Access project label', 'type' => 'text'],
		'lp_single_login_label'          => ['label' => 'Access login label', 'type' => 'text'],
		'lp_single_user_label'           => ['label' => 'Access user label', 'type' => 'text'],
		'lp_single_password_label'       => ['label' => 'Access password label', 'type' => 'text'],
		'lp_single_not_provided_label'   => ['label' => 'Access not provided label', 'type' => 'text'],
		'lp_single_user_fallback'        => ['label' => 'Access user fallback', 'type' => 'text'],
		'lp_single_password_fallback'    => ['label' => 'Access password fallback', 'type' => 'text'],
		'lp_single_stack_title'          => ['label' => 'Stack section title', 'type' => 'text'],
		'lp_single_integrated_apis_title'=> ['label' => 'Integrated APIs section title', 'type' => 'text'],
		'lp_single_admin_features_title' => ['label' => 'Admin features section title', 'type' => 'text'],
		'lp_single_documentation_title'  => ['label' => 'Documentation section title', 'type' => 'text'],
		'lp_single_code_evidence_title'  => ['label' => 'Code evidence section title', 'type' => 'text'],
		'lp_single_integrations_title'   => ['label' => 'Integrations section title', 'type' => 'text'],
		'lp_single_automation_title'     => ['label' => 'Automation section title', 'type' => 'text'],
		'lp_single_outcome_title'        => ['label' => 'Outcome section title', 'type' => 'text'],
	];
}

function lp_page_fields_for_slug(string $slug): array {
	return match ($slug) {
		'documentation' => [
			'lp_doc_hero_eyebrow'     => ['label' => 'Hero eyebrow', 'type' => 'text'],
			'lp_doc_hero_lede'        => ['label' => 'Hero lede', 'type' => 'textarea'],
			'lp_doc_summary_cards'    => ['label' => 'Summary cards JSON', 'type' => 'textarea'],
			'lp_doc_nav_links'        => ['label' => 'Navigation links JSON', 'type' => 'textarea'],
			'lp_doc_sections'         => ['label' => 'Documentation sections JSON', 'type' => 'textarea'],
		],
		'projects' => [
			'lp_projects_eyebrow'     => ['label' => 'Hero eyebrow', 'type' => 'text'],
			'lp_projects_lede'        => ['label' => 'Hero lede', 'type' => 'textarea'],
			'lp_projects_stack_label' => ['label' => 'Stack label', 'type' => 'text'],
			'lp_projects_button_label'=> ['label' => 'Card button label', 'type' => 'text'],
		],
		'brands' => [
			'lp_brands_eyebrow'       => ['label' => 'Hero eyebrow', 'type' => 'text'],
			'lp_brands_lede'          => ['label' => 'Hero lede', 'type' => 'textarea'],
			'lp_brands_stack_prefix'  => ['label' => 'Stack prefix', 'type' => 'text'],
			'lp_brands_button_label'  => ['label' => 'Card button label', 'type' => 'text'],
		],
		'versioning' => [
			'lp_versioning_eyebrow'   => ['label' => 'Hero eyebrow', 'type' => 'text'],
			'lp_versioning_cards'     => ['label' => 'Version cards JSON', 'type' => 'textarea'],
			'lp_versioning_commands_title' => ['label' => 'Commands panel title', 'type' => 'text'],
			'lp_versioning_commands_code'  => ['label' => 'Commands panel code', 'type' => 'textarea'],
		],
		'schedule-next-step' => [
			'lp_schedule_eyebrow'     => ['label' => 'Hero eyebrow', 'type' => 'text'],
			'lp_schedule_card_title'  => ['label' => 'Card title', 'type' => 'text'],
			'lp_schedule_card_body'   => ['label' => 'Card body', 'type' => 'textarea'],
			'lp_schedule_button_label'=> ['label' => 'Button label', 'type' => 'text'],
			'lp_schedule_button_url'  => ['label' => 'Button URL', 'type' => 'url'],
		],
		'zapier-integration' => [
			'lp_zapier_eyebrow'         => ['label' => 'Hero eyebrow', 'type' => 'text'],
			'lp_zapier_panel_title'     => ['label' => 'Panel title', 'type' => 'text'],
			'lp_zapier_panel_body'      => ['label' => 'Panel body', 'type' => 'textarea'],
			'lp_zapier_webhook_label'   => ['label' => 'Webhook field label', 'type' => 'text'],
			'lp_zapier_api_key_label'   => ['label' => 'API key field label', 'type' => 'text'],
			'lp_zapier_test_name_label' => ['label' => 'Test name field label', 'type' => 'text'],
			'lp_zapier_test_email_label'=> ['label' => 'Test email field label', 'type' => 'text'],
			'lp_zapier_submit_label'    => ['label' => 'Submit button label', 'type' => 'text'],
			'lp_zapier_invalid_url_msg' => ['label' => 'Invalid URL message', 'type' => 'text'],
			'lp_zapier_success_msg'     => ['label' => 'Success message', 'type' => 'text'],
			'lp_zapier_default_name'    => ['label' => 'Default test name', 'type' => 'text'],
		],
		'cv' => [
			'lp_cv_meta_description_pt' => ['label' => 'CV meta description (PT)', 'type' => 'textarea'],
			'lp_cv_meta_description_en' => ['label' => 'CV meta description (EN)', 'type' => 'textarea'],
			'lp_cv_header_badge_pt' => ['label' => 'Header badge (PT)', 'type' => 'text'],
			'lp_cv_header_badge_en' => ['label' => 'Header badge (EN)', 'type' => 'text'],
			'lp_cv_role_pt' => ['label' => 'Role (PT)', 'type' => 'text'],
			'lp_cv_role_en' => ['label' => 'Role (EN)', 'type' => 'text'],
			'lp_cv_location_pt' => ['label' => 'Location (PT)', 'type' => 'text'],
			'lp_cv_location_en' => ['label' => 'Location (EN)', 'type' => 'text'],
			'lp_cv_contact_email' => ['label' => 'Contact email', 'type' => 'email'],
			'lp_cv_contact_phone' => ['label' => 'Contact phone', 'type' => 'text'],
			'lp_cv_linkedin_url' => ['label' => 'LinkedIn URL', 'type' => 'url'],
			'lp_cv_summary_title_pt' => ['label' => 'Summary title (PT)', 'type' => 'text'],
			'lp_cv_summary_title_en' => ['label' => 'Summary title (EN)', 'type' => 'text'],
			'lp_cv_summary_pt' => ['label' => 'Summary body (PT)', 'type' => 'textarea'],
			'lp_cv_summary_en' => ['label' => 'Summary body (EN)', 'type' => 'textarea'],
			'lp_cv_experience_title_pt' => ['label' => 'Experience title (PT)', 'type' => 'text'],
			'lp_cv_experience_title_en' => ['label' => 'Experience title (EN)', 'type' => 'text'],
			'lp_cv_experience_json' => ['label' => 'Experience timeline JSON', 'type' => 'textarea'],
			'lp_cv_skills_title_pt' => ['label' => 'Core skills title (PT)', 'type' => 'text'],
			'lp_cv_skills_title_en' => ['label' => 'Core skills title (EN)', 'type' => 'text'],
			'lp_cv_skills_json' => ['label' => 'Core skills groups JSON', 'type' => 'textarea'],
			'lp_cv_infra_title_pt' => ['label' => 'Infrastructure title (PT)', 'type' => 'text'],
			'lp_cv_infra_title_en' => ['label' => 'Infrastructure title (EN)', 'type' => 'text'],
			'lp_cv_infra_json' => ['label' => 'Infrastructure & systems JSON', 'type' => 'textarea'],
			'lp_cv_languages_title_pt' => ['label' => 'Languages title (PT)', 'type' => 'text'],
			'lp_cv_languages_title_en' => ['label' => 'Languages title (EN)', 'type' => 'text'],
			'lp_cv_languages_json' => ['label' => 'Languages JSON', 'type' => 'textarea'],
		],
		'verdian-take-home' => lp_verdian_take_home_fields(),
		'bonus-assessment' => lp_bonus_assessment_fields(),
		default => [],
	};
}

function lp_is_rollout_page(WP_Post $post): bool {
	return $post->post_type === 'page' && $post->post_name === 'rollout-custom-panel';
}

function lp_is_plugin_code_page(WP_Post $post): bool {
	return $post->post_type === 'page' && $post->post_name === 'plugin-code';
}

function lp_is_project_type(string $post_type): bool {
	return array_key_exists($post_type, lp_project_types());
}

function lp_is_portfolio_post_type(string $post_type): bool {
	return lp_is_project_type($post_type) || array_key_exists($post_type, lp_brand_type());
}

function lp_is_verdian_take_home_page(WP_Post $post): bool {
	return $post->post_type === 'page' && $post->post_name === 'verdian-take-home';
}

function lp_editor_field_keys(): array {
	return array_keys(array_filter(lp_verdian_take_home_fields(), static fn(array $field): bool => $field['type'] === 'editor'));
}

function lp_json_field_keys(): array {
	return array_keys(array_filter(lp_verdian_take_home_fields(), static fn(array $field): bool => $field['type'] === 'image_cards'));
}

function lp_sanitize_image_cards_json(string $value): string {
	$decoded = json_decode($value, true);
	if (!is_array($decoded)) {
		return '[]';
	}

	$items = [];
	foreach ($decoded as $item) {
		if (!is_array($item)) {
			continue;
		}

		$items[] = [
			'attachment_id' => absint($item['attachment_id'] ?? 0),
			'title_pt'      => sanitize_text_field((string) ($item['title_pt'] ?? '')),
			'title_en'      => sanitize_text_field((string) ($item['title_en'] ?? '')),
			'caption_pt'    => sanitize_textarea_field((string) ($item['caption_pt'] ?? '')),
			'caption_en'    => sanitize_textarea_field((string) ($item['caption_en'] ?? '')),
			'note_pt'       => sanitize_textarea_field((string) ($item['note_pt'] ?? '')),
			'note_en'       => sanitize_textarea_field((string) ($item['note_en'] ?? '')),
			'alt_pt'        => sanitize_text_field((string) ($item['alt_pt'] ?? '')),
			'alt_en'        => sanitize_text_field((string) ($item['alt_en'] ?? '')),
			'link'          => esc_url_raw((string) ($item['link'] ?? '')),
		];
	}

	return wp_json_encode($items, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '[]';
}

function lp_sanitize_competency_items($value): string {
	if (is_string($value)) {
		$value = json_decode($value, true);
	}

	if (!is_array($value)) {
		return wp_json_encode(lp_verdian_default_competency_items(), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '[]';
	}

	$items = [];
	foreach ($value as $item) {
		if (!is_array($item)) {
			continue;
		}

		$rating = (int) ($item['rating'] ?? 0);
		$items[] = [
			'skill_pt' => sanitize_text_field((string) ($item['skill_pt'] ?? '')),
			'skill_en' => sanitize_text_field((string) ($item['skill_en'] ?? '')),
			'rating'   => max(1, min(5, $rating > 0 ? $rating : 1)),
			'notes_pt' => sanitize_textarea_field((string) ($item['notes_pt'] ?? '')),
			'notes_en' => sanitize_textarea_field((string) ($item['notes_en'] ?? '')),
		];
	}

	if ($items === []) {
		$items = lp_verdian_default_competency_items();
	}

	return wp_json_encode($items, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '[]';
}

function lp_render_image_cards_control(string $key, string $value): void {
	$items = json_decode($value, true);
	if (!is_array($items)) {
		$items = [];
	}
	?>
	<div class="lp-image-cards" data-field="<?php echo esc_attr($key); ?>">
		<input type="hidden" class="lp-image-cards-input" id="<?php echo esc_attr($key); ?>" name="<?php echo esc_attr($key); ?>" value="<?php echo esc_attr($value !== '' ? $value : '[]'); ?>">
		<div class="lp-image-cards-list">
			<?php foreach ($items as $item) : ?>
				<?php if (!is_array($item)) { continue; } ?>
				<?php $attachment_id = absint($item['attachment_id'] ?? 0); ?>
				<div class="lp-image-card">
					<div class="lp-image-card-toolbar">
						<button type="button" class="button lp-image-card-select"><?php echo $attachment_id > 0 ? 'Replace image' : 'Select image'; ?></button>
						<button type="button" class="button-link-delete lp-image-card-remove">Remove</button>
					</div>
					<input type="hidden" class="lp-image-card-attachment-id" value="<?php echo esc_attr((string) $attachment_id); ?>">
					<div class="lp-image-card-preview">
						<?php if ($attachment_id > 0) : ?>
							<?php echo wp_get_attachment_image($attachment_id, 'medium'); ?>
						<?php else : ?>
							<span>No image selected.</span>
						<?php endif; ?>
					</div>
					<p><label><strong>Title (PT)</strong></label><input type="text" class="widefat lp-image-card-title-pt" value="<?php echo esc_attr((string) ($item['title_pt'] ?? '')); ?>"></p>
					<p><label><strong>Title (EN)</strong></label><input type="text" class="widefat lp-image-card-title-en" value="<?php echo esc_attr((string) ($item['title_en'] ?? '')); ?>"></p>
					<p><label><strong>Caption (PT)</strong></label><textarea class="widefat lp-image-card-caption-pt" rows="3"><?php echo esc_textarea((string) ($item['caption_pt'] ?? '')); ?></textarea></p>
					<p><label><strong>Caption (EN)</strong></label><textarea class="widefat lp-image-card-caption-en" rows="3"><?php echo esc_textarea((string) ($item['caption_en'] ?? '')); ?></textarea></p>
					<p><label><strong>Note (PT)</strong></label><textarea class="widefat lp-image-card-note-pt" rows="2"><?php echo esc_textarea((string) ($item['note_pt'] ?? '')); ?></textarea></p>
					<p><label><strong>Note (EN)</strong></label><textarea class="widefat lp-image-card-note-en" rows="2"><?php echo esc_textarea((string) ($item['note_en'] ?? '')); ?></textarea></p>
					<p><label><strong>Alt override (PT)</strong></label><input type="text" class="widefat lp-image-card-alt-pt" value="<?php echo esc_attr((string) ($item['alt_pt'] ?? '')); ?>"></p>
					<p><label><strong>Alt override (EN)</strong></label><input type="text" class="widefat lp-image-card-alt-en" value="<?php echo esc_attr((string) ($item['alt_en'] ?? '')); ?>"></p>
					<p><label><strong>Optional link</strong></label><input type="url" class="widefat lp-image-card-link" value="<?php echo esc_attr((string) ($item['link'] ?? '')); ?>"></p>
				</div>
			<?php endforeach; ?>
		</div>
		<p><button type="button" class="button lp-image-cards-add">Add image card</button></p>
	</div>
	<?php
}

function lp_render_competency_items_control(string $key, string $value): void {
	$items = json_decode($value, true);
	if (!is_array($items) || $items === []) {
		$items = lp_verdian_default_competency_items();
	}
	?>
	<div class="lp-competency-items" data-field="<?php echo esc_attr($key); ?>">
		<table class="widefat striped lp-competency-table">
			<thead>
				<tr>
					<th style="width:16%;">Skill (PT)</th>
					<th style="width:16%;">Skill (EN)</th>
					<th style="width:8%;">Rating</th>
					<th style="width:25%;">Notes (PT)</th>
					<th style="width:25%;">Notes (EN)</th>
					<th style="width:10%;">Action</th>
				</tr>
			</thead>
			<tbody>
				<?php foreach ($items as $index => $item) : ?>
					<tr class="lp-competency-row">
						<td><input type="text" class="widefat" data-subkey="skill_pt" name="<?php echo esc_attr($key); ?>[<?php echo esc_attr((string) $index); ?>][skill_pt]" value="<?php echo esc_attr((string) ($item['skill_pt'] ?? '')); ?>"></td>
						<td><input type="text" class="widefat" data-subkey="skill_en" name="<?php echo esc_attr($key); ?>[<?php echo esc_attr((string) $index); ?>][skill_en]" value="<?php echo esc_attr((string) ($item['skill_en'] ?? '')); ?>"></td>
						<td>
							<select class="widefat" data-subkey="rating" name="<?php echo esc_attr($key); ?>[<?php echo esc_attr((string) $index); ?>][rating]">
								<?php for ($rating = 1; $rating <= 5; $rating++) : ?>
									<option value="<?php echo esc_attr((string) $rating); ?>" <?php selected((int) ($item['rating'] ?? 0), $rating); ?>><?php echo esc_html((string) $rating); ?></option>
								<?php endfor; ?>
							</select>
						</td>
						<td><textarea class="widefat" rows="4" data-subkey="notes_pt" name="<?php echo esc_attr($key); ?>[<?php echo esc_attr((string) $index); ?>][notes_pt]"><?php echo esc_textarea((string) ($item['notes_pt'] ?? '')); ?></textarea></td>
						<td><textarea class="widefat" rows="4" data-subkey="notes_en" name="<?php echo esc_attr($key); ?>[<?php echo esc_attr((string) $index); ?>][notes_en]"><?php echo esc_textarea((string) ($item['notes_en'] ?? '')); ?></textarea></td>
						<td><button type="button" class="button-link-delete lp-competency-remove-row">Remove</button></td>
					</tr>
				<?php endforeach; ?>
			</tbody>
		</table>
		<p><button type="button" class="button lp-competency-add-row">Add row</button></p>
		<p class="description">Each row is editable in wp-admin, you can add or remove rows, and the data is saved as structured JSON for the Verdian assessment template.</p>
	</div>
	<?php
}

function lp_render_field_control(string $key, array $field, string $value, int $rows = 3): void {
	if ($field['type'] === 'checkbox') {
		?>
		<label>
			<input id="<?php echo esc_attr($key); ?>" name="<?php echo esc_attr($key); ?>" type="checkbox" value="1" <?php checked($value, '1'); ?>>
			Enable
		</label>
		<?php
		return;
	}

	if ($field['type'] === 'editor') {
		wp_editor($value, $key, [
			'textarea_name' => $key,
			'textarea_rows' => max($rows, 8),
			'media_buttons' => true,
			'teeny'         => false,
		]);
		return;
	}

	if ($field['type'] === 'image_cards') {
		lp_render_image_cards_control($key, $value);
		return;
	}

	if ($field['type'] === 'competency_items') {
		lp_render_competency_items_control($key, $value);
		return;
	}

	if ($field['type'] === 'image') {
		$attachment_id = absint($value);
		$preview = $attachment_id > 0 ? wp_get_attachment_image($attachment_id, 'medium', false, ['style' => 'max-width:100%;height:auto;display:block;']) : '';
		?>
		<div class="lp-image-field">
			<input id="<?php echo esc_attr($key); ?>" name="<?php echo esc_attr($key); ?>" type="hidden" value="<?php echo esc_attr((string) $attachment_id); ?>" class="lp-image-field-input">
			<div class="lp-image-field-preview"><?php echo $preview !== '' ? $preview : '<span>No image selected.</span>'; ?></div>
			<p style="display:flex;gap:10px;align-items:center;">
				<button type="button" class="button lp-image-field-select"><?php echo $attachment_id > 0 ? 'Replace image' : 'Select image'; ?></button>
				<button type="button" class="button-link-delete lp-image-field-remove"<?php echo $attachment_id > 0 ? '' : ' style="display:none;"'; ?>>Remove</button>
			</p>
		</div>
		<?php
		return;
	}

	if ($field['type'] === 'textarea') {
		?>
		<textarea id="<?php echo esc_attr($key); ?>" name="<?php echo esc_attr($key); ?>" rows="<?php echo esc_attr((string) $rows); ?>" style="width:100%;font-family:monospace;"><?php echo esc_textarea($value); ?></textarea>
		<?php
		return;
	}

	?>
	<input id="<?php echo esc_attr($key); ?>" name="<?php echo esc_attr($key); ?>" type="<?php echo esc_attr($field['type']); ?>" value="<?php echo esc_attr($value); ?>" style="width:100%;">
	<?php
}

function lp_sanitize_field_value(array $field, $value): string {
	return match ($field['type']) {
		'url' => esc_url_raw((string) $value),
		'email' => sanitize_email((string) $value),
		'checkbox' => in_array($value, ['1', 'true', 'yes', 'on'], true) ? '1' : '0',
		'editor' => wp_kses_post((string) $value),
		'image' => (string) absint((string) $value),
		'image_cards' => lp_sanitize_image_cards_json((string) $value),
		'competency_items' => lp_sanitize_competency_items($value),
		default => sanitize_textarea_field((string) $value),
	};
}

// Attach the custom field groups to project, brand and page editing screens.
add_action('add_meta_boxes', function (): void {
	foreach (array_keys(lp_project_types()) as $post_type) {
		add_meta_box(
			'lp_acf_like_fields',
			'ACF-like Project Fields',
			'lp_render_project_fields_box',
			$post_type,
			'normal',
			'high'
		);
	}

	add_meta_box(
		'lp_brand_fields',
		'Brand Details',
		'lp_render_brand_fields_box',
		'lp_brand',
		'normal',
		'high'
	);

	add_meta_box(
		'lp_home_fields',
		'Editable Home Sections',
		'lp_render_home_fields_box',
		'page',
		'normal',
		'high'
	);

	add_meta_box(
		'lp_rollout_fields',
		'Editable Rollout Sections',
		'lp_render_rollout_fields_box',
		'page',
		'normal',
		'high'
	);

	add_meta_box(
		'lp_plugin_code_fields',
		'Editable Plugin Code Sections',
		'lp_render_plugin_code_fields_box',
		'page',
		'normal',
		'high'
	);

	add_meta_box(
		'lp_page_specific_fields',
		'Editable Page Sections',
		'lp_render_page_specific_fields_box',
		'page',
		'normal',
		'high'
	);

	if (post_type_exists('lp_assessment_visual')) {
		add_meta_box(
			'lp_assessment_visual_fields',
			'Assessment Visual Settings',
			'lp_render_assessment_visual_fields_box',
			'lp_assessment_visual',
			'normal',
			'high'
		);
	}
});

// Keep portfolio edit screens focused on structured fields instead of the default WordPress editor UI.
add_action('admin_init', function (): void {
	foreach (array_merge(array_keys(lp_project_types()), array_keys(lp_brand_type())) as $post_type) {
		remove_post_type_support($post_type, 'editor');
		remove_post_type_support($post_type, 'excerpt');
	}

	if (post_type_exists('lp_assessment_visual')) {
		remove_post_type_support('lp_assessment_visual', 'editor');
		remove_post_type_support('lp_assessment_visual', 'excerpt');
	}
});

add_action('add_meta_boxes', function (string $post_type): void {
	if (!lp_is_portfolio_post_type($post_type)) {
		return;
	}

	remove_meta_box('postexcerpt', $post_type, 'normal');
	remove_meta_box('slugdiv', $post_type, 'normal');
}, 100);

// Render the project meta box used as an ACF-like field layer.
function lp_render_project_fields_box(WP_Post $post): void {
	wp_nonce_field('lp_save_project_fields', 'lp_project_fields_nonce');
	$fields = lp_acf_like_fields();
	?>
	<div class="lp-fields">
		<p>This lightweight field layer mimics the ACF workflow used in custom marketing builds: structured project data, reusable field helpers, and flexible content stored as post meta.</p>
		<?php foreach ($fields as $key => $field) :
			$value = (string) get_post_meta($post->ID, $key, true);
			?>
			<p>
				<label for="<?php echo esc_attr($key); ?>"><strong><?php echo esc_html($field['label']); ?></strong></label><br>
				<?php if ($field['type'] === 'textarea') : ?>
					<textarea id="<?php echo esc_attr($key); ?>" name="<?php echo esc_attr($key); ?>" rows="<?php echo $key === 'lp_flexible_json' ? 8 : 3; ?>" style="width:100%;font-family:monospace;"><?php echo esc_textarea($value); ?></textarea>
				<?php else : ?>
					<input id="<?php echo esc_attr($key); ?>" name="<?php echo esc_attr($key); ?>" type="<?php echo esc_attr($field['type']); ?>" value="<?php echo esc_attr($value); ?>" style="width:100%;">
				<?php endif; ?>
			</p>
		<?php endforeach; ?>
		<p><button type="button" class="button" id="lp-insert-flexible-example">Insert flexible section example</button></p>
	</div>
	<?php
}

// Render the served brand fields used on the public brand page.
function lp_render_brand_fields_box(WP_Post $post): void {
	wp_nonce_field('lp_save_brand_fields', 'lp_brand_fields_nonce');
	?>
	<div class="lp-fields">
		<p>Editable fields used to display served brands in the portfolio.</p>
		<?php foreach (lp_brand_fields() as $key => $field) :
			$value = (string) get_post_meta($post->ID, $key, true);
			?>
			<p>
				<label for="<?php echo esc_attr($key); ?>"><strong><?php echo esc_html($field['label']); ?></strong></label><br>
				<?php if ($field['type'] === 'textarea') : ?>
					<textarea id="<?php echo esc_attr($key); ?>" name="<?php echo esc_attr($key); ?>" rows="3" style="width:100%;font-family:monospace;"><?php echo esc_textarea($value); ?></textarea>
				<?php else : ?>
					<input id="<?php echo esc_attr($key); ?>" name="<?php echo esc_attr($key); ?>" type="<?php echo esc_attr($field['type']); ?>" value="<?php echo esc_attr($value); ?>" style="width:100%;">
				<?php endif; ?>
			</p>
		<?php endforeach; ?>
	</div>
	<?php
}

// Render the editable homepage sections, including JSON-based repeatable blocks.
function lp_render_home_fields_box(WP_Post $post): void {
	if ($post->post_name !== 'home') {
		echo '<p>These homepage fields are only used by the page with slug <code>home</code>.</p>';
		return;
	}

	wp_nonce_field('lp_save_home_fields', 'lp_home_fields_nonce');
	?>
	<div class="lp-fields">
		<p>Edit the homepage copy and repeatable blocks here. The public layout remains controlled by the theme, but the text, CTAs and blocks are managed from this classic WordPress editor.</p>
		<p>Use JSON arrays for block fields. Example: <code>[{"title":"Classic Editor","body":"Pages and CPTs open in the classic editor."}]</code></p>
		<?php foreach (lp_home_fields() as $key => $field) :
			$value = (string) get_post_meta($post->ID, $key, true);
			$rows = str_contains($key, '_json') ? 10 : (str_contains($key, '_title') || str_contains($key, '_lede') || str_contains($key, '_body') || str_contains($key, '_description') ? 4 : 2);
			?>
			<p>
				<label for="<?php echo esc_attr($key); ?>"><strong><?php echo esc_html($field['label']); ?></strong></label><br>
				<?php lp_render_field_control($key, $field, $value, $rows); ?>
			</p>
		<?php endforeach; ?>
	</div>
	<?php
}

// Render the rollout custom panel fields used by the rollout page template.
function lp_render_rollout_fields_box(WP_Post $post): void {
	if (!lp_is_rollout_page($post)) {
		echo '<p>These rollout fields are only used by the page with slug <code>rollout-custom-panel</code>.</p>';
		return;
	}

	wp_nonce_field('lp_save_rollout_fields', 'lp_rollout_fields_nonce');
	?>
	<div class="lp-fields">
		<p>Edit the rollout panel copy, CTAs, metrics and repeatable blocks here. Use JSON arrays for stats, proof blocks, timeline steps and screenshot cards.</p>
		<p>Example stats JSON: <code>[{"value":"12","label":"Localized proof screenshots"}]</code></p>
		<?php foreach (lp_rollout_fields() as $key => $field) :
			$value = (string) get_post_meta($post->ID, $key, true);
			$is_json = str_contains($key, '_json') || str_contains($key, '_steps');
			?>
			<p>
				<label for="<?php echo esc_attr($key); ?>"><strong><?php echo esc_html($field['label']); ?></strong></label><br>
				<?php if ($field['type'] === 'textarea') : ?>
					<textarea id="<?php echo esc_attr($key); ?>" name="<?php echo esc_attr($key); ?>" rows="<?php echo $is_json ? 10 : 3; ?>" style="width:100%;font-family:monospace;"><?php echo esc_textarea($value); ?></textarea>
				<?php else : ?>
					<input id="<?php echo esc_attr($key); ?>" name="<?php echo esc_attr($key); ?>" type="<?php echo esc_attr($field['type']); ?>" value="<?php echo esc_attr($value); ?>" style="width:100%;">
				<?php endif; ?>
			</p>
		<?php endforeach; ?>
	</div>
	<?php
}

// Render the plugin code page fields so labels and visible file registry are manageable in wp-admin.
function lp_render_plugin_code_fields_box(WP_Post $post): void {
	if (!lp_is_plugin_code_page($post)) {
		echo '<p>These code-page fields are only used by the page with slug <code>plugin-code</code>.</p>';
		return;
	}

	wp_nonce_field('lp_save_plugin_code_fields', 'lp_plugin_code_fields_nonce');
	?>
	<div class="lp-fields">
		<p>Edit the code-page summary cards, API copy and visible file list here. The public page can now pull the live source directly from the server paths listed below.</p>
		<p>Recommended flow: keep <code>lp_plugin_code_paths</code> filled with real files and use <code>lp_plugin_code_files</code> only if you need to override title/description manually for a specific panel.</p>
		<p>Example summary cards JSON: <code>[{"label":"Toolkit","value":"CPTs, fields, REST"}]</code></p>
		<p>Example paths: <code>/var/www/portfolio.fusioncore.com.br/public/wp-content/themes/lago-process/functions.php</code></p>
		<p>Example panels JSON override: <code>[{"path":"/var/www/portfolio.fusioncore.com.br/public/wp-content/themes/lago-process/functions.php","title":"Theme Functions","description":"Manual override."}]</code></p>
		<?php foreach (lp_plugin_code_fields() as $key => $field) :
			$value = (string) get_post_meta($post->ID, $key, true);
			if ($key === 'lp_plugin_code_paths' && trim($value) === '') {
				$value = lp_plugin_code_default_paths();
			}
			$is_large = str_contains($key, 'json') || str_contains($key, '_code') || $key === 'lp_plugin_code_files' || $key === 'lp_plugin_code_summary_cards';
			?>
			<p>
				<label for="<?php echo esc_attr($key); ?>"><strong><?php echo esc_html($field['label']); ?></strong></label><br>
				<?php if ($field['type'] === 'textarea') : ?>
					<textarea id="<?php echo esc_attr($key); ?>" name="<?php echo esc_attr($key); ?>" rows="<?php echo $is_large ? 10 : 3; ?>" style="width:100%;font-family:monospace;"><?php echo esc_textarea($value); ?></textarea>
				<?php else : ?>
					<input id="<?php echo esc_attr($key); ?>" name="<?php echo esc_attr($key); ?>" type="<?php echo esc_attr($field['type']); ?>" value="<?php echo esc_attr($value); ?>" style="width:100%;">
				<?php endif; ?>
			</p>
		<?php endforeach; ?>
	</div>
	<?php
}

function lp_render_page_specific_fields_box(WP_Post $post): void {
	$fields = lp_page_fields_for_slug($post->post_name);
	if ($fields === [] || $post->post_name === 'home' || lp_is_rollout_page($post) || lp_is_plugin_code_page($post)) {
		echo '<p>No extra page-specific fields are registered for this page.</p>';
		return;
	}

	wp_nonce_field('lp_save_page_specific_fields', 'lp_page_specific_fields_nonce');
	?>
	<div class="lp-fields">
		<p>These fields control the template-level text and media-managed sections for this page while keeping the public layout in the theme.</p>
		<?php foreach ($fields as $key => $field) :
			$value = (string) get_post_meta($post->ID, $key, true);
			$is_large = in_array($field['type'], ['textarea', 'editor', 'image_cards', 'competency_items'], true);
			?>
			<div style="margin:0 0 18px;">
				<label for="<?php echo esc_attr($key); ?>"><strong><?php echo esc_html($field['label']); ?></strong></label><br>
				<?php lp_render_field_control($key, $field, $value, $is_large ? 8 : 3); ?>
			</div>
		<?php endforeach; ?>
	</div>
	<?php
}

function lp_render_assessment_visual_fields_box(WP_Post $post): void {
	wp_nonce_field('lp_save_assessment_visual_fields', 'lp_assessment_visual_fields_nonce');
	?>
	<div class="lp-fields">
		<p>Use the featured image for uploads from the WordPress media library. You can also provide an external image URL if needed. Set the section to control where the visual appears on the Verdian Insights page.</p>
		<?php foreach (lp_assessment_visual_fields() as $key => $field) :
			$value = (string) get_post_meta($post->ID, $key, true);
			?>
			<p>
				<label for="<?php echo esc_attr($key); ?>"><strong><?php echo esc_html($field['label']); ?></strong></label><br>
				<?php if ($field['type'] === 'select') : ?>
					<select id="<?php echo esc_attr($key); ?>" name="<?php echo esc_attr($key); ?>" style="width:100%;">
						<?php foreach ($field['options'] as $option_value => $option_label) : ?>
							<option value="<?php echo esc_attr($option_value); ?>" <?php selected($value, (string) $option_value); ?>><?php echo esc_html((string) $option_label); ?></option>
						<?php endforeach; ?>
					</select>
				<?php else : ?>
					<?php lp_render_field_control($key, $field, $value, $field['type'] === 'textarea' ? 5 : 3); ?>
				<?php endif; ?>
			</p>
		<?php endforeach; ?>
		<p>Ordering uses the built-in "Order" field from page attributes.</p>
	</div>
	<?php
}

add_action('admin_enqueue_scripts', function (string $hook): void {
	$screen = get_current_screen();
	if (!$screen instanceof WP_Screen) {
		return;
	}

	$is_project_screen = lp_is_project_type((string) $screen->post_type);
	$is_assessment_visual = $screen->post_type === 'lp_assessment_visual';
	$is_verdian_page = false;

	if ($screen->post_type === 'page' && isset($_GET['post'])) {
		$is_verdian_page = get_post_field('post_name', (int) $_GET['post']) === 'verdian-take-home';
	}

	if (!$is_project_screen && !$is_assessment_visual && !$is_verdian_page) {
		return;
	}

	wp_enqueue_script(
		'lp-admin-fields',
		plugins_url('assets/js/admin-fields.js', __FILE__),
		['jquery', 'media-editor', 'media-upload'],
		LP_TOOLKIT_VERSION,
		true
	);

	wp_enqueue_media();
	wp_add_inline_style(
		'wp-admin',
		'.lp-image-card{border:1px solid #dcdcde;border-radius:10px;padding:16px;margin:0 0 16px;background:#fff}.lp-image-card-toolbar{display:flex;justify-content:space-between;align-items:center;gap:12px}.lp-image-card-preview,.lp-image-field-preview{margin:10px 0;min-height:80px;display:grid;place-items:center;border:1px dashed #c3c4c7;padding:12px;background:#f6f7f7}.lp-image-card-preview img,.lp-image-field-preview img{max-width:100%;height:auto;display:block}.lp-fields .wp-editor-wrap{max-width:100%}.lp-competency-table th,.lp-competency-table td{vertical-align:top}.lp-competency-table textarea{min-height:88px}.lp-competency-items .button-link-delete{color:#b32d2e}'
	);
});

// Group all portfolio content in one WordPress admin menu for quick screen-share access.
add_action('admin_menu', function (): void {
	add_menu_page(
		'Portfolio Statistics',
		'Statistics',
		'manage_options',
		'lucas-portfolio-analytics',
		'lp_render_analytics_dashboard_page',
		'dashicons-chart-bar',
		26
	);

	add_submenu_page(
		'lucas-portfolio-analytics',
		'Portfolio Statistics',
		'Overview',
		'manage_options',
		'lucas-portfolio-analytics',
		'lp_render_analytics_dashboard_page'
	);

	add_menu_page(
		'Lucas Portfolio',
		'Lucas Portfolio',
		'edit_posts',
		'lucas-portfolio',
		static function (): void {
			echo '<div class="wrap"><h1>Lucas Portfolio</h1><p>Manage projects, served brands, bilingual content, technical fields, project links and assessment workflows.</p></div>';
		},
		'dashicons-portfolio',
		25
	);

	add_submenu_page(
		'lucas-portfolio',
		'Lucas Site Settings',
		'Site Settings',
		'edit_theme_options',
		'lucas-site-settings',
		'lp_render_site_settings_page'
	);

});

function lp_render_site_settings_page(): void {
	if (!current_user_can('edit_theme_options')) {
		return;
	}

	if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['lp_site_settings_nonce']) && wp_verify_nonce((string) $_POST['lp_site_settings_nonce'], 'lp_save_site_settings')) {
		foreach (lp_global_fields() as $key => $field) {
			$value = wp_unslash($_POST[$key] ?? ($field['type'] === 'checkbox' ? '0' : ''));
			update_option($key, lp_sanitize_field_value($field, $value));
		}

		echo '<div class="notice notice-success"><p>Site settings saved.</p></div>';
	}
	?>
	<div class="wrap">
		<h1>Lucas Site Settings</h1>
		<p>These settings control the global header, footer, SEO defaults, archive text and shared labels used across the portfolio.</p>
		<form method="post">
			<?php wp_nonce_field('lp_save_site_settings', 'lp_site_settings_nonce'); ?>
			<table class="form-table" role="presentation">
				<tbody>
				<?php foreach (lp_global_fields() as $key => $field) :
					$value = (string) get_option($key, '');
					?>
					<tr>
						<th scope="row"><label for="<?php echo esc_attr($key); ?>"><?php echo esc_html($field['label']); ?></label></th>
						<td>
				<?php if ($field['type'] === 'textarea') : ?>
								<textarea id="<?php echo esc_attr($key); ?>" name="<?php echo esc_attr($key); ?>" rows="4" class="large-text code"><?php echo esc_textarea($value); ?></textarea>
							<?php else : ?>
								<?php if ($field['type'] === 'checkbox') : ?>
									<label><input id="<?php echo esc_attr($key); ?>" name="<?php echo esc_attr($key); ?>" type="checkbox" value="1" <?php checked($value, '1'); ?>> Enable</label>
								<?php else : ?>
									<input id="<?php echo esc_attr($key); ?>" name="<?php echo esc_attr($key); ?>" type="<?php echo esc_attr($field['type']); ?>" value="<?php echo esc_attr($value); ?>" class="regular-text">
								<?php endif; ?>
							<?php endif; ?>
						</td>
					</tr>
				<?php endforeach; ?>
				</tbody>
			</table>
			<?php submit_button('Save Site Settings'); ?>
		</form>
	</div>
	<?php
}

function lp_analytics_table_name(): string {
	global $wpdb;

	return $wpdb->prefix . 'lp_analytics_events';
}

function lp_analytics_schema_version(): string {
	return '1.1.0';
}

function lp_analytics_install(): void {
	global $wpdb;

	$table_name = lp_analytics_table_name();
	$charset_collate = $wpdb->get_charset_collate();

	require_once ABSPATH . 'wp-admin/includes/upgrade.php';

	dbDelta(
		"CREATE TABLE {$table_name} (
			id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			session_id varchar(64) NOT NULL,
			event_type varchar(20) NOT NULL,
			page_url text NOT NULL,
			page_path varchar(255) NOT NULL,
			page_title varchar(255) NOT NULL DEFAULT '',
			page_post_id bigint(20) unsigned NOT NULL DEFAULT 0,
			element_type varchar(30) NOT NULL DEFAULT '',
			element_label varchar(255) NOT NULL DEFAULT '',
			element_target text NULL,
			referrer_host varchar(190) NOT NULL DEFAULT '',
			locale varchar(16) NOT NULL DEFAULT '',
			user_agent_hash char(64) NOT NULL DEFAULT '',
			device_type varchar(20) NOT NULL DEFAULT 'desktop',
			created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
			PRIMARY KEY  (id),
			KEY event_type_created_at (event_type, created_at),
			KEY session_id_created_at (session_id, created_at),
			KEY page_path_created_at (page_path, created_at),
			KEY page_post_id_created_at (page_post_id, created_at)
		) {$charset_collate};"
	);

	$device_type_column = $wpdb->get_var($wpdb->prepare("SHOW COLUMNS FROM {$table_name} LIKE %s", 'device_type'));
	if ($device_type_column === null) {
		$wpdb->query("ALTER TABLE {$table_name} ADD COLUMN device_type varchar(20) NOT NULL DEFAULT 'desktop' AFTER user_agent_hash");
	}

	update_option('lp_analytics_schema_version', lp_analytics_schema_version());
}

add_action('init', function (): void {
	if (get_option('lp_analytics_schema_version') === lp_analytics_schema_version()) {
		return;
	}

	lp_analytics_install();
}, 5);

function lp_analytics_host(): string {
	return strtolower((string) wp_parse_url(home_url('/'), PHP_URL_HOST));
}

function lp_analytics_cookie_name(): string {
	return 'lp_analytics_session';
}

function lp_analytics_generate_session_id(): string {
	try {
		return 'lp_' . wp_generate_password(32, false, false);
	} catch (Throwable $exception) {
		return 'lp_' . md5(uniqid((string) wp_rand(), true));
	}
}

function lp_analytics_is_public_request(): bool {
	if (is_user_logged_in() || is_admin() || wp_doing_ajax() || (defined('REST_REQUEST') && REST_REQUEST)) {
		return false;
	}

	if (is_feed() || is_trackback() || is_preview()) {
		return false;
	}

	return true;
}

function lp_analytics_normalize_path(string $path): string {
	$path = trim($path);

	if ($path === '') {
		return '/';
	}

	$parsed = wp_parse_url($path, PHP_URL_PATH);
	$path = is_string($parsed) && $parsed !== '' ? $parsed : $path;

	return '/' . ltrim($path, '/');
}

function lp_analytics_clean_text(string $value, int $limit = 255): string {
	$value = sanitize_text_field($value);

	if (function_exists('mb_substr')) {
		return mb_substr($value, 0, $limit);
	}

	return substr($value, 0, $limit);
}

function lp_analytics_get_or_create_session_id(): string {
	$cookie_name = lp_analytics_cookie_name();
	$session_id = preg_replace('/[^a-zA-Z0-9_-]/', '', (string) ($_COOKIE[$cookie_name] ?? ''));

	if ($session_id !== '' && strlen($session_id) >= 12) {
		return $session_id;
	}

	$session_id = lp_analytics_generate_session_id();

	if (!headers_sent()) {
		setcookie($cookie_name, $session_id, [
			'expires' => time() + (30 * MINUTE_IN_SECONDS),
			'path' => COOKIEPATH ?: '/',
			'domain' => defined('COOKIE_DOMAIN') && COOKIE_DOMAIN ? COOKIE_DOMAIN : '',
			'secure' => is_ssl(),
			'httponly' => true,
			'samesite' => 'Lax',
		]);
	}

	$_COOKIE[$cookie_name] = $session_id;

	return $session_id;
}

add_action('template_redirect', function (): void {
	if (!lp_analytics_is_public_request()) {
		return;
	}

	lp_analytics_get_or_create_session_id();
}, 0);

function lp_analytics_get_dashboard_data(int $days = 30): array {
	global $wpdb;

	$days = max(1, min(365, $days));
	$table_name = lp_analytics_table_name();
	$since = gmdate('Y-m-d H:i:s', time() - ($days * DAY_IN_SECONDS));

	$pageviews = (int) $wpdb->get_var(
		$wpdb->prepare(
			"SELECT COUNT(*) FROM {$table_name} WHERE event_type = 'page_view' AND created_at >= %s",
			$since
		)
	);

	$sessions = (int) $wpdb->get_var(
		$wpdb->prepare(
			"SELECT COUNT(DISTINCT session_id) FROM {$table_name} WHERE created_at >= %s",
			$since
		)
	);

	$clicks = (int) $wpdb->get_var(
		$wpdb->prepare(
			"SELECT COUNT(*) FROM {$table_name} WHERE event_type = 'click' AND created_at >= %s",
			$since
		)
	);

	$tracked_pages = (int) $wpdb->get_var(
		$wpdb->prepare(
			"SELECT COUNT(DISTINCT page_path) FROM {$table_name} WHERE event_type = 'page_view' AND created_at >= %s",
			$since
		)
	);

	$top_pages = $wpdb->get_results(
		$wpdb->prepare(
			"SELECT page_path, MAX(page_title) AS page_title, COUNT(*) AS views, COUNT(DISTINCT session_id) AS sessions
			FROM {$table_name}
			WHERE event_type = 'page_view' AND created_at >= %s
			GROUP BY page_path
			ORDER BY views DESC, sessions DESC
			LIMIT 15",
			$since
		),
		ARRAY_A
	);

	$top_clicks = $wpdb->get_results(
		$wpdb->prepare(
			"SELECT page_path, element_type, element_label, MAX(element_target) AS element_target, COUNT(*) AS clicks, COUNT(DISTINCT session_id) AS sessions
			FROM {$table_name}
			WHERE event_type = 'click' AND created_at >= %s
			GROUP BY page_path, element_type, element_label, element_target
			ORDER BY clicks DESC, sessions DESC
			LIMIT 20",
			$since
		),
		ARRAY_A
	);

	$daily = $wpdb->get_results(
		$wpdb->prepare(
			"SELECT DATE(created_at) AS day,
				SUM(CASE WHEN event_type = 'page_view' THEN 1 ELSE 0 END) AS pageviews,
				SUM(CASE WHEN event_type = 'click' THEN 1 ELSE 0 END) AS clicks,
				COUNT(DISTINCT session_id) AS sessions
			FROM {$table_name}
			WHERE created_at >= %s
			GROUP BY DATE(created_at)
			ORDER BY day DESC
			LIMIT 30",
			$since
		),
		ARRAY_A
	);

	return [
		'summary' => [
			'pageviews' => $pageviews,
			'sessions' => $sessions,
			'clicks' => $clicks,
			'tracked_pages' => $tracked_pages,
		],
		'top_pages' => is_array($top_pages) ? $top_pages : [],
		'top_clicks' => is_array($top_clicks) ? $top_clicks : [],
		'daily' => is_array($daily) ? $daily : [],
	];
}

function lp_analytics_percent(float $value, int $precision = 1): string {
	return number_format_i18n($value, $precision) . '%';
}

function lp_analytics_fill_daily_series(array $daily, int $days): array {
	$indexed = [];

	foreach ($daily as $row) {
		$day = (string) ($row['day'] ?? '');
		if ($day === '') {
			continue;
		}

		$indexed[$day] = [
			'day' => $day,
			'pageviews' => (int) ($row['pageviews'] ?? 0),
			'clicks' => (int) ($row['clicks'] ?? 0),
			'sessions' => (int) ($row['sessions'] ?? 0),
		];
	}

	$filled = [];
	$today = current_time('timestamp');

	for ($offset = $days - 1; $offset >= 0; $offset--) {
		$day = wp_date('Y-m-d', $today - ($offset * DAY_IN_SECONDS));
		$filled[] = $indexed[$day] ?? [
			'day' => $day,
			'pageviews' => 0,
			'clicks' => 0,
			'sessions' => 0,
		];
	}

	return $filled;
}

function lp_analytics_sparkline_path(array $values, int $width = 520, int $height = 160, int $padding = 12): string {
	$values = array_values(array_map('floatval', $values));
	$count = count($values);

	if ($count === 0) {
		return '';
	}

	$min = min($values);
	$max = max($values);
	$range = $max - $min;
	$range = $range > 0 ? $range : 1;
	$usable_width = max(1, $width - ($padding * 2));
	$usable_height = max(1, $height - ($padding * 2));
	$step = $count > 1 ? $usable_width / ($count - 1) : 0;
	$path = [];

	foreach ($values as $index => $value) {
		$x = $padding + ($step * $index);
		$normalized = ($value - $min) / $range;
		$y = $height - $padding - ($normalized * $usable_height);
		$path[] = ($index === 0 ? 'M ' : 'L ') . round($x, 2) . ' ' . round($y, 2);
	}

	return implode(' ', $path);
}

function lp_analytics_page_name(array $row): string {
	$title = trim((string) ($row['page_title'] ?? ''));
	$path = (string) ($row['page_path'] ?? '/');

	if ($title !== '') {
		$title = preg_replace('/\s*[|–-]\s*Lucas Bacellar\s*\|\s*WordPress Systems Portfolio\s*$/i', '', $title) ?: $title;
		$title = preg_replace('/\s*[|–-]\s*WordPress Systems Portfolio\s*$/i', '', $title) ?: $title;
		$title = trim($title);
	}

	return $title !== '' ? $title : $path;
}

function lp_analytics_cta_name(array $row): string {
	$label = trim((string) ($row['element_label'] ?? ''));

	return $label !== '' ? $label : '[sem nome]';
}

function lp_request_client_ip(): string {
	$candidates = [
		$_SERVER['HTTP_CF_CONNECTING_IP'] ?? '',
		$_SERVER['HTTP_X_REAL_IP'] ?? '',
		$_SERVER['HTTP_X_FORWARDED_FOR'] ?? '',
		$_SERVER['REMOTE_ADDR'] ?? '',
	];

	foreach ($candidates as $candidate) {
		$candidate = trim((string) $candidate);
		if ($candidate === '') {
			continue;
		}

		if (str_contains($candidate, ',')) {
			$parts = array_map('trim', explode(',', $candidate));
			$candidate = (string) ($parts[0] ?? '');
		}

		if (filter_var($candidate, FILTER_VALIDATE_IP)) {
			return $candidate;
		}
	}

	return '';
}

function lp_ip_is_public(string $ip): bool {
	return $ip !== '' && (bool) filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);
}

function lp_detect_country_code_by_ip(string $ip): string {
	if (!lp_ip_is_public($ip)) {
		return '';
	}

	$header_candidates = [
		$_SERVER['HTTP_CF_IPCOUNTRY'] ?? '',
		$_SERVER['GEOIP_COUNTRY_CODE'] ?? '',
		$_SERVER['HTTP_X_COUNTRY_CODE'] ?? '',
		$_SERVER['HTTP_X_COUNTRY'] ?? '',
	];

	foreach ($header_candidates as $header_candidate) {
		$header_candidate = strtoupper(trim((string) $header_candidate));
		if (preg_match('/^[A-Z]{2}$/', $header_candidate)) {
			return $header_candidate;
		}
	}

	$transient_key = 'lp_ip_country_' . md5($ip);
	$cached = get_transient($transient_key);
	if (is_string($cached) && preg_match('/^[A-Z]{2}$/', $cached)) {
		return $cached;
	}

	$response = wp_remote_get(
		'https://ipwho.is/' . rawurlencode($ip),
		[
			'timeout' => 1.2,
			'redirection' => 1,
			'user-agent' => 'FusionCore Portfolio Notifier/1.0',
		]
	);

	if (is_wp_error($response)) {
		return '';
	}

	$body = wp_remote_retrieve_body($response);
	$data = json_decode($body, true);
	if (!is_array($data)) {
		return '';
	}

	$country_code = strtoupper(trim((string) ($data['country_code'] ?? '')));
	if (!preg_match('/^[A-Z]{2}$/', $country_code)) {
		return '';
	}

	set_transient($transient_key, $country_code, DAY_IN_SECONDS);

	return $country_code;
}

function lp_should_notify_verdian_access(): bool {
	if (is_user_logged_in() || is_admin() || wp_doing_ajax() || (defined('REST_REQUEST') && REST_REQUEST)) {
		return false;
	}

	if (strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET')) !== 'GET') {
		return false;
	}

	$request_path = lp_analytics_normalize_path((string) wp_parse_url((string) ($_SERVER['REQUEST_URI'] ?? '/'), PHP_URL_PATH));
	if ($request_path !== '/verdian-take-home/') {
		return false;
	}

	return sanitize_key((string) ($_GET['lang'] ?? '')) === 'en';
}

function lp_send_verdian_access_notification(): void {
	static $queued = false;

	if ($queued || !lp_should_notify_verdian_access()) {
		return;
	}

	$queued = true;
	$payload = [
		'ip' => lp_request_client_ip(),
		'request_url' => home_url((string) ($_SERVER['REQUEST_URI'] ?? '/')),
		'referrer' => esc_url_raw((string) ($_SERVER['HTTP_REFERER'] ?? '')),
		'user_agent' => sanitize_text_field((string) ($_SERVER['HTTP_USER_AGENT'] ?? '')),
		'requested_at' => current_time('timestamp'),
	];

	register_shutdown_function(static function () use ($payload): void {
		if (function_exists('fastcgi_finish_request')) {
			fastcgi_finish_request();
		}

		if (function_exists('ignore_user_abort')) {
			ignore_user_abort(true);
		}

		$ip = (string) ($payload['ip'] ?? '');
		$country_code = lp_detect_country_code_by_ip($ip);
		$request_url = (string) ($payload['request_url'] ?? home_url('/verdian-take-home/?lang=en'));
		$referrer = (string) ($payload['referrer'] ?? '');
		$user_agent = (string) ($payload['user_agent'] ?? '');
		$requested_at = (int) ($payload['requested_at'] ?? current_time('timestamp'));

		$lines = [
			'Novo acesso ao Verdian Take Home (EN).',
			'',
			'URL: ' . $request_url,
			'IP: ' . ($ip !== '' ? $ip : 'indisponivel'),
			'Pais: ' . ($country_code !== '' ? $country_code : 'nao identificado'),
			'Data: ' . wp_date('d/m/Y H:i:s', $requested_at),
		];

		if ($referrer !== '') {
			$lines[] = 'Referrer: ' . $referrer;
		}

		if ($user_agent !== '') {
			$lines[] = 'User-Agent: ' . $user_agent;
		}

		if ($country_code === 'US') {
			$lines[] = '';
			$lines[] = 'avaliacao comecou';
		}

		wp_mail(
			'contato@fusioncore.com.br',
			'Novo acesso: Verdian Take Home',
			implode("\n", $lines)
		);
	});
}

add_action('template_redirect', 'lp_send_verdian_access_notification', 1);

function lp_render_analytics_dashboard_page(): void {
	if (!current_user_can('manage_options')) {
		return;
	}

	$days = isset($_GET['days']) ? (int) $_GET['days'] : 30;
	$allowed_days = [7, 30, 90];
	$days = in_array($days, $allowed_days, true) ? $days : 30;
	$data = lp_analytics_get_dashboard_data($days);
	$summary = $data['summary'];
	$pageviews = (int) $summary['pageviews'];
	$sessions = (int) $summary['sessions'];
	$clicks = (int) $summary['clicks'];
	$pages_per_session = $sessions > 0 ? $pageviews / $sessions : 0;
	$clicks_per_session = $sessions > 0 ? $clicks / $sessions : 0;
	$ctr = $pageviews > 0 ? ($clicks / $pageviews) * 100 : 0;
	$top_page = $data['top_pages'][0] ?? null;
	$top_click = $data['top_clicks'][0] ?? null;
	$daily_rows = lp_analytics_fill_daily_series($data['daily'], $days);
	$pageview_series = [];
	$click_series = [];
	$session_series = [];
	$session_axis_labels = [];

	foreach ($daily_rows as $row) {
		$pageview_series[] = (int) ($row['pageviews'] ?? 0);
		$click_series[] = (int) ($row['clicks'] ?? 0);
		$session_series[] = (int) ($row['sessions'] ?? 0);
	}

	$label_indexes = array_values(array_unique([
		0,
		(int) floor((count($daily_rows) - 1) / 2),
		max(0, count($daily_rows) - 1),
	]));

	foreach ($label_indexes as $index) {
		if (!isset($daily_rows[$index])) {
			continue;
		}

		$total_points = max(1, count($daily_rows) - 1);
		$session_axis_labels[] = [
			'x' => 12 + (($index / $total_points) * (520 - 24)),
			'label' => wp_date('d/m', strtotime((string) $daily_rows[$index]['day'])),
		];
	}

	$pageview_path = lp_analytics_sparkline_path($pageview_series);
	$click_path = lp_analytics_sparkline_path($click_series);
	$session_path = lp_analytics_sparkline_path($session_series);
	$max_page_views = 0;
	$max_clicks = 0;

	foreach ($data['top_pages'] as $row) {
		$max_page_views = max($max_page_views, (int) ($row['views'] ?? 0));
	}

	foreach ($data['top_clicks'] as $row) {
		$max_clicks = max($max_clicks, (int) ($row['clicks'] ?? 0));
	}
	?>
	<div class="wrap">
		<h1>Statistics</h1>

		<form method="get" style="margin:16px 0 24px;display:flex;align-items:center;gap:10px;flex-wrap:wrap;">
			<input type="hidden" name="page" value="lucas-portfolio-analytics">
			<label for="lp-analytics-days"><strong>Periodo</strong></label>
			<select id="lp-analytics-days" name="days">
				<?php foreach ($allowed_days as $allowed_day) : ?>
					<option value="<?php echo esc_attr((string) $allowed_day); ?>" <?php selected($days, $allowed_day); ?>><?php echo esc_html((string) $allowed_day); ?> dias</option>
				<?php endforeach; ?>
			</select>
			<?php submit_button('Atualizar', 'secondary', '', false); ?>
		</form>

		<style>
			.lp-analytics-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:16px;margin:0 0 24px}
			.lp-analytics-card,.lp-analytics-panel{background:#fff;border:1px solid #dcdcde;border-radius:14px;box-shadow:0 1px 2px rgba(15,23,42,.04)}
			.lp-analytics-card{padding:18px 20px}
			.lp-analytics-panel.is-full{margin:0 0 24px}
			.lp-analytics-card span{display:block;font-size:12px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:#667085;margin-bottom:10px}
			.lp-analytics-card strong{display:block;font-size:30px;line-height:1.05;color:#111827}
			.lp-analytics-panels{display:grid;grid-template-columns:repeat(auto-fit,minmax(360px,1fr));gap:20px}
			.lp-analytics-panel{padding:18px 20px}
			.lp-analytics-panel h2{margin:0 0 16px;font-size:16px}
			.lp-analytics-split{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:20px;margin:0 0 20px}
			.lp-analytics-list{display:grid;gap:12px}
			.lp-analytics-row{display:grid;grid-template-columns:minmax(0,1fr) 72px;gap:12px;align-items:center}
			.lp-analytics-label{display:grid;gap:6px;min-width:0}
			.lp-analytics-label strong{font-size:13px;line-height:1.3;word-break:break-word}
			.lp-analytics-label code{font-size:11px;word-break:break-all;color:#667085;background:#f8fafc;padding:4px 6px;border-radius:6px}
			.lp-analytics-value{text-align:right;font-weight:700;color:#111827}
			.lp-analytics-bar{height:8px;background:#eef2f7;border-radius:999px;overflow:hidden}
			.lp-analytics-bar i{display:block;height:100%;border-radius:999px;background:linear-gradient(90deg,#2563eb,#0ea5e9)}
			.lp-analytics-bar.is-green i{background:linear-gradient(90deg,#16a34a,#22c55e)}
			.lp-analytics-chart{padding:8px 0 0}
			.lp-analytics-chart svg{width:100%;height:auto;display:block}
			.lp-analytics-chart-grid{stroke:#e5e7eb;stroke-width:1}
			.lp-analytics-chart-pageviews{fill:none;stroke:#2563eb;stroke-width:3;stroke-linecap:round;stroke-linejoin:round}
			.lp-analytics-chart-clicks{fill:none;stroke:#16a34a;stroke-width:3;stroke-linecap:round;stroke-linejoin:round}
			.lp-analytics-chart-sessions{fill:none;stroke:#f59e0b;stroke-width:3;stroke-linecap:round;stroke-linejoin:round}
			.lp-analytics-axis-label{fill:#667085;font-size:11px;font-weight:600}
			.lp-analytics-legend{display:flex;gap:16px;flex-wrap:wrap;margin:0 0 10px;color:#667085}
			.lp-analytics-legend span{display:inline-flex;align-items:center;gap:8px;font-size:12px;font-weight:600}
			.lp-analytics-legend i{width:10px;height:10px;border-radius:999px;display:inline-block}
			.lp-analytics-legend .is-blue{background:#2563eb}
			.lp-analytics-legend .is-green{background:#16a34a}
			.lp-analytics-legend .is-amber{background:#f59e0b}
			.lp-analytics-kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px}
			.lp-analytics-kpi{padding:14px;border:1px solid #eef2f7;border-radius:12px;background:#fbfdff}
			.lp-analytics-kpi strong{display:block;font-size:22px;color:#111827}
			.lp-analytics-kpi span{display:block;margin-top:4px;font-size:12px;font-weight:700;text-transform:uppercase;color:#667085}
			.lp-analytics-note{font-size:12px;color:#667085}
			.lp-analytics-context{display:grid;gap:3px;margin:0 0 16px}
			.lp-analytics-context strong{font-size:15px;line-height:1.35}
			.lp-analytics-context small{font-size:12px;color:#667085}
			.lp-analytics-metric-pill{display:inline-flex;align-items:center;gap:6px;padding:4px 8px;border-radius:999px;background:#f3f7ff;color:#1d4ed8;font-size:11px;font-weight:700;text-transform:uppercase}
			.lp-analytics-value small{display:block;font-size:11px;font-weight:600;color:#667085}
			@media (max-width: 782px){.lp-analytics-row{grid-template-columns:minmax(0,1fr) 56px}}
		</style>

		<section class="lp-analytics-panel is-full">
			<h2>Acessos No Tempo</h2>
			<div class="lp-analytics-legend">
				<span><i class="is-amber"></i>Sessoes</span>
			</div>
			<div class="lp-analytics-chart">
				<?php if ($daily_rows === [] || $session_path === '') : ?>
					<div class="lp-analytics-note">Sem dados ainda.</div>
				<?php else : ?>
					<svg viewBox="0 0 520 188" aria-hidden="true" focusable="false">
						<path class="lp-analytics-chart-grid" d="M 12 20 H 508 M 12 80 H 508 M 12 140 H 508"></path>
						<path class="lp-analytics-chart-sessions" d="<?php echo esc_attr($session_path); ?>"></path>
						<?php foreach ($session_axis_labels as $axis_label) : ?>
							<text class="lp-analytics-axis-label" x="<?php echo esc_attr((string) round((float) $axis_label['x'], 2)); ?>" y="178" text-anchor="middle"><?php echo esc_html((string) $axis_label['label']); ?></text>
						<?php endforeach; ?>
					</svg>
					<div class="lp-analytics-note"><?php echo esc_html((string) count($daily_rows)); ?> dias no periodo, incluindo dias sem acesso</div>
				<?php endif; ?>
			</div>
		</section>

		<div class="lp-analytics-grid">
			<div class="lp-analytics-card"><span>Sessoes</span><strong><?php echo esc_html(number_format_i18n($sessions)); ?></strong></div>
			<div class="lp-analytics-card"><span>Views</span><strong><?php echo esc_html(number_format_i18n($pageviews)); ?></strong></div>
			<div class="lp-analytics-card"><span>Cliques</span><strong><?php echo esc_html(number_format_i18n($clicks)); ?></strong></div>
			<div class="lp-analytics-card"><span>Paginas</span><strong><?php echo esc_html(number_format_i18n((int) $summary['tracked_pages'])); ?></strong></div>
			<div class="lp-analytics-card"><span>Views/Sessao</span><strong><?php echo esc_html(number_format_i18n($pages_per_session, 2)); ?></strong></div>
			<div class="lp-analytics-card"><span>Cliques/Sessao</span><strong><?php echo esc_html(number_format_i18n($clicks_per_session, 2)); ?></strong></div>
			<div class="lp-analytics-card"><span>CTR</span><strong><?php echo esc_html(lp_analytics_percent($ctr)); ?></strong></div>
			<div class="lp-analytics-card"><span>Lider</span><strong><?php echo esc_html((string) ($top_page['page_path'] ?? '-')); ?></strong></div>
		</div>

		<div class="lp-analytics-split">
			<section class="lp-analytics-panel">
				<h2>Leitura Rapida</h2>
				<div class="lp-analytics-kpis">
					<div class="lp-analytics-kpi">
						<span>Pagina Mais Vista</span>
						<strong><?php echo esc_html($top_page ? number_format_i18n((int) $top_page['views']) : '0'); ?></strong>
						<div class="lp-analytics-note"><?php echo esc_html($top_page ? lp_analytics_page_name($top_page) : '-'); ?></div>
						<div class="lp-analytics-note"><?php echo esc_html((string) ($top_page['page_path'] ?? '-')); ?></div>
					</div>
					<div class="lp-analytics-kpi">
						<span>CTA Mais Clicado</span>
						<strong><?php echo esc_html($top_click ? number_format_i18n((int) $top_click['clicks']) : '0'); ?></strong>
						<div class="lp-analytics-note"><?php echo esc_html($top_click ? lp_analytics_cta_name($top_click) : '-'); ?></div>
						<div class="lp-analytics-note"><?php echo esc_html($top_click ? (string) ($top_click['page_path'] ?? '-') : '-'); ?></div>
					</div>
					<div class="lp-analytics-kpi">
						<span>CTR Geral</span>
						<strong><?php echo esc_html(lp_analytics_percent($ctr)); ?></strong>
						<div class="lp-analytics-note">Cliques em relacao as views</div>
					</div>
					<div class="lp-analytics-kpi">
						<span>Views Por Sessao</span>
						<strong><?php echo esc_html(number_format_i18n($pages_per_session, 2)); ?></strong>
						<div class="lp-analytics-note">Media de paginas vistas por visita</div>
					</div>
				</div>
			</section>

			<section class="lp-analytics-panel">
				<h2>Tendencia</h2>
				<div class="lp-analytics-legend">
					<span><i class="is-blue"></i>Views</span>
					<span><i class="is-green"></i>Cliques</span>
				</div>
				<div class="lp-analytics-chart">
					<?php if ($daily_rows === [] || $pageview_path === '') : ?>
						<div class="lp-analytics-note">Sem dados ainda.</div>
					<?php else : ?>
						<svg viewBox="0 0 520 160" aria-hidden="true" focusable="false">
							<path class="lp-analytics-chart-grid" d="M 12 20 H 508 M 12 80 H 508 M 12 140 H 508"></path>
							<path class="lp-analytics-chart-pageviews" d="<?php echo esc_attr($pageview_path); ?>"></path>
							<?php if ($click_path !== '') : ?>
								<path class="lp-analytics-chart-clicks" d="<?php echo esc_attr($click_path); ?>"></path>
							<?php endif; ?>
						</svg>
						<div class="lp-analytics-note"><?php echo esc_html((string) count($daily_rows)); ?> pontos diarios</div>
					<?php endif; ?>
				</div>
			</section>
		</div>

		<div class="lp-analytics-panels">
			<section class="lp-analytics-panel">
				<h2>Paginas</h2>
				<div class="lp-analytics-list">
					<?php if ($data['top_pages'] === []) : ?>
						<div class="lp-analytics-note">Sem dados ainda.</div>
					<?php else : ?>
						<?php foreach (array_slice($data['top_pages'], 0, 8) as $row) : ?>
							<?php $width = $max_page_views > 0 ? (((int) $row['views']) / $max_page_views) * 100 : 0; ?>
							<div class="lp-analytics-row">
								<div class="lp-analytics-label">
									<div class="lp-analytics-context">
										<strong><?php echo esc_html(lp_analytics_page_name($row)); ?></strong>
										<small><?php echo esc_html((string) ($row['page_path'] ?: '/')); ?></small>
									</div>
									<div class="lp-analytics-note">
										<span class="lp-analytics-metric-pill"><?php echo esc_html(number_format_i18n((int) ($row['sessions'] ?? 0))); ?> sessoes</span>
									</div>
									<div class="lp-analytics-bar"><i style="width:<?php echo esc_attr((string) round($width, 2)); ?>%"></i></div>
								</div>
								<div class="lp-analytics-value">
									<?php echo esc_html(number_format_i18n((int) $row['views'])); ?>
									<small>views</small>
								</div>
							</div>
						<?php endforeach; ?>
					<?php endif; ?>
				</div>
			</section>

			<section class="lp-analytics-panel">
				<h2>CTAs</h2>
				<div class="lp-analytics-list">
					<?php if ($data['top_clicks'] === []) : ?>
						<div class="lp-analytics-note">Sem dados ainda.</div>
					<?php else : ?>
						<?php foreach (array_slice($data['top_clicks'], 0, 8) as $row) : ?>
							<?php
							$width = $max_clicks > 0 ? (((int) $row['clicks']) / $max_clicks) * 100 : 0;
							$label = lp_analytics_cta_name($row);
							?>
							<div class="lp-analytics-row">
								<div class="lp-analytics-label">
									<div class="lp-analytics-context">
										<strong><?php echo esc_html($label); ?></strong>
										<small><?php echo esc_html('Pagina: ' . (string) ($row['page_path'] ?: '/')); ?></small>
										<?php if (!empty($row['element_target'])) : ?>
											<small><?php echo esc_html('Destino: ' . (string) $row['element_target']); ?></small>
										<?php endif; ?>
									</div>
									<div class="lp-analytics-note">
										<span class="lp-analytics-metric-pill"><?php echo esc_html(number_format_i18n((int) ($row['sessions'] ?? 0))); ?> sessoes</span>
									</div>
									<div class="lp-analytics-bar is-green"><i style="width:<?php echo esc_attr((string) round($width, 2)); ?>%"></i></div>
								</div>
								<div class="lp-analytics-value">
									<?php echo esc_html(number_format_i18n((int) $row['clicks'])); ?>
									<small>cliques</small>
								</div>
							</div>
						<?php endforeach; ?>
					<?php endif; ?>
				</div>
			</section>

			<section class="lp-analytics-panel">
				<h2>Ultimos dias</h2>
				<div class="lp-analytics-list">
					<?php if ($data['daily'] === []) : ?>
						<div class="lp-analytics-note">Sem dados ainda.</div>
					<?php else : ?>
						<?php foreach (array_slice($data['daily'], 0, 8) as $row) : ?>
							<div class="lp-analytics-row">
								<div class="lp-analytics-label">
									<div class="lp-analytics-context">
										<strong><?php echo esc_html(wp_date('d/m/Y', strtotime((string) $row['day']))); ?></strong>
									</div>
									<div class="lp-analytics-note">
										<?php echo esc_html(number_format_i18n((int) $row['clicks'])); ?> cliques • <?php echo esc_html(number_format_i18n((int) $row['sessions'])); ?> sessoes
									</div>
								</div>
								<div class="lp-analytics-value">
									<?php echo esc_html(number_format_i18n((int) $row['pageviews'])); ?>
									<small>views</small>
								</div>
							</div>
						<?php endforeach; ?>
					<?php endif; ?>
				</div>
			</section>
		</div>
	</div>
	<?php
}

add_action('wp_enqueue_scripts', function (): void {
	if (!lp_analytics_is_public_request()) {
		return;
	}

	$request_uri = (string) ($_SERVER['REQUEST_URI'] ?? '/');
	$page_url = home_url($request_uri);
	$page_path = lp_analytics_normalize_path((string) wp_parse_url($request_uri, PHP_URL_PATH));

	wp_enqueue_script(
		'lp-frontend-analytics',
		plugins_url('assets/js/frontend-analytics.js', __FILE__),
		[],
		LP_TOOLKIT_VERSION,
		true
	);

	wp_localize_script('lp-frontend-analytics', 'lpAnalytics', [
		'endpoint' => esc_url_raw(rest_url('lp/v1/analytics/event')),
		'pageUrl' => esc_url_raw($page_url),
		'pagePath' => $page_path,
		'pageTitle' => wp_get_document_title(),
		'postId' => is_singular() ? (int) get_queried_object_id() : 0,
		'locale' => determine_locale(),
		'siteHost' => lp_analytics_host(),
	]);
}, 20);

// Persist field values with nonces, capability checks and type-aware sanitization.
add_action('save_post', function (int $post_id): void {
	if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
		return;
	}

	if (!current_user_can('edit_post', $post_id)) {
		return;
	}

	if (isset($_POST['lp_project_fields_nonce']) && wp_verify_nonce((string) $_POST['lp_project_fields_nonce'], 'lp_save_project_fields')) {
		foreach (lp_acf_like_fields() as $key => $field) {
			if (!isset($_POST[$key])) {
				continue;
			}

			$value = wp_unslash($_POST[$key]);
			update_post_meta($post_id, $key, lp_sanitize_field_value($field, $value));
		}
	}

	if (isset($_POST['lp_brand_fields_nonce']) && wp_verify_nonce((string) $_POST['lp_brand_fields_nonce'], 'lp_save_brand_fields')) {
		foreach (lp_brand_fields() as $key => $field) {
			if (!isset($_POST[$key])) {
				continue;
			}

			$value = wp_unslash($_POST[$key]);
			update_post_meta($post_id, $key, lp_sanitize_field_value($field, $value));
		}
	}

	if (isset($_POST['lp_home_fields_nonce']) && wp_verify_nonce((string) $_POST['lp_home_fields_nonce'], 'lp_save_home_fields')) {
		foreach (lp_home_fields() as $key => $field) {
			if (!isset($_POST[$key]) && $field['type'] !== 'checkbox') {
				continue;
			}

			$value = wp_unslash($_POST[$key] ?? '0');
			update_post_meta($post_id, $key, lp_sanitize_field_value($field, $value));
		}
	}

	if (isset($_POST['lp_rollout_fields_nonce']) && wp_verify_nonce((string) $_POST['lp_rollout_fields_nonce'], 'lp_save_rollout_fields')) {
		foreach (lp_rollout_fields() as $key => $field) {
			if (!isset($_POST[$key])) {
				continue;
			}

			$value = wp_unslash($_POST[$key]);
			update_post_meta($post_id, $key, lp_sanitize_field_value($field, $value));
		}
	}

	if (isset($_POST['lp_plugin_code_fields_nonce']) && wp_verify_nonce((string) $_POST['lp_plugin_code_fields_nonce'], 'lp_save_plugin_code_fields')) {
		foreach (lp_plugin_code_fields() as $key => $field) {
			if (!isset($_POST[$key])) {
				continue;
			}

			$value = wp_unslash($_POST[$key]);
			if (in_array($key, ['lp_plugin_code_api_code', 'lp_plugin_code_files', 'lp_plugin_code_paths'], true)) {
				update_post_meta($post_id, $key, (string) $value);
				continue;
			}

			update_post_meta($post_id, $key, sanitize_textarea_field((string) $value));
		}
	}

	if (isset($_POST['lp_page_specific_fields_nonce']) && wp_verify_nonce((string) $_POST['lp_page_specific_fields_nonce'], 'lp_save_page_specific_fields')) {
		$slug = (string) get_post_field('post_name', $post_id);
		foreach (lp_page_fields_for_slug($slug) as $key => $field) {
			if (!isset($_POST[$key]) && $field['type'] !== 'checkbox') {
				continue;
			}

			$value = wp_unslash($_POST[$key] ?? '0');
			update_post_meta($post_id, $key, lp_sanitize_field_value($field, $value));
		}
	}

	if (isset($_POST['lp_assessment_visual_fields_nonce']) && wp_verify_nonce((string) $_POST['lp_assessment_visual_fields_nonce'], 'lp_save_assessment_visual_fields')) {
		foreach (lp_assessment_visual_fields() as $key => $field) {
			if (!isset($_POST[$key]) && $field['type'] !== 'checkbox') {
				continue;
			}

			$value = wp_unslash($_POST[$key] ?? '0');
			if ($field['type'] === 'select') {
				$options = array_keys($field['options'] ?? []);
				$sanitized = sanitize_key((string) $value);
				update_post_meta($post_id, $key, in_array($sanitized, $options, true) ? $sanitized : 'visuals');
				continue;
			}

			update_post_meta($post_id, $key, lp_sanitize_field_value($field, $value));
		}
	}
});

function lp_portfolio_cache_clear_url(): string {
	return wp_nonce_url(
		add_query_arg(
			[
				'lp_action' => 'clear_portfolio_cache',
			],
			admin_url()
		),
		'lp_clear_portfolio_cache'
	);
}

add_action('admin_bar_menu', function (WP_Admin_Bar $admin_bar): void {
	if (!current_user_can('manage_options')) {
		return;
	}

	$admin_bar->add_node([
		'id'    => 'lp-clear-portfolio-cache',
		'title' => 'Clear Portfolio Cache',
		'href'  => lp_portfolio_cache_clear_url(),
		'meta'  => [
			'title' => 'Purge portfolio page cache and reload the latest version',
		],
	]);
}, 100);

add_action('admin_init', function (): void {
	if (!is_admin() || !current_user_can('manage_options')) {
		return;
	}

	if (($_GET['lp_action'] ?? '') !== 'clear_portfolio_cache') {
		return;
	}

	check_admin_referer('lp_clear_portfolio_cache');

	if (function_exists('portfolio_page_cache_flush')) {
		portfolio_page_cache_flush();
	}

	wp_safe_redirect(add_query_arg('lp_cache_cleared', '1', wp_get_referer() ?: admin_url()));
	exit;
});

add_action('admin_notices', function (): void {
	if (($_GET['lp_cache_cleared'] ?? '') !== '1') {
		return;
	}

	echo '<div class="notice notice-success is-dismissible"><p>Portfolio cache cleared. New requests will serve the latest version.</p></div>';
});

// Theme helper compatible with the common ACF get_field pattern.
function lp_get_field(string $field_name, ?int $post_id = null) {
	$post_id = $post_id ?: get_the_ID();
	if (!$post_id) {
		return '';
	}

	return get_post_meta($post_id, $field_name, true);
}

// Theme helper compatible with the common ACF the_field pattern.
function lp_the_field(string $field_name, ?int $post_id = null): void {
	echo wp_kses_post((string) lp_get_field($field_name, $post_id));
}

function lp_get_option(string $field_name, string $fallback = ''): string {
	$value = get_option($field_name, '');
	return is_string($value) && $value !== '' ? $value : $fallback;
}

// Decode JSON flexible sections so the theme can render reusable layouts.
function lp_get_flexible_sections(?int $post_id = null): array {
	$raw = (string) lp_get_field('lp_flexible_json', $post_id);
	if ($raw === '') {
		return [];
	}

	$decoded = json_decode($raw, true);
	return is_array($decoded) ? $decoded : [];
}

// Expose custom meta fields in the WordPress REST API for integration proof.
add_action('rest_api_init', function (): void {
	foreach (array_keys(lp_project_types()) as $post_type) {
		foreach (array_keys(lp_acf_like_fields()) as $field_name) {
			register_rest_field($post_type, $field_name, [
				'get_callback' => static fn(array $object) => get_post_meta((int) $object['id'], $field_name, true),
				'schema'       => ['type' => 'string'],
			]);
		}
	}

	foreach (array_keys(lp_brand_type()) as $post_type) {
		foreach (array_keys(lp_brand_fields()) as $field_name) {
			register_rest_field($post_type, $field_name, [
				'get_callback' => static fn(array $object) => get_post_meta((int) $object['id'], $field_name, true),
				'schema'       => ['type' => 'string'],
			]);
		}
	}

	register_rest_route('lp/v1', '/analytics/event', [
		'methods' => 'POST',
		'permission_callback' => '__return_true',
		'callback' => static function (WP_REST_Request $request): WP_REST_Response {
			global $wpdb;

			if (is_user_logged_in()) {
				return new WP_REST_Response(['ok' => true, 'ignored' => 'logged_in'], 202);
			}

			$payload = $request->get_json_params();
			if (!is_array($payload)) {
				return new WP_REST_Response(['ok' => false, 'message' => 'Invalid payload'], 400);
			}

			$event_type = sanitize_key((string) ($payload['event_type'] ?? ''));
			$session_id = lp_analytics_get_or_create_session_id();

			if ($session_id === '' || strlen($session_id) < 12 || !in_array($event_type, ['page_view', 'click', 'session_end'], true)) {
				return new WP_REST_Response(['ok' => false, 'message' => 'Invalid event'], 400);
			}

			$page_url = esc_url_raw((string) ($payload['page_url'] ?? home_url('/')));
			$page_path = lp_analytics_normalize_path((string) ($payload['page_path'] ?? '/'));
			$page_title = lp_analytics_clean_text((string) ($payload['page_title'] ?? ''), 255);
			$page_post_id = max(0, (int) ($payload['page_post_id'] ?? 0));
			$element_type = lp_analytics_clean_text((string) ($payload['element_type'] ?? ''), 30);
			$element_label = lp_analytics_clean_text((string) ($payload['element_label'] ?? ''), 255);
			$element_target = esc_url_raw((string) ($payload['element_target'] ?? ''));
			$referrer_host = strtolower((string) wp_parse_url((string) ($payload['referrer'] ?? ''), PHP_URL_HOST));
			$referrer_host = lp_analytics_clean_text($referrer_host, 190);
			$locale = preg_replace('/[^a-zA-Z_-]/', '', (string) ($payload['locale'] ?? ''));
			$device_type = sanitize_key((string) ($payload['device_type'] ?? ''));
			$user_agent = (string) ($request->get_header('user_agent') ?: ($_SERVER['HTTP_USER_AGENT'] ?? ''));
			$visitor_ip = (string) ($_SERVER['REMOTE_ADDR'] ?? '');
			$user_agent_hash = hash('sha256', $user_agent . '|' . $visitor_ip . '|' . wp_salt('auth'));
			$page_host = strtolower((string) wp_parse_url($page_url, PHP_URL_HOST));

			if (!in_array($device_type, ['mobile', 'tablet', 'desktop'], true)) {
				if (preg_match('/ipad|tablet|playbook|silk/i', $user_agent)) {
					$device_type = 'tablet';
				} elseif (preg_match('/mobile|android|iphone|ipod|blackberry|iemobile|opera mini/i', $user_agent)) {
					$device_type = 'mobile';
				} else {
					$device_type = 'desktop';
				}
			}

			if ($page_host !== '' && $page_host !== lp_analytics_host()) {
				return new WP_REST_Response(['ok' => false, 'message' => 'Invalid host'], 400);
			}

			$wpdb->insert(
				lp_analytics_table_name(),
				[
					'session_id' => $session_id,
					'event_type' => $event_type,
					'page_url' => $page_url,
					'page_path' => $page_path,
					'page_title' => $page_title,
					'page_post_id' => $page_post_id,
					'element_type' => $element_type,
					'element_label' => $element_label,
					'element_target' => $element_target,
					'referrer_host' => $referrer_host,
					'locale' => $locale,
					'user_agent_hash' => $user_agent_hash,
					'device_type' => $device_type,
					'created_at' => current_time('mysql', true),
				],
				[
					'%s',
					'%s',
					'%s',
					'%s',
					'%s',
					'%d',
					'%s',
					'%s',
					'%s',
					'%s',
					'%s',
					'%s',
					'%s',
					'%s',
				]
			);

			return new WP_REST_Response(['ok' => true], 201);
		},
	]);
});

register_activation_hook(__FILE__, function (): void {
	lp_analytics_install();

	foreach (lp_project_types() as $post_type => $config) {
		register_post_type($post_type, [
			'public'      => true,
			'has_archive' => true,
			'rewrite'     => ['slug' => $config['slug']],
		]);
	}

	foreach (lp_brand_type() as $post_type => $config) {
		register_post_type($post_type, [
			'public'      => true,
			'has_archive' => true,
			'rewrite'     => ['slug' => $config['slug']],
		]);
	}

	foreach (lp_assessment_visual_type() as $post_type => $config) {
		register_post_type($post_type, [
			'public'      => false,
			'show_ui'     => true,
			'rewrite'     => ['slug' => $config['slug']],
		]);
	}
	flush_rewrite_rules();
});

register_deactivation_hook(__FILE__, 'flush_rewrite_rules');

mu-plugins/portfolio-runtime-optimization.php

MU Plugin de Otimização Runtime do Portfólio

Aplica endurecimento runtime compatível com CSP, correções de dimensão de imagem, trackers adiados, sitemap e limpeza de frontend para o domínio público do portfólio.

<?php
/**
 * Plugin Name: Portfolio Runtime Optimization
 * Description: Conservative runtime optimization, delayed trackers, image attributes, and XML sitemap for Lucas Portfolio.
 */

declare(strict_types=1);

if (!defined('ABSPATH')) {
	exit;
}

// Use a stable CSP nonce so the full-page cache can safely reuse cached HTML.
function portfolio_runtime_csp_nonce(): string {
	static $nonce = null;

	if (is_string($nonce)) {
		return $nonce;
	}

	$seed = (string) (defined('AUTH_SALT') ? AUTH_SALT : __FILE__);
	$nonce = substr(strtr(base64_encode(hash('sha256', 'portfolio-runtime|' . $seed, true)), '+/', '-_'), 0, 24);
	return $nonce;
}

// This runtime hardening is intended only for the public portfolio site.
function portfolio_runtime_is_target_host(): bool {
	$host = strtolower((string) ($_SERVER['HTTP_HOST'] ?? ''));
	return $host === 'portfolio.fusioncore.com.br' || $host === 'www.portfolio.fusioncore.com.br';
}

// Runtime rules run only on public requests, never in the WordPress dashboard.
function portfolio_runtime_should_run(): bool {
	return !is_admin() && portfolio_runtime_is_target_host();
}

// The homepage receives the most aggressive safe cleanup because it is the assessment landing page.
function portfolio_runtime_is_home(): bool {
	$request_uri  = (string) ($_SERVER['REQUEST_URI'] ?? '');
	$request_path = trim((string) wp_parse_url($request_uri, PHP_URL_PATH), '/');

	return is_front_page() || is_home() || $request_path === '';
}

// Marketing trackers can be delayed until user interaction or page load without breaking content.
function portfolio_runtime_is_delayed_tracker(string $src): bool {
	return (
		strpos($src, 'googletagmanager.com') !== false ||
		strpos($src, 'google-analytics.com') !== false ||
		strpos($src, 'connect.facebook.net') !== false ||
		strpos($src, 'facebook.com/tr') !== false ||
		strpos($src, 'linkedin.com/insight') !== false
	);
}

// Resolve image dimensions from local uploads or WordPress thumbnail filename patterns.
function portfolio_runtime_get_image_dimensions(string $src): ?array {
	static $cache = [];

	if (array_key_exists($src, $cache)) {
		return $cache[$src];
	}

	$uploads = wp_get_upload_dir();
	$base_url = (string) ($uploads['baseurl'] ?? '');
	$base_dir = (string) ($uploads['basedir'] ?? '');

	if ($base_url !== '' && $base_dir !== '' && strpos($src, $base_url) === 0) {
		$relative = ltrim(substr($src, strlen($base_url)), '/');
		$file = wp_normalize_path(trailingslashit($base_dir) . $relative);
		if (is_file($file)) {
			$size = @getimagesize($file);
			if (is_array($size) && !empty($size[0]) && !empty($size[1])) {
				$cache[$src] = [(int) $size[0], (int) $size[1]];
				return $cache[$src];
			}
		}
	}

	if (preg_match('#-(\d{2,5})x(\d{2,5})\.(?:jpe?g|png|webp|avif)(?:\?|$)#i', $src, $matches)) {
		$cache[$src] = [(int) $matches[1], (int) $matches[2]];
		return $cache[$src];
	}

	$cache[$src] = null;
	return null;
}

// Add width and height attributes to image tags that were rendered without them.
function portfolio_runtime_add_missing_dimensions(string $html): string {
	if ($html === '' || stripos($html, '<img') === false) {
		return $html;
	}

	$updated = preg_replace_callback('#<img\b([^>]*)>#i', static function (array $matches): string {
		$attrs = (string) ($matches[1] ?? '');

		if (preg_match('#\swidth=(["\']).*?\1#i', $attrs) && preg_match('#\sheight=(["\']).*?\1#i', $attrs)) {
			return $matches[0];
		}

		if (!preg_match('#\ssrc=(["\'])([^"\']+)\1#i', $attrs, $src_match)) {
			return $matches[0];
		}

		$dimensions = portfolio_runtime_get_image_dimensions((string) $src_match[2]);
		if (!is_array($dimensions)) {
			return $matches[0];
		}

		if (!preg_match('#\swidth=(["\']).*?\1#i', $attrs)) {
			$attrs .= ' width="' . (int) $dimensions[0] . '"';
		}

		if (!preg_match('#\sheight=(["\']).*?\1#i', $attrs)) {
			$attrs .= ' height="' . (int) $dimensions[1] . '"';
		}

		return '<img' . $attrs . '>';
	}, $html);

	return is_string($updated) ? $updated : $html;
}

// Reserve layout for common embeds and media blocks rendered without intrinsic placeholders.
function portfolio_runtime_stabilize_media_markup(string $html): string {
	if ($html === '') {
		return $html;
	}

	$updated = preg_replace_callback('#<(iframe|video)\b([^>]*)>#i', static function (array $matches): string {
		$tag = strtolower((string) ($matches[1] ?? ''));
		$attrs = (string) ($matches[2] ?? '');

		if ($tag === 'iframe') {
			if (!preg_match('#\sloading=(["\']).*?\1#i', $attrs)) {
				$attrs .= ' loading="lazy"';
			}
			if (!preg_match('#\stitle=(["\']).*?\1#i', $attrs)) {
				$attrs .= ' title="Embedded content"';
			}
		}

		if (!preg_match('#\swidth=(["\']).*?\1#i', $attrs) && !preg_match('#\sstyle=(["\']).*?aspect-ratio\s*:#i', $attrs)) {
			$attrs .= ' style="aspect-ratio:16/9;width:100%;height:auto;"';
		}

		return '<' . $tag . $attrs . '>';
	}, $html);

	return is_string($updated) ? $updated : $html;
}

// Apply nonce attributes to inline styles/scripts so CSP can stay strict.
function portfolio_runtime_add_csp_nonces(string $html): string {
	if ($html === '') {
		return $html;
	}

	$nonce = portfolio_runtime_csp_nonce();
	$html = preg_replace('#<script\b(?![^>]*\bnonce=)#i', '<script nonce="' . esc_attr($nonce) . '"', $html) ?: $html;
	$html = preg_replace('#<style\b(?![^>]*\bnonce=)#i', '<style nonce="' . esc_attr($nonce) . '"', $html) ?: $html;

	return $html;
}

// Security headers are emitted from PHP because the site sits behind a shared Nginx stack.
function portfolio_runtime_send_security_headers(): void {
	if (!portfolio_runtime_should_run() || headers_sent()) {
		return;
	}

	$nonce = portfolio_runtime_csp_nonce();
	$csp = implode('; ', [
		"default-src 'self' https: data: blob:",
		"base-uri 'self'",
		"object-src 'none'",
		"frame-ancestors 'self'",
		"form-action 'self' https://app.fusioncore.com.br",
		"img-src 'self' https: data: blob:",
		"font-src 'self' https: data:",
		"connect-src 'self' https:",
		"frame-src 'self' https:",
		"style-src 'self' 'nonce-" . $nonce . "' https:",
		"script-src 'self' 'nonce-" . $nonce . "' https:",
		"require-trusted-types-for 'script'",
		"trusted-types default",
		'upgrade-insecure-requests',
	]);

	header('Content-Security-Policy: ' . $csp);
	header('Cross-Origin-Opener-Policy: same-origin');
	header('X-Frame-Options: SAMEORIGIN');
	header('X-Content-Type-Options: nosniff');
	header('Referrer-Policy: strict-origin-when-cross-origin');
	header('Permissions-Policy: accelerometer=(), autoplay=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()');
	header('Strict-Transport-Security: max-age=63072000; includeSubDomains; preload');
}

// Keep theme CSS as a normal stylesheet to avoid extra style recalculation and layout shifts.
function portfolio_runtime_filter_theme_css(string $html, string $handle, string $href): string {
	if ($handle !== 'lucas-portfolio-screen' || $href === '' || !portfolio_runtime_should_run()) {
		return $html;
	}

	return '<link rel="stylesheet" id="' . esc_attr($handle) . '-css" href="' . esc_url($href) . '" media="all">';
}

// Remove WordPress head noise and register the lightweight sitemap route.
add_action('init', function (): void {
	if (!portfolio_runtime_should_run()) {
		return;
	}

	remove_action('wp_head', 'print_emoji_detection_script', 7);
	remove_action('wp_print_styles', 'print_emoji_styles');
	remove_action('wp_head', 'rsd_link');
	remove_action('wp_head', 'wlwmanifest_link');
	remove_action('wp_head', 'wp_generator');
	remove_action('wp_head', 'wp_shortlink_wp_head', 10);
	remove_action('wp_head', 'rest_output_link_wp_head', 10);
	remove_action('wp_head', 'wp_oembed_add_discovery_links', 10);
	remove_action('wp_head', 'wp_oembed_add_host_js');
	remove_action('wp_head', 'feed_links_extra', 3);
	remove_action('wp_head', 'adjacent_posts_rel_link_wp_head', 10);
	remove_action('wp_enqueue_scripts', 'wp_enqueue_global_styles');
	remove_action('wp_footer', 'wp_enqueue_global_styles', 1);

	add_rewrite_rule('^sitemap\.xml$', 'index.php?portfolio_sitemap=1', 'top');
}, 20);

// Remove unused block/global styles on the homepage because this theme does not use Gutenberg blocks.
add_action('wp_enqueue_scripts', function (): void {
	if (!portfolio_runtime_is_home()) {
		return;
	}

	foreach (['wp-block-library', 'wp-block-library-theme', 'global-styles', 'classic-theme-styles'] as $handle) {
		wp_dequeue_style($handle);
		wp_deregister_style($handle);
	}

	if (!is_user_logged_in()) {
		wp_dequeue_style('dashicons');
		wp_deregister_style('dashicons');
	}
}, 1000);

// Delay trackers and defer non-core scripts to keep the first render clean.
add_filter('script_loader_tag', function (string $tag, string $handle, string $src): string {
	if (!portfolio_runtime_should_run()) {
		return $tag;
	}

	$nonce_attr = ' nonce="' . esc_attr(portfolio_runtime_csp_nonce()) . '"';

	if ($src !== '' && portfolio_runtime_is_delayed_tracker($src)) {
		return "<script" . $nonce_attr . " type='text/plain' data-portfolio-delay='1' data-portfolio-delay-src='" . esc_url($src) . "'></script>";
	}

	if (strpos($tag, ' nonce=') === false) {
		$tag = str_replace('<script ', '<script' . $nonce_attr . ' ', $tag);
	}

	if (strpos($tag, ' defer') === false && !in_array($handle, ['jquery', 'jquery-core'], true)) {
		return str_replace(' src', ' defer src', $tag);
	}

	return $tag;
}, 10, 3);

// Keep non-critical styles from blocking first paint when a known handle is safe to async-load.
add_filter('style_loader_tag', function (string $html, string $handle, string $href): string {
	if (!portfolio_runtime_is_home() || $href === '') {
		return $html;
	}

	$async_styles = ['wp-block-library', 'global-styles'];
	if (!in_array($handle, $async_styles, true)) {
		return $html;
	}

	return '<link rel="stylesheet" id="' . esc_attr($handle) . '-css" href="' . esc_url($href) . '" media="print" onload="this.media=\'all\'">'
		. '<noscript><link rel="stylesheet" href="' . esc_url($href) . '" media="all"></noscript>';
}, 10, 3);

// Override the theme CSS loading strategy depending on device class.
add_filter('style_loader_tag', 'portfolio_runtime_filter_theme_css', 100, 3);

// Normalize image markup in post content generated by editors or plugins.
add_filter('the_content', static fn(string $content): string => portfolio_runtime_add_missing_dimensions($content), 20);

// Make WordPress attachment images lazy and async by default on the public site.
add_filter('wp_get_attachment_image_attributes', function (array $attr): array {
	if (is_admin() || !portfolio_runtime_should_run()) {
		return $attr;
	}

	if (empty($attr['loading'])) {
		$attr['loading'] = 'lazy';
	}
	$attr['decoding'] = 'async';

	return $attr;
}, 20);

// Add accessible skip-link styling early in the head without external font preconnects.
add_action('wp_head', function (): void {
	if (!portfolio_runtime_is_home()) {
		return;
	}
	?>
	<style id="portfolio-runtime-css">
		.skip-link:focus{position:fixed;left:12px;top:12px;z-index:100000;padding:12px 16px;background:#fff;color:#111;border:2px solid #111}
		.proof-section,.architecture-section,.documentation-band,.scheduler-band,.projects-section,.brands-section,.systems-section,.site-footer{content-visibility:auto;contain-intrinsic-size:1px 900px}
		@media(max-width:920px){
			.site-main,.site-footer{width:min(1180px,calc(100% - 28px))}
			.site-header-inner{gap:14px;padding:14px 16px}
			.brand{gap:10px;min-width:0}
			.brand strong{font-size:14px;line-height:1.15}
			.brand em{font-size:11px}
			.brand span:last-child{min-width:0}
			.menu-toggle{display:inline-flex;flex-shrink:0}
			.hero-copy{padding:28px 16px}
			.hero-copy h1{font-size:clamp(28px,8vw,38px)}
			.hero-lede{margin-top:18px;font-size:18px}
			.hero-actions{margin-top:22px}
			.hero-banner{min-height:320px}
			.hero-banner>div{width:calc(100% - 24px);margin:12px;padding:16px}
			.stat-strip{grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;padding:12px}
		}
	</style>
	<?php
}, 1);

// Final HTML pass for dimensions and a main landmark, kept conservative and reversible.
add_action('template_redirect', function (): void {
	if (!portfolio_runtime_should_run()) {
		return;
	}

	if (!headers_sent() && !is_user_logged_in() && in_array(strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET')), ['GET', 'HEAD'], true)) {
		header('Cache-Control: public, max-age=300, stale-while-revalidate=600');
		header('Vary: Accept-Encoding', false);
	}
	portfolio_runtime_send_security_headers();

	ob_start(static function (string $html): string {
		$html = portfolio_runtime_add_missing_dimensions($html);
		$html = portfolio_runtime_stabilize_media_markup($html);
		$html = portfolio_runtime_add_csp_nonces($html);
		if (stripos($html, 'role="main"') === false) {
			$html = preg_replace('#<main\b#i', '<main role="main"', $html, 1) ?: $html;
		}
		if (portfolio_runtime_is_home()) {
			$html = preg_replace_callback(
				'#<img\b([^>]*\bloading=(["\'])lazy\2[^>]*)>#i',
				static function (array $matches): string {
					$attrs = (string) ($matches[1] ?? '');

					if (!preg_match('#\sfetchpriority=(["\']).*?\1#i', $attrs)) {
						$attrs .= ' fetchpriority="low"';
					}
					if (!preg_match('#\sdecoding=(["\']).*?\1#i', $attrs)) {
						$attrs .= ' decoding="async"';
					}

					return '<img' . $attrs . '>';
				},
				$html
			) ?: $html;
				$html = preg_replace_callback(
					'#<img\b([^>]*\bhero-showcase\.webp[^>]*)>#i',
					static function (array $matches): string {
						$attrs = (string) ($matches[1] ?? '');

					if (!preg_match('#\sfetchpriority=(["\']).*?\1#i', $attrs)) {
						$attrs .= ' fetchpriority="high"';
					}
					if (!preg_match('#\sloading=(["\']).*?\1#i', $attrs)) {
						$attrs .= ' loading="eager"';
					}
					if (!preg_match('#\sdecoding=(["\']).*?\1#i', $attrs)) {
						$attrs .= ' decoding="async"';
					}
					if (!preg_match('#\sstyle=(["\']).*?aspect-ratio\s*:#i', $attrs)) {
						$attrs .= ' style="aspect-ratio:1536/1024;"';
					}

					return '<img' . $attrs . '>';
					},
					$html,
					1
				) ?: $html;
				$html = preg_replace('/>\s+</', '><', $html) ?: $html;
			}
			return $html;
	});
}, 0);

// Load delayed tracker scripts after interaction, load, or a conservative timeout fallback.
add_action('wp_footer', function (): void {
	if (!portfolio_runtime_should_run()) {
		return;
	}
	?>
	<script id="portfolio-runtime-trackers">
	(function(){
		var booted=false;
		function loadDelayedTrackers(){
			if(booted) return;
			booted=true;
			document.querySelectorAll('script[data-portfolio-delay="1"][data-portfolio-delay-src]').forEach(function(node){
				var src=node.getAttribute('data-portfolio-delay-src') || '';
				if(!src || document.querySelector('script[src="'+src+'"]')) return;
				var s=document.createElement('script');
				s.src=src;
				s.async=true;
				(document.head || document.body || document.documentElement).appendChild(s);
			});
		}
		['touchstart','pointerdown','scroll','click','keydown'].forEach(function(ev){
			window.addEventListener(ev, loadDelayedTrackers, {once:true, passive:true});
		});
		window.addEventListener('load', function(){ setTimeout(loadDelayedTrackers, 4500); }, {once:true});
		setTimeout(loadDelayedTrackers, 12000);
	})();
	</script>
	<?php
}, 999);

// Short-circuit known noisy vendor API calls that can slow down WordPress admin/front requests.
add_filter('pre_http_request', function ($pre, array $args, string $url) {
	$blocked_hosts = ['api.rometheme.pro'];
	foreach ($blocked_hosts as $host) {
		if (strpos($url, $host) !== false) {
			return [
				'headers'  => [],
				'body'     => '{}',
				'response' => ['code' => 200, 'message' => 'OK'],
				'cookies'  => [],
				'filename' => null,
			];
		}
	}

	return $pre;
}, 10, 3);

add_filter('query_vars', function (array $vars): array {
	$vars[] = 'portfolio_sitemap';
	return $vars;
});

// Serve a minimal XML sitemap using published WordPress pages and portfolio CPTs.
add_action('template_redirect', function (): void {
	if (!get_query_var('portfolio_sitemap')) {
		return;
	}

	header('Content-Type: text/xml; charset=utf-8');
	echo '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
	echo '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";

	$urls = [home_url('/')];
	$query = new WP_Query([
		'post_type'      => array_merge(['page', 'post'], array_keys(function_exists('lp_project_types') ? lp_project_types() : [])),
		'post_status'    => 'publish',
		'posts_per_page' => 200,
		'fields'         => 'ids',
		'no_found_rows'  => true,
	]);

	foreach ($query->posts as $post_id) {
		$urls[] = get_permalink((int) $post_id);
	}

	foreach (array_unique(array_filter($urls)) as $url) {
		if (strpos((string) $url, '?') !== false) {
			continue;
		}
		echo "\t<url><loc>" . esc_url($url) . "</loc><changefreq>weekly</changefreq><priority>0.8</priority></url>\n";
	}

	echo '</urlset>';
	exit;
}, 5);

mu-plugins/portfolio-page-cache.php

MU Plugin de Cache de Página do Portfólio

Implementa cache full-page para visitantes anônimos, regras seguras de bypass, geração de chave de cache e invalidação automática quando o conteúdo muda.

<?php
/**
 * Plugin Name: Portfolio Page Cache
 * Description: Lightweight full-page cache for anonymous visitors with safe WordPress bypass rules.
 */

declare(strict_types=1);

if (!defined('ABSPATH')) {
	exit;
}

const PORTFOLIO_PAGE_CACHE_TTL = 1800;

// Store cached HTML outside theme/plugin folders so deployments stay clean.
function portfolio_page_cache_dir(): string {
	return WP_CONTENT_DIR . '/cache/portfolio-page-cache';
}

// Allow only safe public query params that do not make the response user-specific.
function portfolio_page_cache_is_cacheable_query(): bool {
	if (empty($_GET)) {
		return true;
	}

	$allowed_keys = ['lang'];
	$keys = array_keys($_GET);
	foreach ($keys as $key) {
		if (!in_array((string) $key, $allowed_keys, true)) {
			return false;
		}
	}

	$lang = sanitize_key((string) ($_GET['lang'] ?? ''));
	return $lang === '' || in_array($lang, ['pt', 'en'], true);
}

// Bypass cache for unsafe requests, authenticated sessions and dynamic WordPress contexts.
function portfolio_page_cache_should_bypass(): bool {
	if (is_admin() || wp_doing_ajax() || wp_is_json_request()) {
		return true;
	}

	if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'GET') {
		return true;
	}

	if (!portfolio_page_cache_is_cacheable_query()) {
		return true;
	}

	if (is_user_logged_in() || is_feed() || is_search() || is_404()) {
		return true;
	}

	$request_uri = (string) ($_SERVER['REQUEST_URI'] ?? '');
	if ($request_uri === '' || str_contains($request_uri, '/wp-admin') || str_contains($request_uri, '/wp-login.php')) {
		return true;
	}

	foreach ($_COOKIE as $name => $value) {
		if (str_starts_with((string) $name, 'wordpress_logged_in_') || str_starts_with((string) $name, 'wp-postpass_')) {
			return true;
		}
	}

	return false;
}

// Build a stable cache key from host and URI so each public URL gets its own file.
function portfolio_page_cache_key(): string {
	$host = strtolower((string) ($_SERVER['HTTP_HOST'] ?? 'site'));
	$path = (string) wp_parse_url((string) ($_SERVER['REQUEST_URI'] ?? '/'), PHP_URL_PATH);
	$query_args = [];
	if (isset($_GET['lang'])) {
		$query_args['lang'] = sanitize_key((string) $_GET['lang']);
	}
	$query = $query_args ? '?' . http_build_query($query_args) : '';
	$theme_version = defined('PORTFOLIO_THEME_VERSION') ? (string) PORTFOLIO_THEME_VERSION : '1';
	return hash('sha256', $theme_version . '|' . $host . '|' . $path . $query) . '.html';
}

// Resolve the full cache file path for the current request.
function portfolio_page_cache_file(): string {
	return trailingslashit(portfolio_page_cache_dir()) . portfolio_page_cache_key();
}

// Send a simple response header for debugging HIT, MISS and BYPASS behavior.
function portfolio_page_cache_send_headers(string $status): void {
	if (headers_sent()) {
		return;
	}

	header('X-Portfolio-Cache: ' . $status);
	header('Cache-Control: public, max-age=300, stale-while-revalidate=1800');

	if (function_exists('portfolio_runtime_send_security_headers')) {
		portfolio_runtime_send_security_headers();
	}
}

// Serve a valid cached page before WordPress renders the template.
function portfolio_page_cache_try_serve(): void {
	if (portfolio_page_cache_should_bypass()) {
		portfolio_page_cache_send_headers('BYPASS');
		return;
	}

	$file = portfolio_page_cache_file();
	if (!is_file($file) || filemtime($file) < (time() - PORTFOLIO_PAGE_CACHE_TTL)) {
		portfolio_page_cache_send_headers('MISS');
		return;
	}

	portfolio_page_cache_send_headers('HIT');
	readfile($file);
	exit;
}

// Persist anonymous HTML responses atomically after WordPress finishes rendering.
function portfolio_page_cache_store(string $html): string {
	if (portfolio_page_cache_should_bypass() || $html === '' || http_response_code() >= 400) {
		return $html;
	}

	if (!is_dir(portfolio_page_cache_dir())) {
		wp_mkdir_p(portfolio_page_cache_dir());
	}

	$file = portfolio_page_cache_file();
	$tmp = $file . '.' . getmypid() . '.tmp';
	file_put_contents($tmp, $html, LOCK_EX);
	rename($tmp, $file);

	return $html;
}

// Clear cached HTML when content or theme state changes.
function portfolio_page_cache_flush(): void {
	$dir = portfolio_page_cache_dir();
	if (!is_dir($dir)) {
		return;
	}

	$files = glob(trailingslashit($dir) . '*.html');
	if (!is_array($files)) {
		return;
	}

	foreach ($files as $file) {
		if (is_file($file)) {
			@unlink($file);
		}
	}
}

// Run the cache before normal template rendering and capture the generated HTML on misses.
add_action('template_redirect', function (): void {
	portfolio_page_cache_try_serve();
	ob_start('portfolio_page_cache_store');
}, -1000);

// Invalidate the cache on common content lifecycle events.
foreach (['save_post', 'deleted_post', 'trashed_post', 'clean_post_cache', 'switch_theme', 'wp_update_nav_menu'] as $hook) {
	add_action($hook, 'portfolio_page_cache_flush');
}

foreach (['updated_option', 'added_option', 'deleted_option'] as $hook) {
	add_action($hook, 'portfolio_page_cache_flush');
}

themes/lago-process/functions.php

Funções do tema

Inicializa o tema bilíngue, a localização de navegação, helpers de SEO, acessores de campos de página e utilitários compartilhados de renderização.

<?php
declare(strict_types=1);

if (!defined('ABSPATH')) {
	exit;
}

const PORTFOLIO_THEME_VERSION = '1.5.9';
const LAGO_THEME_VERSION = PORTFOLIO_THEME_VERSION; // Backward compatibility for copied runtime helpers.

add_action('after_setup_theme', function (): void {
	add_theme_support('title-tag');
	add_theme_support('post-thumbnails');
	add_theme_support('html5', ['search-form', 'comment-form', 'gallery', 'caption', 'style', 'script']);
	add_theme_support('custom-logo', ['height' => 80, 'width' => 240, 'flex-width' => true]);

	register_nav_menus([
		'primary' => __('Primary Menu', 'lago-process'),
		'primary_pt' => __('Menu PT', 'lucas-portfolio'),
		'primary_en' => __('Menu EN', 'lucas-portfolio'),
	]);
});

add_action('wp_enqueue_scripts', function (): void {
	wp_enqueue_style(
		'lucas-portfolio-screen',
		get_theme_file_uri('assets/css/screen.css'),
		[],
		PORTFOLIO_THEME_VERSION
	);

	wp_enqueue_script(
		'lucas-portfolio-navigation',
		get_theme_file_uri('assets/js/navigation.js'),
		[],
		PORTFOLIO_THEME_VERSION,
		true
	);
});

remove_action('wp_head', 'rel_canonical');

function get_lang(): string {
	$lang = sanitize_key((string) ($_GET['lang'] ?? 'en'));
	return $lang === 'pt' ? 'pt' : 'en';
}

function t(string $pt, string $en): string {
	return get_lang() === 'pt' ? $pt : $en;
}

function lago_is_internal_url(string $url): bool {
	if ($url === '' || str_starts_with($url, '#') || str_starts_with($url, '/')) {
		return true;
	}

	$parts = wp_parse_url($url);
	$host = strtolower((string) ($parts['host'] ?? ''));
	$site_host = strtolower((string) wp_parse_url(home_url('/'), PHP_URL_HOST));

	if ($host === '' || $site_host === '') {
		return true;
	}

	return $host === $site_host || $host === 'portfolio.fusioncore.com.br' || $host === 'lagoprocess.fusioncore.com.br';
}

function lago_localize_url(string $url, ?string $lang = null): string {
	$lang = $lang ?: get_lang();
	$url = trim($url);

	if ($url === '' || $lang === '') {
		return $url;
	}

	if (!lago_is_internal_url($url)) {
		return $url;
	}

	$parts = wp_parse_url($url);
	if ($parts === false) {
		return $url;
	}

	$path = (string) ($parts['path'] ?? '/');
	$query = [];
	if (!empty($parts['query'])) {
		parse_str((string) $parts['query'], $query);
	}

	$query['lang'] = $lang;
	$target = home_url($path);
	$target = add_query_arg($query, $target);

	if (!empty($parts['fragment'])) {
		$target .= '#' . $parts['fragment'];
	}

	return $target;
}

function lago_current_url(): string {
	$request_uri = (string) ($_SERVER['REQUEST_URI'] ?? '/');
	$scheme = is_ssl() ? 'https' : 'http';
	$host = (string) ($_SERVER['HTTP_HOST'] ?? wp_parse_url(home_url('/'), PHP_URL_HOST));
	return $scheme . '://' . $host . $request_uri;
}

function lago_switch_lang_url(string $lang): string {
	return lago_localize_url(lago_current_url(), $lang);
}

add_filter('language_attributes', function (string $output): string {
	return get_lang() === 'pt' ? 'lang="pt-BR"' : 'lang="en"';
}, 20);

function lago_site_setting(string $key, string $fallback = ''): string {
	if (function_exists('lp_get_option')) {
		return lp_get_option($key, $fallback);
	}

	return $fallback;
}

function lago_field(string $key, ?int $post_id = null): string {
	if (function_exists('lp_get_field')) {
		return (string) lp_get_field($key, $post_id);
	}

	return '';
}

function lago_translated_field(string $base, string $fallback = '', ?int $post_id = null): string {
	$post_id = $post_id ?: get_the_ID();
	if (!$post_id) {
		return $fallback;
	}

	return lago_translate_meta($base, $fallback, $post_id);
}

function lago_translate_option(string $base, string $fallback = ''): string {
	$lang = get_lang();
	$value = lago_site_setting($base . '_' . $lang);
	if ($value !== '') {
		return $value;
	}

	$value = lago_site_setting($base);
	return $value !== '' ? $value : $fallback;
}

function lago_translate_meta(string $base, string $fallback = '', ?int $post_id = null): string {
	$post_id = $post_id ?: (int) get_the_ID();
	if ($post_id <= 0) {
		return $fallback;
	}

	$lang = get_lang();
	$value = (string) get_post_meta($post_id, $base . '_' . $lang, true);
	if ($value !== '') {
		return $value;
	}

	$value = (string) get_post_meta($post_id, $base, true);
	return $value !== '' ? $value : $fallback;
}

function lago_page_field(string $key, string $fallback = ''): string {
	$value = (string) get_post_meta((int) get_the_ID(), $key, true);
	return $value !== '' ? $value : $fallback;
}

function lago_page_t(string $base, string $fallback = '', ?int $post_id = null): string {
	return lago_translate_meta($base, $fallback, $post_id);
}

function lago_page_html_t(string $base, string $fallback = '', ?int $post_id = null): string {
	return wp_kses_post(lago_translate_meta($base, $fallback, $post_id));
}

function lago_translated_title(?int $post_id = null, string $fallback = ''): string {
	$post_id = $post_id ?: (int) get_the_ID();
	if ($post_id <= 0) {
		return $fallback;
	}

	$default_title = get_the_title($post_id);
	$default_title = is_string($default_title) && $default_title !== '' ? $default_title : $fallback;
	$meta_key = get_post_type($post_id) === 'page' ? 'lp_page_title' : 'lp_title';

	return lago_translate_meta($meta_key, $default_title, $post_id);
}

function lago_page_content_t(?int $post_id = null): string {
	$post_id = $post_id ?: (int) get_the_ID();
	if ($post_id <= 0) {
		return '';
	}

	$default_content = (string) get_post_field('post_content', $post_id);
	$content = lago_translate_meta('lp_page_content', $default_content, $post_id);
	return apply_filters('the_content', $content);
}

function lago_home_field(string $key, string $fallback = ''): string {
	$page_id = (int) get_option('page_on_front');
	if ($page_id <= 0 && is_page()) {
		$page_id = (int) get_the_ID();
	}

	$value = $page_id > 0 ? (string) get_post_meta($page_id, $key, true) : '';
	return $value !== '' ? $value : $fallback;
}

function lago_home_t(string $base, string $fallback = ''): string {
	$page_id = (int) get_option('page_on_front');
	return lago_translate_meta($base, $fallback, $page_id > 0 ? $page_id : null);
}

function lago_home_enabled(string $key, bool $default = true): bool {
	$page_id = (int) get_option('page_on_front');
	$value = $page_id > 0 ? (string) get_post_meta($page_id, $key, true) : '';
	if ($value === '') {
		return $default;
	}

	return in_array($value, ['1', 'true', 'yes', 'on'], true);
}

function lago_json_field(string $key, array $fallback = [], ?int $post_id = null): array {
	$post_id = $post_id ?: (int) get_the_ID();
	if ($post_id <= 0) {
		return $fallback;
	}

	$json = (string) get_post_meta($post_id, $key, true);
	return lago_decode_json_blocks($json, $fallback);
}

function lago_json_t(string $key, array $fallback = [], ?int $post_id = null): array {
	$post_id = $post_id ?: (int) get_the_ID();
	if ($post_id <= 0) {
		return $fallback;
	}

	$json = lago_translate_meta($key, '', $post_id);
	return $json !== '' ? lago_decode_json_blocks($json, $fallback) : $fallback;
}

function lago_home_json(string $key, array $fallback = []): array {
	$page_id = (int) get_option('page_on_front');
	return $page_id > 0 ? lago_json_field($key, $fallback, $page_id) : $fallback;
}

function lago_item_text(array $item, string $base, string $fallback = ''): string {
	$lang = get_lang();
	$value = (string) ($item[$base . '_' . $lang] ?? '');
	if ($value !== '') {
		return $value;
	}

	$value = (string) ($item[$base] ?? '');
	return $value !== '' ? $value : $fallback;
}

function lago_item_lines(array $item, string $base): array {
	$value = lago_item_text($item, $base);
	$lines = preg_split('/\r\n|\r|\n/', $value) ?: [];
	return array_values(array_filter(array_map('trim', $lines)));
}

function lago_attachment_alt(int $attachment_id, string $fallback = ''): string {
	$alt = trim((string) get_post_meta($attachment_id, '_wp_attachment_image_alt', true));
	if ($alt !== '') {
		return $alt;
	}

	$title = get_the_title($attachment_id);
	return is_string($title) && $title !== '' ? $title : $fallback;
}

add_filter('wp_nav_menu_items', function (string $items, stdClass $args): string {
	if (!in_array((string) ($args->theme_location ?? ''), ['primary', 'primary_pt', 'primary_en'], true)) {
		return $items;
	}

	$fallback_pages = [
		'verdian-take-home' => get_lang() === 'pt' ? 'Assessment' : 'Assessment',
		'bonus-assessment'  => get_lang() === 'pt' ? 'Bonus' : 'Bonus',
	];

	foreach ($fallback_pages as $slug => $label) {
		$page = get_page_by_path($slug, OBJECT, 'page');
		if (!$page instanceof WP_Post) {
			continue;
		}

		$url = lago_localize_url((string) get_permalink($page), get_lang());
		$path = (string) wp_parse_url($url, PHP_URL_PATH);

		if ($path !== '' && str_contains($items, $path)) {
			continue;
		}

		$is_current = is_page($page->ID);
		$items .= '<li class="menu-item menu-item-type-custom menu-item-object-custom' . ($is_current ? ' current-menu-item' : '') . '"><a href="' . esc_url($url) . '">' . esc_html($label) . '</a></li>';
	}

	return $items;
}, 20, 2);

function lago_seo_description(): string {
	if (is_page('home')) {
		return lago_home_t('lp_home_meta_description', 'Senior WordPress and PHP engineer portfolio focused on custom themes, plugins, integrations, infrastructure and production ownership.');
	}

	if (is_page('cv')) {
		return lago_page_t('lp_cv_meta_description', 'Technical resume covering WordPress, PHP, infrastructure, integrations and production operations.');
	}

	if (is_singular()) {
		$excerpt = trim(wp_strip_all_tags((string) get_the_excerpt()));
		if ($excerpt !== '') {
			return $excerpt;
		}
	}

	return lago_translate_option('lp_default_seo_description', 'Senior WordPress and PHP engineer focused on systems, integrations and production-ready delivery.');
}

function lago_canonical_url(): string {
	$url = is_singular() ? (string) get_permalink() : home_url(add_query_arg([], (string) wp_parse_url((string) ($_SERVER['REQUEST_URI'] ?? '/'), PHP_URL_PATH)));
	return get_lang() === 'pt' ? lago_localize_url($url, 'pt') : $url;
}

function lago_should_show_schedule_section(): bool {
	if (is_admin() || is_page('schedule-next-step')) {
		return false;
	}

	return is_front_page()
		|| is_page(['cv', 'documentation', 'plugin-code', 'rollout-custom-panel', 'projects', 'brands', 'versioning', 'zapier-integration'])
		|| is_singular(lago_project_post_types())
		|| is_post_type_archive(lago_project_post_types());
}

function lago_render_schedule_section(): void {
	if (!lago_should_show_schedule_section()) {
		return;
	}

	$eyebrow = lago_translate_option('lp_schedule_global_eyebrow', t('Próximo passo', 'Next step'));
	$title = lago_translate_option('lp_schedule_global_title', t('Agende uma conversa técnica curta.', 'Schedule a short technical conversation.'));
	$body = lago_translate_option('lp_schedule_global_body', t('Se quiser avaliar arquitetura, WordPress como camada de aplicação ou ownership de produção, este é o melhor próximo passo.', 'If you want to evaluate architecture, WordPress as an application layer or production ownership, this is the best next step.'));
	$button_label = lago_translate_option('lp_schedule_global_button_label', 'Schedule');
	$button_url = lago_site_setting('lp_schedule_global_button_url', home_url('/schedule-next-step/'));
	?>
	<section class="global-schedule-section" aria-labelledby="global-schedule-title">
		<div class="global-schedule-card">
			<div>
				<p class="eyebrow"><?php echo esc_html($eyebrow); ?></p>
				<h2 id="global-schedule-title"><?php echo esc_html($title); ?></h2>
				<p><?php echo esc_html($body); ?></p>
			</div>
			<a class="button primary" href="<?php echo esc_url(lago_localize_url($button_url)); ?>"><?php echo esc_html($button_label); ?></a>
		</div>
	</section>
	<?php
}

add_filter('wp_robots', function (array $robots): array {
	$robots['index'] = true;
	$robots['follow'] = true;
	$robots['max-image-preview'] = 'large';
	$robots['max-snippet'] = -1;
	$robots['max-video-preview'] = -1;

	return $robots;
});

add_filter('robots_txt', function (): string {
	return "User-agent: *\nAllow: /\nSitemap: " . home_url('/sitemap.xml') . "\n";
}, 10, 1);

add_filter('nav_menu_link_attributes', function (array $atts): array {
	$href = (string) ($atts['href'] ?? '');
	if ($href !== '') {
		$atts['href'] = lago_localize_url($href);
	}

	return $atts;
}, 10, 1);

add_action('wp_head', function (): void {
	$title = wp_get_document_title();
	$description = lago_seo_description();
	$canonical = lago_canonical_url();
	$image = lago_default_social_image();
	$hero_mobile = get_theme_file_uri('assets/img/hero-showcase-mobile.webp');
	$knows_about = lago_decode_json_blocks(lago_site_setting('lp_schema_knows_about_json'), ['WordPress', 'Custom themes', 'Custom plugins', 'REST APIs', 'Infrastructure', 'Email systems']);
	?>
	<link rel="preload" as="image" href="<?php echo esc_url($hero_mobile); ?>" imagesrcset="<?php echo esc_url($hero_mobile); ?> 768w, <?php echo esc_url($image); ?> 1536w" imagesizes="(max-width: 920px) 100vw, 61vw" fetchpriority="high">
	<meta name="description" content="<?php echo esc_attr($description); ?>">
	<link rel="canonical" href="<?php echo esc_url($canonical); ?>">
	<meta property="og:type" content="<?php echo is_singular() ? 'article' : 'website'; ?>">
	<meta property="og:title" content="<?php echo esc_attr($title); ?>">
	<meta property="og:description" content="<?php echo esc_attr($description); ?>">
	<meta property="og:url" content="<?php echo esc_url($canonical); ?>">
	<meta property="og:image" content="<?php echo esc_url($image); ?>">
	<meta name="twitter:card" content="summary_large_image">
	<meta name="twitter:title" content="<?php echo esc_attr($title); ?>">
	<meta name="twitter:description" content="<?php echo esc_attr($description); ?>">
	<meta name="twitter:image" content="<?php echo esc_url($image); ?>">
	<script type="application/ld+json"><?php echo wp_json_encode([
		'@context' => 'https://schema.org',
		'@type' => 'Person',
		'name' => lago_site_setting('lp_schema_person_name', 'Lucas Bacellar'),
		'url' => home_url('/'),
		'jobTitle' => lago_translate_option('lp_schema_job_title', 'Senior WordPress / PHP Engineer'),
		'knowsAbout' => $knows_about,
	], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); ?></script>
	<?php
}, 1);

add_filter('wp_speculation_rules_configuration', '__return_null');

function lago_project_post_types(): array {
	return function_exists('lp_project_types') ? lp_project_types() : [];
}

function lago_project_query_args(int $posts_per_page = 10): array {
	return [
		'post_type'      => array_keys(lago_project_post_types()),
		'post_status'    => 'publish',
		'posts_per_page' => $posts_per_page,
		'orderby'        => 'menu_order date',
		'order'          => 'DESC',
		'no_found_rows'  => true,
	];
}

function lago_brand_query_args(int $posts_per_page = 12): array {
	return [
		'post_type'      => 'lp_brand',
		'post_status'    => 'publish',
		'posts_per_page' => $posts_per_page,
		'orderby'        => 'menu_order title',
		'order'          => 'ASC',
		'no_found_rows'  => true,
	];
}

function lago_default_social_image(): string {
	$custom = lago_site_setting('lp_default_social_image_url');
	if ($custom !== '') {
		return $custom;
	}

	$front_page_id = (int) get_option('page_on_front');
	if ($front_page_id > 0 && has_post_thumbnail($front_page_id)) {
		$image = get_the_post_thumbnail_url($front_page_id, 'full');
		if (is_string($image) && $image !== '') {
			return $image;
		}
	}

	return get_theme_file_uri('assets/img/hero-showcase.webp');
}

function lago_home_hero_image_mobile(): string {
	return get_theme_file_uri('assets/img/hero-showcase-mobile.webp');
}

function lago_field_lines(string $key, ?int $post_id = null): array {
	$value = lago_field($key, $post_id);
	$lines = preg_split('/\r\n|\r|\n/', $value) ?: [];

	return array_values(array_filter(array_map('trim', $lines)));
}

function lago_translated_field_lines(string $key, ?int $post_id = null): array {
	$value = lago_translated_field($key, '', $post_id);
	$lines = preg_split('/\r\n|\r|\n/', $value) ?: [];

	return array_values(array_filter(array_map('trim', $lines)));
}

function lago_decode_json_blocks(string $json, array $fallback = []): array {
	if ($json === '') {
		return $fallback;
	}

	$decoded = json_decode($json, true);
	return is_array($decoded) ? $decoded : $fallback;
}

function lago_project_type_label(?string $post_type = null): string {
	$post_type = $post_type ?: get_post_type();
	$config = lago_project_post_types()[$post_type] ?? null;
	return is_array($config) ? (string) $config['singular'] : (string) $post_type;
}

function lago_project_accent(?string $post_type = null): string {
	$post_type = $post_type ?: get_post_type();
	$config = lago_project_post_types()[$post_type] ?? null;
	return is_array($config) ? (string) $config['accent'] : '#111827';
}

function lago_project_visual_uri(?int $post_id = null, ?string $post_type = null): string {
	$post_id = $post_id ?: get_the_ID();
	if ($post_id > 0 && has_post_thumbnail($post_id)) {
		$image = get_the_post_thumbnail_url($post_id, 'large');
		if (is_string($image) && $image !== '') {
			return $image;
		}
	}

	$post_type = $post_type ?: get_post_type($post_id);
	$images = [
		'lp_delivery'  => 'project-delivery.svg',
		'lp_app'       => 'project-app.svg',
		'lp_pms'       => 'project-pms.svg',
		'lp_crm'       => 'project-crm.svg',
		'lp_fusion_ai' => 'project-ai.svg',
	];

	return get_theme_file_uri('assets/img/' . ($images[$post_type] ?? 'project-app.svg'));
}

function lago_render_flexible_sections(?int $post_id = null): void {
	$sections = function_exists('lp_get_flexible_sections') ? lp_get_flexible_sections($post_id) : [];
	if (!$sections) {
		return;
	}

	foreach ($sections as $section) {
		if (!is_array($section)) {
			continue;
		}

		$layout = (string) ($section['layout'] ?? '');
		if ($layout === 'hero') {
			?>
			<section class="flex-section flex-hero">
				<p class="eyebrow"><?php echo esc_html((string) ($section['eyebrow'] ?? 'Flexible content')); ?></p>
				<h2><?php echo esc_html((string) ($section['headline'] ?? 'Structured section')); ?></h2>
				<p><?php echo esc_html((string) ($section['body'] ?? '')); ?></p>
			</section>
			<?php
		}

		if ($layout === 'metric_grid' && !empty($section['items']) && is_array($section['items'])) {
			?>
			<section class="metric-grid">
				<?php foreach ($section['items'] as $item) :
					if (!is_array($item)) {
						continue;
					}
					?>
					<div>
						<strong><?php echo esc_html((string) ($item['value'] ?? '')); ?></strong>
						<span><?php echo esc_html((string) ($item['label'] ?? '')); ?></span>
					</div>
				<?php endforeach; ?>
			</section>
			<?php
		}
	}
}

themes/lago-process/front-page.php

Template da home

Renderiza o hero da home, seções de prova, guia para reviewer, showcase de projetos, marcas atendidas e CTA global de agendamento.

<?php
declare(strict_types=1);

get_header();

$front_page_id = (int) get_option('page_on_front');
$hero_image = $front_page_id > 0 && has_post_thumbnail($front_page_id)
	? (string) get_the_post_thumbnail_url($front_page_id, 'full')
	: get_theme_file_uri('assets/img/hero-showcase.webp');

$hero_highlights = lago_home_json('lp_home_hero_highlights_json', [
	['title_pt' => 'WordPress como camada de aplicação', 'title_en' => 'WordPress as an application layer', 'body_pt' => 'Temas customizados, plugins próprios e modelagem de conteúdo sem depender do page builder.', 'body_en' => 'Custom themes, custom plugins and structured content modelling without page-builder dependency.'],
	['title_pt' => 'Integrações reais', 'title_en' => 'Real integrations', 'body_pt' => 'REST APIs, CRM, calendários, automações, webhooks e fluxos operacionais.', 'body_en' => 'REST APIs, CRM, calendars, automations, webhooks and operational workflows.'],
	['title_pt' => 'Responsabilidade de produção', 'title_en' => 'Production responsibility', 'body_pt' => 'Nginx, cache, versionamento, DNS, SSL e manutenção pós-entrega.', 'body_en' => 'Nginx, caching, versioning, DNS, SSL and post-launch maintenance.'],
	['title_pt' => 'Prova técnica visível', 'title_en' => 'Visible technical proof', 'body_pt' => 'Documentação, código, painel admin e páginas de evidência para revisão técnica.', 'body_en' => 'Documentation, code, admin workflows and proof pages for technical review.'],
]);

$quick_signals = lago_home_json('lp_home_quick_items_json', [
	['title_pt' => 'Arquitetura de tema', 'title_en' => 'Theme architecture', 'body_pt' => 'Layout em PHP com sections reutilizáveis.', 'body_en' => 'PHP-rendered layout with reusable sections.'],
	['title_pt' => 'Modelagem de CPTs', 'title_en' => 'CPT modelling', 'body_pt' => 'Projetos e marcas com campos estruturados.', 'body_en' => 'Projects and brands modelled with structured fields.'],
	['title_pt' => 'Estrutura de plugins', 'title_en' => 'Plugin structure', 'body_pt' => 'Toolkit e MU plugins para runtime e admin.', 'body_en' => 'Toolkit and MU plugins for runtime and admin workflows.'],
	['title_pt' => 'APIs e automações', 'title_en' => 'APIs and automation', 'body_pt' => 'Webhooks, Google Calendar, CRM e IA.', 'body_en' => 'Webhooks, Google Calendar, CRM and AI integrations.'],
	['title_pt' => 'Performance', 'title_en' => 'Performance', 'body_pt' => 'Cache de página, payload enxuto e otimização de runtime.', 'body_en' => 'Page cache, lean payload and runtime optimization.'],
]);

$core_skills = lago_home_json('lp_home_skills_items_json', [
	['title_pt' => 'Custom themes', 'title_en' => 'Custom themes', 'headline_pt' => 'Temas construídos para produto, não para remendo.', 'headline_en' => 'Themes built as products, not as patched-together pages.', 'body_pt' => 'Arquitetura própria em PHP, com layout previsível, sections reutilizáveis e controle total da camada pública.', 'body_en' => 'Custom PHP architecture with predictable layouts, reusable sections and full control over the public-facing layer.', 'bullets_pt' => "Tema orientado por código\nSem dependência de page builder", 'bullets_en' => "Code-owned theme layer\nNo builder dependency"],
	['title_pt' => 'Plugins', 'title_en' => 'Plugins', 'headline_pt' => 'Regra de negócio colocada no lugar certo.', 'headline_en' => 'Business logic placed where it belongs.', 'body_pt' => 'Plugins próprios concentram integrações, campos, utilidades de admin e comportamento que não deve ficar espalhado no tema.', 'body_en' => 'Custom plugins concentrate integrations, field layers, admin utilities and behavior that should not be scattered across the theme.', 'bullets_pt' => "Lógica desacoplada do front\nFerramentas de operação no admin", 'bullets_en' => "Logic decoupled from presentation\nOperational tooling in admin"],
	['title_pt' => 'CPTs + fields', 'title_en' => 'CPTs + fields', 'headline_pt' => 'Conteúdo estruturado para escalar sem bagunça.', 'headline_en' => 'Structured content that scales without turning messy.', 'body_pt' => 'Projetos, marcas e páginas especiais usam modelagem própria para manter consistência, edição rápida e revisão técnica clara.', 'body_en' => 'Projects, brands and special pages use a custom content model that keeps editing consistent, fast and reviewable.', 'bullets_pt' => "Campos previsíveis\nAdmin editável com estrutura", 'bullets_en' => "Predictable field model\nAdmin-editable with structure"],
	['title_pt' => 'REST APIs', 'title_en' => 'REST APIs', 'headline_pt' => 'Integrações reais, não só páginas bonitas.', 'headline_en' => 'Real integrations, not just polished pages.', 'body_pt' => 'WordPress conversa com CRM, webhooks, calendários e serviços externos com exposição controlada e foco operacional.', 'body_en' => 'WordPress integrates with CRMs, webhooks, calendars and external services through controlled endpoints and operational flows.', 'bullets_pt' => "Endpoints sob controle\nFluxos conectados ao negócio", 'bullets_en' => "Controlled endpoints\nBusiness-connected flows"],
	['title_pt' => 'Hooks', 'title_en' => 'Hooks', 'headline_pt' => 'Controle fino do ciclo de execução.', 'headline_en' => 'Fine-grained control over the execution cycle.', 'body_pt' => 'Ajustes de render, automação e comportamento são feitos via hooks para manter previsibilidade e reduzir gambiarra.', 'body_en' => 'Rendering, automation and behavior are tuned through hooks to keep the system predictable and avoid fragile shortcuts.', 'bullets_pt' => "Render sob medida\nAutomação orientada por eventos", 'bullets_en' => "Tailored rendering\nEvent-driven automation"],
	['title_pt' => 'Production ownership', 'title_en' => 'Production ownership', 'headline_pt' => 'A entrega continua depois que o site sobe.', 'headline_en' => 'Delivery does not stop when the site goes live.', 'body_pt' => 'Operação real envolve rollout, ajustes em produção, correções rápidas, observação de impacto e manutenção com responsabilidade.', 'body_en' => 'Real delivery includes rollout, production fixes, fast adjustments, impact observation and long-term maintenance ownership.', 'bullets_pt' => "Correção sob pressão\nResposta rápida em ambiente real", 'bullets_en' => "Fixes under pressure\nFast response in real environments"],
	['title_pt' => 'Arquitetura admin-first', 'title_en' => 'Admin-first', 'headline_pt' => 'Edição clara para quem opera o negócio.', 'headline_en' => 'Clear editing flows for the people running the business.', 'body_pt' => 'O que precisa mudar no dia a dia fica no admin. O que precisa ser estável continua protegido pela arquitetura.', 'body_en' => 'What needs daily change stays in admin. What must remain stable stays protected by the underlying architecture.', 'bullets_pt' => "Conteúdo editável sem quebrar layout\nFluxo claro para operação", 'bullets_en' => "Editable content without breaking layout\nClear operational workflow"],
	['title_pt' => 'Infraestrutura', 'title_en' => 'Infrastructure', 'headline_pt' => 'DNS, SSL, deploy e ambiente fazem parte do escopo.', 'headline_en' => 'DNS, SSL, deploys and environments are part of the scope.', 'body_pt' => 'Infra não aparece só no servidor: ela impacta performance, segurança, entrega e continuidade de operação.', 'body_en' => 'Infrastructure does not live only on the server: it directly affects performance, security, delivery and operational continuity.', 'bullets_pt' => "DNS e SSL sob controle\nEntrega preparada para produção", 'bullets_en' => "DNS and SSL under control\nProduction-ready delivery"],
]);

$proof_items = lago_home_json('lp_home_proof_items_json', [
	['title_pt' => 'Arquitetura controlada pelo tema', 'title_en' => 'Theme-owned architecture', 'body_pt' => 'A home e as páginas de prova são renderizadas em PHP com estrutura previsível.', 'body_en' => 'The homepage and proof pages are rendered in PHP with predictable structure.'],
	['title_pt' => 'Conteúdo estruturado', 'title_en' => 'Structured content', 'body_pt' => 'Metadados, campos repetíveis e JSON administrável para seções complexas.', 'body_en' => 'Metadata, repeatable fields and admin-managed JSON for complex sections.'],
	['title_pt' => 'Workflows editáveis no admin', 'title_en' => 'Editable admin workflows', 'body_pt' => 'A avaliação técnica consegue verificar o que é editável e onde.', 'body_en' => 'Technical reviewers can verify what is editable and where.'],
	['title_pt' => 'Separação plugin / MU', 'title_en' => 'Plugin / MU separation', 'body_pt' => 'Toolkit e runtime layer isolam responsabilidades de negócio e de performance.', 'body_en' => 'Toolkit and runtime layer separate business logic from performance concerns.'],
	['title_pt' => 'Camada de runtime', 'title_en' => 'Runtime layer', 'body_pt' => 'Cache, headers, limpeza de output e salvaguardas de produção.', 'body_en' => 'Caching, headers, output cleanup and production safeguards.'],
	['title_pt' => 'Infra visível', 'title_en' => 'Visible infrastructure', 'body_pt' => 'Versioning, Zapier test, admin, DNS e deployment aparecem como parte da avaliação.', 'body_en' => 'Versioning, Zapier test, admin, DNS and deployment are part of the evaluation surface.'],
]);

$review_items = lago_home_json('lp_home_review_items_json', [
	['title_pt' => 'Documentação', 'title_en' => 'Documentation', 'body_pt' => 'Visão guiada da arquitetura, edição e evidências.', 'body_en' => 'Guided view of architecture, editing model and proof.','url' => home_url('/documentation/')],
	['title_pt' => 'Código de plugin', 'title_en' => 'Plugin code', 'body_pt' => 'Registro de CPTs, campos e exemplos de integração.', 'body_en' => 'CPT registration, field layer and integration examples.','url' => home_url('/plugin-code/')],
	['title_pt' => 'Teste Zapier', 'title_en' => 'Zapier test', 'body_pt' => 'Página utilitária para validar webhook e integração.', 'body_en' => 'Utility page to validate webhook and integration flow.','url' => home_url('/zapier-integration/')],
	['title_pt' => 'Versionamento', 'title_en' => 'Versioning', 'body_pt' => 'Como o projeto é organizado, versionado e preparado para deploy.', 'body_en' => 'How the project is structured, versioned and prepared for deployment.','url' => home_url('/versioning/')],
	['title_pt' => 'WordPress Admin', 'title_en' => 'WordPress admin', 'body_pt' => 'Fluxo de edição e manutenção visível para revisão técnica.', 'body_en' => 'Editing and maintenance workflow visible for technical review.','url' => admin_url()],
	['title_pt' => 'Currículo técnico', 'title_en' => 'Technical resume', 'body_pt' => 'Resumo estruturado de senioridade, escopo e experiência.', 'body_en' => 'Structured summary of seniority, scope and experience.','url' => home_url('/cv/')],
]);

$related_items = lago_home_json('lp_home_related_items_json', [
	['title_pt' => 'Astro / front-end moderno', 'title_en' => 'Astro / modern front-end', 'body_pt' => 'Quando a camada pública pede velocidade e composição fora do WordPress.', 'body_en' => 'Used when the public layer needs speed and composition beyond WordPress.'],
	['title_pt' => 'Node / serviços', 'title_en' => 'Node / services', 'body_pt' => 'APIs, painéis e serviços auxiliares para operação.', 'body_en' => 'APIs, dashboards and support services for operations.'],
	['title_pt' => 'Laravel / sistemas', 'title_en' => 'Laravel / systems', 'body_pt' => 'Backoffices e fluxos maiores quando o escopo sai do CMS.', 'body_en' => 'Back-office systems when the scope moves beyond the CMS.'],
	['title_pt' => 'AI / automação', 'title_en' => 'AI / automation', 'body_pt' => 'Assistentes, classificação, geração e fluxos operacionais.', 'body_en' => 'Assistants, classification, generation and operational workflows.'],
	['title_pt' => 'CRM / growth ops', 'title_en' => 'CRM / growth ops', 'body_pt' => 'Captação, cadência, calendário e jornada do lead.', 'body_en' => 'Lead capture, cadence, calendar sync and lifecycle automation.'],
]);

$project_query = new WP_Query(lago_project_query_args(4));
$brand_query = new WP_Query(lago_brand_query_args(6));

$context = [
	'hero_image' => $hero_image,
	'hero_highlights' => $hero_highlights,
	'quick_signals' => $quick_signals,
	'core_skills' => $core_skills,
	'proof_items' => $proof_items,
	'review_items' => $review_items,
	'related_items' => $related_items,
	'project_query' => $project_query,
	'brand_query' => $brand_query,
];

foreach ([
	'hero',
	'quick-signals',
	'core-skills',
	'technical-proof',
	'reviewer-guide',
	'featured-work',
	'related-systems',
	'trust',
	'final-cta',
] as $section) {
	get_template_part('template-parts/home/' . $section, null, $context);
}

?>
<div class="mobile-experience-notice" data-mobile-home-notice hidden aria-hidden="true" role="dialog" aria-modal="true" aria-labelledby="mobile-home-notice-title">
	<div class="mobile-experience-notice__card">
		<p class="eyebrow"><?php echo esc_html(t('Melhor experiência', 'Best experience')); ?></p>
		<h2 id="mobile-home-notice-title"><?php echo esc_html(t('Para uma melhor experiência, acesse via tablet ou desktop.', 'For a better experience, access this site on tablet or desktop.')); ?></h2>
		<p><?php echo esc_html(t('No celular o conteúdo continua disponível, mas a navegação e a leitura técnica ficam melhores em tela maior.', 'The content is still available on mobile, but technical reading and navigation work better on a larger screen.')); ?></p>
		<button class="button primary mobile-experience-notice__dismiss" type="button" data-mobile-home-notice-close><?php echo esc_html(t('Continuar no celular', 'Continue on mobile')); ?></button>
	</div>
</div>
<?php

wp_reset_postdata();
get_footer();

themes/lago-process/single.php

Template single de projeto

Monta páginas de case de projeto com stack, APIs, integrações, screenshots, credenciais, resultados e seções flexíveis de conteúdo.

<?php
declare(strict_types=1);

get_header();
?>
<?php while (have_posts()) : the_post(); ?>
	<?php
	$summary          = trim((string) (lago_translated_field('lp_project_summary') ?: get_the_excerpt()));
	$brand_url        = trim((string) lago_translated_field('lp_brand_url'));
	$client_type      = trim((string) lago_translated_field('lp_client_type'));
	$source_path      = trim((string) lago_translated_field('lp_source_path'));
	$demo_url         = trim((string) lago_translated_field('lp_demo_url'));
	$project_url      = trim((string) (lago_translated_field('lp_project_url') ?: $demo_url));
	$access_url       = trim((string) (lago_translated_field('lp_access_url') ?: $project_url));
	$demo_user        = trim((string) lago_translated_field('lp_demo_user'));
	$demo_password    = trim((string) lago_translated_field('lp_demo_password'));
	$access_notes     = trim((string) lago_translated_field('lp_access_notes'));
	$stack_items      = lago_translated_field_lines('lp_stack_used');
	$integrated_items = lago_translated_field_lines('lp_integrated_apis');
	$admin_features   = trim((string) lago_translated_field('lp_admin_features'));
	$content          = trim((string) get_the_content());
	$code_evidence    = trim((string) lago_translated_field('lp_code_evidence'));
	$integrations     = trim((string) lago_translated_field('lp_integrations'));
	$automation_flow  = trim((string) lago_translated_field('lp_automation_flow'));
	$results          = trim((string) lago_translated_field('lp_results'));
	$has_case_meta    = $client_type !== '' || $source_path !== '' || $demo_url !== '';
	$has_access_panel = $project_url !== '' || $access_url !== '' || $demo_user !== '' || $demo_password !== '' || $access_notes !== '';
	$has_technical    = $stack_items !== [] || $integrated_items !== [] || $admin_features !== '';
	$has_main_body    = $content !== '' || $code_evidence !== '';
	$has_aside_body   = $integrations !== '' || $automation_flow !== '' || $results !== '';
	?>
	<article class="single-project" style="--accent: <?php echo esc_attr(lago_project_accent()); ?>">
		<header class="single-hero project-detail-hero">
			<div>
				<p class="eyebrow"><?php echo esc_html(lago_project_type_label()); ?></p>
				<h1><?php echo esc_html(lago_translated_title()); ?></h1>
				<?php if ($summary !== '') : ?>
					<p><?php echo esc_html($summary); ?></p>
				<?php endif; ?>
				<?php if ($brand_url !== '') : ?>
					<p><a class="button ghost" href="<?php echo esc_url($brand_url); ?>" target="_blank" rel="noopener">Visit website</a></p>
				<?php endif; ?>
			</div>
			<figure class="project-hero-image">
				<img src="<?php echo esc_url(lago_project_visual_uri()); ?>" width="1200" height="760" loading="eager" decoding="async" alt="<?php echo esc_attr(lago_translated_title()); ?> project visual">
			</figure>
		</header>

		<?php if ($has_case_meta) : ?>
			<section class="case-meta">
				<?php if ($client_type !== '') : ?>
					<div><span><?php echo esc_html(lago_translate_option('lp_single_client_label', 'Client type')); ?></span><strong><?php echo esc_html($client_type); ?></strong></div>
				<?php endif; ?>
				<?php if ($source_path !== '') : ?>
					<div><span><?php echo esc_html(lago_translate_option('lp_single_source_label', 'Source')); ?></span><strong><?php echo esc_html($source_path); ?></strong></div>
				<?php endif; ?>
				<?php if ($demo_url !== '') : ?>
					<div><span><?php echo esc_html(lago_translate_option('lp_single_demo_label', 'Demo')); ?></span><strong><a href="<?php echo esc_url($demo_url); ?>"><?php echo esc_html(lago_translate_option('lp_single_demo_link_label', 'View link')); ?></a></strong></div>
				<?php endif; ?>
			</section>
		<?php endif; ?>

		<?php if ($has_access_panel) : ?>
			<section class="access-panel">
				<div>
					<p class="eyebrow"><?php echo esc_html(lago_translate_option('lp_single_access_eyebrow', 'Temporary Access')); ?></p>
					<h2><?php echo esc_html(lago_translate_option('lp_single_access_title', 'Project link and demo credentials')); ?></h2>
					<p><?php echo esc_html(lago_translate_option('lp_single_access_body', 'These details are editable in WordPress for each CPT and are meant for controlled assessment review.')); ?></p>
				</div>
				<div class="credential-card">
					<dl>
						<?php if ($project_url !== '') : ?>
							<dt><?php echo esc_html(lago_translate_option('lp_single_project_label', 'Project')); ?></dt>
							<dd><a href="<?php echo esc_url($project_url); ?>" target="_blank" rel="noopener"><?php echo esc_html($project_url); ?></a></dd>
						<?php endif; ?>
						<?php if ($access_url !== '') : ?>
							<dt><?php echo esc_html(lago_translate_option('lp_single_login_label', 'Login')); ?></dt>
							<dd><a href="<?php echo esc_url($access_url); ?>" target="_blank" rel="noopener"><?php echo esc_html($access_url); ?></a></dd>
						<?php endif; ?>
						<?php if ($demo_user !== '') : ?>
							<dt><?php echo esc_html(lago_translate_option('lp_single_user_label', 'User')); ?></dt>
							<dd><code><?php echo esc_html($demo_user); ?></code></dd>
						<?php endif; ?>
						<?php if ($demo_password !== '') : ?>
							<dt><?php echo esc_html(lago_translate_option('lp_single_password_label', 'Password')); ?></dt>
							<dd><code><?php echo esc_html($demo_password); ?></code></dd>
						<?php endif; ?>
					</dl>
					<?php if ($access_notes !== '') : ?>
						<p><?php echo nl2br(esc_html($access_notes)); ?></p>
					<?php endif; ?>
				</div>
			</section>
		<?php endif; ?>

		<?php if ($has_technical) : ?>
			<section class="technical-grid">
				<?php if ($stack_items !== []) : ?>
					<div class="tech-panel">
						<h2><?php echo esc_html(lago_translate_option('lp_single_stack_title', 'Technology Stack')); ?></h2>
						<ul class="check-list">
							<?php foreach ($stack_items as $item) : ?>
								<li><?php echo esc_html($item); ?></li>
							<?php endforeach; ?>
						</ul>
					</div>
				<?php endif; ?>
				<?php if ($integrated_items !== []) : ?>
					<div class="tech-panel dark">
						<h2><?php echo esc_html(lago_translate_option('lp_single_integrated_apis_title', 'Integrated APIs')); ?></h2>
						<ul class="check-list">
							<?php foreach ($integrated_items as $item) : ?>
								<li><?php echo esc_html($item); ?></li>
							<?php endforeach; ?>
						</ul>
					</div>
				<?php endif; ?>
				<?php if ($admin_features !== '') : ?>
					<div class="tech-panel">
						<h2><?php echo esc_html(lago_translate_option('lp_single_admin_features_title', 'Admin features')); ?></h2>
						<p><?php echo nl2br(esc_html($admin_features)); ?></p>
					</div>
				<?php endif; ?>
			</section>
		<?php endif; ?>

		<?php if ($has_main_body || $has_aside_body) : ?>
			<div class="case-body">
				<?php if ($has_main_body) : ?>
					<div>
						<?php if ($content !== '') : ?>
							<h2><?php echo esc_html(lago_translate_option('lp_single_documentation_title', 'Project Documentation')); ?></h2>
							<?php the_content(); ?>
						<?php endif; ?>
						<?php if ($code_evidence !== '') : ?>
							<h2><?php echo esc_html(lago_translate_option('lp_single_code_evidence_title', 'Server code evidence')); ?></h2>
							<p><?php echo nl2br(esc_html($code_evidence)); ?></p>
						<?php endif; ?>
					</div>
				<?php endif; ?>
				<?php if ($has_aside_body) : ?>
					<aside>
						<?php if ($integrations !== '') : ?>
							<h2><?php echo esc_html(lago_translate_option('lp_single_integrations_title', 'Integrations')); ?></h2>
							<p><?php echo nl2br(esc_html($integrations)); ?></p>
						<?php endif; ?>
						<?php if ($automation_flow !== '') : ?>
							<h2><?php echo esc_html(lago_translate_option('lp_single_automation_title', 'Automation flow')); ?></h2>
							<p><?php echo nl2br(esc_html($automation_flow)); ?></p>
						<?php endif; ?>
						<?php if ($results !== '') : ?>
							<h2><?php echo esc_html(lago_translate_option('lp_single_outcome_title', 'Outcome')); ?></h2>
							<p><?php echo nl2br(esc_html($results)); ?></p>
						<?php endif; ?>
					</aside>
				<?php endif; ?>
			</div>
		<?php endif; ?>

		<?php lago_render_flexible_sections(get_the_ID()); ?>
	</article>
<?php endwhile; ?>
<?php
get_footer();

themes/lago-process/page-documentation.php

Template de documentação

Transforma JSON gerenciado no admin e campos traduzidos da página no layout de documentação exibido no site.

<?php
declare(strict_types=1);

get_header();
?>
<?php while (have_posts()) : the_post(); ?>
	<?php
	$doc_summary_cards = lago_json_t('lp_doc_summary_cards', [
		['label' => 'Editor', 'value' => 'Classic WordPress'],
		['label' => 'Layout', 'value' => 'Custom PHP theme'],
		['label' => 'Content model', 'value' => 'CPTs + page field groups'],
		['label' => 'Performance', 'value' => 'Runtime + page cache'],
	]);
	$doc_nav_links = lago_json_t('lp_doc_nav_links', [
		['href' => '#structure', 'label' => 'Structure'],
		['href' => '#editor', 'label' => 'Classic editor'],
		['href' => '#cpts', 'label' => 'CPTs and fields'],
		['href' => '#custom-fields', 'label' => 'Custom fields'],
		['href' => '#plugins', 'label' => 'Plugins'],
		['href' => '#operations', 'label' => 'Operations'],
		['href' => '#content-guide', 'label' => 'Content guide'],
		['href' => '#evidence', 'label' => 'Evidence'],
	]);
	$doc_sections = lago_json_t('lp_doc_sections', [
		[
			'id' => 'structure',
			'title' => 'Build Structure',
			'body' => 'The site is split into a custom theme, one toolkit plugin for content architecture and two MU plugins for runtime behavior and page cache. This keeps editing simple in wp-admin while preserving code ownership, content modeling and special-page workflows in the repository.',
			'items' => [
				'wp-content/themes/lago-process: custom portfolio theme templates, layout, CSS, navigation and page rendering.',
				'wp-content/plugins/lucas-portfolio-toolkit: CPTs, custom field architecture, admin menus, options and page-specific editable fields.',
				'wp-content/mu-plugins/portfolio-runtime-optimization.php: runtime cleanup, delayed trackers, image handling, sitemap and frontend hardening.',
				'wp-content/mu-plugins/portfolio-page-cache.php: anonymous visitor page cache with bypass rules and invalidation.',
			],
		],
		[
			'id' => 'editor',
			'title' => 'Editing Model',
			'body' => 'The project intentionally disables Gutenberg and works with the classic editor plus structured meta fields. The goal is to show a disciplined WordPress build that is editable in admin without relying on page builders for the core portfolio layout.',
			'items' => [
				'Pages keep long-form body content in the classic editor.',
				'Projects, brands and assessment visuals use dedicated post types with structured meta fields.',
				'Special templates such as home, documentation, plugin-code, rollout, versioning, projects, brands, resume/CV, schedule, assessment and bonus assessment expose dedicated wp-admin fields.',
				'Global header, footer, SEO defaults and shared labels are managed in the portfolio Site Settings screen.',
			],
		],
		[
			'id' => 'cpts',
			'title' => 'Content Types and What They Control',
			'body' => 'The portfolio is organized around real content models so the public site, admin workflow and REST output stay aligned. The CPT layer handles repeatable entities while page field groups handle one-off presentation flows like resume and assessment pages.',
			'items' => [
				'App Projects: product sites, marketing apps and web application cases.',
				'CRM Projects: lead capture, cadences, integrations and automation layers.',
				'Delivery Projects: operational commerce and ordering/admin systems.',
				'PMS Projects: hospitality systems, booking, operations and hotel flows.',
				'Fusion AI Projects: assistants, chatbot and OpenAI-related cases.',
				'Served Brands: hospitality websites and brand-level delivery evidence.',
				'Assessment Visuals: reusable visual entries used by the Verdian take-home template galleries.',
				'Resume / CV: special page field group for summary, experience timeline, skills, infrastructure and languages.',
				'Assessment pages: special page field groups for the Verdian take-home and bonus assessment flows.',
			],
		],
		[
			'id' => 'custom-fields',
			'title' => 'Custom Field Groups',
			'body' => 'Custom fields are split by editing context so each screen exposes only the data it needs. This keeps the admin lean while preserving a structured content contract for the theme.',
			'items' => [
				'Project CPT fields: summary, client type, stack, stack-used list, integrated APIs, integrations, automation flow, admin features, code evidence, source path, URLs, temporary credentials, results, icon and flexible JSON sections.',
				'Brand CPT fields: brand summary, brand URL, scope, stack, client type, source path, access details, integrations, results and flexible JSON sections.',
				'Documentation page fields: hero eyebrow, lede, summary cards JSON, navigation JSON and section blocks JSON.',
				'Resume / CV page fields: meta description, badge, role, location, contact details, summary, experience JSON, skills JSON, infrastructure JSON and languages JSON.',
				'Verdian assessment fields: bilingual hero copy, competency assessment items, analytics sections, architecture sections, growth questions, visual reference blocks, AI disclosure blocks and closing CTA/content.',
				'Bonus assessment fields: bilingual intro, environment, implementation, plugin explanation and closing sections.',
				'Assessment visual fields: target section, public label, caption, alt override, external image URL and featured image.',
			],
		],
		[
			'id' => 'plugins',
			'title' => 'Plugin Summary and Implemented Features',
			'body' => 'Three custom plugins/modules organize the portfolio build.',
			'items' => [
				'Lucas Portfolio Toolkit: registers CPTs, exposes structured custom fields for projects, brands, assessment visuals and special pages, groups portfolio screens in the admin, adds page-specific meta boxes and provides global site settings.',
				'Portfolio Runtime Optimization: removes unnecessary WordPress noise, optimizes asset output, delays trackers, improves image markup and serves a lightweight sitemap.',
				'Portfolio Page Cache: stores generated HTML for anonymous GET traffic, bypasses sessions/admin/unsafe requests and purges cached files when content changes.',
			],
		],
		[
			'id' => 'operations',
			'title' => 'Admin Operations and Cache Control',
			'body' => 'The WordPress admin now includes explicit controls for code-evidence sourcing and cache invalidation so the published site can be refreshed without shell access.',
			'items' => [
				'Plugin Code page: the admin metabox includes lp_plugin_code_paths, one real server path per line, already prefilled with the current plugin, MU plugins and theme files.',
				'Code page rendering: the public template reads those paths directly, resolves labels from the registry when possible and falls back to inferred title/description for unknown files.',
				'WordPress top bar: a Clear Portfolio Cache action is available for admins and purges the full-page cache before redirecting back with a success notice.',
				'Menu mobile: the header now uses explicit open/close state plus collapsible submenus on mobile, reducing overflow and ensuring internal pages behave the same as the homepage.',
			],
		],
		[
			'id' => 'content-guide',
			'title' => 'Content Guide',
			'body' => 'Everything visible on the site should be updated through one of four places in wp-admin.',
			'items' => [
				'Settings > General and Menus: site title and primary navigation.',
				'Lucas Portfolio > Site Settings: header mark/subtitle, CTA, footer text, SEO defaults, archive labels and shared single-project labels.',
				'Pages: body copy and page-specific template fields for documentation, projects, brands, schedule, versioning, Zapier, plugin-code, home, rollout, resume/CV, assessment and bonus assessment.',
				'Plugin Code admin fields: use the real path list to decide exactly which files appear on the public code evidence page.',
				'Lucas Portfolio CPTs: project summaries, stacks, integrations, credentials, evidence, icons and featured images.',
				'Assessment Visuals CPT: gallery images and captions used inside the assessment page.',
			],
		],
		[
			'id' => 'evidence',
			'title' => 'Server Evidence and Good Practices',
			'body' => 'The site itself documents both implementation and operational thinking.',
			'items' => [
				'Projects reference real systems hosted on this server and expose controlled assessment details through CPT fields.',
				'The theme preserves semantic templates instead of embedding presentation logic into the editor.',
				'Structured fields reduce accidental layout breakage and make content governance easier.',
				'Performance is handled conservatively with MU plugins instead of relying only on third-party optimization plugins.',
				'Versioning, rollback points and documentation pages make the build easier to review and safer to evolve.',
			],
		],
	]);

	$has_operations_nav = false;
	foreach ($doc_nav_links as $link) {
		if (is_array($link) && ((string) ($link['href'] ?? '')) === '#operations') {
			$has_operations_nav = true;
			break;
		}
	}

	if (!$has_operations_nav) {
		$doc_nav_links[] = ['href' => '#operations', 'label' => 'Operations'];
	}

	$has_operations_section = false;
	foreach ($doc_sections as $section) {
		if (is_array($section) && ((string) ($section['id'] ?? '')) === 'operations') {
			$has_operations_section = true;
			break;
		}
	}

	if (!$has_operations_section) {
		$doc_sections[] = [
			'id' => 'operations',
			'title' => 'Admin Operations and Cache Control',
			'body' => 'The WordPress admin now includes explicit controls for code-evidence sourcing and cache invalidation so the published site can be refreshed without shell access.',
			'items' => [
				'Plugin Code page: the admin metabox includes lp_plugin_code_paths, one real server path per line, already prefilled with the current plugin, MU plugins and theme files.',
				'Code page rendering: the public template reads those paths directly, resolves labels from the registry when possible and falls back to inferred title/description for unknown files.',
				'WordPress top bar: a Clear Portfolio Cache action is available for admins and purges the full-page cache before redirecting back with a success notice.',
				'Menu mobile: the header now uses explicit open/close state plus collapsible submenus on mobile, reducing overflow and ensuring internal pages behave the same as the homepage.',
			],
		];
	}
	?>
	<article class="documentation-page">
		<header class="doc-hero">
			<p class="eyebrow"><?php echo esc_html(lago_page_t('lp_doc_hero_eyebrow', t('Documentação técnica', 'Technical Documentation'))); ?></p>
			<h1><?php echo esc_html(lago_translated_title()); ?></h1>
			<p><?php echo esc_html(lago_page_t('lp_doc_hero_lede', t('Um registro prático de como o portfólio foi construído, onde cada parte é editada e quais evidências técnicas devem ser revisadas.', 'A practical record of how the portfolio was built, where each part is edited, and which technical evidence should be reviewed.'))); ?></p>
		</header>

		<section class="doc-summary-grid">
			<?php foreach ($doc_summary_cards as $card) : ?>
				<?php if (!is_array($card)) { continue; } ?>
				<div><span><?php echo esc_html((string) ($card['label'] ?? '')); ?></span><strong><?php echo esc_html((string) ($card['value'] ?? '')); ?></strong></div>
			<?php endforeach; ?>
		</section>

		<div class="doc-layout">
			<aside class="doc-nav">
				<?php foreach ($doc_nav_links as $link) : ?>
					<?php if (!is_array($link)) { continue; } ?>
					<a href="<?php echo esc_attr((string) ($link['href'] ?? '#')); ?>"><?php echo esc_html((string) ($link['label'] ?? 'Section')); ?></a>
				<?php endforeach; ?>
			</aside>
			<div class="doc-content">
				<?php echo lago_page_content_t(); ?>

				<?php foreach ($doc_sections as $section) : ?>
					<?php if (!is_array($section)) { continue; } ?>
					<section id="<?php echo esc_attr((string) ($section['id'] ?? 'section')); ?>">
						<h2><?php echo esc_html((string) ($section['title'] ?? 'Documentation section')); ?></h2>
						<p><?php echo esc_html((string) ($section['body'] ?? '')); ?></p>
						<?php if (!empty($section['items']) && is_array($section['items'])) : ?>
							<ul>
								<?php foreach ($section['items'] as $item) : ?>
									<li><?php echo esc_html(is_array($item) ? (string) ($item['text'] ?? '') : (string) $item); ?></li>
								<?php endforeach; ?>
							</ul>
						<?php endif; ?>
					</section>
				<?php endforeach; ?>

			</div>
		</div>
	</article>
<?php endwhile; ?>
<?php
get_footer();

themes/lago-process/header.php

Template do header

Renderiza o header global, seletor de idioma, navegação mobile e roteamento do CTA em todas as páginas públicas.

<?php
declare(strict_types=1);
?><!doctype html>
<html <?php language_attributes(); ?>>
<head>
	<meta charset="<?php bloginfo('charset'); ?>">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<?php wp_head(); ?>
</head>
<body <?php body_class(); ?>>
<?php wp_body_open(); ?>
<a class="skip-link" href="#content"><?php esc_html_e('Skip to content', 'lucas-portfolio'); ?></a>
<header class="site-header">
	<div class="site-header-inner">
		<a class="brand" href="<?php echo esc_url(lago_localize_url(home_url('/'))); ?>" aria-label="<?php bloginfo('name'); ?>">
			<span class="brand-mark"><?php echo esc_html(lago_site_setting('lp_brand_mark', 'LB')); ?></span>
			<span>
				<strong><?php bloginfo('name'); ?></strong>
				<em><?php echo esc_html(lago_translate_option('lp_brand_subtitle', t('Engenheiro WordPress / PHP', 'WordPress / PHP Engineer'))); ?></em>
			</span>
		</a>
		<button class="menu-toggle" type="button" aria-controls="site-navigation" aria-expanded="false">
			<span></span>
			<span></span>
			<span></span>
			<strong>Menu</strong>
		</button>
		<nav id="site-navigation" class="main-nav" aria-label="<?php esc_attr_e('Primary navigation', 'lucas-portfolio'); ?>" data-mobile-label-open="<?php echo esc_attr(t('Abrir submenu', 'Open submenu')); ?>" data-mobile-label-close="<?php echo esc_attr(t('Fechar submenu', 'Close submenu')); ?>">
			<?php
			$menu_location = get_lang() === 'pt' ? 'primary_pt' : 'primary_en';
			if (!has_nav_menu($menu_location)) {
				$menu_location = 'primary';
			}
			wp_nav_menu([
				'theme_location' => $menu_location,
				'container'      => false,
				'fallback_cb'    => false,
				'depth'          => 2,
			]);
			?>
			<div class="lang-switch" aria-label="<?php echo esc_attr(t('Alternar idioma', 'Switch language')); ?>">
				<a href="<?php echo esc_url(lago_switch_lang_url('pt')); ?>" class="<?php echo get_lang() === 'pt' ? 'is-active' : ''; ?>">PT</a>
				<a href="<?php echo esc_url(lago_switch_lang_url('en')); ?>" class="<?php echo get_lang() === 'en' ? 'is-active' : ''; ?>">EN</a>
			</div>
			<a class="nav-cta" href="<?php echo esc_url(lago_localize_url(lago_site_setting('lp_header_cta_url', home_url('/schedule-next-step/')))); ?>"><?php echo esc_html(lago_translate_option('lp_header_cta_label', t('Agendar', 'Schedule'))); ?></a>
		</nav>
	</div>
</header>
<script>
(function () {
	function initMobileMenu() {
		if (document.documentElement.getAttribute('data-nav-menu-ready') === '1') {
			return;
		}

		var header = document.querySelector('.site-header');
		var toggle = document.querySelector('.menu-toggle');
		var nav = document.getElementById('site-navigation');
		var viewport = window.matchMedia ? window.matchMedia('(max-width: 920px)') : null;

		if (!header || !toggle || !nav) {
			return;
		}

		function isMobileViewport() {
			return viewport ? viewport.matches : window.innerWidth <= 920;
		}

		function getDirectChild(parent, className) {
			if (!parent || !parent.children) {
				return null;
			}

			for (var i = 0; i < parent.children.length; i += 1) {
				var child = parent.children[i];
				if (child.classList && child.classList.contains(className)) {
					return child;
				}
			}

			return null;
		}

		function getDirectLink(parent) {
			if (!parent || !parent.children) {
				return null;
			}

			for (var i = 0; i < parent.children.length; i += 1) {
				var child = parent.children[i];
				if (child.tagName === 'A') {
					return child;
				}
			}

			return null;
		}

		function closeAllSubmenus() {
			var items = nav.querySelectorAll('.menu-item-has-children');
			Array.prototype.forEach.call(items, function (item) {
				item.classList.remove('is-submenu-open');
				var button = getDirectChild(item, 'submenu-toggle');
				if (button) {
					button.setAttribute('aria-expanded', 'false');
					button.setAttribute('aria-label', nav.getAttribute('data-mobile-label-open') || 'Open submenu');
				}
			});
		}

		function openCurrentSubmenu() {
			var currentItem = nav.querySelector('.menu-item-has-children.current-menu-ancestor, .menu-item-has-children.current-menu-parent, .menu-item-has-children.current_page_ancestor');
			if (!currentItem) {
				return;
			}

			currentItem.classList.add('is-submenu-open');
			var button = getDirectChild(currentItem, 'submenu-toggle');
			if (button) {
				button.setAttribute('aria-expanded', 'true');
				button.setAttribute('aria-label', nav.getAttribute('data-mobile-label-close') || 'Close submenu');
			}
		}

		function closeMenu() {
			header.classList.remove('is-menu-open');
			toggle.setAttribute('aria-expanded', 'false');
			closeAllSubmenus();
			syncMenuVisibility();
		}

		function openMenu() {
			header.classList.add('is-menu-open');
			toggle.setAttribute('aria-expanded', 'true');
			closeAllSubmenus();
			openCurrentSubmenu();
		}

		function syncMenuVisibility() {
			if (!isMobileViewport()) {
				header.classList.remove('is-menu-open');
				toggle.setAttribute('aria-expanded', 'false');
				closeAllSubmenus();
			}
		}

		Array.prototype.forEach.call(nav.querySelectorAll('.menu-item-has-children'), function (item, index) {
			var link = getDirectLink(item);
			var submenu = getDirectChild(item, 'sub-menu');

			if (!link || !submenu) {
				return;
			}

			if (!submenu.id) {
				submenu.id = 'site-submenu-' + (index + 1);
			}

			var button = getDirectChild(item, 'submenu-toggle');
			if (!button) {
				button = document.createElement('button');
				button.type = 'button';
				button.className = 'submenu-toggle';
				button.setAttribute('aria-controls', submenu.id);
				button.setAttribute('aria-expanded', 'false');
				button.setAttribute('aria-label', nav.getAttribute('data-mobile-label-open') || 'Open submenu');
				item.insertBefore(button, submenu);
			}

			function setSubmenuState(isOpen) {
				if (isOpen) {
					item.classList.add('is-submenu-open');
				} else {
					item.classList.remove('is-submenu-open');
				}
				button.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
				button.setAttribute('aria-label', isOpen ? (nav.getAttribute('data-mobile-label-close') || 'Close submenu') : (nav.getAttribute('data-mobile-label-open') || 'Open submenu'));
			}

			button.addEventListener('click', function (event) {
				event.preventDefault();
				event.stopPropagation();
				var nextState = !item.classList.contains('is-submenu-open');
				closeAllSubmenus();
				setSubmenuState(nextState);
			});

			link.addEventListener('click', function (event) {
				if (!isMobileViewport()) {
					return;
				}

				if (!item.classList.contains('is-submenu-open')) {
					event.preventDefault();
					closeAllSubmenus();
					setSubmenuState(true);
				}
			});
		});

		toggle.addEventListener('click', function (event) {
			event.preventDefault();
			if (header.classList.contains('is-menu-open')) {
				closeMenu();
				return;
			}

			openMenu();
		});

		nav.addEventListener('click', function (event) {
			var target = event.target;
			if (target && target.tagName === 'A' && target.getAttribute('href') !== '#' && !event.defaultPrevented) {
				closeMenu();
			}
		});

		document.addEventListener('click', function (event) {
			if (!event.target || header.contains(event.target)) {
				return;
			}

			closeMenu();
		});

		if (viewport) {
			if (typeof viewport.addEventListener === 'function') {
				viewport.addEventListener('change', syncMenuVisibility);
			} else if (typeof viewport.addListener === 'function') {
				viewport.addListener(syncMenuVisibility);
			}
		}

		window.addEventListener('resize', syncMenuVisibility);
		document.documentElement.setAttribute('data-nav-menu-ready', '1');
		syncMenuVisibility();
	}

	initMobileMenu();
})();
</script>
<main id="content" class="site-main">

themes/lago-process/footer.php

Template do footer

Imprime o footer de avaliação, links de prova, CTA de agendamento e a mensagem global do portfólio.

<?php lago_render_schedule_section(); ?>
	</main>
	<footer class="site-footer">
		<div class="footer-inner">
			<div class="footer-kicker">
				<span><?php echo esc_html(t('Portfolio técnico auditável', 'Auditable technical portfolio')); ?></span>
				<a href="<?php echo esc_url(lago_localize_url(home_url('/assessment-video/'))); ?>"><?php echo esc_html(t('Assistir tour guiado', 'Watch guided tour')); ?></a>
			</div>
			<div class="footer-brand">
				<span class="footer-mark"><?php echo esc_html(lago_site_setting('lp_brand_mark', 'LB')); ?></span>
				<div>
					<strong><?php bloginfo('name'); ?></strong>
					<p><?php echo esc_html(lago_translate_option('lp_footer_text', t('Portfólio WordPress/PHP com tema customizado, campos estruturados, APIs, runtime layer e foco em produção.', 'WordPress/PHP portfolio with a custom theme, structured fields, APIs, runtime layer and production focus.'))); ?></p>
				</div>
			</div>
			<div class="footer-signal-grid" aria-label="<?php echo esc_attr(t('Sinais técnicos', 'Technical signals')); ?>">
				<span><?php echo esc_html(t('WordPress como aplicação', 'WordPress as application')); ?></span>
				<span><?php echo esc_html(t('PHP, plugins e CPTs', 'PHP, plugins and CPTs')); ?></span>
				<span><?php echo esc_html(t('Infraestrutura e produção', 'Infrastructure and production')); ?></span>
			</div>
			<nav class="footer-nav" aria-label="<?php echo esc_attr(t('Links do rodapé', 'Footer links')); ?>">
				<div>
					<h2><?php echo esc_html(t('Avaliação', 'Evaluation')); ?></h2>
					<a href="<?php echo esc_url(lago_localize_url(home_url('/assessment-video/'))); ?>"><?php echo esc_html(t('Tour guiado', 'Guided tour')); ?></a>
					<a href="<?php echo esc_url(lago_localize_url(home_url('/documentation/'))); ?>"><?php echo esc_html(t('Documentação', 'Documentation')); ?></a>
					<a href="<?php echo esc_url(lago_localize_url(home_url('/plugin-code/'))); ?>"><?php echo esc_html(t('Código do plugin', 'Plugin code')); ?></a>
					<a href="<?php echo esc_url(lago_localize_url(home_url('/versioning/'))); ?>"><?php echo esc_html(t('Versionamento', 'Versioning')); ?></a>
				</div>
				<div>
					<h2><?php echo esc_html(t('Portfólio', 'Portfolio')); ?></h2>
					<a href="<?php echo esc_url(lago_localize_url(home_url('/projects/'))); ?>"><?php echo esc_html(t('Projetos', 'Projects')); ?></a>
					<a href="<?php echo esc_url(lago_localize_url(home_url('/brands/'))); ?>"><?php echo esc_html(t('Marcas atendidas', 'Served brands')); ?></a>
					<a href="<?php echo esc_url(lago_localize_url(home_url('/cv/'))); ?>"><?php echo esc_html(t('Currículo', 'Resume')); ?></a>
				</div>
				<div>
					<h2><?php echo esc_html(t('Próximo passo', 'Next step')); ?></h2>
					<a href="<?php echo esc_url(lago_localize_url(home_url('/schedule-next-step/'))); ?>"><?php echo esc_html(t('Agendar conversa', 'Schedule a meeting')); ?></a>
					<a href="<?php echo esc_url(lago_localize_url(home_url('/rollout-custom-panel/'))); ?>"><?php echo esc_html(t('Painel customizado', 'Custom panel')); ?></a>
					<a href="<?php echo esc_url(home_url('/sitemap.xml')); ?>"><?php echo esc_html(lago_site_setting('lp_footer_link_label', 'Sitemap XML')); ?></a>
				</div>
			</nav>
			<div class="footer-bottom">
				<span><?php echo esc_html(t('WordPress como camada de aplicação. PHP, integrações, infraestrutura e produção.', 'WordPress as an application layer. PHP, integrations, infrastructure and production.')); ?></span>
				<span><?php echo esc_html('© ' . gmdate('Y') . ' Lucas Bacellar'); ?></span>
			</div>
		</div>
	</footer>
	<script>
	(function() {
	  var host = "https://app.fusioncore.com.br";
	  var start = Date.now();
	  var maxScroll = 0;
	  var monitoringId = null;

	  function post(url, data) {
	    try {
	      var payload = JSON.stringify(data);
	      if (navigator.sendBeacon) {
	        var blob = new Blob([payload], { type: "application/json" });
	        return navigator.sendBeacon(url, blob);
	      }
	      return fetch(url, {
	        method: "POST",
	        headers: { "Content-Type": "application/json" },
	        body: payload,
	        keepalive: true
	      });
	    } catch (e) {}
	  }

	  function pageview() {
	    var qs = new URLSearchParams(window.location.search);
	    var data = {
	      domain: location.hostname,
	      page_url: location.href,
	      page_title: document.title,
	      referrer: document.referrer || null,
	      utm_source: qs.get("utm_source"),
	      utm_medium: qs.get("utm_medium"),
	      utm_campaign: qs.get("utm_campaign"),
	      utm_term: qs.get("utm_term"),
	      utm_content: qs.get("utm_content"),
	      screen_resolution: window.screen.width + "x" + window.screen.height
	    };
	    fetch(host + "/api/track/pageview.php", {
	      method: "POST",
	      headers: { "Content-Type": "application/json" },
	      body: JSON.stringify(data)
	    }).then(function(r) {
	      return r.json();
	    }).then(function(j) {
	      if (j && j.monitoring_id) {
	        monitoringId = j.monitoring_id;
	      }
	    }).catch(function(){});
	  }

	  function trackScroll() {
	    var h = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);
	    var vp = window.innerHeight;
	    var sc = window.scrollY || window.pageYOffset || 0;
	    var depth = Math.min(100, Math.round(((sc + vp) / h) * 100));
	    if (depth > maxScroll) {
	      maxScroll = depth;
	    }
	  }

	  function sendMetrics(exitPage) {
	    if (!monitoringId) return;
	    var timing = window.performance && performance.timing ? performance.timing : null;
	    var data = {
	      monitoring_id: monitoringId,
	      time_on_page: Math.max(0, Date.now() - start),
	      scroll_depth: maxScroll,
	      bounce: exitPage ? 0 : (maxScroll < 10 ? 1 : 0),
	      exit_page: exitPage ? 1 : 0,
	      page_load_time: timing && timing.loadEventEnd ? (timing.loadEventEnd - timing.navigationStart) : 0
	    };
	    post(host + "/api/track/metrics.php", data);
	  }

	  function clickHeatmap(e) {
	    if (!monitoringId) return;
	    var t = e.target;
	    if (t === document.body || t === document.documentElement) return;

	    var rect = t.getBoundingClientRect();
	    var data = {
	      monitoring_id: monitoringId,
	      event_type: "click",
	      element_id: t.id || null,
	      element_class: typeof t.className === "string" ? t.className : null,
	      element_tag: t.tagName || null,
	      element_text: (t.innerText || "").slice(0, 50),
	      event_data: {
	        x: e.clientX,
	        y: e.clientY,
	        el_x: rect.left,
	        el_y: rect.top,
	        el_w: rect.width,
	        el_h: rect.height,
	        vp_w: window.innerWidth,
	        vp_h: window.innerHeight,
	        page_x: window.scrollX || 0,
	        page_y: window.scrollY || 0
	      }
	    };
	    post(host + "/api/track/event.php", data);
	  }

	  document.addEventListener("scroll", trackScroll, { passive: true });
	  document.addEventListener("click", clickHeatmap, true);
	  window.addEventListener("beforeunload", function() { sendMetrics(true); });
	  document.addEventListener("visibilitychange", function() {
	    if (document.visibilityState === "hidden") {
	      sendMetrics(true);
	    }
	  });

	  if (document.readyState === "complete") {
	      pageview();
	  } else {
	      window.addEventListener("load", pageview, { once: true });
	  }
	})();
	</script>
	<?php wp_footer(); ?>
</body>
</html>

themes/lago-process/assets/css/screen.css

Stylesheet principal do tema

Stylesheet principal de layout, tipografia, navegação, responsividade, páginas de case e telas de código/documentação.

/*
 * Main public stylesheet for the portfolio theme.
 *
 * This file is layered: the first blocks define the older/base layout and the
 * later blocks intentionally override colors, spacing, section flow, footer and
 * page-specific screens. When changing a rule, search below for the same
 * selector before assuming this is the final value in the browser.
 */

/* Base design tokens used by the first layout pass. Later :root blocks replace
 * many of these values for the current visual theme. */
:root {
	--bg: #f4f4f2;
	--surface: #ffffff;
	--surface-soft: #ededeb;
	--ink: #171717;
	--muted: #6d6d6a;
	--line: #d7d7d3;
	--line-strong: #b8b8b2;
	--charcoal: #2b2b2b;
	--silver: #9c9c96;
	--shadow: 0 24px 70px rgba(20, 20, 20, .08);
	--radius: 24px;
}

/* Universal sizing and document-level overflow protection. */
* { box-sizing: border-box; }

html { scroll-behavior: smooth; }

html,
body {
	overflow-x: hidden;
}

/* Body baseline: subtle grid texture, text color and default font stack. */
body {
	margin: 0;
	background:
		linear-gradient(90deg, rgba(0, 0, 0, .028) 1px, transparent 1px),
		linear-gradient(rgba(0, 0, 0, .026) 1px, transparent 1px),
		var(--bg);
	background-size: 64px 64px;
	color: var(--ink);
	font-family: Aptos, "Segoe UI", sans-serif;
	line-height: 1.65;
}

/* Locks the page behind the mobile warning overlay. */
body.has-mobile-home-notice {
	overflow: hidden;
}

a { color: inherit; }

/* Accessibility skip link: hidden until focused by keyboard users. */
.skip-link {
	position: absolute;
	left: -999px;
	top: 0;
}

.skip-link:focus {
	left: 16px;
	top: 16px;
	z-index: 1000;
	padding: 10px 14px;
	background: var(--ink);
	color: #fff;
}

/* Default content width for the main area and the early footer layout. */
.site-main,
.site-footer {
	width: min(1180px, calc(100% - 40px));
	margin: 0 auto;
}

/* Sticky top header. JS may add .is-scroll-idle while scrolling. */
.site-header {
	position: sticky;
	top: 0;
	z-index: 100;
	width: 100%;
	margin: 0;
	border-top: 1px solid var(--line);
	border-bottom: 1px solid var(--line);
	background: rgba(244, 244, 242, .92);
	transition: transform .22s ease, opacity .22s ease;
	will-change: transform, opacity;
}

/* Auto-hides the header after scroll idle; JS removes this while interacting. */
.site-header.is-scroll-idle {
	transform: translateY(calc(-100% - 8px));
	opacity: 0;
}

/* Safety net: if the cursor or keyboard focus is inside the header, keep it visible. */
.site-header.is-scroll-idle:hover,
.site-header.is-scroll-idle:focus-within {
	transform: translateY(0);
	opacity: 1;
}

/* Header row that contains the brand, nav and CTA. */
.site-header-inner {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 28px;
	width: 100%;
	padding: 18px 24px;
}

/* Brand mark and site name inside the header. */
.brand {
	display: flex;
	align-items: center;
	gap: 14px;
	text-decoration: none;
}

.brand-mark {
	display: grid;
	place-items: center;
	width: 44px;
	height: 44px;
	border-radius: 50%;
	background: var(--charcoal);
	color: #fff;
	font-size: 13px;
	font-weight: 800;
	letter-spacing: .06em;
}

.brand strong,
.brand em {
	display: block;
}

.brand strong {
	font-weight: 800;
	letter-spacing: -.02em;
}

.brand em {
	color: var(--muted);
	font-size: 12px;
	font-style: normal;
}

/* Desktop navigation row. Mobile behavior is redefined in the max-width blocks. */
.main-nav {
	display: flex;
	align-items: center;
	gap: 18px;
}

.main-nav[hidden] {
	display: none !important;
}

/* Hidden on desktop; shown on mobile to open the menu. */
.menu-toggle {
	display: none;
	align-items: center;
	gap: 5px;
	border: 1px solid var(--line-strong);
	border-radius: 999px;
	padding: 10px 12px;
	background: rgba(255, 255, 255, .78);
	color: var(--ink);
	cursor: pointer;
}

.menu-toggle span {
	display: block;
	width: 16px;
	height: 2px;
	border-radius: 999px;
	background: currentColor;
}

.menu-toggle strong {
	margin-left: 4px;
	font-size: 11px;
	font-weight: 900;
	letter-spacing: .12em;
	text-transform: uppercase;
}

.submenu-toggle {
	display: none;
}

/* WordPress menu list reset and desktop item spacing. */
.main-nav ul {
	display: flex;
	align-items: center;
	gap: 8px;
	margin: 0;
	padding: 0;
	list-style: none;
}

.main-nav li {
	position: relative;
}

.main-nav a {
	display: inline-flex;
	align-items: center;
	border-radius: 999px;
	padding: 10px 12px;
	color: var(--muted);
	font-size: 12px;
	font-weight: 800;
	letter-spacing: .09em;
	text-decoration: none;
	text-transform: uppercase;
}

.main-nav a:hover {
	background: rgba(255, 255, 255, .72);
	color: var(--ink);
}

.main-nav .menu-item-has-children > a:after {
	content: "";
	width: 6px;
	height: 6px;
	margin-left: 8px;
	border-right: 1.5px solid currentColor;
	border-bottom: 1.5px solid currentColor;
	transform: rotate(45deg) translateY(-2px);
}

/* Desktop dropdown. It starts invisible and opens on hover or keyboard focus. */
.main-nav .sub-menu {
	position: absolute;
	left: 0;
	top: 100%;
	z-index: 50;
	display: grid;
	min-width: 220px;
	gap: 4px;
	border: 1px solid var(--line);
	border-radius: 18px;
	padding: 10px 8px 8px;
	background: rgba(255, 255, 255, .96);
	box-shadow: var(--shadow);
	opacity: 0;
	pointer-events: none;
	transform: translateY(4px);
	transition: opacity .16s ease, transform .16s ease;
}

.main-nav li:hover > .sub-menu,
.main-nav li:focus-within > .sub-menu {
	opacity: 1;
	pointer-events: auto;
	transform: translateY(0);
}

.main-nav .sub-menu a {
	justify-content: flex-start;
	width: 100%;
	border-radius: 12px;
	padding: 10px 12px;
	background: transparent;
	color: var(--charcoal);
	letter-spacing: .04em;
	text-transform: none;
}

.main-nav .sub-menu a:hover {
	background: var(--surface-soft);
}

/* Shared CTA/button baseline. Page-specific variants below override color and sizing. */
.nav-cta,
.button {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	border: 1px solid var(--ink);
	border-radius: 999px;
	padding: 11px 18px;
	font-size: 13px;
	font-weight: 800;
	letter-spacing: .02em;
	text-decoration: none;
	transition: background .18s ease, color .18s ease, border-color .18s ease;
}

.nav-cta,
.button.primary {
	background: var(--ink);
	color: #fff !important;
}

.button.ghost {
	background: transparent;
	color: var(--ink);
}

.button:hover,
.nav-cta:hover {
	background: var(--charcoal);
	border-color: var(--charcoal);
	color: #fff;
}

/* Mobile-only notice shown by JS on small screens when needed. */
.mobile-experience-notice {
	position: fixed;
	inset: 0;
	z-index: 160;
	display: none;
	align-items: flex-end;
	padding: 18px;
	background: rgba(23, 23, 23, .46);
}

.mobile-experience-notice[aria-hidden="false"] {
	display: flex;
}

.mobile-experience-notice__card {
	width: 100%;
	border: 1px solid var(--line);
	border-radius: 24px;
	padding: 22px 18px 18px;
	background: rgba(255, 255, 255, .98);
	box-shadow: 0 30px 60px rgba(20, 20, 20, .18);
}

.mobile-experience-notice__card h2 {
	margin-bottom: 12px;
	font-size: clamp(24px, 7vw, 32px);
	line-height: 1.02;
}

.mobile-experience-notice__card p:last-of-type {
	margin-bottom: 18px;
}

.mobile-experience-notice__dismiss {
	width: 100%;
}

/* Legacy homepage hero layout: full viewport width, split copy/media columns. */
.hero-section {
	display: grid;
	grid-template-columns: minmax(340px, .78fr) minmax(0, 1.22fr);
	gap: 0;
	width: 100vw;
	margin-left: calc(50% - 50vw);
	margin-top: 0;
	padding: 0;
	border-bottom: 1px solid var(--line);
}

/* Shared card surface used by several early pages. */
.hero-copy,
.hero-panel,
.architecture-grid article,
.project-card,
.brand-card,
.systems-list,
.qa-list,
.list-card,
.page-content,
.single-project,
.tech-panel,
.access-panel,
.credential-card {
	border: 1px solid var(--line);
	border-radius: var(--radius);
	background: rgba(255, 255, 255, .86);
	box-shadow: var(--shadow);
}

/* Left side of the legacy hero. */
.hero-copy {
	position: relative;
	z-index: 2;
	align-self: stretch;
	display: flex;
	flex-direction: column;
	justify-content: center;
	border-top: 0;
	border-left: 0;
	border-bottom: 0;
	border-radius: 0;
	box-shadow: none;
	padding: clamp(34px, 5vw, 72px);
}

/* Global heading rhythm for the original layout layer. */
.hero-copy h1 {
	max-width: 590px;
	font-size: clamp(24px, 2.5vw, 34px);
	line-height: 1.08;
	letter-spacing: -.025em;
}

.eyebrow {
	margin: 0 0 14px;
	color: var(--muted);
	font-size: 11px;
	font-weight: 800;
	letter-spacing: .18em;
	text-transform: uppercase;
}

h1,
h2,
h3 {
	margin: 0;
	color: var(--ink);
	font-family: Georgia, "Times New Roman", serif;
	font-weight: 600;
	line-height: .98;
	letter-spacing: -.035em;
}

h1 {
	max-width: 720px;
	font-size: clamp(22px, 2.3vw, 30px);
}

h2 {
	font-size: clamp(20px, 2.1vw, 28px);
}

h3 {
	font-size: clamp(17px, 1.6vw, 22px);
}

.hero-lede {
	max-width: 720px;
	margin: 24px 0 0;
	color: var(--muted);
	font-size: clamp(17px, 1.8vw, 22px);
}

.hero-actions {
	display: flex;
	flex-wrap: wrap;
	gap: 12px;
	margin-top: 30px;
}

/* Right side of the legacy hero: media area and overlay card. */
.hero-panel {
	display: grid;
	align-content: space-between;
	gap: 18px;
	border-radius: 0 var(--radius) var(--radius) 0;
	border-top: 0;
	border-right: 0;
	border-bottom: 0;
	border-radius: 0;
	box-shadow: none;
	padding: 0;
	background: var(--surface);
	color: var(--ink);
	overflow: hidden;
}

.hero-banner {
	position: relative;
	display: flex;
	align-items: end;
	min-height: 520px;
	border: 0;
	border-radius: 0;
	overflow: hidden;
	background: #1f1f1f;
}

.hero-banner img {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
	filter: grayscale(70%) contrast(1.04);
}

.hero-banner:before {
	content: "";
	position: absolute;
	inset: 0;
	background:
		linear-gradient(90deg, rgba(20,20,20,.2), rgba(20,20,20,.06)),
		linear-gradient(0deg, rgba(20,20,20,.48), rgba(20,20,20,0) 52%);
}

.hero-banner > div {
	position: relative;
	z-index: 1;
	width: min(420px, calc(100% - 44px));
	margin: 28px;
	border-radius: 18px;
	padding: 22px;
	background: rgba(255,255,255,.92);
	backdrop-filter: blur(8px);
}

.hero-banner span {
	display: block;
	margin-bottom: 8px;
	color: var(--muted);
	font-size: 11px;
	font-weight: 900;
	letter-spacing: .14em;
	text-transform: uppercase;
}

.hero-banner strong {
	display: block;
	font-size: 20px;
	line-height: 1.15;
}

.hero-banner p {
	margin: 10px 0 0;
	color: var(--muted);
}

.hero-banner em {
	display: inline-flex;
	margin-top: 16px;
	border: 1px solid var(--line);
	border-radius: 999px;
	padding: 8px 11px;
	background: #f4f4f2;
	color: var(--ink);
	font-size: 12px;
	font-style: normal;
	font-weight: 800;
}

.stat-strip {
	display: grid;
	grid-template-columns: auto 1fr;
	gap: 3px 14px;
	border-radius: 0;
	padding: 18px;
	background: #f5f5f2;
	color: var(--ink);
}

.stat-strip strong {
	font-size: 30px;
	line-height: 1;
}

.stat-strip span {
	align-self: center;
	color: var(--muted);
	font-weight: 700;
}

/* Original section/grid system for architecture, projects, proof and systems. */
.architecture-section,
.projects-section,
.brands-section,
.proof-section,
.systems-section,
.assessment-section,
.archive-intro {
	padding: 48px 0;
}

.architecture-section,
.section-heading,
.systems-section,
.assessment-section,
.access-panel {
	display: grid;
	grid-template-columns: .82fr 1.18fr;
	gap: 26px;
}

.architecture-grid,
.project-grid,
.brand-grid,
.proof-grid,
.technical-grid {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: 18px;
}

.proof-grid {
	grid-template-columns: repeat(4, minmax(0, 1fr));
}

/* Card padding and proof/documentation surfaces. */
.architecture-grid article,
.proof-grid article,
.project-card,
.brand-card,
.list-card,
.systems-list,
.qa-list,
.tech-panel,
.credential-card {
	padding: 24px;
}

.proof-grid article,
.documentation-band {
	border: 1px solid var(--line);
	border-radius: var(--radius);
	background: rgba(255, 255, 255, .86);
	box-shadow: var(--shadow);
}

.proof-grid strong {
	display: block;
	margin-bottom: 12px;
	color: var(--ink);
	font-size: 17px;
}

.proof-grid p {
	margin: 0;
	color: var(--muted);
}

.documentation-band {
	display: grid;
	grid-template-columns: .7fr 1fr auto;
	align-items: center;
	gap: 24px;
	margin: 42px 0;
	padding: 30px;
}

.documentation-band p {
	margin: 0;
	color: var(--muted);
}

/* Reusable horizontal CTA bands and command/result panels. */
.scheduler-band,
.integration-panel,
.integration-result,
.schedule-card,
.command-panel {
	border: 1px solid var(--line);
	border-radius: var(--radius);
	background: rgba(255, 255, 255, .9);
	box-shadow: var(--shadow);
}

.scheduler-band {
	display: grid;
	grid-template-columns: minmax(0, 1fr) auto;
	align-items: center;
	gap: 24px;
	margin: 42px 0;
	padding: 30px;
}

.scheduler-band h2,
.schedule-card h2,
.integration-panel h2,
.command-panel h2 {
	font-size: 24px;
}

.scheduler-band p,
.schedule-card p,
.integration-panel p,
.version-grid p {
	margin: 12px 0 0;
	color: var(--muted);
}

.architecture-grid article span {
	display: inline-flex;
	margin-bottom: 22px;
	color: var(--silver);
	font-size: 12px;
	font-weight: 800;
	letter-spacing: .18em;
}

.architecture-grid p,
.project-card p,
.brand-card p,
.systems-list,
.qa-list,
.site-footer,
.case-body,
.tech-panel p,
.access-panel p {
	color: var(--muted);
}

/* Project and brand card media framing. */
.project-card,
.brand-card {
	display: flex;
	flex-direction: column;
	min-height: 360px;
}

.project-thumb {
	display: block;
	margin: -8px -8px 22px;
	border-radius: 18px;
	overflow: hidden;
	background: var(--surface-soft);
	aspect-ratio: 1200 / 760;
}

.brand-thumb {
	display: block;
	margin: -8px -8px 18px;
	border-radius: 18px;
	overflow: hidden;
	background: var(--surface-soft);
	aspect-ratio: 1200 / 760;
}

.project-thumb img,
.brand-thumb img,
.project-hero-image img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: center;
}

.project-card-top {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 16px;
	margin-bottom: 24px;
}

.project-icon,
.brand-card span {
	display: grid;
	place-items: center;
	width: 54px;
	height: 54px;
	border: 1px solid var(--line-strong);
	border-radius: 50%;
	background: var(--surface-soft);
	color: var(--charcoal);
	font-size: 14px;
	font-weight: 900;
	letter-spacing: .06em;
	text-transform: uppercase;
}

.project-type {
	margin: 0;
	color: var(--silver);
	font-size: 11px;
	font-weight: 900;
	letter-spacing: .12em;
	text-align: right;
	text-transform: uppercase;
}

.project-card h3 a,
.list-card h2 a,
.brand-card a {
	text-decoration: none;
}

.brand-card h3 {
	margin-top: 28px;
}

.brand-thumb + h3 {
	margin-top: 0;
}

.brand-card a {
	margin-top: auto;
	color: var(--ink);
	font-size: 12px;
	font-weight: 900;
	letter-spacing: .1em;
	text-transform: uppercase;
}

/* Compact metadata and pill labels inside cards. */
.pill-list {
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
	margin: 18px 0 0;
	padding: 0;
	list-style: none;
}

.pill-list li {
	border: 1px solid var(--line);
	border-radius: 999px;
	padding: 6px 10px;
	background: #f2f2f0;
	color: var(--charcoal);
	font-size: 12px;
	font-weight: 700;
}

.project-meta {
	margin-top: auto;
	padding-top: 22px;
	border-top: 1px solid var(--line);
	color: var(--silver);
	font: 600 11px/1.6 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

/* Vertical list rows used in systems and QA panels. */
.systems-list p,
.qa-list p {
	margin: 0;
	padding: 18px 0;
	border-bottom: 1px solid var(--line);
}

.systems-list p:first-child,
.qa-list p:first-child {
	padding-top: 0;
}

.systems-list p:last-child,
.qa-list p:last-child {
	border-bottom: 0;
	padding-bottom: 0;
}

/* Early footer layout. Later footer blocks fully replace this visual treatment. */
.site-footer {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 24px;
	padding: 40px 0 52px;
	border-top: 1px solid var(--line);
}

.content-list {
	display: grid;
	gap: 18px;
	padding-bottom: 56px;
}

.single-project,
.page-content {
	margin: 42px auto 72px;
	padding: clamp(28px, 5vw, 64px);
}

.documentation-page {
	margin: 42px auto 72px;
}

/* Documentation page: hero, summary cards, sticky side nav and content panel. */
.doc-hero {
	border: 1px solid var(--line);
	border-radius: var(--radius);
	background: var(--surface);
	box-shadow: var(--shadow);
	padding: clamp(34px, 6vw, 72px);
}

.doc-hero p {
	max-width: 760px;
	color: var(--muted);
	font-size: 20px;
}

.doc-summary-grid {
	display: grid;
	grid-template-columns: repeat(4, minmax(0, 1fr));
	gap: 14px;
	margin: 22px 0;
}

.doc-summary-grid div {
	border: 1px solid var(--line);
	border-radius: 18px;
	background: #fff;
	padding: 18px;
}

.doc-summary-grid span {
	display: block;
	color: var(--silver);
	font-size: 11px;
	font-weight: 900;
	letter-spacing: .12em;
	text-transform: uppercase;
}

.doc-summary-grid strong {
	display: block;
	margin-top: 8px;
	font-size: 17px;
}

.doc-layout {
	display: grid;
	grid-template-columns: 250px minmax(0, 1fr);
	gap: 22px;
	align-items: start;
}

.doc-nav,
.doc-content {
	border: 1px solid var(--line);
	border-radius: var(--radius);
	background: var(--surface);
	box-shadow: var(--shadow);
}

.doc-nav {
	position: sticky;
	top: 20px;
	display: grid;
	gap: 6px;
	padding: 18px;
}

.doc-nav a {
	border-radius: 12px;
	padding: 10px 12px;
	color: var(--muted);
	font-size: 13px;
	font-weight: 800;
	text-decoration: none;
}

.doc-nav a:hover {
	background: var(--surface-soft);
	color: var(--ink);
}

.doc-content {
	padding: clamp(24px, 4vw, 48px);
}

.doc-content section {
	padding: 30px 0;
	border-top: 1px solid var(--line);
}

.doc-content h2 {
	margin-bottom: 16px;
	font-size: 24px;
}

.doc-content code {
	border: 1px solid var(--line);
	border-radius: 8px;
	padding: 2px 6px;
	background: #f2f2f0;
	font-size: .92em;
}

/* Single project/case-study page layout and metric panels. */
.single-hero {
	max-width: 900px;
}

.project-detail-hero {
	display: grid;
	grid-template-columns: minmax(0, .95fr) minmax(320px, .85fr);
	gap: 28px;
	align-items: center;
	max-width: none;
}

.project-hero-image {
	margin: 0;
	border: 1px solid var(--line);
	border-radius: var(--radius);
	overflow: hidden;
	background: var(--surface-soft);
	box-shadow: var(--shadow);
}

.single-hero h1 {
	font-size: clamp(24px, 2.5vw, 34px);
}

.single-hero p {
	color: var(--muted);
	font-size: 20px;
}

.case-meta,
.metric-grid {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: 14px;
	margin: 34px 0;
}

.case-meta div,
.metric-grid div,
.flex-section {
	border: 1px solid var(--line);
	border-radius: 18px;
	padding: 20px;
	background: #f8f8f6;
}

.case-meta span,
.metric-grid span,
.credential-card dt {
	display: block;
	color: var(--silver);
	font-size: 11px;
	font-weight: 900;
	letter-spacing: .12em;
	text-transform: uppercase;
}

.case-meta strong,
.metric-grid strong {
	display: block;
	overflow-wrap: anywhere;
	font-size: 16px;
}

.access-panel {
	align-items: start;
	margin: 0 0 34px;
	padding: 26px;
	background: #fafafa;
}

.access-panel h2 {
	font-size: 24px;
}

.credential-card {
	background: var(--surface);
	box-shadow: none;
}

.credential-card dl {
	display: grid;
	gap: 10px;
	margin: 0;
}

.credential-card dd {
	margin: 0 0 8px;
	overflow-wrap: anywhere;
	color: var(--charcoal);
	font-weight: 700;
}

.credential-card code {
	border: 1px solid var(--line);
	border-radius: 8px;
	padding: 3px 7px;
	background: #f2f2f0;
	color: var(--ink);
}

.technical-grid {
	margin: 24px 0 36px;
}

.tech-panel {
	min-height: 250px;
	background: var(--surface);
	box-shadow: none;
}

.tech-panel.dark {
	background: var(--charcoal);
	color: #fff;
}

.tech-panel.dark h2,
.tech-panel.dark .check-list,
.tech-panel.dark p {
	color: #fff;
}

.tech-panel h2 {
	margin-bottom: 20px;
	font-size: 24px;
}

.check-list {
	display: grid;
	gap: 10px;
	margin: 0;
	padding: 0;
	list-style: none;
	color: var(--charcoal);
}

.check-list li {
	position: relative;
	padding-left: 22px;
}

.check-list li:before {
	content: "";
	position: absolute;
	left: 0;
	top: .67em;
	width: 7px;
	height: 7px;
	border-radius: 50%;
	background: currentColor;
}

.case-body {
	display: grid;
	grid-template-columns: 1.1fr .9fr;
	gap: 28px;
}

.case-body h2 {
	margin: 26px 0 12px;
	font-size: 24px;
}

.case-body h2:first-child {
	margin-top: 0;
}

.case-body aside {
	border-left: 1px solid var(--line-strong);
	padding-left: 26px;
}

/* Directory pages listing projects/brands. */
.directory-page {
	margin: 42px auto 72px;
}

.directory-hero {
	border: 1px solid var(--line);
	border-radius: var(--radius);
	background: var(--surface);
	box-shadow: var(--shadow);
	padding: clamp(32px, 5vw, 64px);
}

.directory-hero p {
	max-width: 760px;
	color: var(--muted);
	font-size: 20px;
}

.project-directory-grid {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 20px;
	margin-top: 24px;
}

.directory-card {
	border: 1px solid var(--line);
	border-radius: var(--radius);
	background: var(--surface);
	box-shadow: var(--shadow);
	padding: 24px;
}

.directory-card h2 {
	margin-bottom: 14px;
	font-size: 24px;
}

.directory-card h2 a {
	text-decoration: none;
}

.directory-meta {
	margin: 22px 0;
	border-top: 1px solid var(--line);
	padding-top: 18px;
}

.directory-meta span {
	display: block;
	color: var(--silver);
	font-size: 11px;
	font-weight: 900;
	letter-spacing: .12em;
	text-transform: uppercase;
}

.directory-meta strong {
	display: block;
	margin-top: 6px;
}

.section-link {
	margin-top: 22px;
}

.brand-grid.standalone {
	margin-top: 24px;
}

/* Integration test, versioning and schedule pages. */
.flex-hero {
	margin-top: 28px;
	background: var(--charcoal);
	color: #fff;
}

.flex-hero h2,
.flex-hero p,
.flex-hero .eyebrow {
	color: #fff;
}

.integration-panel {
	display: grid;
	grid-template-columns: .8fr 1.2fr;
	gap: 28px;
	margin-top: 28px;
	padding: 28px;
}

.integration-form {
	display: grid;
	gap: 14px;
}

.integration-form label {
	display: grid;
	gap: 7px;
	color: var(--muted);
	font-size: 12px;
	font-weight: 900;
	letter-spacing: .08em;
	text-transform: uppercase;
}

.integration-form input {
	width: 100%;
	border: 1px solid var(--line);
	border-radius: 14px;
	padding: 13px 14px;
	background: #fff;
	color: var(--ink);
	font: inherit;
}

.integration-result {
	margin-top: 20px;
	padding: 22px;
}

.integration-result.is-success {
	border-color: #9fb89f;
	background: #f2f7f1;
}

.integration-result.is-error {
	border-color: #c7a2a2;
	background: #fbf1f1;
}

.integration-result pre,
.command-panel pre {
	overflow: auto;
	border: 1px solid var(--line);
	border-radius: 16px;
	padding: 16px;
	background: #191919;
	color: #f7f7f2;
	white-space: pre-wrap;
}

.version-grid {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: 18px;
	margin-top: 28px;
}

.version-grid article {
	border: 1px solid var(--line);
	border-radius: var(--radius);
	background: #fff;
	padding: 24px;
}

.version-grid span {
	display: inline-flex;
	margin-bottom: 18px;
	color: var(--silver);
	font-size: 12px;
	font-weight: 900;
	letter-spacing: .16em;
}

.version-grid h2 {
	font-size: 24px;
}

.version-grid code,
.command-panel code {
	overflow-wrap: anywhere;
}

.command-panel {
	margin-top: 24px;
	padding: 28px;
}

.schedule-card {
	display: grid;
	grid-template-columns: minmax(0, 1fr) auto;
	align-items: center;
	gap: 22px;
	margin-top: 28px;
	padding: 28px;
}

/* Plugin/code inspection page with sticky file navigation and code panels. */
.plugin-code-page {
	margin: 42px auto 72px;
}

.code-layout {
	display: grid;
	grid-template-columns: minmax(220px, 280px) minmax(0, 1fr);
	gap: 24px;
	align-items: start;
}

.code-side-nav {
	position: sticky;
	top: 112px;
	align-self: start;
}

.code-side-nav > div {
	border: 1px solid var(--line);
	border-radius: var(--radius);
	padding: 22px;
	background: rgba(255, 255, 255, .9);
	box-shadow: var(--shadow);
}

.code-side-nav a {
	display: block;
	padding: 10px 0;
	border-bottom: 1px solid var(--line);
	color: var(--muted);
	font-size: 13px;
	font-weight: 700;
	text-decoration: none;
}

.code-side-nav a:last-child {
	border-bottom: 0;
	padding-bottom: 0;
}

.code-side-nav a:hover {
	color: var(--ink);
}

.code-main {
	min-width: 0;
}

.code-summary-grid {
	display: grid;
	grid-template-columns: repeat(4, minmax(0, 1fr));
	gap: 14px;
	margin: 22px 0;
}

.code-summary-grid div,
.api-evidence-panel,
.code-file-panel {
	border: 1px solid var(--line);
	border-radius: var(--radius);
	background: var(--surface);
	box-shadow: var(--shadow);
}

.code-summary-grid div {
	padding: 18px;
}

.code-summary-grid span {
	display: block;
	color: var(--silver);
	font-size: 11px;
	font-weight: 900;
	letter-spacing: .12em;
	text-transform: uppercase;
}

.code-summary-grid strong {
	display: block;
	margin-top: 8px;
	font-size: 17px;
}

.api-evidence-panel,
.code-file-panel {
	margin-top: 24px;
	padding: clamp(22px, 4vw, 34px);
}

.api-evidence-panel {
	display: grid;
	grid-template-columns: .75fr 1.25fr;
	gap: 24px;
	align-items: start;
}

.api-evidence-panel h2,
.code-file-panel h2 {
	font-size: 24px;
}

.api-evidence-panel p,
.code-file-intro p {
	color: var(--muted);
}

.api-evidence-panel pre,
.code-file-panel pre {
	overflow: auto;
	max-height: 680px;
	border: 1px solid #2f2f2f;
	border-radius: 18px;
	padding: 20px;
	background: #171717;
	color: #f6f6ef;
	font: 500 13px/1.7 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
	tab-size: 4;
	white-space: pre;
}

.code-file-intro {
	margin-bottom: 18px;
}

/* First mobile pass: collapse early multi-column layouts and turn nav into a panel. */
@media (max-width: 920px) {
	.site-header,
	.site-footer,
	.hero-section,
	.architecture-section,
	.section-heading,
	.systems-section,
	.assessment-section,
	.access-panel,
	.documentation-band,
	.scheduler-band,
	.integration-panel,
	.schedule-card,
	.api-evidence-panel,
	.code-layout,
	.doc-layout,
	.project-detail-hero,
	.case-body {
		display: block;
	}

	.site-header {
		position: sticky;
		top: 0;
		z-index: 100;
	}

	.site-header-inner {
		position: relative;
		flex-wrap: wrap;
		padding: 14px 18px;
	}

	.menu-toggle {
		display: inline-flex;
	}

	.main-nav {
		position: absolute;
		left: 12px;
		right: 12px;
		top: calc(100% + 8px);
		z-index: 90;
		display: grid;
		gap: 14px;
		border: 1px solid var(--line);
		border-radius: 22px;
		padding: 14px;
		background: rgba(255, 255, 255, .98);
		box-shadow: var(--shadow);
		max-height: min(calc(100vh - 96px), 34rem);
		overflow-y: auto;
		opacity: 0;
		pointer-events: none;
		transform: translateY(-8px);
		transition: opacity .18s ease, transform .18s ease;
	}

	.site-header.is-menu-open .main-nav {
		opacity: 1;
		pointer-events: auto;
		transform: translateY(0);
	}

	.main-nav ul,
	.main-nav .sub-menu {
		position: static;
		display: grid;
		width: 100%;
		min-width: 0;
		gap: 6px;
		border: 0;
		border-radius: 0;
		padding: 0;
		background: transparent;
		box-shadow: none;
		opacity: 1;
		pointer-events: auto;
		transform: none;
	}

	.main-nav > ul {
		gap: 12px;
	}

	.main-nav li {
		width: 100%;
	}

	.main-nav .menu-item-has-children {
		display: grid;
		grid-template-columns: minmax(0, 1fr) auto;
		gap: 8px;
		align-items: start;
	}

	.main-nav a,
	.main-nav .sub-menu a {
		justify-content: space-between;
		width: 100%;
		border-radius: 14px;
		padding: 12px 14px;
		background: #f5f5f2;
		color: var(--ink);
	}

	.main-nav .menu-item-has-children > a {
		background: var(--charcoal);
		color: #fff;
	}

	.main-nav .menu-item-has-children > a:after {
		display: none;
	}

	.submenu-toggle {
		display: inline-flex;
		align-items: center;
		justify-content: center;
		width: 46px;
		min-height: 46px;
		border: 0;
		border-radius: 14px;
		background: var(--charcoal);
		color: #fff;
		cursor: pointer;
	}

	.submenu-toggle:before {
		content: "+";
		font-size: 20px;
		font-weight: 600;
		line-height: 1;
	}

	.main-nav .menu-item-has-children.is-submenu-open > .submenu-toggle:before {
		content: "\2212";
	}

	.main-nav .sub-menu {
		display: none;
		grid-column: 1 / -1;
		margin-top: 6px;
		padding-left: 8px;
	}

	.main-nav .menu-item-has-children.is-submenu-open > .sub-menu {
		display: grid;
	}

	.main-nav .sub-menu a {
		background: #fff;
		color: var(--charcoal);
	}

	.main-nav .nav-cta,
	.nav-cta {
		width: 100%;
	}

	.main-nav .nav-cta {
		justify-content: center;
		border: 1px solid rgba(59, 130, 246, .42);
		background:
			linear-gradient(135deg, #2563eb 0%, #1d4ed8 52%, #0f172a 100%);
		box-shadow:
			0 14px 28px rgba(37, 99, 235, .28),
			inset 0 1px 0 rgba(255, 255, 255, .18);
		color: #eff6ff !important;
	}

	.main-nav .nav-cta:hover {
		background:
			linear-gradient(135deg, #3b82f6 0%, #2563eb 48%, #0f172a 100%);
		color: #ffffff !important;
	}

	.hero-panel,
	.architecture-grid,
	.project-grid,
	.brand-grid,
	.proof-grid,
	.technical-grid,
	.project-directory-grid,
	.doc-summary-grid,
	.credential-card {
		margin-top: 18px;
	}

	.hero-section {
		width: 100vw;
		margin-left: calc(50% - 50vw);
		padding: 0;
	}

	.hero-copy,
	.hero-panel {
		border-radius: 0;
	}

	.architecture-grid,
	.project-grid,
	.brand-grid,
	.proof-grid,
	.project-directory-grid,
	.doc-summary-grid,
	.case-meta,
	.metric-grid,
	.technical-grid {
		grid-template-columns: 1fr;
	}

	.version-grid,
	.code-summary-grid {
		grid-template-columns: 1fr;
	}

	.code-side-nav {
		position: static;
		margin-bottom: 18px;
	}

	h1 {
		font-size: clamp(22px, 6vw, 28px);
	}
}

/* Hide the mobile notice above phone/tablet width. */
@media (min-width: 821px) {
	.mobile-experience-notice {
		display: none !important;
	}
}

/* Current theme foundation: warm palette, larger spacing scale and updated surfaces.
 * This overrides the first :root block above. */
:root {
	--bg: #f3efe8;
	--surface: #fffdf8;
	--surface-soft: #f7f1e8;
	--surface-strong: #f0e7db;
	--ink: #171412;
	--muted: #665f58;
	--line: #ddd2c3;
	--line-strong: #bead98;
	--charcoal: #1d1a17;
	--silver: #8d8173;
	--accent: #aa5f3b;
	--accent-soft: #eee0d3;
	--accent-deep: #7c452d;
	--shadow: 0 24px 80px rgba(27, 20, 13, .08);
	--shadow-soft: 0 18px 44px rgba(27, 20, 13, .05);
	--radius: 28px;
	--radius-sm: 20px;
	--space-1: 8px;
	--space-2: 16px;
	--space-3: 24px;
	--space-4: 32px;
	--space-5: 48px;
	--space-6: 72px;
	--space-7: 104px;
}

body {
	background:
		radial-gradient(circle at top left, rgba(170, 95, 59, .08), transparent 23%),
		linear-gradient(180deg, #f7f2ea, #f2ece3);
	color: var(--ink);
	font-family: Aptos, "Segoe UI", system-ui, sans-serif;
}

/* Locks page scroll while image lightboxes are open. */
body.has-lightbox-open {
	overflow: hidden;
}

.site-main,
.site-footer {
	width: min(1220px, calc(100% - 44px));
}

.site-header {
	position: sticky;
	top: 0;
	z-index: 1000;
	border-top: 0;
	border-bottom: 1px solid rgba(221, 210, 195, .92);
	background: rgba(247, 242, 234, .86);
	backdrop-filter: blur(18px);
}

.site-header-inner {
	padding: 18px 20px;
}

.brand-mark {
	border-radius: 14px;
	background:
		linear-gradient(135deg, #1d1a17, #393129);
}

.brand strong {
	font-size: 15px;
}

.brand em {
	color: var(--silver);
	font-size: 12px;
}

.main-nav {
	gap: 12px;
}

.main-nav ul {
	gap: 2px;
}

.main-nav a {
	padding: 10px 12px;
	color: var(--silver);
	font-size: 11px;
	font-weight: 800;
	letter-spacing: .08em;
}

.main-nav a:hover {
	background: rgba(255, 255, 255, .72);
	color: var(--ink);
}

.lang-switch {
	display: inline-flex;
	align-items: center;
	gap: 4px;
	border: 1px solid var(--line);
	border-radius: 999px;
	padding: 4px;
	background: rgba(255, 255, 255, .74);
}

.lang-switch a {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 38px;
	border-radius: 999px;
	padding: 7px 10px;
	color: var(--silver);
	font-size: 11px;
	font-weight: 800;
	letter-spacing: .1em;
	text-decoration: none;
	text-transform: uppercase;
}

.lang-switch a.is-active {
	background: var(--charcoal);
	color: #fff;
}

.nav-cta,
.button {
	padding: 12px 18px;
	border-radius: 999px;
	font-size: 12px;
	font-weight: 800;
	letter-spacing: .04em;
	text-transform: uppercase;
}

.button.primary,
.nav-cta {
	background: var(--charcoal);
	border-color: var(--charcoal);
}

.button.ghost {
	border-color: var(--line-strong);
	background: transparent;
}

/* Shared section headers and page spacing for the current design system. */
.eyebrow {
	margin: 0 0 12px;
	color: var(--silver);
	font-size: 10px;
	font-weight: 800;
	letter-spacing: .22em;
	text-transform: uppercase;
}

.home-section,
.cv-section,
.rollout-proof-section,
.rollout-screens-section {
	padding: var(--space-5) 0;
}

.home-section-head,
.section-heading {
	display: grid;
	gap: 10px;
	max-width: 760px;
	margin-bottom: 28px;
}

.home-section-head.compact {
	margin-bottom: 20px;
}

.home-section-head.split {
	max-width: none;
	grid-template-columns: minmax(0, 1fr) auto;
	align-items: end;
	gap: 20px;
}

.home-section-head h2,
.cv-section h2,
.cv-panel h2,
.rollout-page h2,
.documentation-page h2,
.page-content h2,
.version-grid h2,
.command-panel h2 {
	max-width: 18ch;
	font-family: Georgia, "Times New Roman", serif;
	font-size: clamp(1.45rem, 2.8vw, 2.35rem);
	line-height: 1.04;
	letter-spacing: -.05em;
}

.section-support {
	max-width: 62ch;
	margin: 0;
	color: var(--muted);
}

.page-content,
.documentation-page,
.directory-page,
.versioning-page,
.single-project {
	margin: 36px auto 80px;
}

/* Current homepage hero: full-bleed photographic intro with dark overlay. */
.home-hero {
	position: relative;
	width: 100vw;
	margin-left: calc(50% - 50vw);
	min-height: 0;
	padding: clamp(22px, 3.4vh, 36px) 0 clamp(18px, 2.8vh, 28px);
	overflow: hidden;
	background:
		linear-gradient(135deg, rgba(15, 13, 12, .88), rgba(15, 13, 12, .54)),
		radial-gradient(circle at top right, rgba(170, 95, 59, .32), transparent 28%);
}

.home-hero-backdrop,
.home-hero-backdrop img {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
}

.home-hero-backdrop img {
	object-fit: cover;
	filter: saturate(.72) contrast(1.04) brightness(.56);
}

.home-hero-inner {
	position: relative;
	z-index: 1;
	display: grid;
	grid-template-columns: minmax(0, .92fr) minmax(360px, .8fr);
	gap: clamp(14px, 2.2vh, 24px);
	width: min(1220px, calc(100% - 44px));
	margin: 0 auto;
	padding: 0;
	align-content: center;
	align-items: center;
}

/* Current shared card surface for hero, CV, rollout, docs and directory panels. */
.home-hero-copy,
.final-cta-card,
.cv-hero,
.cv-panel,
.cv-summary-card,
.rollout-hero-copy,
.rollout-hero-panel,
.rollout-timeline-card,
.rollout-doc-band,
.rollout-proof-grid article,
.rollout-screen-card,
.doc-hero,
.doc-nav,
.doc-content,
.directory-hero,
.directory-card,
.command-panel,
.scheduler-band,
.documentation-band {
	border: 1px solid var(--line);
	border-radius: var(--radius);
	background: rgba(255, 253, 248, .94);
	box-shadow: var(--shadow-soft);
}

/* Homepage hero copy and action styles. */
.home-hero-copy {
	max-width: 760px;
	border: 0;
	background: transparent;
	box-shadow: none;
	padding: clamp(8px, 2vw, 16px) 0 0;
}

.home-hero-copy h1 {
	max-width: 11ch;
	font-family: Georgia, "Times New Roman", serif;
	font-size: clamp(1.95rem, 3.8vw, 3.25rem);
	line-height: .96;
	letter-spacing: -.055em;
	color: #fff8f0;
}

.home-hero-lede {
	max-width: 60ch;
	margin: 14px 0 0;
	color: rgba(255, 248, 240, .82);
	font-size: clamp(15px, 1.2vw, 17px);
	line-height: 1.55;
}

.home-hero-actions {
	display: flex;
	flex-wrap: wrap;
	gap: 12px;
	margin-top: 18px;
}

.home-hero .eyebrow {
	color: rgba(255, 248, 240, .62);
}

.home-hero .button.ghost {
	border-color: rgba(255, 248, 240, .28);
	color: #fff8f0;
	background: rgba(255, 255, 255, .08);
}

.home-hero-proof {
	display: grid;
	grid-template-columns: 1fr;
	gap: 16px;
	align-items: stretch;
}

.home-hero-panel-copy {
	display: grid;
	gap: 8px;
	align-content: start;
	padding: clamp(16px, 2vw, 22px);
	border: 1px solid rgba(255, 255, 255, .14);
	border-radius: var(--radius);
	background: rgba(18, 16, 15, .58);
	backdrop-filter: blur(16px);
}

.home-panel-kicker {
	color: rgba(255, 248, 240, .54);
	font-size: 10px;
	font-weight: 800;
	letter-spacing: .18em;
	text-transform: uppercase;
}

.home-hero-panel-copy strong {
	max-width: 22ch;
	font-family: Georgia, "Times New Roman", serif;
	font-size: clamp(1.05rem, 1.5vw, 1.28rem);
	line-height: 1.08;
	letter-spacing: -.035em;
	color: #fff8f0;
}

.home-hero-panel-copy p,
.home-hero-panel-copy em,
.signal-item p,
.signal-card p,
.skill-card p,
.proof-card-refined p,
.review-card p,
.related-card p,
.featured-project-card p,
.brand-card-refined p,
.final-cta-card p,
.cv-panel p,
.cv-summary-card p,
.cv-timeline-item p,
.language-card p,
.rollout-page p,
.documentation-page p {
	color: var(--muted);
}

.home-hero-panel-copy em {
	font-style: normal;
	font-size: 12px;
	color: rgba(255, 248, 240, .72);
}

.home-highlight-list {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 10px;
}

/* Reusable card family for homepage, CV and language panels. */
.signal-item,
.signal-card,
.skill-card,
.proof-card-refined,
.review-card,
.related-card,
.brand-card-refined,
.language-card,
.cv-infra-card,
.cv-skill-group,
.cv-signal-chip {
	border: 1px solid var(--line);
	border-radius: var(--radius-sm);
	background: rgba(255, 255, 255, .74);
}

/* Dark hero variants for cards placed over the image backdrop. */
.home-hero .signal-item {
	border-color: rgba(255, 255, 255, .12);
	background: rgba(18, 16, 15, .58);
	backdrop-filter: blur(16px);
}

.home-hero .signal-item h3 {
	color: #fff8f0;
}

.home-hero .signal-item p {
	color: rgba(255, 248, 240, .72);
}

.signal-item,
.signal-card,
.skill-card,
.proof-card-refined,
.review-card,
.related-card {
	padding: 16px;
}

.signal-item h3,
.signal-card h3,
.skill-card h3,
.proof-card-refined h3,
.review-card h3,
.related-card h3,
.featured-project-card h3,
.brand-card-refined h3,
.language-card h3,
.cv-timeline-body h3,
.cv-infra-card h3,
.cv-skill-group h3,
.rollout-screen-card h3 {
	margin: 0 0 8px;
	font-size: 16px;
	line-height: 1.12;
	letter-spacing: -.03em;
}

.home-hero .signal-item p {
	margin: 0;
	font-size: 13px;
	line-height: 1.45;
}

/* Main card grids used by the homepage sections. */
.signal-grid,
.skill-grid,
.proof-grid-refined,
.review-grid,
.related-grid,
.featured-project-grid,
.brand-grid-refined,
.language-grid {
	display: grid;
	gap: 18px;
}

.signal-grid {
	grid-template-columns: repeat(3, minmax(0, 1fr));
}

.skill-grid,
.proof-grid-refined,
.review-grid {
	grid-template-columns: repeat(3, minmax(0, 1fr));
}

.related-grid {
	grid-template-columns: repeat(3, minmax(0, 1fr));
}

.featured-project-grid {
	grid-template-columns: repeat(2, minmax(0, 1fr));
}

.featured-project-card {
	overflow: hidden;
	border: 1px solid var(--line);
	border-radius: var(--radius);
	background: rgba(255, 253, 248, .96);
	box-shadow: var(--shadow-soft);
}

.featured-project-body {
	padding: 20px 20px 22px;
}

.featured-project-card h3 a {
	text-decoration: none;
}

.project-kicker,
.featured-stack {
	color: var(--silver);
	font-size: 12px;
	font-weight: 800;
	letter-spacing: .04em;
}

.featured-stack {
	margin: 8px 0 12px;
}

.trust-section {
	padding-bottom: 64px;
}

.brand-grid-refined {
	grid-template-columns: repeat(3, minmax(0, 1fr));
}

.brand-card-refined {
	padding: 20px;
}

.brand-card-refined span {
	display: inline-grid;
	place-items: center;
	width: 40px;
	height: 40px;
	margin-bottom: 14px;
	border-radius: 14px;
	background: var(--accent-soft);
	color: var(--accent-deep);
	font-size: 12px;
	font-weight: 800;
	letter-spacing: .08em;
}

.final-cta-card {
	padding: clamp(28px, 5vw, 44px);
	max-width: 980px;
	margin: 0 auto;
}

.final-cta-card h2 {
	max-width: 16ch;
	margin: 0 0 16px;
	font-family: Georgia, "Times New Roman", serif;
	font-size: clamp(1.7rem, 3vw, 2.8rem);
	line-height: 1.02;
	letter-spacing: -.05em;
}

/* CV/resume page layout. */
.cv-page {
	padding: 44px 0 96px;
}

.cv-hero {
	display: grid;
	gap: 22px;
	max-width: 980px;
	margin: 0 auto;
	padding: clamp(28px, 5vw, 44px);
}

.cv-hero-grid {
	display: grid;
	grid-template-columns: 1fr 320px;
	gap: 22px;
	align-items: start;
}

.cv-memory-mark {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	margin-bottom: 18px;
	padding: 8px 12px;
	border: 1px solid var(--line);
	border-radius: 999px;
	background: var(--accent-soft);
	color: var(--accent-deep);
	font-size: 11px;
	font-weight: 900;
	letter-spacing: .24em;
	text-transform: uppercase;
}

.cv-hero h1 {
	margin: 0;
	font-family: Georgia, "Times New Roman", serif;
	font-size: clamp(2rem, 4vw, 3.2rem);
	line-height: .98;
	letter-spacing: -.055em;
}

.cv-role {
	max-width: 40ch;
	margin: 14px 0 0;
	font-size: 17px;
}

.cv-contact-card {
	border-radius: var(--radius-sm);
	padding: 20px;
}

.cv-contact-card dl {
	display: grid;
	gap: 14px;
	margin: 0;
}

.cv-contact-card div {
	display: grid;
	gap: 4px;
}

.cv-contact-card dt {
	color: var(--silver);
	font-size: 10px;
	font-weight: 800;
	letter-spacing: .18em;
	text-transform: uppercase;
}

.cv-contact-card dd {
	margin: 0;
	font-weight: 700;
	overflow-wrap: anywhere;
}

.cv-summary-card {
	padding: 24px;
}

.cv-action-row {
	display: flex;
	flex-wrap: wrap;
	gap: 12px;
	margin-top: 20px;
}

.cv-signal-bar {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: 12px;
	margin-top: 20px;
}

.cv-signal-chip {
	padding: 16px;
}

.cv-signal-chip span {
	display: block;
	color: var(--silver);
	font-size: 10px;
	font-weight: 800;
	letter-spacing: .18em;
	text-transform: uppercase;
}

.cv-signal-chip strong {
	display: block;
	margin-top: 8px;
	font-size: 15px;
	line-height: 1.2;
}

.cv-section {
	max-width: 980px;
	margin: 0 auto;
}

.cv-timeline {
	display: grid;
	gap: 20px;
}

.cv-timeline-item {
	display: grid;
	grid-template-columns: 170px minmax(0, 1fr);
	gap: 18px;
	padding: 22px 0;
	border-top: 1px solid var(--line);
}

.cv-timeline-item:first-child {
	border-top: 0;
	padding-top: 0;
}

.cv-timeline-meta span {
	display: inline-flex;
	align-items: center;
	border: 1px solid var(--line);
	border-radius: 999px;
	padding: 9px 12px;
	background: rgba(255, 255, 255, .74);
	color: var(--silver);
	font-size: 11px;
	font-weight: 800;
	letter-spacing: .1em;
	text-transform: uppercase;
}

.cv-company {
	margin: 0 0 10px;
	font-weight: 700;
}

.cv-list {
	display: grid;
	gap: 8px;
	margin: 14px 0 0;
	padding-left: 18px;
}

.cv-grid-section {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 18px;
	max-width: 980px;
	margin: 0 auto;
	padding-top: var(--space-5);
}

.cv-panel {
	padding: 24px;
}

.cv-skill-groups,
.cv-infra-grid {
	display: grid;
	gap: 14px;
	margin-top: 18px;
}

.cv-skill-group,
.cv-infra-card,
.language-card {
	padding: 18px;
}

.language-grid {
	grid-template-columns: repeat(3, minmax(0, 1fr));
}

/* Documentation and directory refinements in the current theme layer. */
.doc-hero,
.directory-hero {
	padding: clamp(28px, 5vw, 54px);
}

.doc-layout {
	grid-template-columns: 240px minmax(0, 1fr);
	gap: 18px;
}

.doc-nav {
	top: 16px;
	border-radius: var(--radius);
}

.doc-content {
	padding: clamp(24px, 4vw, 42px);
}

.doc-content section {
	padding: 24px 0;
}

.doc-summary-grid,
.project-directory-grid {
	gap: 16px;
}

/* Rollout/custom-panel case page and its screenshot lightbox. */
.rollout-page {
	display: grid;
	gap: 22px;
	padding: 44px 0 96px;
}

.rollout-hero {
	display: grid;
	grid-template-columns: 1fr;
	gap: 18px;
}

.rollout-hero-copy,
.rollout-hero-panel,
.rollout-timeline-card,
.rollout-doc-band,
.rollout-screen-card,
.rollout-proof-grid article {
	padding: 24px;
}

.rollout-hero-panel {
	padding: 0;
	overflow: hidden;
}

.rollout-hero-panel img {
	display: block;
	width: 100%;
	height: 420px;
	object-fit: cover;
}

.rollout-hero-card {
	position: static;
	width: auto;
	margin: 0;
	border: 0;
	border-top: 1px solid rgba(255, 255, 255, .14);
	border-radius: 0;
	padding: 24px;
	background: linear-gradient(180deg, rgba(26, 23, 20, .92), rgba(26, 23, 20, .96));
	backdrop-filter: none;
}

.rollout-hero-card span,
.rollout-screen-kicker {
	color: rgba(255, 255, 255, .62);
	font-size: 10px;
	letter-spacing: .18em;
}

.rollout-hero-card strong {
	max-width: 22ch;
	color: #fff;
	font-size: 1.45rem;
	line-height: 1.1;
}

.rollout-hero-card p {
	color: rgba(255, 255, 255, .74);
}

.rollout-stat-strip {
	grid-template-columns: repeat(4, minmax(0, 1fr));
	gap: 12px;
}

.rollout-stat-strip div {
	border: 1px solid var(--line);
	border-radius: 18px;
	padding: 14px 16px;
	background: rgba(255, 255, 255, .68);
}

.rollout-proof-grid,
.rollout-screen-grid {
	display: grid;
	gap: 18px;
}

.rollout-proof-grid {
	grid-template-columns: repeat(2, minmax(0, 1fr));
}

.rollout-story,
.rollout-doc-band {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(280px, .9fr);
	gap: 18px;
}

.rollout-story-copy,
.rollout-timeline-card {
	border: 1px solid var(--line);
	border-radius: var(--radius);
	padding: 24px;
	background: rgba(255, 253, 248, .94);
	box-shadow: var(--shadow-soft);
}

.rollout-screen-grid {
	grid-template-columns: repeat(2, minmax(0, 1fr));
}

.rollout-screen-card {
	padding: 0;
	overflow: hidden;
}

.rollout-screen-card figcaption {
	padding: 22px 22px 14px;
	border-bottom: 1px solid var(--line);
}

.rollout-shot-link {
	display: block;
	background: #151210;
	cursor: zoom-in;
}

.rollout-shot-link img {
	display: block;
	width: 100%;
	max-height: 380px;
	height: auto;
	object-fit: contain;
	object-position: top center;
}

.rollout-lightbox {
	position: fixed;
	inset: 0;
	z-index: 300;
	display: grid;
	align-items: center;
	padding: 24px;
	background: rgba(8, 7, 6, .84);
	opacity: 0;
	pointer-events: none;
	transition: opacity .18s ease;
}

.rollout-lightbox.is-open {
	opacity: 1;
	pointer-events: auto;
}

.rollout-lightbox-dialog {
	position: relative;
	display: grid;
	grid-template-columns: minmax(0, 1.4fr) minmax(320px, .6fr);
	gap: 18px;
	width: min(1320px, 100%);
	max-height: calc(100vh - 48px);
	margin: 0 auto;
}

.rollout-lightbox-media,
.rollout-lightbox-copy {
	border: 1px solid rgba(255, 255, 255, .12);
	border-radius: var(--radius);
	background: rgba(22, 18, 16, .94);
}

.rollout-lightbox-media {
	display: grid;
	place-items: center;
	padding: 24px;
	overflow: auto;
}

.rollout-lightbox-media img {
	display: block;
	max-width: 100%;
	height: auto;
}

.rollout-lightbox-copy {
	display: grid;
	align-content: start;
	gap: 12px;
	padding: 28px;
	color: #fff8f0;
}

.rollout-lightbox-copy p {
	color: rgba(255, 248, 240, .72);
}

.rollout-lightbox-kicker {
	color: rgba(255, 248, 240, .56);
	font-size: 10px;
	font-weight: 800;
	letter-spacing: .18em;
	text-transform: uppercase;
}

.rollout-lightbox-title {
	margin: 0;
	font-family: Georgia, "Times New Roman", serif;
	font-size: clamp(1.5rem, 2vw, 2.2rem);
	line-height: 1.02;
	letter-spacing: -.04em;
}

.rollout-lightbox-close {
	position: absolute;
	top: 10px;
	right: 10px;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 42px;
	height: 42px;
	border: 1px solid rgba(255, 255, 255, .16);
	border-radius: 999px;
	background: rgba(255, 255, 255, .08);
	color: #fff;
	font-size: 20px;
	cursor: pointer;
}

/* Footer spacing in the current theme layer. Later footer blocks replace structure. */
.site-footer {
	padding: 36px 0 56px;
	border-top: 1px solid var(--line);
}

/* Elevated section system: intentionally scoped below the approved hero/header. */
.site-main > .home-section,
.global-schedule-section {
	position: relative;
	width: 100vw;
	margin-left: calc(50% - 50vw);
	padding: clamp(64px, 8vw, 112px) 0;
}

.site-main > .home-section > *,
.global-schedule-card {
	width: min(1160px, calc(100% - 44px));
	margin-right: auto;
	margin-left: auto;
}

.site-main > .home-section:nth-of-type(odd) {
	background:
		radial-gradient(circle at 8% 10%, rgba(170, 95, 59, .07), transparent 25%),
		#fffdf8;
}

.site-main > .home-section:nth-of-type(even) {
	background:
		linear-gradient(90deg, rgba(29, 26, 23, .032) 1px, transparent 1px),
		linear-gradient(rgba(29, 26, 23, .026) 1px, transparent 1px),
		#f3eee6;
	background-size: 56px 56px;
}

.site-main > .home-section + .home-section {
	border-top: 1px solid rgba(190, 173, 152, .45);
}

.home-section-head,
.section-heading {
	gap: 12px;
	max-width: 820px;
	margin-bottom: clamp(28px, 4vw, 46px);
}

/* Refined homepage section cards: denser signal grids and stronger hover affordance. */
.home-section-head h2 {
	max-width: 20ch;
	margin: 0;
	font-size: clamp(1.75rem, 3.2vw, 2.7rem);
}

.section-support,
.home-section-head p:not(.eyebrow) {
	max-width: 62ch;
	font-size: 16px;
	line-height: 1.7;
}

.signal-grid {
	grid-template-columns: repeat(5, minmax(0, 1fr));
	gap: 10px;
}

.signal-card {
	min-height: 150px;
	padding: 18px;
	border-color: rgba(29, 26, 23, .11);
	background: rgba(255, 255, 255, .62);
	box-shadow: none;
	transition: transform .18s ease, border-color .18s ease, background .18s ease;
}

.signal-card:hover {
	transform: translateY(-4px);
	border-color: rgba(170, 95, 59, .46);
	background: #fff;
}

.signal-card h3 {
	padding-top: 22px;
}

.signal-card h3:before,
.skill-card h3:before,
.proof-card-refined h3:before,
.review-card h3:before {
	content: "";
	display: block;
	width: 22px;
	height: 3px;
	margin-bottom: 14px;
	border-radius: 999px;
	background: var(--accent);
}

.skill-grid {
	grid-template-columns: repeat(4, minmax(0, 1fr));
	gap: 16px;
}

.skill-card {
	min-height: 220px;
	padding: 24px;
	background:
		linear-gradient(180deg, rgba(255, 255, 255, .88), rgba(255, 253, 248, .72));
	box-shadow: 0 18px 50px rgba(27, 20, 13, .055);
}

.skill-card:nth-child(1),
.skill-card:nth-child(7) {
	grid-column: span 2;
	background:
		linear-gradient(135deg, rgba(29, 26, 23, .96), rgba(55, 44, 34, .92));
	color: #fff8f0;
}

.skill-card:nth-child(1) p,
.skill-card:nth-child(7) p {
	color: rgba(255, 248, 240, .72);
}

.skill-card:nth-child(1) h3:before,
.skill-card:nth-child(7) h3:before {
	background: #d99b73;
}

.proof-grid-refined {
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: 18px;
	counter-reset: proof;
}

.proof-card-refined {
	position: relative;
	min-height: 210px;
	padding: 24px;
	overflow: hidden;
	background: #fffdf8;
}

.proof-card-refined:after {
	counter-increment: proof;
	content: "0" counter(proof);
	position: absolute;
	right: 18px;
	bottom: 12px;
	color: rgba(170, 95, 59, .11);
	font-family: Georgia, "Times New Roman", serif;
	font-size: 70px;
	font-weight: 700;
	line-height: 1;
}

.proof-card-refined:nth-child(3n + 1) {
	border-top: 4px solid #aa5f3b;
}

.proof-card-refined:nth-child(3n + 2) {
	border-top: 4px solid #6e7561;
}

.proof-card-refined:nth-child(3n) {
	border-top: 4px solid #38342f;
}

.review-grid {
	grid-template-columns: repeat(6, minmax(0, 1fr));
	gap: 12px;
	padding: 16px;
	border: 1px solid rgba(29, 26, 23, .1);
	border-radius: calc(var(--radius) + 10px);
	background:
		linear-gradient(135deg, rgba(29, 26, 23, .96), rgba(44, 36, 29, .94));
	box-shadow: var(--shadow);
}

.review-card {
	min-height: 190px;
	padding: 20px;
	border-color: rgba(255, 255, 255, .11);
	background: rgba(255, 255, 255, .07);
	color: #fff8f0;
	text-decoration: none;
	transition: transform .18s ease, background .18s ease, border-color .18s ease;
}

.review-card p {
	color: rgba(255, 248, 240, .68);
}

.review-card:hover {
	transform: translateY(-5px);
	border-color: rgba(217, 155, 115, .7);
	background: rgba(255, 255, 255, .12);
}

.featured-project-grid {
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 26px;
}

.featured-project-card {
	border-color: rgba(29, 26, 23, .11);
	background: #fffdf8;
	box-shadow: 0 24px 70px rgba(27, 20, 13, .075);
	transition: transform .2s ease, box-shadow .2s ease;
}

.featured-project-card:hover {
	transform: translateY(-6px);
	box-shadow: 0 30px 90px rgba(27, 20, 13, .11);
}

.featured-project-card .project-thumb {
	display: block;
	border-bottom: 1px solid var(--line);
	background: #1d1a17;
}

.featured-project-body {
	padding: 28px;
}

.featured-project-card h3 {
	font-size: clamp(1.25rem, 2vw, 1.7rem);
}

.related-systems-section {
	padding-top: clamp(46px, 6vw, 76px);
	padding-bottom: clamp(46px, 6vw, 76px);
}

.related-grid {
	grid-template-columns: repeat(5, minmax(0, 1fr));
	gap: 10px;
}

.related-card {
	min-height: auto;
	padding: 16px;
	border-style: dashed;
	background: rgba(255, 255, 255, .46);
	box-shadow: none;
}

.related-card h3 {
	font-size: 14px;
}

.related-card p {
	font-size: 13px;
	line-height: 1.55;
}

.brand-grid-refined {
	grid-template-columns: repeat(6, minmax(0, 1fr));
	gap: 12px;
}

.brand-card-refined {
	display: grid;
	align-content: start;
	min-height: 170px;
	padding: 18px;
	filter: grayscale(1);
	opacity: .78;
	transition: filter .18s ease, opacity .18s ease, transform .18s ease;
}

.brand-card-refined:hover {
	filter: grayscale(0);
	opacity: 1;
	transform: translateY(-4px);
}

.brand-card-refined span {
	background: #ece4d8;
	color: #1d1a17;
}

.final-cta-card {
	position: relative;
	max-width: 1160px;
	overflow: hidden;
	padding: clamp(34px, 6vw, 72px);
	background:
		linear-gradient(135deg, rgba(255, 253, 248, .96), rgba(240, 231, 219, .94));
}

.final-cta-card:after {
	content: "";
	position: absolute;
	right: -120px;
	bottom: -140px;
	width: 360px;
	height: 360px;
	border-radius: 50%;
	background: rgba(170, 95, 59, .12);
}

.global-schedule-section {
	background:
		radial-gradient(circle at 80% 10%, rgba(170, 95, 59, .13), transparent 28%),
		#1d1a17;
}

.global-schedule-card {
	display: grid;
	grid-template-columns: minmax(0, 1fr) auto;
	align-items: center;
	gap: 24px;
	padding: clamp(28px, 5vw, 52px);
	border: 1px solid rgba(255, 255, 255, .12);
	border-radius: calc(var(--radius) + 8px);
	background:
		linear-gradient(135deg, rgba(255, 255, 255, .08), rgba(255, 255, 255, .035));
	color: #fff8f0;
	box-shadow: 0 28px 80px rgba(0, 0, 0, .18);
}

.global-schedule-card h2 {
	max-width: 18ch;
	margin: 0 0 12px;
	font-family: Georgia, "Times New Roman", serif;
	font-size: clamp(1.8rem, 3vw, 2.8rem);
	line-height: 1.02;
	letter-spacing: -.05em;
}

.global-schedule-card p {
	max-width: 62ch;
	margin: 0;
	color: rgba(255, 248, 240, .72);
}

.global-schedule-card .button.primary {
	border-color: #fff8f0;
	background: #fff8f0;
	color: #1d1a17 !important;
}

/* JS adds .is-visible as sections enter the viewport. */
.will-reveal {
	opacity: 0;
	transform: translateY(18px);
	transition: opacity .45s ease, transform .45s ease;
}

.will-reveal.is-visible {
	opacity: 1;
	transform: translateY(0);
}

.signal-card,
.skill-card,
.proof-card-refined,
.review-card,
.featured-project-card,
.related-card,
.brand-card-refined,
.global-schedule-card,
.button {
	will-change: transform;
}

.button {
	transition: transform .16s ease, background .18s ease, color .18s ease, border-color .18s ease, box-shadow .18s ease;
}

.button:hover,
.nav-cta:hover {
	transform: translateY(-2px);
	box-shadow: 0 12px 30px rgba(27, 20, 13, .12);
}

/* Media proof strip on the homepage. A later fix changes this to non-overlay captions. */
.proof-media-strip {
	display: grid;
	grid-template-columns: 1.15fr .85fr .85fr;
	gap: 14px;
	margin-top: 28px;
}

.proof-media-strip figure {
	position: relative;
	min-height: 280px;
	margin: 0;
	overflow: hidden;
	border: 1px solid rgba(29, 26, 23, .1);
	border-radius: calc(var(--radius) + 2px);
	background: #1d1a17;
	box-shadow: 0 22px 62px rgba(27, 20, 13, .08);
}

.proof-media-strip img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
	opacity: .86;
	transform: scale(1.01);
	transition: opacity .25s ease, transform .35s ease;
}

.proof-media-strip figure:hover img {
	opacity: .98;
	transform: scale(1.045);
}

.proof-media-strip figcaption {
	position: absolute;
	right: 14px;
	bottom: 14px;
	left: 14px;
	display: grid;
	gap: 4px;
	border: 1px solid rgba(255, 255, 255, .12);
	border-radius: 18px;
	padding: 14px;
	background: rgba(20, 17, 15, .72);
	backdrop-filter: blur(16px);
	color: #fff8f0;
}

.proof-media-strip figcaption span {
	color: rgba(255, 248, 240, .7);
	font-size: 13px;
	line-height: 1.45;
}

/* Accessibility: respect reduced-motion preferences across transitions and reveal effects. */
@media (prefers-reduced-motion: reduce) {
	*,
	*::before,
	*::after {
		scroll-behavior: auto !important;
		transition-duration: .01ms !important;
		animation-duration: .01ms !important;
		animation-iteration-count: 1 !important;
	}

	.will-reveal {
		opacity: 1;
		transform: none;
	}
}

@media (max-width: 1120px) {
	.signal-grid,
	.skill-grid,
	.proof-grid-refined,
	.review-grid,
	.related-grid,
	.brand-grid-refined,
	.language-grid,
	.cv-signal-bar,
	.rollout-stat-strip {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}

	.review-grid {
		grid-template-columns: repeat(3, minmax(0, 1fr));
	}

	.related-grid,
	.brand-grid-refined {
		grid-template-columns: repeat(3, minmax(0, 1fr));
	}

	.skill-card:nth-child(1),
	.skill-card:nth-child(7) {
		grid-column: span 1;
	}
}

/* Current mobile pass: one-column cards, full-width buttons and smaller media/lightbox. */
@media (max-width: 920px) {
	.site-main,
	.site-footer {
		width: min(1220px, calc(100% - 28px));
	}

	.site-header-inner {
		position: relative;
		align-items: center;
		padding: 16px 14px;
	}

	.main-nav {
		position: absolute;
		top: calc(100% + 8px);
		right: 0;
		left: 0;
		display: none;
		gap: 12px;
		border: 1px solid var(--line);
		border-radius: 24px;
		padding: 16px;
		background: rgba(255, 253, 248, .98);
		box-shadow: var(--shadow);
	}

	.main-nav ul,
	.home-section-head.split,
	.cv-hero-grid,
	.cv-grid-section,
	.cv-timeline-item,
	.doc-layout,
	.rollout-story,
	.rollout-doc-band {
		grid-template-columns: 1fr;
	}

	.main-nav ul {
		gap: 8px;
	}

	.home-highlight-list,
	.signal-grid,
	.skill-grid,
	.proof-grid-refined,
	.review-grid,
	.related-grid,
	.featured-project-grid,
	.brand-grid-refined,
	.language-grid,
	.cv-signal-bar,
	.rollout-proof-grid,
	.rollout-screen-grid,
	.rollout-stat-strip {
		grid-template-columns: 1fr;
	}

	.site-main > .home-section,
	.global-schedule-section {
		padding: 52px 0;
	}

	.site-main > .home-section > *,
	.global-schedule-card {
		width: min(1220px, calc(100% - 28px));
	}

	.home-section-head,
	.section-heading {
		margin-bottom: 24px;
	}

	.home-section-head h2 {
		font-size: clamp(1.55rem, 8vw, 2.15rem);
	}

	.signal-card,
	.skill-card,
	.proof-card-refined,
	.review-card,
	.featured-project-body,
	.final-cta-card,
	.global-schedule-card {
		padding: 20px;
	}

	.signal-card,
	.skill-card,
	.proof-card-refined,
	.review-card {
		min-height: auto;
	}

	.review-grid {
		padding: 10px;
	}

	.featured-project-grid,
	.related-grid,
	.brand-grid-refined,
	.proof-media-strip,
	.global-schedule-card {
		grid-template-columns: 1fr;
	}

	.related-card,
	.brand-card-refined,
	.proof-media-strip figure {
		min-height: auto;
	}

	.proof-media-strip figure {
		aspect-ratio: 16 / 10;
	}

	.global-schedule-card {
		align-items: start;
	}

	.global-schedule-card .button {
		width: 100%;
	}

	.home-hero-inner,
	.site-main,
	.site-footer {
		width: min(1220px, calc(100% - 28px));
	}

	.home-hero-inner {
		grid-template-columns: 1fr;
	}

	.home-hero-proof,
	.rollout-lightbox-dialog {
		grid-template-columns: 1fr;
	}

	.home-hero {
		min-height: auto;
		max-height: none;
		padding: 28px 0 22px;
	}

	.home-hero-inner {
		min-height: auto;
		align-content: start;
		gap: 20px;
	}

	.home-hero-copy h1 {
		font-size: clamp(2rem, 10vw, 3.1rem);
	}

	.home-hero-lede {
		margin-top: 16px;
		font-size: 16px;
	}

	.home-hero-copy h1,
	.home-section-head h2,
	.cv-hero h1,
	.final-cta-card h2,
	.rollout-page h2 {
		max-width: none;
	}

	.home-hero-media,
	.rollout-hero-panel img {
		min-height: 240px;
		height: 240px;
	}

	.rollout-shot-link img {
		max-height: 280px;
	}

	.rollout-lightbox {
		padding: 16px;
	}

	.rollout-lightbox-copy,
	.rollout-lightbox-media {
		padding: 18px;
	}
}

/* Final polish layer: typography, image framing and card balance below the approved header/hero.
 * This intentionally overrides the current theme layer above without rewriting markup. */
.site-main > .home-section {
	background-color: #fffdfa;
}

.site-main > .home-section:nth-of-type(odd) {
	background:
		radial-gradient(circle at 12% 8%, rgba(170, 95, 59, .055), transparent 24%),
		linear-gradient(180deg, #fffdfa 0%, #fbf7ef 100%);
}

.site-main > .home-section:nth-of-type(even) {
	background:
		linear-gradient(90deg, rgba(29, 26, 23, .022) 1px, transparent 1px),
		linear-gradient(rgba(29, 26, 23, .018) 1px, transparent 1px),
		#f6f1e9;
	background-size: 64px 64px;
}

.home-section-head,
.section-heading {
	max-width: 700px;
	gap: 14px;
	margin-bottom: clamp(34px, 4.4vw, 54px);
}

.home-section-head.split {
	max-width: 1160px;
}

.home-section-head .eyebrow,
.section-heading .eyebrow {
	margin-bottom: 2px;
	color: #8d6a55;
	letter-spacing: .18em;
}

.home-section-head h2,
.cv-section h2,
.cv-panel h2,
.rollout-page h2,
.documentation-page h2,
.page-content h2,
.version-grid h2,
.command-panel h2,
.global-schedule-card h2,
.final-cta-card h2 {
	max-width: 680px;
	text-wrap: balance;
	font-size: clamp(1.58rem, 2.55vw, 2.34rem);
	line-height: 1.06;
	letter-spacing: -.045em;
}

.section-support,
.home-section-head p:not(.eyebrow) {
	max-width: 660px;
	color: #665f57;
	font-size: 15.5px;
	line-height: 1.66;
}

.signal-grid,
.skill-grid,
.proof-grid-refined,
.review-grid,
.related-grid,
.featured-project-grid,
.brand-grid-refined {
	align-items: stretch;
	grid-auto-rows: 1fr;
}

.signal-card,
.skill-card,
.proof-card-refined,
.review-card,
.related-card,
.brand-card-refined,
.featured-project-card {
	height: 100%;
	border-color: rgba(54, 45, 36, .13);
}

.signal-card,
.skill-card,
.proof-card-refined,
.review-card,
.related-card {
	display: grid;
	align-content: start;
	gap: 8px;
	padding: 22px;
}

.signal-card {
	min-height: 132px;
	background: rgba(255, 255, 255, .74);
}

.skill-card {
	min-height: 210px;
	background:
		linear-gradient(180deg, rgba(255, 255, 255, .94), rgba(253, 249, 241, .86));
	box-shadow: 0 16px 44px rgba(27, 20, 13, .045);
}

.skill-card:nth-child(1),
.skill-card:nth-child(7) {
	background:
		linear-gradient(135deg, rgba(50, 43, 36, .94), rgba(91, 72, 56, .88));
	box-shadow: 0 18px 54px rgba(27, 20, 13, .075);
}

.skill-card:nth-child(1) p,
.skill-card:nth-child(7) p {
	color: rgba(255, 248, 240, .76);
}

.proof-card-refined {
	min-height: 196px;
	background:
		linear-gradient(180deg, #fffefa 0%, #fbf6ee 100%);
	box-shadow: 0 14px 42px rgba(27, 20, 13, .04);
}

.proof-card-refined:after {
	right: 20px;
	bottom: 14px;
	color: rgba(170, 95, 59, .085);
	font-size: 60px;
}

.review-grid {
	padding: 18px;
	border-color: rgba(54, 45, 36, .12);
	background:
		radial-gradient(circle at 12% 14%, rgba(217, 155, 115, .18), transparent 26%),
		linear-gradient(135deg, rgba(42, 36, 31, .94), rgba(69, 55, 43, .9));
}

.review-card {
	min-height: 172px;
	border-color: rgba(255, 255, 255, .14);
	background: rgba(255, 255, 255, .085);
}

.review-card p {
	color: rgba(255, 248, 240, .74);
}

.related-card {
	min-height: 138px;
	padding: 18px;
	background: rgba(255, 255, 255, .56);
}

.signal-item h3,
.signal-card h3,
.skill-card h3,
.proof-card-refined h3,
.review-card h3,
.related-card h3,
.featured-project-card h3,
.brand-card-refined h3 {
	max-width: 24ch;
	margin-bottom: 4px;
	text-wrap: balance;
	font-size: 15.5px;
	line-height: 1.16;
}

.featured-project-card h3 {
	font-size: clamp(1.12rem, 1.75vw, 1.45rem);
}

.signal-card p,
.skill-card p,
.proof-card-refined p,
.review-card p,
.related-card p,
.featured-project-card p,
.brand-card-refined p {
	margin-top: 0;
	font-size: 14.5px;
	line-height: 1.58;
}

.project-thumb,
.brand-thumb,
.featured-project-card .project-thumb {
	position: relative;
	margin: 0;
	aspect-ratio: 16 / 10;
	border-radius: 0;
	background:
		linear-gradient(135deg, rgba(29, 26, 23, .06), rgba(170, 95, 59, .08)),
		#ebe3d7;
}

.featured-project-card .project-thumb {
	border-bottom-color: rgba(54, 45, 36, .12);
}

.project-thumb img,
.brand-thumb img,
.featured-project-card .project-thumb img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: center top;
	transform: scale(.985);
	transition: transform .32s ease, filter .32s ease;
}

.featured-project-card:hover .project-thumb img {
	transform: scale(1.015);
}

.proof-media-strip {
	gap: 16px;
	margin-top: 34px;
	align-items: stretch;
}

.proof-media-strip figure {
	min-height: 0;
	aspect-ratio: 16 / 10;
	border-color: rgba(54, 45, 36, .13);
	background: #201c18;
}

.proof-media-strip figure:first-child {
	aspect-ratio: 16 / 9;
}

.proof-media-strip img {
	object-fit: cover;
	object-position: center top;
	opacity: .9;
	transform: scale(.992);
}

.proof-media-strip figure:hover img {
	opacity: .98;
	transform: scale(1.025);
}

.proof-media-strip figcaption {
	border-color: rgba(255, 255, 255, .14);
	background: rgba(22, 18, 15, .68);
}

.brand-grid-refined {
	gap: 14px;
}

.brand-card-refined {
	min-height: 150px;
	padding: 18px;
	background: rgba(255, 255, 255, .66);
}

.final-cta-card,
.global-schedule-card {
	max-width: 1080px;
}

/* Tablet refinements for the final polish layer. */
@media (max-width: 1120px) {
	.home-section-head,
	.section-heading {
		max-width: 680px;
	}

	.signal-grid {
		grid-template-columns: repeat(2, minmax(0, 1fr));
		gap: 14px;
	}

	.related-grid,
	.brand-grid-refined {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}

/* Mobile refinements for the final polish layer. */
@media (max-width: 920px) {
	.home-section-head,
	.section-heading {
		max-width: 100%;
		gap: 10px;
		margin-bottom: 26px;
	}

	.home-section-head h2,
	.cv-section h2,
	.cv-panel h2,
	.rollout-page h2,
	.documentation-page h2,
	.page-content h2,
	.version-grid h2,
	.command-panel h2,
	.global-schedule-card h2,
	.final-cta-card h2 {
		max-width: 12.5em;
		font-size: clamp(1.42rem, 7vw, 2rem);
		line-height: 1.09;
	}

	.section-support,
	.home-section-head p:not(.eyebrow) {
		max-width: 100%;
		font-size: 15px;
		line-height: 1.62;
	}

	.signal-card,
	.skill-card,
	.proof-card-refined,
	.review-card,
	.related-card,
	.brand-card-refined {
		min-height: auto;
		padding: 18px;
	}

	.review-grid {
		padding: 12px;
		background:
			linear-gradient(135deg, rgba(42, 36, 31, .94), rgba(69, 55, 43, .9));
	}

	.project-thumb,
	.brand-thumb,
	.featured-project-card .project-thumb,
	.proof-media-strip figure,
	.proof-media-strip figure:first-child {
		aspect-ratio: 4 / 3;
	}

	.proof-media-strip {
		gap: 12px;
		margin-top: 24px;
	}
}

/* Header reliability fixes: keep language/header controls visible while scrolling. */
html,
body {
	overflow-x: clip;
}

/* These !important values protect the sticky header from page-specific overrides. */
.site-header {
	position: sticky !important;
	top: 0 !important;
	z-index: 9999 !important;
	isolation: isolate;
}

.admin-bar .site-header {
	top: 32px !important;
}

.site-header .brand,
.site-header .main-nav a,
.site-header .lang-switch a,
.site-header .nav-cta {
	white-space: nowrap;
}

@media (max-width: 782px) {
	.admin-bar .site-header {
		top: 46px !important;
	}
}

@media (max-width: 920px) {
	.site-header {
		position: sticky !important;
		top: 0 !important;
	}

	.admin-bar .site-header {
		top: 32px !important;
	}

	.main-nav {
		z-index: 10000;
	}
}

/* High-contrast visual system: final readability pass.
 * Kept for history but currently overridden again by the mineral palette below. */
:root {
	--ink: #161412;
	--muted: #5b554d;
	--surface: #fffdf8;
	--surface-soft: #f0e8dc;
	--line: #cbbba7;
	--line-strong: #a89078;
	--shadow-soft: 0 18px 48px rgba(36, 27, 19, .1);
	--dark-surface: #221d18;
	--dark-surface-2: #3a3028;
	--dark-title: #fffaf2;
	--dark-body: rgba(255, 250, 242, .82);
	--dark-muted: rgba(255, 250, 242, .66);
}

body {
	color: var(--ink);
	background:
		linear-gradient(90deg, rgba(22, 20, 18, .04) 1px, transparent 1px),
		linear-gradient(rgba(22, 20, 18, .035) 1px, transparent 1px),
		#f2eadf;
	background-size: 64px 64px;
}

.site-main > .home-section:nth-of-type(odd) {
	background:
		radial-gradient(circle at 10% 8%, rgba(170, 95, 59, .08), transparent 24%),
		#fffaf2;
}

.site-main > .home-section:nth-of-type(even) {
	background:
		linear-gradient(90deg, rgba(29, 26, 23, .045) 1px, transparent 1px),
		linear-gradient(rgba(29, 26, 23, .04) 1px, transparent 1px),
		#eee4d6;
	background-size: 58px 58px;
}

.site-main > .home-section + .home-section,
.global-schedule-section {
	border-top: 1px solid rgba(120, 97, 76, .28);
}

.home-section-head h2,
.section-heading h2,
.cv-section h2,
.cv-panel h2,
.rollout-page h2,
.documentation-page h2,
.page-content h2,
.version-grid h2,
.command-panel h2,
.final-cta-card h2 {
	color: var(--ink);
}

.section-support,
.home-section-head p:not(.eyebrow),
.signal-card p,
.skill-card p,
.proof-card-refined p,
.related-card p,
.featured-project-card p,
.brand-card-refined p,
.cv-panel p,
.cv-summary-card p,
.rollout-page p,
.documentation-page p,
.page-content p {
	color: var(--muted);
}

.home-section-head .eyebrow,
.section-heading .eyebrow,
.project-kicker,
.featured-stack {
	color: #7a4d35;
}

.signal-card,
.skill-card,
.proof-card-refined,
.related-card,
.brand-card-refined,
.featured-project-card,
.cv-panel,
.cv-summary-card,
.language-card,
.cv-infra-card,
.cv-skill-group,
.rollout-proof-grid article,
.rollout-screen-card,
.directory-card,
.schedule-card {
	border-color: rgba(92, 70, 51, .24);
	background: #fffdf8;
	box-shadow: var(--shadow-soft);
}

.signal-card,
.related-card,
.brand-card-refined {
	background: #fbf6ee;
}

.skill-card,
.proof-card-refined,
.featured-project-card {
	background:
		linear-gradient(180deg, #fffefa 0%, #f8f0e5 100%);
}

.signal-card h3,
.skill-card h3,
.proof-card-refined h3,
.related-card h3,
.featured-project-card h3,
.brand-card-refined h3,
.cv-panel h3,
.cv-summary-card h3,
.rollout-screen-card h3 {
	color: var(--ink);
}

.skill-card:nth-child(1),
.skill-card:nth-child(7),
.review-grid,
.global-schedule-section,
.home-hero-panel-copy,
.home-hero .signal-item,
.tech-panel.dark {
	background:
		radial-gradient(circle at 14% 16%, rgba(217, 155, 115, .16), transparent 26%),
		linear-gradient(135deg, var(--dark-surface), var(--dark-surface-2));
	color: var(--dark-title);
}

.skill-card:nth-child(1) h3,
.skill-card:nth-child(7) h3,
.review-card h3,
.global-schedule-card h2,
.global-schedule-card .eyebrow,
.home-hero-panel-copy strong,
.home-hero .signal-item h3,
.tech-panel.dark h2,
.tech-panel.dark h3,
.tech-panel.dark strong {
	color: var(--dark-title);
}

.skill-card:nth-child(1) p,
.skill-card:nth-child(7) p,
.review-card p,
.global-schedule-card p,
.home-hero-panel-copy p,
.home-hero .signal-item p,
.tech-panel.dark p,
.tech-panel.dark li,
.tech-panel.dark .check-list {
	color: var(--dark-body);
}

.home-hero-panel-copy em,
.global-schedule-card .eyebrow {
	color: var(--dark-muted);
}

.review-card {
	border-color: rgba(255, 250, 242, .2);
	background: rgba(255, 255, 255, .1);
	color: var(--dark-title);
}

.review-card:hover {
	border-color: rgba(255, 250, 242, .48);
	background: rgba(255, 255, 255, .16);
}

.global-schedule-card {
	border-color: rgba(255, 250, 242, .24);
	background: rgba(255, 255, 255, .08);
}

.home-hero .button.ghost,
.button.ghost {
	border-color: rgba(22, 20, 18, .26);
	background: rgba(255, 253, 248, .72);
	color: var(--ink) !important;
}

.home-hero .button.ghost {
	border-color: rgba(255, 250, 242, .36);
	background: rgba(255, 255, 255, .12);
	color: #fffaf2 !important;
}

.project-thumb,
.brand-thumb,
.featured-project-card .project-thumb,
.proof-media-strip figure,
.rollout-shot-link {
	border-color: rgba(92, 70, 51, .22);
	background: #d8c7b2;
}

.project-thumb img,
.brand-thumb img,
.featured-project-card .project-thumb img,
.proof-media-strip img,
.rollout-shot-link img {
	object-fit: cover;
	object-position: center top;
	filter: saturate(.98) contrast(1.03);
}

.proof-media-strip figcaption {
	background: rgba(20, 17, 14, .78);
	color: var(--dark-title);
}

.proof-media-strip figcaption span {
	color: var(--dark-body);
}

@media (max-width: 920px) {
	.signal-card p,
	.skill-card p,
	.proof-card-refined p,
	.related-card p,
	.featured-project-card p,
	.brand-card-refined p {
		font-size: 15px;
		line-height: 1.62;
	}
}

/* Active palette: mineral light, deep navy and teal accents.
 * This is the final color/token layer for most public pages. */
:root {
	--bg: #eef3f6;
	--surface: #ffffff;
	--surface-soft: #e8eef2;
	--ink: #111827;
	--muted: #475569;
	--line: #cbd5df;
	--line-strong: #94a3b8;
	--charcoal: #0f172a;
	--silver: #64748b;
	--accent: #0f766e;
	--accent-soft: #d9f0ed;
	--accent-deep: #115e59;
	--shadow-soft: 0 18px 54px rgba(15, 23, 42, .1);
	--shadow: 0 28px 80px rgba(15, 23, 42, .13);
	--dark-surface: #0f172a;
	--dark-surface-2: #1e293b;
	--dark-title: #f8fafc;
	--dark-body: rgba(226, 232, 240, .86);
	--dark-muted: rgba(203, 213, 225, .72);
}

body {
	color: var(--ink);
	background:
		radial-gradient(circle at 14% 0%, rgba(15, 118, 110, .1), transparent 28%),
		linear-gradient(90deg, rgba(15, 23, 42, .035) 1px, transparent 1px),
		linear-gradient(rgba(15, 23, 42, .032) 1px, transparent 1px),
		var(--bg);
	background-size: auto, 72px 72px, 72px 72px, auto;
}

.site-header {
	border-bottom-color: rgba(148, 163, 184, .42);
	background: rgba(248, 250, 252, .86);
}

.brand-mark,
.nav-cta,
.button.primary {
	background: linear-gradient(135deg, #0f172a, #164e63);
	color: #fff !important;
}

.lang-switch {
	border-color: rgba(148, 163, 184, .5);
	background: rgba(255, 255, 255, .78);
}

.lang-switch a.is-active {
	background: #0f766e;
	color: #fff;
}

.main-nav a,
.brand em {
	color: #526173;
}

.main-nav a:hover {
	background: rgba(15, 118, 110, .08);
	color: #0f172a;
}

.home-hero {
	background:
		linear-gradient(135deg, rgba(8, 13, 24, .9), rgba(14, 63, 71, .68)),
		radial-gradient(circle at top right, rgba(20, 184, 166, .28), transparent 30%);
}

.home-hero-backdrop img {
	filter: saturate(.86) contrast(1.06) brightness(.48);
}

.site-main > .home-section:nth-of-type(odd) {
	background:
		radial-gradient(circle at 12% 10%, rgba(15, 118, 110, .075), transparent 24%),
		#f8fafc;
}

.site-main > .home-section:nth-of-type(even) {
	background:
		linear-gradient(90deg, rgba(15, 23, 42, .036) 1px, transparent 1px),
		linear-gradient(rgba(15, 23, 42, .032) 1px, transparent 1px),
		#e9f0f4;
	background-size: 64px 64px;
}

.site-main > .home-section + .home-section,
.global-schedule-section,
.site-footer {
	border-top-color: rgba(100, 116, 139, .26);
}

.home-section-head .eyebrow,
.section-heading .eyebrow,
.project-kicker,
.featured-stack,
.home-panel-kicker {
	color: #0f766e;
}

.home-section-head h2,
.section-heading h2,
.cv-section h2,
.cv-panel h2,
.rollout-page h2,
.documentation-page h2,
.page-content h2,
.version-grid h2,
.command-panel h2,
.final-cta-card h2,
.signal-card h3,
.skill-card h3,
.proof-card-refined h3,
.related-card h3,
.featured-project-card h3,
.brand-card-refined h3,
.cv-panel h3,
.cv-summary-card h3,
.rollout-screen-card h3 {
	color: #111827;
}

.section-support,
.home-section-head p:not(.eyebrow),
.signal-card p,
.skill-card p,
.proof-card-refined p,
.related-card p,
.featured-project-card p,
.brand-card-refined p,
.cv-panel p,
.cv-summary-card p,
.rollout-page p,
.documentation-page p,
.page-content p {
	color: #475569;
}

.signal-card,
.skill-card,
.proof-card-refined,
.related-card,
.brand-card-refined,
.featured-project-card,
.cv-panel,
.cv-summary-card,
.language-card,
.cv-infra-card,
.cv-skill-group,
.rollout-proof-grid article,
.rollout-screen-card,
.directory-card,
.schedule-card,
.final-cta-card {
	border-color: rgba(100, 116, 139, .24);
	background: rgba(255, 255, 255, .92);
	box-shadow: var(--shadow-soft);
}

.signal-card,
.related-card,
.brand-card-refined {
	background: rgba(248, 250, 252, .92);
}

.related-systems-section .related-card {
	transition: background .22s ease, border-color .22s ease, box-shadow .22s ease, transform .22s ease;
}

.related-systems-section .related-card:hover {
	border-color: rgba(20, 184, 166, .38);
	background:
		radial-gradient(circle at top right, rgba(45, 212, 191, .18), transparent 34%),
		linear-gradient(180deg, rgba(255, 255, 255, .98) 0%, rgba(224, 242, 254, .94) 100%);
	box-shadow: 0 16px 34px rgba(14, 116, 144, .14);
	transform: translateY(-4px);
}

.related-systems-section .related-card:hover h3 {
	color: #0f172a;
}

.related-systems-section .related-card:hover p {
	color: #334155;
}

.signal-card,
.cv-summary-card,
.cv-panel,
.cv-skill-group,
.cv-infra-card,
.cv-signal-chip,
.cv-contact-card {
	transition: background .22s ease, border-color .22s ease, box-shadow .22s ease, transform .22s ease;
}

.signal-card:hover,
.cv-summary-card:hover,
.cv-panel:hover,
.cv-skill-group:hover,
.cv-infra-card:hover,
.cv-signal-chip:hover,
.cv-contact-card:hover {
	border-color: rgba(20, 184, 166, .38);
	background:
		radial-gradient(circle at top right, rgba(45, 212, 191, .18), transparent 34%),
		linear-gradient(180deg, rgba(255, 255, 255, .98) 0%, rgba(224, 242, 254, .94) 100%);
	box-shadow: 0 16px 34px rgba(14, 116, 144, .14);
	transform: translateY(-4px);
}

.signal-card:hover h3,
.cv-summary-card:hover h3,
.cv-panel:hover h2,
.cv-panel:hover h3,
.cv-skill-group:hover h3,
.cv-infra-card:hover h3,
.cv-contact-card:hover dt,
.cv-contact-card:hover dd,
.cv-signal-chip:hover strong,
.cv-signal-chip:hover span {
	color: #0f172a;
}

.signal-card:hover p,
.cv-summary-card:hover p,
.cv-panel:hover p,
.cv-skill-group:hover p,
.cv-infra-card:hover p,
.cv-contact-card:hover p {
	color: #334155;
}

.skill-card,
.proof-card-refined,
.featured-project-card {
	background:
		linear-gradient(180deg, #ffffff 0%, #f1f5f9 100%);
}

.signal-card h3:before,
.skill-card h3:before,
.proof-card-refined h3:before,
.review-card h3:before {
	background: #0f766e;
}

.skill-card:nth-child(1),
.skill-card:nth-child(7),
.review-grid,
.global-schedule-section,
.home-hero-panel-copy,
.home-hero .signal-item,
.tech-panel.dark {
	background:
		radial-gradient(circle at 16% 14%, rgba(20, 184, 166, .18), transparent 26%),
		linear-gradient(135deg, #0f172a, #1e293b);
	color: var(--dark-title);
}

.review-card {
	border-color: rgba(226, 232, 240, .2);
	background: rgba(255, 255, 255, .08);
}

.review-card:hover {
	border-color: rgba(45, 212, 191, .52);
	background: rgba(255, 255, 255, .14);
}

/* Special tall skill card for performance/monitoring content. */
.skill-card-performance-tower {
	position: relative;
	display: grid;
	align-content: start;
	gap: 14px;
	grid-column: 4;
	grid-row: 2 / span 2;
	min-height: auto;
	background:
		radial-gradient(circle at top right, rgba(14, 165, 233, .18), transparent 34%),
		linear-gradient(180deg, rgba(255, 255, 255, .98) 0%, rgba(241, 245, 249, .94) 100%);
}

.skill-card-performance-tower .skill-card-kicker {
	margin: 0;
	color: #0f766e;
	font-size: 11px;
	font-weight: 800;
	letter-spacing: .14em;
	text-transform: uppercase;
}

.skill-card-performance-tower h3 {
	margin: 0;
	max-width: 12ch;
	font-size: clamp(1.35rem, 2.2vw, 1.8rem);
	line-height: 1.02;
	letter-spacing: -.04em;
}

.skill-card-performance-tower h3:before {
	background: linear-gradient(90deg, #0f766e, #38bdf8);
}

.skill-card-performance-tower p {
	margin: 0;
}

/* Skill cards with structured kicker text, bullets and metric lists. */
.skill-card:not(.skill-card-performance-tower) {
	position: relative;
	overflow: hidden;
	background:
		radial-gradient(circle at top right, rgba(14, 165, 233, .12), transparent 34%),
		linear-gradient(180deg, rgba(255, 255, 255, .98) 0%, rgba(241, 245, 249, .92) 100%);
}

.skill-card:not(.skill-card-performance-tower):before {
	content: "";
	position: absolute;
	inset: 0;
	background:
		linear-gradient(135deg, rgba(255, 255, 255, .22), transparent 48%);
	pointer-events: none;
}

.skill-card .skill-card-kicker {
	margin: 0;
	color: #0f766e;
	font-size: 11px;
	font-weight: 800;
	letter-spacing: .14em;
	text-transform: uppercase;
}

.skill-card-bullets {
	display: grid;
	gap: 8px;
	margin: 6px 0 0;
	padding: 0;
	list-style: none;
}

.skill-card-bullets li {
	position: relative;
	padding-left: 16px;
	color: #475569;
	font-size: 13px;
	line-height: 1.45;
}

.skill-card-bullets li:before {
	content: "";
	position: absolute;
	left: 0;
	top: 7px;
	width: 7px;
	height: 7px;
	border-radius: 999px;
	background: linear-gradient(180deg, #14b8a6, #0ea5e9);
	box-shadow: 0 0 0 4px rgba(20, 184, 166, .1);
}

.skill-card:nth-child(1) .skill-card-kicker,
.skill-card:nth-child(7) .skill-card-kicker {
	color: #d7e8f5;
}

.skill-card:nth-child(1):not(.skill-card-performance-tower),
.skill-card:nth-child(7):not(.skill-card-performance-tower) {
	background:
		radial-gradient(circle at 16% 14%, rgba(20, 184, 166, .18), transparent 26%),
		linear-gradient(135deg, #0f172a, #1e293b);
	color: var(--dark-title);
}

.skill-card:nth-child(1) .skill-card-bullets li,
.skill-card:nth-child(7) .skill-card-bullets li {
	color: rgba(226, 232, 240, .82);
}

.skill-card:nth-child(1) .skill-card-bullets li:before,
.skill-card:nth-child(7) .skill-card-bullets li:before {
	background: linear-gradient(180deg, #d99b73, #f7c8a5);
	box-shadow: 0 0 0 4px rgba(217, 155, 115, .12);
}

.skill-card-metrics {
	display: grid;
	gap: 10px;
	margin: 2px 0 0;
	padding: 0;
	list-style: none;
}

.skill-card-metrics li {
	position: relative;
	padding-left: 18px;
	color: #334155;
	font-size: 14px;
	line-height: 1.5;
}

.skill-card-metrics li:before {
	content: "";
	position: absolute;
	left: 0;
	top: 8px;
	width: 8px;
	height: 8px;
	border-radius: 999px;
	background: linear-gradient(180deg, #14b8a6, #0ea5e9);
	box-shadow: 0 0 0 4px rgba(20, 184, 166, .12);
}

/* Mobile menu hard override: closed menu must stay display:none until JS opens it. */
@media (max-width: 920px) {
	.site-header .main-nav,
	.site-header .main-nav[hidden] {
		display: none !important;
	}

	.site-header.is-menu-open .main-nav,
	.site-header.is-menu-open .main-nav[hidden] {
		display: grid !important;
		opacity: 1;
		pointer-events: auto;
		transform: translateY(0);
	}

	.skill-card-performance-tower {
		grid-column: auto;
		grid-row: auto;
		min-height: 220px;
	}
}

.global-schedule-card {
	border-color: rgba(226, 232, 240, .22);
	background:
		linear-gradient(135deg, rgba(255, 255, 255, .11), rgba(255, 255, 255, .05));
}

.global-schedule-card .button.primary {
	background: #f8fafc;
	color: #0f172a !important;
	border-color: #f8fafc;
}

.button.ghost {
	border-color: rgba(15, 23, 42, .22);
	background: rgba(255, 255, 255, .76);
	color: #111827 !important;
}

.home-hero .button.ghost {
	border-color: rgba(226, 232, 240, .34);
	background: rgba(255, 255, 255, .1);
	color: #f8fafc !important;
}

.project-thumb,
.brand-thumb,
.featured-project-card .project-thumb,
.proof-media-strip figure,
.rollout-shot-link {
	border-color: rgba(100, 116, 139, .24);
	background: #d8e2e8;
}

.project-thumb img,
.brand-thumb img,
.featured-project-card .project-thumb img,
.proof-media-strip img,
.rollout-shot-link img {
	filter: saturate(.95) contrast(1.04);
}

.proof-media-strip figcaption {
	background: rgba(15, 23, 42, .78);
	color: #f8fafc;
}

.proof-media-strip figcaption span {
	color: rgba(226, 232, 240, .86);
}

.brand-card-refined span {
	background: #d9f0ed;
	color: #115e59;
}

/* Section title alignment: keep every section heading anchored left. */
.home-section-head,
.section-heading,
.cv-section,
.cv-panel,
.rollout-proof-section,
.rollout-screens-section,
.page-content,
.documentation-page,
.directory-page,
.versioning-page,
.single-project,
.final-cta-card,
.global-schedule-card,
.schedule-card {
	text-align: left;
}

.home-section-head,
.section-heading {
	justify-items: start;
	align-items: start;
	margin-left: 0;
	margin-right: auto;
}

.home-section-head h1,
.home-section-head h2,
.section-heading h1,
.section-heading h2,
.cv-section h1,
.cv-section h2,
.cv-panel h1,
.cv-panel h2,
.rollout-page h1,
.rollout-page h2,
.documentation-page h1,
.documentation-page h2,
.page-content h1,
.page-content h2,
.version-grid h2,
.command-panel h2,
.final-cta-card h2,
.global-schedule-card h2,
.schedule-card h2 {
	margin-left: 0;
	margin-right: auto;
	text-align: left;
}

.section-support,
.home-section-head p:not(.eyebrow),
.final-cta-card p,
.global-schedule-card p,
.schedule-card p {
	margin-left: 0;
	margin-right: auto;
	text-align: left;
}

/* Section flow: soften transitions so the page reads as one continuous system. */
.site-main {
	overflow: visible;
}

.site-main > .home-section,
.global-schedule-section {
	overflow: hidden;
	border-top: 0 !important;
}

.site-main > .home-section {
	padding-top: clamp(74px, 8vw, 118px);
	padding-bottom: clamp(74px, 8vw, 118px);
}

.site-main > .home-section + .home-section {
	margin-top: -1px;
}

.site-main > .home-section::before,
.site-main > .home-section::after,
.global-schedule-section::before {
	content: "";
	position: absolute;
	right: 0;
	left: 0;
	z-index: 0;
	pointer-events: none;
}

.site-main > .home-section::before {
	top: 0;
	height: clamp(44px, 6vw, 88px);
	background: linear-gradient(180deg, rgba(238, 243, 246, .72), rgba(238, 243, 246, 0));
}

.site-main > .home-section::after {
	bottom: 0;
	height: clamp(58px, 7vw, 110px);
	background:
		radial-gradient(ellipse at 20% 100%, rgba(15, 118, 110, .075), transparent 46%),
		linear-gradient(180deg, rgba(248, 250, 252, 0), rgba(226, 235, 240, .42));
}

.site-main > .home-section:nth-of-type(even)::before {
	background: linear-gradient(180deg, rgba(248, 250, 252, .68), rgba(248, 250, 252, 0));
}

.site-main > .home-section:nth-of-type(even)::after {
	background:
		radial-gradient(ellipse at 78% 100%, rgba(14, 78, 99, .075), transparent 48%),
		linear-gradient(180deg, rgba(233, 240, 244, 0), rgba(248, 250, 252, .54));
}

.site-main > .home-section > *,
.global-schedule-card {
	position: relative;
	z-index: 1;
}

.site-main > .home-section:nth-of-type(odd) {
	background:
		radial-gradient(circle at 14% 12%, rgba(15, 118, 110, .07), transparent 24%),
		linear-gradient(180deg, #f8fafc 0%, #f4f8fa 56%, #e9f0f4 100%);
}

.site-main > .home-section:nth-of-type(even) {
	background:
		linear-gradient(90deg, rgba(15, 23, 42, .03) 1px, transparent 1px),
		linear-gradient(rgba(15, 23, 42, .028) 1px, transparent 1px),
		linear-gradient(180deg, #e9f0f4 0%, #edf4f7 52%, #f8fafc 100%);
	background-size: 64px 64px, 64px 64px, auto;
}

.global-schedule-section {
	margin-top: -1px;
	padding-top: clamp(86px, 9vw, 132px);
	background:
		linear-gradient(180deg, #f8fafc 0%, rgba(248, 250, 252, 0) 30%),
		radial-gradient(circle at 82% 18%, rgba(20, 184, 166, .2), transparent 30%),
		linear-gradient(135deg, #0f172a, #1e293b);
}

.global-schedule-section::before {
	top: 0;
	height: clamp(70px, 9vw, 132px);
	background:
		linear-gradient(180deg, rgba(248, 250, 252, .98), rgba(248, 250, 252, 0));
}

.site-footer {
	background:
		linear-gradient(180deg, #0f172a 0%, #111827 100%);
	color: rgba(226, 232, 240, .78);
	border-top: 0;
}

.site-footer a {
	color: #99f6e4;
}

@media (max-width: 920px) {
	.site-main > .home-section {
		padding-top: 58px;
		padding-bottom: 58px;
	}

	.site-main > .home-section::before {
		height: 38px;
	}

	.site-main > .home-section::after {
		height: 54px;
	}

	.global-schedule-section {
		padding-top: 76px;
	}
}

/* Hard alignment override for section headers that inherit centered composition. */
.site-main .home-section .home-section-head,
.site-main .home-section .section-heading,
.site-main .related-systems-section .home-section-head {
	display: grid;
	justify-content: start !important;
	justify-items: start !important;
	align-items: start !important;
	text-align: left !important;
}

.site-main .home-section .home-section-head > *,
.site-main .home-section .section-heading > *,
.site-main .related-systems-section .home-section-head > * {
	margin-left: 0 !important;
	margin-right: auto !important;
	text-align: left !important;
}

.site-main .home-section .home-section-head h1,
.site-main .home-section .home-section-head h2,
.site-main .home-section .section-heading h1,
.site-main .home-section .section-heading h2 {
	text-align: left !important;
}

/* Continuous page background: connect sections instead of splitting them into bands. */
body {
	background:
		radial-gradient(circle at 14% 12%, rgba(15, 118, 110, .12), transparent 28%),
		radial-gradient(circle at 86% 34%, rgba(14, 78, 99, .1), transparent 30%),
		radial-gradient(circle at 18% 72%, rgba(59, 130, 246, .075), transparent 32%),
		linear-gradient(180deg, #eef3f6 0%, #f8fafc 38%, #edf4f7 68%, #f8fafc 100%);
	background-attachment: fixed, fixed, fixed, scroll;
}

/* Main content gets a fixed grid overlay behind sections. */
.site-main {
	position: relative;
	width: min(1180px, calc(100% - 40px));
	overflow: visible;
}

/* Decorative grid layer; pointer-events prevents it from blocking links/buttons. */
.site-main::before {
	content: "";
	position: fixed;
	inset: 0;
	z-index: -1;
	pointer-events: none;
	background:
		linear-gradient(90deg, rgba(15, 23, 42, .026) 1px, transparent 1px),
		linear-gradient(rgba(15, 23, 42, .024) 1px, transparent 1px);
	background-size: 72px 72px;
	mask-image: linear-gradient(180deg, rgba(0, 0, 0, .72), rgba(0, 0, 0, .28));
}

.site-main > .home-section {
	background: transparent !important;
	overflow: visible;
	padding-top: clamp(72px, 7.6vw, 110px);
	padding-bottom: clamp(72px, 7.6vw, 110px);
}

.site-main > .home-section + .home-section {
	margin-top: 0;
}

.site-main > .home-section::before,
.site-main > .home-section::after {
	display: none !important;
}

.site-main > .home-section > * {
	position: relative;
	z-index: 1;
}

.site-main > .home-section > *::before {
	content: "";
	position: absolute;
	inset: -28px -34px;
	z-index: -1;
	pointer-events: none;
	border-radius: 40px;
	background:
		radial-gradient(circle at 0% 0%, rgba(255, 255, 255, .44), transparent 34%),
		linear-gradient(180deg, rgba(255, 255, 255, .18), rgba(255, 255, 255, 0));
	opacity: .72;
}

.site-main > .home-section:nth-of-type(even) > *::before {
	background:
		radial-gradient(circle at 100% 0%, rgba(255, 255, 255, .36), transparent 36%),
		linear-gradient(180deg, rgba(255, 255, 255, .12), rgba(255, 255, 255, 0));
}

.global-schedule-section {
	margin-top: clamp(22px, 4vw, 46px);
	background:
		linear-gradient(180deg, rgba(248, 250, 252, 0) 0%, rgba(15, 23, 42, .18) 28%, rgba(15, 23, 42, .98) 72%),
		radial-gradient(circle at 80% 22%, rgba(20, 184, 166, .2), transparent 30%),
		linear-gradient(135deg, #0f172a, #1e293b);
}

.global-schedule-section::before {
	display: none !important;
}

@media (max-width: 920px) {
	.site-main {
		width: min(1220px, calc(100% - 28px));
	}

	.site-main > .home-section {
		padding-top: 56px;
		padding-bottom: 56px;
	}

	.site-main > .home-section > *::before {
		inset: -18px -16px;
		border-radius: 28px;
	}
}

/* Title block width fix: keep heading blocks on the content grid, not centered as narrow islands. */
.site-main > .home-section > .home-section-head,
.site-main > .home-section > .section-heading,
.site-main > .home-section > .home-section-head.compact,
.site-main > .home-section > .home-section-head.split,
.site-main > .related-systems-section > .home-section-head {
	width: min(1160px, calc(100% - 44px)) !important;
	max-width: min(1160px, calc(100% - 44px)) !important;
	margin-left: auto !important;
	margin-right: auto !important;
	text-align: left !important;
}

.site-main > .home-section > .home-section-head:not(.split),
.site-main > .home-section > .section-heading:not(.split),
.site-main > .related-systems-section > .home-section-head:not(.split) {
	display: grid !important;
	grid-template-columns: minmax(0, 700px) 1fr;
}

.site-main > .home-section > .home-section-head:not(.split) > *,
.site-main > .home-section > .section-heading:not(.split) > *,
.site-main > .related-systems-section > .home-section-head:not(.split) > * {
	grid-column: 1;
}

.site-main > .home-section > .home-section-head h1,
.site-main > .home-section > .home-section-head h2,
.site-main > .home-section > .section-heading h1,
.site-main > .home-section > .section-heading h2 {
	max-width: 700px;
	margin-left: 0 !important;
	margin-right: 0 !important;
	text-align: left !important;
}

.site-main > .home-section > .home-section-head p,
.site-main > .home-section > .section-heading p {
	max-width: 660px;
	margin-left: 0 !important;
	margin-right: 0 !important;
	text-align: left !important;
}

@media (max-width: 920px) {
	.site-main > .home-section > .home-section-head,
	.site-main > .home-section > .section-heading,
	.site-main > .home-section > .home-section-head.compact,
	.site-main > .home-section > .home-section-head.split,
	.site-main > .related-systems-section > .home-section-head {
		width: min(1220px, calc(100% - 28px)) !important;
		max-width: min(1220px, calc(100% - 28px)) !important;
	}

	.site-main > .home-section > .home-section-head:not(.split),
	.site-main > .home-section > .section-heading:not(.split),
	.site-main > .related-systems-section > .home-section-head:not(.split) {
		grid-template-columns: 1fr;
	}
}

/* Full-width structured footer. */
.site-footer {
	width: 100vw !important;
	max-width: none !important;
	margin-left: calc(50% - 50vw) !important;
	margin-right: 0 !important;
	padding: 0 !important;
	border-top: 1px solid rgba(148, 163, 184, .16);
	background:
		radial-gradient(circle at 14% 0%, rgba(20, 184, 166, .12), transparent 28%),
		linear-gradient(180deg, #0f172a 0%, #090e1a 100%);
	color: rgba(226, 232, 240, .8);
}

/* Footer content wrapper and brand/navigation layout. */
.footer-inner {
	width: min(1160px, calc(100% - 44px));
	margin: 0 auto;
	padding: clamp(46px, 6vw, 76px) 0 32px;
}

.footer-brand {
	display: grid;
	grid-template-columns: auto minmax(0, 660px);
	gap: 18px;
	align-items: start;
	padding-bottom: clamp(32px, 4vw, 46px);
	border-bottom: 1px solid rgba(226, 232, 240, .12);
}

.footer-mark {
	display: grid;
	place-items: center;
	width: 54px;
	height: 54px;
	border: 1px solid rgba(226, 232, 240, .16);
	border-radius: 18px;
	background: linear-gradient(135deg, rgba(20, 184, 166, .18), rgba(255, 255, 255, .06));
	color: #f8fafc;
	font-size: 13px;
	font-weight: 900;
	letter-spacing: .08em;
}

.footer-brand strong {
	display: block;
	margin-bottom: 10px;
	color: #f8fafc;
	font-size: clamp(1.25rem, 2vw, 1.65rem);
	line-height: 1.1;
	letter-spacing: -.035em;
}

.footer-brand p {
	max-width: 720px;
	margin: 0;
	color: rgba(226, 232, 240, .76);
	font-size: 15.5px;
	line-height: 1.65;
}

.footer-nav {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: clamp(24px, 5vw, 72px);
	padding: clamp(32px, 5vw, 56px) 0;
}

.footer-nav h2 {
	margin: 0 0 16px;
	color: #f8fafc;
	font-family: Aptos, "Segoe UI", sans-serif;
	font-size: 11px;
	font-weight: 900;
	letter-spacing: .16em;
	text-transform: uppercase;
}

.footer-nav a {
	display: table;
	margin: 10px 0 0;
	color: rgba(226, 232, 240, .74);
	font-size: 14px;
	font-weight: 700;
	text-decoration: none;
	transition: color .18s ease, transform .18s ease;
}

.footer-nav a:hover {
	color: #99f6e4;
	transform: translateX(3px);
}

.footer-bottom {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 18px;
	padding-top: 22px;
	border-top: 1px solid rgba(226, 232, 240, .12);
	color: rgba(203, 213, 225, .66);
	font-size: 13px;
}

/* Footer stacks on mobile. */
@media (max-width: 920px) {
	.footer-inner {
		width: min(1220px, calc(100% - 28px));
		padding-top: 42px;
	}

	.footer-brand,
	.footer-nav {
		grid-template-columns: 1fr;
	}

	.footer-nav {
		gap: 24px;
	}

	.footer-bottom {
		align-items: flex-start;
		flex-direction: column;
	}
}

/* Footer editorial upgrade: stronger rhythm, guided tour entry and technical signal chips. */
.site-footer {
	position: relative;
	overflow: hidden;
	background:
		radial-gradient(circle at 12% 0%, rgba(45, 212, 191, .2), transparent 28%),
		radial-gradient(circle at 86% 18%, rgba(59, 130, 246, .16), transparent 30%),
		linear-gradient(180deg, #101827 0%, #080d17 100%);
}

/* Decorative footer grid overlay. */
.site-footer::before {
	content: "";
	position: absolute;
	inset: 0;
	pointer-events: none;
	background:
		linear-gradient(90deg, rgba(226, 232, 240, .045) 1px, transparent 1px),
		linear-gradient(rgba(226, 232, 240, .035) 1px, transparent 1px);
	background-size: 72px 72px;
	mask-image: linear-gradient(180deg, rgba(0, 0, 0, .42), transparent 76%);
}

.footer-inner {
	position: relative;
	z-index: 1;
}

/* Top footer pill with guided tour link. */
.footer-kicker {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 16px;
	margin-bottom: 26px;
	border: 1px solid rgba(226, 232, 240, .13);
	border-radius: 999px;
	padding: 8px 8px 8px 18px;
	background: rgba(255, 255, 255, .045);
	backdrop-filter: blur(14px);
}

.footer-kicker span {
	color: rgba(226, 232, 240, .76);
	font-size: 11px;
	font-weight: 900;
	letter-spacing: .16em;
	text-transform: uppercase;
}

.footer-kicker a {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	border-radius: 999px;
	padding: 10px 14px;
	background: #99f6e4;
	color: #0f172a;
	font-size: 12px;
	font-weight: 900;
	letter-spacing: .08em;
	text-decoration: none;
	text-transform: uppercase;
}

.footer-brand {
	grid-template-columns: auto minmax(0, 760px);
	padding: clamp(30px, 4vw, 46px);
	border: 1px solid rgba(226, 232, 240, .13);
	border-radius: 32px;
	background:
		linear-gradient(135deg, rgba(255, 255, 255, .08), rgba(255, 255, 255, .035));
	box-shadow: 0 30px 90px rgba(0, 0, 0, .18);
}

.footer-brand strong {
	max-width: 15ch;
	font-family: Georgia, "Times New Roman", serif;
	font-size: clamp(1.85rem, 4vw, 3.3rem);
	line-height: .98;
	letter-spacing: -.055em;
}

.footer-brand p {
	margin-top: 18px;
	color: rgba(226, 232, 240, .82);
	font-size: clamp(15px, 1.4vw, 18px);
}

/* Technical signal chips inside the footer. */
.footer-signal-grid {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: 12px;
	margin: 16px 0 clamp(30px, 5vw, 52px);
}

.footer-signal-grid span {
	border: 1px solid rgba(226, 232, 240, .12);
	border-radius: 18px;
	padding: 14px 16px;
	background: rgba(255, 255, 255, .045);
	color: rgba(226, 232, 240, .78);
	font-size: 13px;
	font-weight: 800;
}

.footer-nav {
	gap: 18px;
	padding: 0 0 clamp(30px, 4vw, 44px);
}

.footer-nav > div {
	border: 1px solid rgba(226, 232, 240, .12);
	border-radius: 24px;
	padding: 22px;
	background: rgba(255, 255, 255, .035);
}

.footer-bottom {
	border-top-color: rgba(226, 232, 240, .14);
}

@media (max-width: 920px) {
	.footer-kicker,
	.footer-bottom {
		align-items: flex-start;
		flex-direction: column;
		border-radius: 24px;
	}

	.footer-kicker a {
		width: 100%;
	}

	.footer-brand,
	.footer-signal-grid {
		grid-template-columns: 1fr;
	}

	.footer-brand {
		padding: 24px;
		border-radius: 26px;
	}

	.footer-brand strong {
		max-width: 100%;
	}
}

/* Portfolio fixes 2026-04-22: proof media overlap + clickable brand mocks. */
.proof-media-strip {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: clamp(18px, 2.2vw, 28px);
	align-items: stretch;
	margin-top: clamp(36px, 5vw, 64px);
}

.proof-media-strip figure,
.proof-media-strip figure:first-child {
	display: flex;
	flex-direction: column;
	min-width: 0;
	min-height: 0;
	aspect-ratio: auto;
	overflow: hidden;
	border-radius: 24px;
	background: rgba(248, 250, 252, .94);
	box-shadow: 0 22px 62px rgba(15, 23, 42, .09);
}

.proof-media-strip img {
	width: 100%;
	height: auto;
	aspect-ratio: 16 / 9;
	object-fit: cover;
	object-position: center top;
	opacity: 1;
	transform: none;
}

.proof-media-strip figure:hover img {
	opacity: 1;
	transform: none;
}

.proof-media-strip figcaption {
	position: static;
	display: grid;
	gap: 5px;
	min-height: 116px;
	border: 0;
	border-top: 1px solid rgba(100, 116, 139, .16);
	border-radius: 0;
	padding: 18px 20px 20px;
	background: #0f172a;
	backdrop-filter: none;
	color: #f8fafc;
}

.proof-media-strip figcaption strong {
	font-size: 16px;
	line-height: 1.2;
}

.proof-media-strip figcaption span {
	color: rgba(226, 232, 240, .86);
	font-size: 13.5px;
	line-height: 1.48;
}

.brand-grid-refined {
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: clamp(18px, 2vw, 24px);
}

.brand-card-refined {
	display: grid;
	grid-template-rows: auto auto auto 1fr;
	gap: 12px;
	min-width: 0;
	min-height: 100%;
	padding: 18px;
	color: inherit;
	text-decoration: none;
	filter: none;
	opacity: 1;
}

.brand-card-refined:hover {
	filter: none;
	opacity: 1;
	transform: translateY(-4px);
}

.brand-mock {
	position: relative;
	margin: 0 0 2px;
	overflow: hidden;
	aspect-ratio: 16 / 10;
	border: 1px solid rgba(100, 116, 139, .18);
	border-radius: 18px;
	background: linear-gradient(180deg, #f8fafc, #e2e8f0);
	box-shadow: inset 0 1px 0 rgba(255, 255, 255, .88);
}

.brand-mock-bar {
	position: absolute;
	top: 0;
	left: 0;
	z-index: 2;
	display: flex;
	align-items: center;
	gap: 5px;
	width: 100%;
	height: 24px;
	padding: 0 10px;
	border-bottom: 1px solid rgba(100, 116, 139, .14);
	background: rgba(248, 250, 252, .92);
}

.brand-mock-bar i {
	width: 6px;
	height: 6px;
	border-radius: 999px;
	background: rgba(15, 23, 42, .2);
}

.brand-mock img {
	display: block;
	width: 100%;
	height: 100%;
	padding-top: 24px;
	object-fit: cover;
	object-position: center top;
	filter: saturate(.98) contrast(1.03);
	transform: none;
	transition: transform .24s ease;
}

.brand-card-refined:hover .brand-mock img {
	transform: scale(1.025);
}

.brand-card-refined > span:not(.brand-mock-bar) {
	margin-bottom: 0;
}

@media (max-width: 1100px) {
	.brand-grid-refined,
	.proof-media-strip {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}

@media (max-width: 720px) {
	.brand-grid-refined,
	.proof-media-strip {
		grid-template-columns: 1fr;
	}
}

/* Verdian assessment/take-home page: custom layout, gallery and lightbox. */
.verdian-page {
	display: grid;
	gap: 34px;
	margin: clamp(28px, 4vw, 54px) auto clamp(72px, 8vw, 120px);
}

.verdian-hero,
.verdian-panel,
.verdian-gallery-card {
	border: 1px solid rgba(23, 23, 23, .08);
	border-radius: 28px;
	background: rgba(255, 255, 255, .92);
	box-shadow: 0 18px 50px rgba(20, 20, 20, .06);
}

.verdian-hero {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(220px, .34fr);
	gap: 24px;
	padding: clamp(28px, 4vw, 46px);
	background:
		radial-gradient(circle at top right, rgba(14, 165, 233, .16), transparent 28%),
		radial-gradient(circle at left top, rgba(249, 115, 22, .14), transparent 26%),
		linear-gradient(180deg, rgba(255,255,255,.97), rgba(247,247,244,.94));
}

.verdian-hero-copy {
	display: grid;
	align-content: start;
}

.verdian-hero h1,
.verdian-section h2 {
	margin: 0;
	letter-spacing: -.045em;
	line-height: .98;
}

.verdian-hero h1 {
	font-size: clamp(2.2rem, 4vw, 4rem);
}

.verdian-subtitle {
	max-width: 56rem;
	margin: 14px 0 0;
	color: var(--muted);
	font-size: clamp(1rem, 1.5vw, 1.2rem);
}

.verdian-hero-copy .button.primary {
	margin-top: 16px;
}

.verdian-hero-copy .verdian-richtext + .button.primary {
	margin-top: 18px;
}

.verdian-anchor-nav {
	display: grid;
	gap: 8px;
	align-content: start;
}

/* Anchor pills jump to each assessment question section. */
.verdian-anchor-nav a {
	border: 1px solid var(--line);
	border-radius: 999px;
	padding: 10px 14px;
	background: rgba(244, 244, 242, .8);
	color: var(--charcoal);
	font-size: 12px;
	font-weight: 800;
	letter-spacing: .08em;
	text-decoration: none;
	text-transform: uppercase;
}

.verdian-section {
	display: grid;
	gap: 18px;
	scroll-margin-top: 120px;
}

.verdian-section-head {
	display: flex;
	align-items: end;
	justify-content: space-between;
	gap: 20px;
}

.verdian-question-badge {
	position: relative;
	display: inline-grid;
	grid-template-columns: auto auto;
	align-items: center;
	gap: 10px;
	margin: 0;
	padding: 10px 18px 10px 14px;
	border-radius: 999px;
	border: 1px solid rgba(14, 116, 144, .16);
	background: linear-gradient(90deg, rgba(8, 145, 178, .18) 0 44%, rgba(255, 255, 255, .94) 44% 100%);
	box-shadow: 0 12px 28px rgba(15, 23, 42, .06);
}

/* Decorative connector line from the question badge to the heading content. */
.verdian-question-badge::after {
	content: "";
	position: absolute;
	left: calc(100% + 10px);
	top: 50%;
	width: 36px;
	height: 1px;
	background: linear-gradient(90deg, rgba(8, 145, 178, .55), rgba(8, 145, 178, 0));
	transform: translateY(-50%);
}

.verdian-question-label,
.verdian-question-number {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-height: 34px;
}

.verdian-question-label {
	color: #0f3d59;
	font-size: 11px;
	font-weight: 800;
	letter-spacing: .12em;
	text-transform: uppercase;
}

.verdian-question-number {
	min-width: 42px;
	padding: 0 10px;
	border-radius: 999px;
	background: linear-gradient(180deg, #0891b2, #155e75);
	color: #fff;
	font-size: 15px;
	font-weight: 900;
	letter-spacing: .08em;
	box-shadow: inset 0 1px 0 rgba(255,255,255,.2);
}

.verdian-grid {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 20px;
}

.verdian-panel {
	padding: 24px;
}

.verdian-panel-full {
	grid-column: 1 / -1;
}

.verdian-panel h3 {
	margin: 0 0 12px;
	font-size: 1.1rem;
}

.verdian-feature-band {
	padding: 10px 0 0;
}

.verdian-richtext {
	color: var(--charcoal);
	font-size: 15px;
	line-height: 1.72;
}

.verdian-richtext > *:first-child {
	margin-top: 0;
}

.verdian-richtext > *:last-child {
	margin-bottom: 0;
}

.verdian-richtext ul,
.verdian-richtext ol {
	padding-left: 1.2rem;
}

.verdian-table-wrap {
	overflow-x: auto;
}

/* Horizontal overflow wrapper keeps tables readable on narrow screens. */
.verdian-table {
	width: 100%;
	border-collapse: collapse;
	color: var(--charcoal);
	font-size: 15px;
	line-height: 1.6;
}

.verdian-table th,
.verdian-table td {
	padding: 14px 16px;
	text-align: left;
	vertical-align: top;
	border-bottom: 1px solid rgba(15, 23, 42, .08);
}

.verdian-table th {
	font-size: .78rem;
	font-weight: 800;
	letter-spacing: .08em;
	text-transform: uppercase;
	color: rgba(15, 23, 42, .68);
}

.verdian-table td:nth-child(2) {
	white-space: nowrap;
	font-weight: 700;
}

.verdian-gallery {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 20px;
}

/* Gallery cards use buttons so JS can open the full-size lightbox. */
.verdian-visual-grid .verdian-gallery {
	grid-template-columns: repeat(2, minmax(0, 1fr));
}

.verdian-gallery-card {
	overflow: hidden;
}

.verdian-gallery-media {
	position: relative;
	display: grid;
	padding: 14px;
	background:
		linear-gradient(180deg, rgba(248,250,252,.98), rgba(226,232,240,.9));
}

.verdian-gallery-trigger {
	display: grid;
	place-items: center;
	width: 100%;
	min-height: 320px;
	border: 0;
	padding: 0;
	background:
		linear-gradient(180deg, rgba(255,255,255,.96), rgba(241,245,249,.92));
	border-radius: 18px;
	cursor: zoom-in;
}

.verdian-gallery-media img {
	display: block;
	width: auto;
	max-width: 100%;
	max-height: 520px;
	height: auto;
	object-fit: contain;
	background: transparent;
	border-radius: 12px;
	box-shadow: 0 12px 28px rgba(15, 23, 42, .10);
}

.verdian-gallery-copy {
	display: grid;
	gap: 8px;
	padding: 18px 20px 20px;
}

.verdian-gallery-copy strong {
	font-size: 1rem;
}

.verdian-gallery-copy p,
.verdian-gallery-copy span {
	margin: 0;
	color: var(--muted);
}

.verdian-gallery-copy span,
.verdian-lightbox-copy span {
	white-space: pre-line;
}

.verdian-lightbox {
	position: fixed;
	inset: 0;
	z-index: 2000;
	display: none;
}

/* JS toggles .is-open to display the Verdian lightbox. */
.verdian-lightbox.is-open {
	display: grid;
}

.verdian-lightbox-backdrop {
	position: absolute;
	inset: 0;
	background: rgba(15, 23, 42, .76);
	backdrop-filter: blur(8px);
}

.verdian-lightbox-dialog {
	position: relative;
	z-index: 1;
	display: grid;
	grid-template-columns: auto minmax(0, 1fr) auto;
	gap: 14px;
	align-items: center;
	width: min(1400px, calc(100% - 32px));
	margin: auto;
	padding: 20px;
	border-radius: 28px;
	background: rgba(15, 23, 42, .92);
	box-shadow: 0 30px 80px rgba(0, 0, 0, .35);
}

.verdian-lightbox-stage {
	display: grid;
	min-height: 70vh;
	place-items: center;
	padding: 12px;
}

.verdian-lightbox-stage img {
	display: block;
	max-width: 100%;
	max-height: 70vh;
	width: auto;
	height: auto;
	object-fit: contain;
	border-radius: 18px;
	background: #0f172a;
}

.verdian-lightbox-close,
.verdian-lightbox-nav {
	border: 1px solid rgba(255,255,255,.18);
	background: rgba(255,255,255,.08);
	color: #fff;
	cursor: pointer;
}

.verdian-lightbox-close {
	position: absolute;
	top: 16px;
	right: 16px;
	z-index: 2;
	width: 42px;
	height: 42px;
	border-radius: 999px;
	font-size: 28px;
	line-height: 1;
}

.verdian-lightbox-nav {
	width: 52px;
	height: 52px;
	border-radius: 999px;
	font-size: 34px;
	line-height: 1;
}

.verdian-lightbox-copy {
	grid-column: 1 / -1;
	display: grid;
	gap: 8px;
	padding: 0 8px 4px;
	color: rgba(255,255,255,.84);
}

.verdian-lightbox-copy strong {
	color: #fff;
	font-size: 1rem;
}

.verdian-lightbox-copy p,
.verdian-lightbox-copy span {
	margin: 0;
}

.verdian-lightbox-copy a {
	color: #93c5fd;
	text-decoration: none;
}

.verdian-next-steps {
	display: grid;
	grid-template-columns: minmax(0, 1.35fr) auto;
	gap: 22px;
	align-items: center;
	padding: clamp(22px, 3vw, 32px);
	border: 1px solid rgba(23, 23, 23, .08);
	border-radius: 28px;
	background:
		radial-gradient(circle at top right, rgba(14, 165, 233, .12), transparent 34%),
		radial-gradient(circle at left bottom, rgba(249, 115, 22, .10), transparent 28%),
		rgba(255, 255, 255, .94);
	box-shadow: 0 18px 50px rgba(20, 20, 20, .06);
}

.verdian-next-steps-copy {
	display: grid;
	gap: 10px;
}

.verdian-next-steps-copy h2,
.verdian-next-steps-copy p {
	margin: 0;
}

.verdian-next-steps-copy h2 {
	font-size: clamp(1.5rem, 2.5vw, 2.2rem);
	line-height: 1.02;
	letter-spacing: -.04em;
}

.verdian-next-steps-copy p:last-child {
	color: var(--muted);
	font-size: 15px;
	line-height: 1.72;
}

.verdian-next-steps-actions {
	display: grid;
	gap: 12px;
	justify-items: stretch;
	min-width: min(100%, 240px);
}

#member-portal-architecture .verdian-gallery,
#visual-references .verdian-gallery {
	grid-template-columns: repeat(3, minmax(0, 1fr));
}

#visual-references .verdian-gallery-card:first-child {
	grid-column: span 2;
}

#member-portal-architecture .verdian-gallery {
	grid-template-columns: repeat(2, minmax(0, 1fr));
}

#member-portal-architecture .verdian-gallery-card {
	grid-column: auto;
}

#member-portal-architecture .verdian-gallery-card:last-child {
	grid-column: 1 / -1;
}

/* Verdian mobile layout: one-column content, simplified badge and smaller lightbox. */
@media (max-width: 920px) {
	.verdian-page {
		gap: 22px;
		margin-top: 22px;
		margin-bottom: 72px;
	}

	.verdian-hero,
	.verdian-grid,
	.verdian-gallery {
		grid-template-columns: 1fr;
	}

	.verdian-hero {
		gap: 18px;
		padding: 22px;
		border-radius: 24px;
	}

	.verdian-hero-copy,
	.verdian-hero-side {
		gap: 0;
	}

	.verdian-hero h1 {
		font-size: clamp(2rem, 11vw, 3.2rem);
		line-height: .95;
	}

	.verdian-subtitle {
		font-size: 18px;
		line-height: 1.45;
	}

	.verdian-richtext {
		font-size: 14.5px;
		line-height: 1.66;
	}

	.verdian-anchor-nav {
		grid-template-columns: 1fr 1fr;
	}

	.verdian-anchor-nav a {
		justify-content: center;
		min-height: 44px;
		padding: 10px 12px;
		text-align: center;
		letter-spacing: .05em;
	}

	.verdian-panel,
	.verdian-gallery-copy,
	.verdian-next-steps,
	.bonus-assessment-hero,
	.bonus-assessment-card,
	.bonus-assessment-closing {
		padding: 18px;
		border-radius: 22px;
	}

	.verdian-gallery-trigger {
		min-height: 220px;
	}

	.verdian-section-head {
		align-items: start;
		flex-direction: column;
	}

	.verdian-question-badge::after {
		display: none;
	}

	#visual-references .verdian-gallery-card:first-child {
		grid-column: auto;
	}

	#member-portal-architecture .verdian-gallery-card:last-child {
		grid-column: auto;
	}

	.verdian-lightbox-dialog {
		grid-template-columns: 1fr;
		padding: 14px;
		width: min(100% - 16px, 1400px);
		border-radius: 20px;
	}

	.verdian-lightbox-stage {
		min-height: auto;
	}

	.verdian-lightbox-stage img {
		max-height: 62vh;
	}

	.verdian-lightbox-nav {
		position: absolute;
		top: calc(50% - 26px);
		z-index: 2;
	}

	.verdian-lightbox-nav.is-prev {
		left: 12px;
	}

	.verdian-lightbox-nav.is-next {
		right: 12px;
	}

	.verdian-next-steps {
		grid-template-columns: 1fr;
	}

	.global-schedule-card {
		grid-template-columns: 1fr;
		gap: 18px;
		padding: 20px;
	}

	.global-schedule-card .button.primary,
	.verdian-next-steps-actions .button,
	.verdian-hero-copy .button.primary,
	.verdian-panel .button,
	.bonus-assessment-page .button {
		width: 100%;
	}
}

/* Extra-small Verdian tuning for phone screens. */
@media (max-width: 640px) {
	.site-main,
	.site-footer {
		width: min(1180px, calc(100% - 22px));
	}

	.verdian-page {
		gap: 18px;
		margin-top: 16px;
	}

	.verdian-hero {
		padding: 18px 16px;
	}

	.verdian-hero h1 {
		font-size: clamp(1.75rem, 12vw, 2.6rem);
	}

	.verdian-subtitle {
		font-size: 16px;
	}

	.verdian-hero-copy .button.primary {
		margin-top: 16px;
	}

	.verdian-anchor-nav {
		grid-template-columns: 1fr;
	}

	.verdian-section {
		gap: 14px;
	}

	.verdian-section-head {
		gap: 8px;
	}

	.verdian-question-badge {
		gap: 8px;
		padding: 8px 14px 8px 10px;
	}

	.verdian-question-label {
		font-size: 10px;
	}

	.verdian-question-number {
		min-width: 38px;
		min-height: 32px;
		font-size: 14px;
	}

	.verdian-section h2 {
		font-size: clamp(1.35rem, 7vw, 2rem);
		line-height: 1.02;
	}

	.verdian-panel,
	.verdian-gallery-copy,
	.verdian-next-steps {
		padding: 16px;
	}

	.verdian-panel h3,
	.bonus-assessment-card h2 {
		font-size: 1rem;
	}

	.verdian-gallery {
		gap: 14px;
	}

	.verdian-gallery-media {
		padding: 10px;
	}

	.verdian-gallery-trigger {
		min-height: 180px;
		border-radius: 14px;
	}

	.verdian-gallery-media img {
		max-height: 320px;
	}

	.verdian-lightbox-close {
		top: 10px;
		right: 10px;
		width: 38px;
		height: 38px;
		font-size: 24px;
	}

	.verdian-lightbox-nav {
		width: 42px;
		height: 42px;
		font-size: 28px;
	}

	.verdian-lightbox-copy {
		padding: 0 2px 2px;
		font-size: 14px;
	}

	.bonus-assessment-page {
		gap: 18px;
		margin-top: 18px;
	}

	.bonus-assessment-hero h1 {
		font-size: clamp(1.7rem, 10vw, 2.5rem);
	}

	.bonus-assessment-richtext {
		font-size: 14.5px;
		line-height: 1.66;
	}
}

/* Bonus assessment page: simple two-column card grid with a wide closing card. */
.bonus-assessment-page {
	display: grid;
	gap: 24px;
	margin: clamp(32px, 5vw, 68px) auto clamp(72px, 8vw, 120px);
}

.bonus-assessment-hero,
.bonus-assessment-card,
.bonus-assessment-closing {
	border: 1px solid rgba(23, 23, 23, .08);
	border-radius: 28px;
	padding: clamp(24px, 4vw, 40px);
	background: linear-gradient(180deg, rgba(255,255,255,.96), rgba(247,247,244,.92));
	box-shadow: 0 18px 50px rgba(20, 20, 20, .06);
}

.bonus-assessment-hero h1 {
	margin: 0;
	font-size: clamp(2rem, 4vw, 3.6rem);
	line-height: .98;
	letter-spacing: -.045em;
}

.bonus-assessment-grid {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 20px;
}

.bonus-assessment-card h2 {
	margin: 0 0 12px;
	font-size: 1.15rem;
}

.bonus-assessment-card-wide {
	grid-column: 1 / -1;
}

.bonus-assessment-richtext {
	font-size: 15px;
	line-height: 1.72;
	color: var(--charcoal);
}

.bonus-assessment-richtext > *:first-child {
	margin-top: 0;
}

.bonus-assessment-richtext > *:last-child {
	margin-bottom: 0;
}

/* Bonus assessment stacks into one column on mobile. */
@media (max-width: 920px) {
	.bonus-assessment-grid {
		grid-template-columns: 1fr;
	}
}

app/Controllers/AgendadorController.php

Controller da API de agendamento

Controller principal do scheduler no domínio do app. Expõe slots, criação da solicitação, aprovação, recusa e cancelamento.

<?php
namespace App\Controllers;

use App\Core\Controller;
use App\Models\EmailSender;
use App\Models\MeetingScheduler;
use App\Services\GoogleCalendarMeetService;

class AgendadorController extends Controller
{
    private array $config;

    public function __construct()
    {
        parent::__construct();
        $this->config = require __DIR__ . '/../../config/meeting_scheduler.php';
    }

    public function index(): void
    {
        $lang = $this->requestLang();
        $dados = [
            'titulo' => $lang === 'en' ? 'Schedule a call' : 'Agendar call',
            'pageLang' => $lang,
            'config' => $this->config,
            'canonicalUrl' => base_url('agendador'),
        ];
        $this->view('agendador/index', $dados, 'layouts/public');
    }

    public function slots(): void
    {
        $date = $_GET['date'] ?? date('Y-m-d');
        if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
            $this->respondJson(['success' => false, 'message' => 'Data inválida.'], 422);
        }

        $slots = $this->buildSlotsForDate($date);
        $this->respondJson(['success' => true, 'date' => $date, 'slots' => $slots]);
    }

    public function booking(): void
    {
        if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
            $this->respondJson(['success' => false, 'message' => 'Método inválido.'], 405);
        }

        $input = $this->getJsonInput();
        if (!$input) {
            $input = $_POST;
        }

        $name = trim((string)($input['name'] ?? ''));
        $email = trim((string)($input['email'] ?? ''));
        $phone = trim((string)($input['phone'] ?? ''));
        $company = trim((string)($input['company'] ?? ''));
        $subject = trim((string)($input['subject'] ?? ''));
        $source = trim((string)($input['source'] ?? ($_GET['source'] ?? '')));
        $notes = trim((string)($input['notes'] ?? ''));
        $slot = trim((string)($input['slot'] ?? ''));
        $lang = $this->requestLang($input);

        if ($name === '' || !filter_var($email, FILTER_VALIDATE_EMAIL) || $slot === '') {
            $this->respondJson(['success' => false, 'message' => $lang === 'en' ? 'Enter your name, a valid email and a time slot.' : 'Informe nome, e-mail válido e horário.'], 422);
        }

        $tz = new \DateTimeZone($this->config['timezone']);
        try {
            $start = new \DateTimeImmutable($slot, $tz);
        } catch (\Throwable $e) {
            $this->respondJson(['success' => false, 'message' => $lang === 'en' ? 'Invalid time slot.' : 'Horário inválido.'], 422);
        }

        $now = new \DateTimeImmutable('now', $tz);
        $minStart = $now->modify('+' . (int)$this->config['min_notice_hours'] . ' hours');
        if ($start < $minStart || !$this->isBusinessTime($start)) {
            $this->respondJson(['success' => false, 'message' => $lang === 'en' ? 'This time slot is no longer available.' : 'Esse horário não está mais disponível.'], 409);
        }

        $end = $start->modify('+' . (int)$this->config['meeting_minutes'] . ' minutes');
        if (!$this->isRangeAvailable($start, $end)) {
            $this->respondJson(['success' => false, 'message' => $lang === 'en' ? 'This time slot was just booked. Choose another one.' : 'Esse horário acabou de ser reservado. Escolha outro.'], 409);
        }

        $model = new MeetingScheduler();
        $startSql = $start->format('Y-m-d H:i:s');
        $endSql = $end->format('Y-m-d H:i:s');
        $token = bin2hex(random_bytes(20));
        $uid = $token . '@app.fusioncore.com.br';
        $meetingUrl = $this->generateMeetingUrl($token, $start);
        $calendarUrl = $this->googleCalendarUrl($start, $end, $name, $email, $company, $notes, $meetingUrl);

        try {
            $bookingId = $model->createBooking([
                'token' => $token,
                'client_name' => $name,
                'client_email' => $email,
                'client_phone' => $phone !== '' ? $phone : null,
                'company' => $company !== '' ? $company : null,
                'subject' => $subject !== '' ? $subject : null,
                'source' => $source !== '' ? $source : null,
                'notes' => $notes !== '' ? $notes : null,
                'starts_at' => $startSql,
                'ends_at' => $endSql,
                'timezone' => $this->config['timezone'],
                'status' => 'pending',
                'meeting_url' => $meetingUrl,
                'google_calendar_url' => $calendarUrl,
                'ics_uid' => $uid,
            ]);
        } catch (\Throwable $e) {
            $this->respondJson(['success' => false, 'message' => $lang === 'en' ? 'This time slot was just booked. Choose another one.' : 'Esse horário acabou de ser reservado. Escolha outro.'], 409);
        }

        $booking = $model->find((int)$bookingId) ?: [];
        $booking['google_calendar_url'] = $calendarUrl;
        $emailResult = $this->sendApprovalRequest($booking, $start, $end);

        $this->respondJson([
            'success' => true,
            'message' => $lang === 'en' ? 'Request sent successfully.' : 'Solicitação enviada com sucesso.',
            'booking' => [
                'token' => $token,
                'date' => $start->format('d/m/Y'),
                'time' => $start->format('H:i'),
                'confirmation_url' => base_url('agendador/confirmacao/' . $token) . '?lang=' . $lang,
            ],
            'email_sent' => (bool)($emailResult['success'] ?? false),
            'email_message' => (string)($emailResult['message'] ?? ''),
        ]);
    }

    public function revisar($token = ''): void
    {
        $model = new MeetingScheduler();
        $booking = $token ? $model->findByToken((string)$token) : null;
        if (!$booking) {
            http_response_code(404);
        }

        $dados = [
            'titulo' => $booking ? 'Revisar agendamento' : 'Agendamento não encontrado',
            'config' => $this->config,
            'booking' => $booking,
            'canonicalUrl' => base_url('agendador/revisar/' . $token),
        ];
        $this->view('agendador/revisar', $dados, 'layouts/public');
    }

    public function aprovar($token = ''): void
    {
        if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
            $this->redirect(base_url('agendador/revisar/' . $token));
        }

        $model = new MeetingScheduler();
        $booking = $token ? $model->findByToken((string)$token) : null;
        if (!$booking || (string)$booking['status'] !== 'pending') {
            $this->redirect(base_url('agendador/revisar/' . $token));
        }

        $start = new \DateTimeImmutable((string)$booking['starts_at'], new \DateTimeZone($this->config['timezone']));
        $durationMinutes = $this->durationFromRequest($booking);
        $end = $start->modify('+' . $durationMinutes . ' minutes');
        $startSql = $start->format('Y-m-d H:i:s');
        $endSql = $end->format('Y-m-d H:i:s');
        if (!$this->isBusinessRange($start, $end)) {
            $_SESSION['error'] = 'A duracao escolhida ultrapassa o horario de atendimento.';
            $this->redirect(base_url('agendador/revisar/' . $token));
        }
        if (!$this->isRangeAvailable($start, $end, (string)$token)) {
            $_SESSION['error'] = 'A duracao escolhida entra em conflito com outro agendamento.';
            $this->redirect(base_url('agendador/revisar/' . $token));
        }

        $meetingUrl = trim((string)($booking['meeting_url'] ?? ''));
        $calendarUrl = '';
        $googleEventId = null;

        try {
            $google = new GoogleCalendarMeetService($this->config);
            if ($google->isConfigured() && $google->hasToken()) {
                $googleEvent = $google->createMeetEvent($booking, $start, $end);
                $meetingUrl = (string)($googleEvent['meet_link'] ?? $meetingUrl);
                $calendarUrl = (string)($googleEvent['html_link'] ?? '');
                $googleEventId = (string)($googleEvent['event_id'] ?? '');
            }
        } catch (\Throwable $e) {
            try { \App\Services\AppLogger::warning('google_meet_create_failed', ['error' => $e->getMessage(), 'token' => $token]); } catch (\Throwable $logError) {}
        }

        if ($meetingUrl === '') {
            $meetingUrl = $this->generateMeetingUrl((string)$token, $start);
        }

        if ($calendarUrl === '') {
            $calendarUrl = $this->googleCalendarUrl(
                $start,
                $end,
                (string)$booking['client_name'],
                (string)$booking['client_email'],
                (string)($booking['company'] ?? ''),
                (string)($booking['notes'] ?? ''),
                $meetingUrl
            );
        }

        $approved = $model->approve((string)$token, $meetingUrl, $calendarUrl, $googleEventId, $endSql);
        if ($approved) {
            $ics = $this->buildIcs($approved, $start, $end, (string)$approved['ics_uid']);
            $this->sendConfirmation($approved, $start, $end, $ics);
        }

        $this->redirect(base_url('agendador/admin'));
    }

    public function recusar($token = ''): void
    {
        if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
            $this->redirect(base_url('agendador/revisar/' . $token));
        }

        $model = new MeetingScheduler();
        $declined = $token ? $model->decline((string)$token) : null;
        if ($declined) {
            $this->sendDeclinedEmail($declined);
        }

        $this->redirect(base_url('agendador/admin'));
    }

    public function cancelar($token = ''): void
    {
        $this->requireAuth();
        if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
            $this->redirect(base_url('agendador/admin'));
        }

        $model = new MeetingScheduler();
        $booking = $token ? $model->findByToken((string)$token) : null;
        if (!$booking || in_array((string)$booking['status'], ['cancelled', 'declined'], true)) {
            $_SESSION['error'] = 'Agendamento nao encontrado ou ja encerrado.';
            $this->redirect(base_url('agendador/admin'));
        }

        $eventId = trim((string)($booking['google_calendar_event_id'] ?? ''));
        if ($eventId !== '') {
            try {
                $google = new GoogleCalendarMeetService($this->config);
                if ($google->isConfigured() && $google->hasToken()) {
                    $google->deleteEvent($eventId);
                }
            } catch (\Throwable $e) {
                try { \App\Services\AppLogger::warning('google_meet_cancel_failed', ['error' => $e->getMessage(), 'token' => $token]); } catch (\Throwable $logError) {}
            }
        }

        $cancelled = $model->cancel((string)$token);
        if ($cancelled) {
            $this->sendCancellationEmail($cancelled);
            $_SESSION['success'] = 'Reuniao cancelada e e-mail enviado ao lead.';
        } else {
            $_SESSION['error'] = 'Nao foi possivel cancelar este agendamento.';
        }

        $this->redirect(base_url('agendador/admin'));
    }

    public function confirmacao($token = ''): void
    {
        $lang = $this->requestLang();
        $model = new MeetingScheduler();
        $booking = $token ? $model->findByToken((string)$token) : null;
        if (!$booking) {
            http_response_code(404);
        }

        $dados = [
            'titulo' => $booking ? ($lang === 'en' ? 'Meeting request' : 'Reunião confirmada') : ($lang === 'en' ? 'Booking not found' : 'Agendamento não encontrado'),
            'pageLang' => $lang,
            'config' => $this->config,
            'booking' => $booking,
            'canonicalUrl' => base_url('agendador/confirmacao/' . $token),
        ];
        $this->view('agendador/confirmacao', $dados, 'layouts/public');
    }

    public function admin(): void
    {
        $this->requireAuth();
        $model = new MeetingScheduler();
        $googleStatus = (new GoogleCalendarMeetService($this->config))->getStatus();
        $dados = [
            'titulo' => t('scheduler.title', [], 'Meeting scheduler'),
            'user' => $this->getUserData(),
            'config' => $this->config,
            'bookings' => $model->latest(30),
            'schedulerLink' => base_url('agendador'),
            'googleStatus' => $googleStatus,
        ];
        $dados['content'] = $this->renderPartial('agendador/admin', $dados);
        $this->view('layouts/main', $dados);
    }

    public function enviar_convite(): void
    {
        $this->requireAuth();
        if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
            $this->redirect(base_url('agendador/admin'));
        }

        $recipient = trim((string)($_POST['recipient_email'] ?? ''));
        $subject = trim((string)($_POST['invite_subject'] ?? ''));

        if (!filter_var($recipient, FILTER_VALIDATE_EMAIL)) {
            $_SESSION['error'] = 'Informe um destinatario valido para enviar o convite.';
            $this->redirect(base_url('agendador/admin'));
        }

        if ($subject === '') {
            $subject = 'Diagnostico FusionCore';
        }

        $schedulerUrl = base_url('agendador') . '?' . http_build_query([
            'source' => 'convite_manual',
            'subject' => $subject,
        ]);

        $html = $this->meetingInviteEmailHtml($subject, $schedulerUrl);
        $sender = new EmailSender();
        $result = $sender->sendEmail(
            $recipient,
            'Agende seu diagnostico com a FusionCore',
            $html,
            strip_tags(str_replace(['<br>', '<br/>', '<br />'], "\n", $html)),
            null,
            false
        );

        if (!empty($result['success'])) {
            $_SESSION['success'] = 'Convite de agendamento enviado para ' . $recipient . '.';
        } else {
            $_SESSION['error'] = 'Nao foi possivel enviar o convite: ' . (string)($result['message'] ?? 'erro desconhecido');
        }

        $this->redirect(base_url('agendador/admin'));
    }

    private function requestLang(array $input = []): string
    {
        $lang = (string)($input['lang'] ?? $_GET['lang'] ?? 'pt');
        return $lang === 'en' ? 'en' : 'pt';
    }

    public function googleConnect(): void
    {
        $this->requireAuth();
        $google = new GoogleCalendarMeetService($this->config);
        if (!$google->isConfigured()) {
            $_SESSION['error'] = 'Configure GOOGLE_CALENDAR_CLIENT_ID e GOOGLE_CALENDAR_CLIENT_SECRET antes de conectar.';
            $this->redirect(base_url('agendador/admin'));
        }

        $state = bin2hex(random_bytes(16));
        $_SESSION['google_calendar_oauth_state'] = $state;
        $this->redirect($google->authUrl($state));
    }

    public function google_connect(): void
    {
        $this->googleConnect();
    }

    public function googleCallback(): void
    {
        $state = (string)($_GET['state'] ?? '');
        $expectedState = (string)($_SESSION['google_calendar_oauth_state'] ?? '');
        if ($expectedState === '' || !hash_equals($expectedState, $state)) {
            $_SESSION['error'] = 'Falha na validacao do OAuth Google.';
            $this->redirect(base_url('agendador/admin'));
        }

        unset($_SESSION['google_calendar_oauth_state']);
        $code = (string)($_GET['code'] ?? '');
        if ($code === '') {
            $_SESSION['error'] = 'Google nao retornou codigo de autorizacao.';
            $this->redirect(base_url('agendador/admin'));
        }

        try {
            $result = (new GoogleCalendarMeetService($this->config))->handleCallback($code);
            $_SESSION['success'] = 'Conta Google conectada: ' . ($result['email'] ?: 'autorizada');
        } catch (\Throwable $e) {
            $_SESSION['error'] = 'Erro ao conectar Google Calendar: ' . $e->getMessage();
        }

        $this->redirect(base_url('agendador/admin'));
    }

    public function google_callback(): void
    {
        $this->googleCallback();
    }

    private function buildSlotsForDate(string $date): array
    {
        $tz = new \DateTimeZone($this->config['timezone']);
        $day = new \DateTimeImmutable($date . ' 00:00:00', $tz);
        $window = $this->businessWindowForDate($day);
        if ($window === null) {
            return [];
        }

        $start = $window['start'];
        $close = $window['end'];
        $now = new \DateTimeImmutable('now', $tz);
        $minStart = $now->modify('+' . (int)$this->config['min_notice_hours'] . ' hours');
        $lastDay = $now->setTime(0, 0)->modify('+' . (int)$this->config['days_ahead'] . ' days');
        if ($day > $lastDay) {
            return [];
        }

        $busy = $this->busyRangesBetween($start, $close);
        $slots = [];
        $cursor = $start;
        while ($cursor->modify('+' . (int)$this->config['meeting_minutes'] . ' minutes') <= $close) {
            $slotEnd = $cursor->modify('+' . (int)$this->config['meeting_minutes'] . ' minutes');
            $available = $cursor >= $minStart && !$this->overlapsBusy($cursor, $slotEnd, $busy);
            $slots[] = [
                'iso' => $cursor->format(DATE_ATOM),
                'label' => $cursor->format('H:i'),
                'available' => $available,
            ];
            $cursor = $cursor->modify('+' . (int)$this->config['slot_minutes'] . ' minutes');
        }

        return $slots;
    }

    private function isBusinessTime(\DateTimeImmutable $start): bool
    {
        $window = $this->businessWindowForDate($start);
        if ($window === null) {
            return false;
        }
        $end = $start->modify('+' . (int)$this->config['meeting_minutes'] . ' minutes');
        return $start >= $window['start'] && $end <= $window['end'];
    }

    private function isBusinessRange(\DateTimeImmutable $start, \DateTimeImmutable $end): bool
    {
        $window = $this->businessWindowForDate($start);
        if ($window === null) {
            return false;
        }
        return $start >= $window['start'] && $end <= $window['end'] && $end > $start;
    }

    private function businessWindowForDate(\DateTimeImmutable $date): ?array
    {
        $tz = new \DateTimeZone($this->config['timezone']);
        $dateKey = $date->setTimezone($tz)->format('Y-m-d');
        $overrides = (array)($this->config['date_overrides'] ?? []);

        if (isset($overrides[$dateKey]) && is_array($overrides[$dateKey])) {
            $override = $overrides[$dateKey];
            $start = preg_match('/^\d{2}:\d{2}$/', (string)($override['start'] ?? '')) ? (string)$override['start'] : null;
            $end = preg_match('/^\d{2}:\d{2}$/', (string)($override['end'] ?? '')) ? (string)$override['end'] : null;
            if ($start && $end) {
                return [
                    'start' => new \DateTimeImmutable($dateKey . ' ' . $start . ':00', $tz),
                    'end' => new \DateTimeImmutable($dateKey . ' ' . $end . ':00', $tz),
                ];
            }
        }

        if ((int)$date->format('N') > 5) {
            return null;
        }

        return [
            'start' => new \DateTimeImmutable($dateKey . ' ' . $this->config['weekday_start'] . ':00', $tz),
            'end' => new \DateTimeImmutable($dateKey . ' ' . $this->config['weekday_end'] . ':00', $tz),
        ];
    }

    private function durationFromRequest(array $booking): int
    {
        $currentStart = new \DateTimeImmutable((string)$booking['starts_at'], new \DateTimeZone($this->config['timezone']));
        $currentEnd = new \DateTimeImmutable((string)$booking['ends_at'], new \DateTimeZone($this->config['timezone']));
        $currentMinutes = max(1, (int)(($currentEnd->getTimestamp() - $currentStart->getTimestamp()) / 60));
        $requested = (int)($_POST['duration_minutes'] ?? $currentMinutes);
        $options = array_map('intval', (array)($this->config['duration_options_minutes'] ?? []));
        if (empty($options)) {
            $options = [(int)$this->config['meeting_minutes']];
        }
        if (!in_array($requested, $options, true)) {
            return in_array($currentMinutes, $options, true) ? $currentMinutes : (int)$this->config['meeting_minutes'];
        }
        return $requested;
    }

    private function overlapsBusy(\DateTimeImmutable $start, \DateTimeImmutable $end, array $busy): bool
    {
        foreach ($busy as $item) {
            $busyStart = new \DateTimeImmutable((string)$item['starts_at'], new \DateTimeZone($this->config['timezone']));
            $busyEnd = new \DateTimeImmutable((string)$item['ends_at'], new \DateTimeZone($this->config['timezone']));
            if ($start < $busyEnd && $end > $busyStart) {
                return true;
            }
        }
        return false;
    }

    private function busyRangesBetween(\DateTimeImmutable $start, \DateTimeImmutable $end): array
    {
        $model = new MeetingScheduler();
        $busy = $model->busyBetween($start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s'));

        try {
            $googleBusy = (new GoogleCalendarMeetService($this->config))->busyBetween($start, $end);
            if (!empty($googleBusy)) {
                $busy = array_merge($busy, $googleBusy);
            }
        } catch (\Throwable $e) {
            try { \App\Services\AppLogger::warning('google_calendar_busy_failed', ['error' => $e->getMessage()]); } catch (\Throwable $logError) {}
        }

        return $busy;
    }

    private function isRangeAvailable(\DateTimeImmutable $start, \DateTimeImmutable $end, ?string $excludeToken = null): bool
    {
        $model = new MeetingScheduler();
        $startSql = $start->format('Y-m-d H:i:s');
        $endSql = $end->format('Y-m-d H:i:s');

        $internalAvailable = $excludeToken === null
            ? $model->isAvailable($startSql, $endSql)
            : $model->isAvailableExcludingToken($startSql, $endSql, $excludeToken);

        if (!$internalAvailable) {
            return false;
        }

        $busy = $this->busyRangesBetween($start, $end);

        if ($excludeToken !== null) {
            $booking = $model->findByToken($excludeToken);
            if ($booking) {
                $busy = array_values(array_filter($busy, function (array $item) use ($booking): bool {
                    return !(
                        (string)($item['starts_at'] ?? '') === (string)($booking['starts_at'] ?? '')
                        && (string)($item['ends_at'] ?? '') === (string)($booking['ends_at'] ?? '')
                    );
                }));
            }
        }

        return !$this->overlapsBusy($start, $end, $busy);
    }

    private function googleCalendarUrl(\DateTimeImmutable $start, \DateTimeImmutable $end, string $name, string $email, string $company, string $notes, string $meetingUrl): string
    {
        $startUtc = $start->setTimezone(new \DateTimeZone('UTC'))->format('Ymd\THis\Z');
        $endUtc = $end->setTimezone(new \DateTimeZone('UTC'))->format('Ymd\THis\Z');
        $details = "Reunião agendada por {$name} ({$email}).";
        if ($company !== '') {
            $details .= "\nEmpresa: {$company}.";
        }
        if ($notes !== '') {
            $details .= "\n\nObservações:\n{$notes}";
        }
        if ($meetingUrl !== '') {
            $details .= "\n\nLink da reunião: " . $meetingUrl;
        }

        return 'https://calendar.google.com/calendar/render?' . http_build_query([
            'action' => 'TEMPLATE',
            'text' => $this->config['title'],
            'dates' => $startUtc . '/' . $endUtc,
            'details' => $details,
            'location' => $meetingUrl !== '' ? $meetingUrl : $this->config['location'],
            'add' => $this->config['owner_email'] . ',' . $email,
        ]);
    }

    private function generateMeetingUrl(string $token, \DateTimeImmutable $start): string
    {
        $configuredUrl = trim((string)($this->config['default_meet_url'] ?? ''));
        if ($configuredUrl !== '') {
            return $configuredUrl;
        }

        $baseUrl = rtrim((string)($this->config['meeting_base_url'] ?? 'https://meet.jit.si'), '/');
        $room = 'fusioncore-' . $start->format('Ymd-Hi') . '-' . substr($token, 0, 10);
        $room = strtolower(preg_replace('/[^a-zA-Z0-9-]/', '-', $room));

        return $baseUrl . '/' . $room;
    }

    private function buildIcs(array $booking, \DateTimeImmutable $start, \DateTimeImmutable $end, string $uid): string
    {
        $nowUtc = (new \DateTimeImmutable('now', new \DateTimeZone('UTC')))->format('Ymd\THis\Z');
        $startUtc = $start->setTimezone(new \DateTimeZone('UTC'))->format('Ymd\THis\Z');
        $endUtc = $end->setTimezone(new \DateTimeZone('UTC'))->format('Ymd\THis\Z');
        $summary = $this->icsEscape($this->config['title']);
        $meetingUrl = (string)($booking['meeting_url'] ?? '');
        $descriptionText = 'Call agendada via FusionCore com ' . ($booking['client_name'] ?? '') . ' (' . ($booking['client_email'] ?? '') . ').';
        if ($meetingUrl !== '') {
            $descriptionText .= "\nLink da call: " . $meetingUrl;
        }
        $description = $this->icsEscape($descriptionText);
        $location = $this->icsEscape($meetingUrl !== '' ? $meetingUrl : $this->config['location']);
        $organizerName = $this->icsEscape($this->config['owner_name']);
        $clientName = $this->icsEscape((string)($booking['client_name'] ?? 'Cliente'));

        return "BEGIN:VCALENDAR\r\n" .
            "VERSION:2.0\r\n" .
            "PRODID:-//FusionCore//Meeting Scheduler//PT-BR\r\n" .
            "CALSCALE:GREGORIAN\r\n" .
            "METHOD:REQUEST\r\n" .
            "BEGIN:VEVENT\r\n" .
            "UID:{$uid}\r\n" .
            "DTSTAMP:{$nowUtc}\r\n" .
            "DTSTART:{$startUtc}\r\n" .
            "DTEND:{$endUtc}\r\n" .
            "SUMMARY:{$summary}\r\n" .
            "DESCRIPTION:{$description}\r\n" .
            "LOCATION:{$location}\r\n" .
            "STATUS:CONFIRMED\r\n" .
            "SEQUENCE:0\r\n" .
            "ORGANIZER;CN={$organizerName}:mailto:{$this->config['owner_email']}\r\n" .
            "ATTENDEE;CN={$clientName};ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:{$booking['client_email']}\r\n" .
            "ATTENDEE;CN={$organizerName};ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;RSVP=FALSE:mailto:{$this->config['owner_email']}\r\n" .
            "END:VEVENT\r\n" .
            "END:VCALENDAR\r\n";
    }

    private function sendApprovalRequest(array $booking, \DateTimeImmutable $start, \DateTimeImmutable $end): array
    {
        $reviewUrl = base_url('agendador/revisar/' . $booking['token']);
        $html = $this->approvalEmailHtml($booking, $start, $end, $reviewUrl);
        $sender = new EmailSender();
        return $sender->sendEmail(
            $this->config['approval_email'],
            'FusionCore | Agendamento aguardando sua confirmação - ' . $start->format('d/m/Y H:i'),
            $html,
            strip_tags(str_replace(['<br>', '<br/>', '<br />'], "\n", $html)),
            null,
            false
        );
    }

    private function approvalEmailHtml(array $booking, \DateTimeImmutable $start, \DateTimeImmutable $end, string $reviewUrl): string
    {
        $client = htmlspecialchars((string)$booking['client_name'], ENT_QUOTES, 'UTF-8');
        $email = htmlspecialchars((string)$booking['client_email'], ENT_QUOTES, 'UTF-8');
        $phone = htmlspecialchars((string)($booking['client_phone'] ?? ''), ENT_QUOTES, 'UTF-8');
        $company = htmlspecialchars((string)($booking['company'] ?? ''), ENT_QUOTES, 'UTF-8');
        $notes = nl2br(htmlspecialchars((string)($booking['notes'] ?? ''), ENT_QUOTES, 'UTF-8'));
        $url = htmlspecialchars($reviewUrl, ENT_QUOTES, 'UTF-8');

        return '<div style="font-family:Inter,Arial,sans-serif;background:#f6f7fb;padding:28px;color:#172033">' .
            '<div style="max-width:680px;margin:0 auto;background:#fff;border:1px solid #e4e7ee;border-radius:14px;overflow:hidden">' .
            '<div style="background:#14213d;color:#fff;padding:26px 30px"><h1 style="margin:0;font-size:24px">Agendamento FusionCore aguardando analise</h1><p style="margin:8px 0 0;color:#dbe7ff">Revise os dados e confirme o melhor proximo passo com o lead.</p></div>' .
            '<div style="padding:30px">' .
            '<p>Um novo lead demonstrou interesse em conversar com a FusionCore. Avalie o horario solicitado para manter a experiencia comercial organizada e profissional.</p>' .
            '<p><strong>Cliente:</strong> ' . $client . '<br><strong>E-mail:</strong> ' . $email . '<br><strong>Telefone:</strong> ' . ($phone ?: '-') . '<br><strong>Empresa:</strong> ' . ($company ?: '-') . '</p>' .
            '<p><strong>Data:</strong> ' . htmlspecialchars($start->format('d/m/Y'), ENT_QUOTES, 'UTF-8') . '<br><strong>Horário:</strong> ' . htmlspecialchars($start->format('H:i') . ' - ' . $end->format('H:i'), ENT_QUOTES, 'UTF-8') . '</p>' .
            ($notes ? '<p><strong>Observações:</strong><br>' . $notes . '</p>' : '') .
            '<p style="margin:28px 0"><a href="' . $url . '" style="background:#1f7a5a;color:#fff;text-decoration:none;padding:13px 18px;border-radius:8px;display:inline-block;font-weight:700">Confirmar ou solicitar reagendamento</a></p>' .
            '<p style="color:#596579;font-size:14px">O lead só receberá o e-mail final depois da confirmação ou recusa.</p>' .
            '</div></div></div>';
    }

    private function sendConfirmation(array $booking, \DateTimeImmutable $start, \DateTimeImmutable $end, string $ics): array
    {
        $calendarUrl = (string)($booking['google_calendar_url'] ?? '');
        $html = $this->confirmationEmailHtml($booking, $start, $end, $calendarUrl);
        $text = strip_tags(str_replace(['<br>', '<br/>', '<br />'], "\n", $html));
        $sender = new EmailSender();
        return $sender->sendEmail(
            [
                (string)$booking['client_email'] => (string)$booking['client_name'],
                $this->config['owner_email'] => $this->config['owner_name'],
            ],
            'Seu agendamento com a FusionCore esta confirmado',
            $html,
            $text,
            null,
            false,
            [[
                'content' => $ics,
                'name' => 'convite-fusioncore.ics',
                'type' => 'text/calendar; method=REQUEST; charset=UTF-8',
            ]]
        );
    }

    private function meetingInviteEmailHtml(string $subject, string $schedulerUrl): string
    {
        $subjectEsc = htmlspecialchars($subject, ENT_QUOTES, 'UTF-8');
        $url = htmlspecialchars($schedulerUrl, ENT_QUOTES, 'UTF-8');
        $logoUrl = 'https://app.fusioncore.com.br/assets/images/fusioncore-email-logo.png';
        $calendarIcon = 'https://www.gstatic.com/images/branding/product/1x/calendar_2020q4_48dp.png';

        return '<div style="font-family:Arial,Helvetica,sans-serif;background:#e8edf3;padding:30px;color:#0f172a">' .
            '<div style="max-width:680px;margin:0 auto;background:#ffffff;border:1px solid #cbd5e1;border-radius:14px;overflow:hidden;color:#0f172a;box-shadow:0 12px 30px rgba(15,23,42,0.10)">' .
            '<div style="background:#0b1220;color:#ffffff;padding:26px 30px 28px">' .
            '<div style="margin-bottom:22px"><img src="' . htmlspecialchars($logoUrl, ENT_QUOTES, 'UTF-8') . '" width="148" alt="FusionCore" style="display:block;max-width:148px;height:auto"></div>' .
            '<h1 style="margin:0;font-size:26px;line-height:1.25;color:#ffffff">Vamos agendar seu diagnostico FusionCore?</h1>' .
            '<p style="margin:12px 0 0;color:#e2e8f0;font-size:16px;line-height:1.55">Escolha o melhor horario para uma conversa objetiva sobre tecnologia, automacao e IA aplicada ao seu cenario.</p>' .
            '</div>' .
            '<div style="padding:30px;color:#0f172a;background:#ffffff">' .
            '<div style="background:#fff7ed;border:1px solid #fed7aa;border-radius:12px;padding:18px;margin:0 0 22px;color:#0f172a"><div style="font-size:13px;font-weight:800;color:#9a3412;text-transform:uppercase;letter-spacing:.04em;margin-bottom:5px">Assunto sugerido</div><div style="font-size:17px;font-weight:800;line-height:1.45;color:#0f172a">' . $subjectEsc . '</div></div>' .
            '<p style="margin:0 0 18px;color:#0f172a;font-size:16px;line-height:1.65">Nossa equipe preparou um canal direto para voce escolher um horario disponivel. Depois da solicitacao, validamos internamente e enviamos a confirmacao com o link oficial da call.</p>' .
            '<p style="margin:28px 0"><a href="' . $url . '" style="background:#166534;color:#ffffff;text-decoration:none;padding:14px 18px;border-radius:8px;display:inline-block;font-weight:800"><img src="' . $calendarIcon . '" width="18" height="18" alt="" style="vertical-align:-4px;margin-right:6px">Agendar meu diagnostico</a></p>' .
            '<p style="color:#334155;font-size:14px;line-height:1.6;margin:16px 0 0">A conversa e online, com horarios de segunda a sexta, das ' . htmlspecialchars($this->config['weekday_start'], ENT_QUOTES, 'UTF-8') . ' as ' . htmlspecialchars($this->config['weekday_end'], ENT_QUOTES, 'UTF-8') . '.</p>' .
            '</div></div></div>';
    }

    private function confirmationEmailHtml(array $booking, \DateTimeImmutable $start, \DateTimeImmutable $end, string $calendarUrl): string
    {
        $name = htmlspecialchars((string)$booking['client_name'], ENT_QUOTES, 'UTF-8');
        $date = htmlspecialchars($start->format('d/m/Y'), ENT_QUOTES, 'UTF-8');
        $time = htmlspecialchars($start->format('H:i') . ' - ' . $end->format('H:i'), ENT_QUOTES, 'UTF-8');
        $url = htmlspecialchars($calendarUrl, ENT_QUOTES, 'UTF-8');
        $meetingUrl = htmlspecialchars((string)($booking['meeting_url'] ?? ''), ENT_QUOTES, 'UTF-8');
        $subjectText = trim((string)($booking['subject'] ?? ''));
        $source = strtolower(trim((string)($booking['source'] ?? '')));
        $isDiagnostic = in_array($source, ['diagnostico', 'diagnosticos', 'diagnostic', 'site_diagnostico', 'fusioncore-site-diagnostico'], true);
        $introHtml = '';
        if ($subjectText !== '') {
            $introHtml = '<div style="background:#fff7ed;border:1px solid #fed7aa;border-radius:12px;padding:18px;margin:0 0 22px;color:#0f172a"><div style="font-size:13px;font-weight:800;color:#9a3412;text-transform:uppercase;letter-spacing:.04em;margin-bottom:5px">Assunto da conversa</div><div style="font-size:17px;font-weight:800;line-height:1.45;color:#0f172a">' . htmlspecialchars($subjectText, ENT_QUOTES, 'UTF-8') . '</div></div>';
        } elseif ($isDiagnostic) {
            $introHtml = '<p style="margin:0 0 22px;color:#0f172a;font-size:16px;line-height:1.65">Sua conversa com a FusionCore foi confirmada. Vamos entender seu cenario, mapear oportunidades praticas e apresentar caminhos para melhorar sua operacao com solucoes digitais, automacao e IA.</p>';
        }
        $logoUrl = 'https://app.fusioncore.com.br/assets/images/fusioncore-email-logo.png';
        $meetIcon = 'https://www.gstatic.com/images/branding/product/1x/meet_2020q4_48dp.png';
        $calendarIcon = 'https://www.gstatic.com/images/branding/product/1x/calendar_2020q4_48dp.png';
        return '<div style="font-family:Arial,Helvetica,sans-serif;background:#e8edf3;padding:30px;color:#0f172a">' .
            '<div style="max-width:680px;margin:0 auto;background:#ffffff;border:1px solid #cbd5e1;border-radius:14px;overflow:hidden;color:#0f172a;box-shadow:0 12px 30px rgba(15,23,42,0.10)">' .
            '<div style="background:#0b1220;color:#ffffff;padding:26px 30px 28px">' .
            '<div style="margin-bottom:22px"><img src="' . htmlspecialchars($logoUrl, ENT_QUOTES, 'UTF-8') . '" width="148" alt="FusionCore" style="display:block;max-width:148px;height:auto"></div>' .
            '<h1 style="margin:0;font-size:26px;line-height:1.25;color:#ffffff">Seu agendamento com a FusionCore esta confirmado</h1>' .
            '<p style="margin:12px 0 0;color:#e2e8f0;font-size:16px;line-height:1.55">Vamos conversar sobre tecnologia, automacao e IA para acelerar resultados com clareza e foco comercial.</p>' .
            '</div>' .
            '<div style="padding:30px;color:#0f172a;background:#ffffff">' .
            '<p style="margin:0 0 14px;color:#0f172a;font-size:16px;line-height:1.65">Olá, ' . $name . '.</p>' .
            $introHtml .
            '<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="background:#f8fafc;border:1px solid #cbd5e1;border-radius:12px;margin:0 0 24px;color:#0f172a"><tr>' .
            '<td style="padding:18px;vertical-align:top;width:50%"><div style="font-size:13px;font-weight:700;color:#475569;text-transform:uppercase;letter-spacing:.04em">Data</div><div style="font-size:18px;font-weight:800;color:#0f172a;margin-top:4px">' . $date . '</div></td>' .
            '<td style="padding:18px;vertical-align:top;width:50%"><div style="font-size:13px;font-weight:700;color:#475569;text-transform:uppercase;letter-spacing:.04em">Horario</div><div style="font-size:18px;font-weight:800;color:#0f172a;margin-top:4px">' . $time . '</div></td>' .
            '</tr></table>' .
            '<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="margin:0 0 22px"><tr>' .
            '<td style="background:#eef6ff;border:1px solid #bfdbfe;border-radius:12px;padding:18px;color:#0f172a">' .
            '<table role="presentation" cellspacing="0" cellpadding="0"><tr><td style="padding-right:12px;vertical-align:middle"><img src="' . $meetIcon . '" width="38" height="38" alt="Google Meet" style="display:block"></td><td style="vertical-align:middle"><div style="font-size:13px;font-weight:700;color:#1d4ed8;text-transform:uppercase;letter-spacing:.04em">Google Meet</div><a href="' . $meetingUrl . '" style="color:#0057d8;font-size:16px;font-weight:800;text-decoration:none">' . $meetingUrl . '</a></td></tr></table>' .
            '</td></tr></table>' .
            '<p style="margin:0 0 18px"><a href="' . $meetingUrl . '" style="background:#166534;color:#ffffff;text-decoration:none;padding:14px 18px;border-radius:8px;display:inline-block;font-weight:800;margin:0 10px 10px 0">Entrar no Google Meet</a><a href="' . $url . '" style="background:#0f172a;color:#ffffff;text-decoration:none;padding:14px 18px;border-radius:8px;display:inline-block;font-weight:800;margin:0 0 10px 0"><img src="' . $calendarIcon . '" width="18" height="18" alt="" style="vertical-align:-4px;margin-right:6px">Abrir no Google Calendar</a></p>' .
            '<p style="color:#334155;font-size:14px;line-height:1.6;margin:16px 0 0">Recomendamos entrar alguns minutos antes. Se precisar remarcar, responda este e-mail e nossa equipe ajuda com um novo horario.</p>' .
            '</div></div></div>';
    }

    private function sendDeclinedEmail(array $booking): array
    {
        $schedulerUrl = base_url('agendador');
        $name = htmlspecialchars((string)$booking['client_name'], ENT_QUOTES, 'UTF-8');
        $url = htmlspecialchars($schedulerUrl, ENT_QUOTES, 'UTF-8');
        $html = '<div style="font-family:Inter,Arial,sans-serif;background:#f6f7fb;padding:28px;color:#172033">' .
            '<div style="max-width:640px;margin:0 auto;background:#fff;border:1px solid #e4e7ee;border-radius:14px;overflow:hidden">' .
            '<div style="background:#14213d;color:#fff;padding:28px 30px"><h1 style="margin:0;font-size:25px">Vamos encontrar um horario melhor para sua conversa com a FusionCore</h1><p style="margin:8px 0 0;color:#dbe7ff">Queremos garantir uma reuniao produtiva e com a atencao certa para o seu projeto.</p></div>' .
            '<div style="padding:30px"><p>Olá, ' . $name . '.</p><p>O horario solicitado nao pôde ser confirmado pela nossa equipe, mas queremos seguir com a conversa.</p>' .
            '<p>Escolha uma nova disponibilidade para falarmos sobre suas necessidades, oportunidades de automacao e como a FusionCore pode apoiar sua operacao.</p>' .
            '<p style="margin:28px 0"><a href="' . $url . '" style="background:#1f7a5a;color:#fff;text-decoration:none;padding:13px 18px;border-radius:8px;display:inline-block;font-weight:700">Escolher novo horario</a></p>' .
            '</div></div></div>';
        $sender = new EmailSender();
        return $sender->sendEmail(
            (string)$booking['client_email'],
            'Vamos reagendar sua conversa com a FusionCore',
            $html,
            strip_tags(str_replace(['<br>', '<br/>', '<br />'], "\n", $html)),
            null,
            false
        );
    }

    private function sendCancellationEmail(array $booking): array
    {
        $tz = new \DateTimeZone((string)($booking['timezone'] ?? $this->config['timezone']));
        $start = new \DateTimeImmutable((string)$booking['starts_at'], $tz);
        $end = new \DateTimeImmutable((string)$booking['ends_at'], $tz);
        $schedulerUrl = base_url('agendador');
        $name = htmlspecialchars((string)$booking['client_name'], ENT_QUOTES, 'UTF-8');
        $date = htmlspecialchars($start->format('d/m/Y'), ENT_QUOTES, 'UTF-8');
        $time = htmlspecialchars($start->format('H:i') . ' - ' . $end->format('H:i'), ENT_QUOTES, 'UTF-8');
        $url = htmlspecialchars($schedulerUrl, ENT_QUOTES, 'UTF-8');
        $logoUrl = 'https://app.fusioncore.com.br/assets/images/fusioncore-email-logo.png';
        $calendarIcon = 'https://www.gstatic.com/images/branding/product/1x/calendar_2020q4_48dp.png';

        $html = '<div style="font-family:Arial,Helvetica,sans-serif;background:#e8edf3;padding:30px;color:#0f172a">' .
            '<div style="max-width:680px;margin:0 auto;background:#ffffff;border:1px solid #cbd5e1;border-radius:14px;overflow:hidden;color:#0f172a;box-shadow:0 12px 30px rgba(15,23,42,0.10)">' .
            '<div style="background:#0b1220;color:#ffffff;padding:26px 30px 28px">' .
            '<div style="margin-bottom:22px"><img src="' . htmlspecialchars($logoUrl, ENT_QUOTES, 'UTF-8') . '" width="148" alt="FusionCore" style="display:block;max-width:148px;height:auto"></div>' .
            '<h1 style="margin:0;font-size:26px;line-height:1.25;color:#ffffff">Sua reunião com a FusionCore foi cancelada</h1>' .
            '<p style="margin:12px 0 0;color:#e2e8f0;font-size:16px;line-height:1.55">Estamos avisando para evitar qualquer desencontro de agenda.</p>' .
            '</div>' .
            '<div style="padding:30px;color:#0f172a;background:#ffffff">' .
            '<p style="margin:0 0 18px;color:#0f172a;font-size:16px;line-height:1.65">Olá, ' . $name . '.</p>' .
            '<p style="margin:0 0 22px;color:#0f172a;font-size:16px;line-height:1.65">A reunião que estava prevista com a FusionCore foi cancelada pela nossa equipe.</p>' .
            '<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="background:#f8fafc;border:1px solid #cbd5e1;border-radius:12px;margin:0 0 24px;color:#0f172a"><tr>' .
            '<td style="padding:18px;vertical-align:top;width:50%"><div style="font-size:13px;font-weight:700;color:#475569;text-transform:uppercase;letter-spacing:.04em">Data cancelada</div><div style="font-size:18px;font-weight:800;color:#0f172a;margin-top:4px">' . $date . '</div></td>' .
            '<td style="padding:18px;vertical-align:top;width:50%"><div style="font-size:13px;font-weight:700;color:#475569;text-transform:uppercase;letter-spacing:.04em">Horario</div><div style="font-size:18px;font-weight:800;color:#0f172a;margin-top:4px">' . $time . '</div></td>' .
            '</tr></table>' .
            '<p style="margin:28px 0"><a href="' . $url . '" style="background:#0f172a;color:#ffffff;text-decoration:none;padding:14px 18px;border-radius:8px;display:inline-block;font-weight:800"><img src="' . $calendarIcon . '" width="18" height="18" alt="" style="vertical-align:-4px;margin-right:6px">Escolher novo horario</a></p>' .
            '<p style="color:#334155;font-size:14px;line-height:1.6;margin:16px 0 0">Se essa conversa ainda fizer sentido, escolha uma nova disponibilidade pelo link acima ou responda este e-mail.</p>' .
            '</div></div></div>';

        $sender = new EmailSender();
        return $sender->sendEmail(
            (string)$booking['client_email'],
            'Sua reuniao com a FusionCore foi cancelada',
            $html,
            strip_tags(str_replace(['<br>', '<br/>', '<br />'], "\n", $html)),
            null,
            false
        );
    }

    private function icsEscape(string $value): string
    {
        return str_replace(["\\", "\n", "\r", ",", ";"], ["\\\\", "\\n", '', "\\,", "\\;"], $value);
    }
}

app/Models/MeetingScheduler.php

Model de disponibilidade e bookings

Model que persiste os agendamentos, valida conflito de horário e controla mudança de status em `meeting_bookings`.

<?php
namespace App\Models;

use App\Core\Model;

class MeetingScheduler extends Model
{
    protected $table = 'meeting_bookings';

    public function ensureTable(): void
    {
        $this->db->exec("CREATE TABLE IF NOT EXISTS meeting_bookings (
            id INT AUTO_INCREMENT PRIMARY KEY,
            token VARCHAR(64) NOT NULL UNIQUE,
            client_name VARCHAR(160) NOT NULL,
            client_email VARCHAR(190) NOT NULL,
            client_phone VARCHAR(60) NULL,
            company VARCHAR(160) NULL,
            subject VARCHAR(190) NULL,
            source VARCHAR(80) NULL,
            notes TEXT NULL,
            starts_at DATETIME NOT NULL,
            ends_at DATETIME NOT NULL,
            timezone VARCHAR(80) NOT NULL DEFAULT 'America/Sao_Paulo',
            status VARCHAR(30) NOT NULL DEFAULT 'pending',
            meeting_url TEXT NULL,
            google_calendar_url TEXT NULL,
            google_calendar_event_id VARCHAR(190) NULL,
            ics_uid VARCHAR(190) NOT NULL,
            reviewed_at DATETIME NULL,
            created_at DATETIME NOT NULL,
            updated_at DATETIME NULL,
            INDEX idx_meeting_bookings_starts_at (starts_at),
            INDEX idx_meeting_bookings_client_email (client_email),
            INDEX idx_meeting_bookings_status (status),
            INDEX idx_meeting_bookings_status_start (status, starts_at)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");

        $this->dropIndexIfExists('uniq_meeting_bookings_status_start');
        $stmt = $this->db->prepare(
            "SELECT COUNT(*) FROM information_schema.statistics
             WHERE table_schema = DATABASE() AND table_name = ? AND index_name = ?"
        );
        $stmt->execute([$this->table, 'idx_meeting_bookings_status_start']);
        if ((int)$stmt->fetchColumn() === 0) {
            $this->db->exec("ALTER TABLE {$this->table} ADD INDEX idx_meeting_bookings_status_start (status, starts_at)");
        }

        $this->ensureColumn('meeting_url', "TEXT NULL");
        $this->ensureColumn('reviewed_at', "DATETIME NULL");
        $this->ensureColumn('google_calendar_event_id', "VARCHAR(190) NULL");
        $this->ensureColumn('subject', "VARCHAR(190) NULL");
        $this->ensureColumn('source', "VARCHAR(80) NULL");
        try {
            $this->db->exec("ALTER TABLE {$this->table} MODIFY status VARCHAR(30) NOT NULL DEFAULT 'pending'");
        } catch (\Throwable $e) {
            // Mantem compatibilidade com bancos que bloqueiem ALTER; createBooking sempre envia status.
        }
    }

    private function ensureColumn(string $column, string $definition): void
    {
        $stmt = $this->db->prepare(
            "SELECT COUNT(*) FROM information_schema.columns
             WHERE table_schema = DATABASE() AND table_name = ? AND column_name = ?"
        );
        $stmt->execute([$this->table, $column]);
        if ((int)$stmt->fetchColumn() === 0) {
            $this->db->exec("ALTER TABLE {$this->table} ADD COLUMN {$column} {$definition}");
        }
    }

    private function dropIndexIfExists(string $indexName): void
    {
        $stmt = $this->db->prepare(
            "SELECT COUNT(*) FROM information_schema.statistics
             WHERE table_schema = DATABASE() AND table_name = ? AND index_name = ?"
        );
        $stmt->execute([$this->table, $indexName]);
        if ((int)$stmt->fetchColumn() > 0) {
            $this->db->exec("ALTER TABLE {$this->table} DROP INDEX {$indexName}");
        }
    }

    public function busyBetween(string $from, string $to): array
    {
        $this->ensureTable();
        return $this->fetchAll(
            "SELECT starts_at, ends_at FROM {$this->table}
             WHERE status IN ('pending', 'confirmed') AND starts_at < ? AND ends_at > ?
             ORDER BY starts_at ASC",
            [$to, $from]
        );
    }

    public function isAvailable(string $start, string $end): bool
    {
        $this->ensureTable();
        $stmt = $this->query(
            "SELECT COUNT(*) FROM {$this->table}
             WHERE status IN ('pending', 'confirmed') AND starts_at < ? AND ends_at > ?",
            [$end, $start]
        );
        return (int)$stmt->fetchColumn() === 0;
    }

    public function isAvailableExcludingToken(string $start, string $end, string $token): bool
    {
        $this->ensureTable();
        $stmt = $this->query(
            "SELECT COUNT(*) FROM {$this->table}
             WHERE status IN ('pending', 'confirmed') AND token <> ? AND starts_at < ? AND ends_at > ?",
            [$token, $end, $start]
        );
        return (int)$stmt->fetchColumn() === 0;
    }

    public function createBooking(array $data): int
    {
        $this->ensureTable();
        $data['created_at'] = date('Y-m-d H:i:s');
        return $this->create($data);
    }

    public function findByToken(string $token): ?array
    {
        $this->ensureTable();
        return $this->fetch("SELECT * FROM {$this->table} WHERE token = ? LIMIT 1", [$token]);
    }

    public function approve(string $token, string $meetingUrl, string $calendarUrl, ?string $googleEventId = null, ?string $endsAt = null): ?array
    {
        $this->ensureTable();
        $booking = $this->findByToken($token);
        if (!$booking || (string)$booking['status'] !== 'pending') {
            return null;
        }

        $data = [
            'status' => 'confirmed',
            'meeting_url' => $meetingUrl,
            'google_calendar_url' => $calendarUrl,
            'google_calendar_event_id' => $googleEventId,
            'reviewed_at' => date('Y-m-d H:i:s'),
            'updated_at' => date('Y-m-d H:i:s'),
        ];
        if ($endsAt !== null) {
            $data['ends_at'] = $endsAt;
        }

        $this->updateTable($this->table, $data, 'token = :token', [':token' => $token]);

        return $this->findByToken($token);
    }

    public function decline(string $token): ?array
    {
        $this->ensureTable();
        $booking = $this->findByToken($token);
        if (!$booking || (string)$booking['status'] !== 'pending') {
            return null;
        }

        $this->updateTable($this->table, [
            'status' => 'declined',
            'reviewed_at' => date('Y-m-d H:i:s'),
            'updated_at' => date('Y-m-d H:i:s'),
        ], 'token = :token', [':token' => $token]);

        return $this->findByToken($token);
    }

    public function cancel(string $token): ?array
    {
        $this->ensureTable();
        $booking = $this->findByToken($token);
        if (!$booking || in_array((string)$booking['status'], ['cancelled', 'declined'], true)) {
            return null;
        }

        $this->updateTable($this->table, [
            'status' => 'cancelled',
            'reviewed_at' => date('Y-m-d H:i:s'),
            'updated_at' => date('Y-m-d H:i:s'),
        ], 'token = :token', [':token' => $token]);

        return $this->findByToken($token);
    }

    public function latest(int $limit = 20): array
    {
        $this->ensureTable();
        return $this->fetchAll(
            "SELECT * FROM {$this->table} ORDER BY starts_at DESC LIMIT " . max(1, min(100, $limit))
        );
    }
}

app/Services/GoogleCalendarMeetService.php

Serviço Google Calendar + Meet

Serviço que autentica no Google, cria o evento da reunião, gera o link do Meet e remove o evento quando necessário.

<?php
namespace App\Services;

class GoogleCalendarMeetService
{
    private array $config;
    private array $rootConfig;
    private \PDO $db;

    public function __construct(array $config)
    {
        if (is_file(__DIR__ . '/../../vendor/autoload.php')) {
            require_once __DIR__ . '/../../vendor/autoload.php';
        }

        $this->rootConfig = $config;
        $this->config = $config['google_calendar'] ?? [];
        require_once __DIR__ . '/../../config/Database.php';
        $this->db = \Database::getInstance()->getConnection();
        $this->ensureTable();
    }

    public function isConfigured(): bool
    {
        return !empty($this->config['client_id']) && !empty($this->config['client_secret']);
    }

    public function hasToken(): bool
    {
        return $this->getTokenRow() !== null;
    }

    public function getStatus(): array
    {
        $row = $this->getTokenRow();
        return [
            'configured' => $this->isConfigured(),
            'connected' => $row !== null,
            'email' => $row['google_email'] ?? null,
            'expires_at' => $row['expires_at'] ?? null,
            'calendar_id' => $this->config['calendar_id'] ?? 'primary',
            'redirect_uri' => $this->redirectUri(),
        ];
    }

    public function authUrl(string $state): string
    {
        $client = $this->client();
        $client->setState($state);
        $client->setPrompt('consent');
        $client->setAccessType('offline');
        $client->setIncludeGrantedScopes(true);
        return $client->createAuthUrl();
    }

    public function handleCallback(string $code): array
    {
        $client = $this->client();
        $token = $client->fetchAccessTokenWithAuthCode($code);
        if (!empty($token['error'])) {
            throw new \RuntimeException((string)($token['error_description'] ?? $token['error']));
        }

        $client->setAccessToken($token);
        $email = $this->fetchUserEmail($client);
        $this->storeToken($token, $email);

        return ['email' => $email];
    }

    public function createMeetEvent(array $booking, \DateTimeImmutable $start, \DateTimeImmutable $end): array
    {
        $client = $this->authorizedClient();
        $service = new \Google\Service\Calendar($client);

        $summary = 'Call FusionCore - ' . (string)($booking['client_name'] ?? 'Lead');
        $description = "Reuniao agendada via FusionCore.\n\n" .
            'Cliente: ' . (string)($booking['client_name'] ?? '') . "\n" .
            'E-mail: ' . (string)($booking['client_email'] ?? '') . "\n" .
            'Empresa: ' . (string)($booking['company'] ?? '') . "\n" .
            'Assunto: ' . (string)($booking['subject'] ?? '') . "\n" .
            'Observacoes: ' . (string)($booking['notes'] ?? '');

        $event = new \Google\Service\Calendar\Event([
            'summary' => $summary,
            'description' => $description,
            'start' => [
                'dateTime' => $start->format(DATE_ATOM),
                'timeZone' => $start->getTimezone()->getName(),
            ],
            'end' => [
                'dateTime' => $end->format(DATE_ATOM),
                'timeZone' => $end->getTimezone()->getName(),
            ],
            'attendees' => [
                ['email' => (string)$booking['client_email']],
                ['email' => (string)($this->rootConfig['owner_email'] ?? 'lucas.contato.fenix@gmail.com')],
            ],
            'conferenceData' => [
                'createRequest' => [
                    'requestId' => 'fusioncore-' . substr((string)$booking['token'], 0, 24),
                    'conferenceSolutionKey' => ['type' => 'hangoutsMeet'],
                ],
            ],
        ]);

        $created = $service->events->insert(
            (string)($this->config['calendar_id'] ?? 'primary'),
            $event,
            [
                'conferenceDataVersion' => 1,
                'sendUpdates' => 'none',
            ]
        );

        return [
            'event_id' => $created->getId(),
            'html_link' => $created->getHtmlLink(),
            'meet_link' => $created->getHangoutLink(),
        ];
    }

    public function deleteEvent(string $eventId): bool
    {
        $eventId = trim($eventId);
        if ($eventId === '') {
            return false;
        }

        $client = $this->authorizedClient();
        $service = new \Google\Service\Calendar($client);
        $service->events->delete(
            (string)($this->config['calendar_id'] ?? 'primary'),
            $eventId,
            ['sendUpdates' => 'none']
        );
        return true;
    }

    public function busyBetween(\DateTimeImmutable $start, \DateTimeImmutable $end): array
    {
        if (!$this->isConfigured() || !$this->hasToken()) {
            return [];
        }

        $client = $this->authorizedClient();
        $service = new \Google\Service\Calendar($client);
        $calendarId = (string)($this->config['calendar_id'] ?? 'primary');
        $freeBusyRequest = new \Google\Service\Calendar\FreeBusyRequest([
            'timeMin' => $start->format(DATE_ATOM),
            'timeMax' => $end->format(DATE_ATOM),
            'timeZone' => $start->getTimezone()->getName(),
            'items' => [
                ['id' => $calendarId],
            ],
        ]);

        $response = $service->freebusy->query($freeBusyRequest);
        $calendars = $response->getCalendars();
        if (!is_array($calendars) || !isset($calendars[$calendarId])) {
            return [];
        }

        $busyCollection = $calendars[$calendarId]->getBusy();
        if (!is_array($busyCollection)) {
            return [];
        }

        $busy = [];
        foreach ($busyCollection as $item) {
            $busy[] = [
                'starts_at' => (string)$item->getStart(),
                'ends_at' => (string)$item->getEnd(),
            ];
        }

        return $busy;
    }

    private function client(): \Google\Client
    {
        if (!$this->isConfigured()) {
            throw new \RuntimeException('Credenciais Google Calendar nao configuradas.');
        }

        $client = new \Google\Client();
        $client->setClientId((string)$this->config['client_id']);
        $client->setClientSecret((string)$this->config['client_secret']);
        $client->setRedirectUri($this->redirectUri());
        $client->setScopes([
            \Google\Service\Calendar::CALENDAR,
            \Google\Service\Calendar::CALENDAR_EVENTS,
            'openid',
            'email',
        ]);
        return $client;
    }

    private function authorizedClient(): \Google\Client
    {
        $row = $this->getTokenRow();
        if (!$row) {
            throw new \RuntimeException('Conta Google Calendar nao conectada.');
        }

        $client = $this->client();
        $token = json_decode((string)$row['token_json'], true) ?: [];
        $client->setAccessToken($token);

        if ($client->isAccessTokenExpired()) {
            $refreshToken = $client->getRefreshToken() ?: ($token['refresh_token'] ?? null);
            if (!$refreshToken) {
                throw new \RuntimeException('Token Google expirado e sem refresh token. Reconecte a conta.');
            }
            $newToken = $client->fetchAccessTokenWithRefreshToken($refreshToken);
            if (!empty($newToken['error'])) {
                throw new \RuntimeException((string)($newToken['error_description'] ?? $newToken['error']));
            }
            if (empty($newToken['refresh_token'])) {
                $newToken['refresh_token'] = $refreshToken;
            }
            $this->storeToken($newToken, (string)($row['google_email'] ?? ''));
            $client->setAccessToken($newToken);
        }

        return $client;
    }

    private function fetchUserEmail(\Google\Client $client): string
    {
        try {
            $oauth = new \Google\Service\Oauth2($client);
            $userInfo = $oauth->userinfo->get();
            return (string)$userInfo->getEmail();
        } catch (\Throwable $e) {
            return '';
        }
    }

    private function storeToken(array $token, string $email): void
    {
        $expiresAt = null;
        if (!empty($token['created']) && !empty($token['expires_in'])) {
            $expiresAt = date('Y-m-d H:i:s', (int)$token['created'] + (int)$token['expires_in']);
        }

        $stmt = $this->db->prepare(
            "REPLACE INTO meeting_google_tokens (id, google_email, token_json, expires_at, updated_at)
             VALUES (1, ?, ?, ?, NOW())"
        );
        $stmt->execute([$email, json_encode($token, JSON_UNESCAPED_SLASHES), $expiresAt]);
    }

    private function getTokenRow(): ?array
    {
        $this->ensureTable();
        $stmt = $this->db->query("SELECT * FROM meeting_google_tokens WHERE id = 1 LIMIT 1");
        $row = $stmt->fetch(\PDO::FETCH_ASSOC);
        return $row ?: null;
    }

    private function ensureTable(): void
    {
        $this->db->exec("CREATE TABLE IF NOT EXISTS meeting_google_tokens (
            id TINYINT PRIMARY KEY,
            google_email VARCHAR(190) NULL,
            token_json TEXT NOT NULL,
            expires_at DATETIME NULL,
            updated_at DATETIME NOT NULL
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
    }

    private function redirectUri(): string
    {
        $path = (string)($this->config['redirect_path'] ?? 'agendador/google-callback');
        return base_url($path);
    }
}

config/meeting_scheduler.php

Configuração do scheduler

Arquivo de configuração do agendador com timezone, janelas de horário, duração, provider da reunião e credenciais lidas por ambiente.

<?php

$env = [];
$envPath = __DIR__ . '/../.env';
if (is_file($envPath)) {
    foreach (file($envPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [] as $line) {
        $line = trim($line);
        if ($line === '' || str_starts_with($line, '#') || strpos($line, '=') === false) {
            continue;
        }
        [$key, $value] = explode('=', $line, 2);
        $env[trim($key)] = trim($value, " \t\n\r\0\x0B\"'");
    }
}
$envValue = static function (string $key, string $default = '') use ($env): string {
    $value = getenv($key);
    if ($value !== false && $value !== '') {
        return (string)$value;
    }
    return (string)($env[$key] ?? $default);
};

return [
    'owner_name' => 'Lucas',
    'owner_email' => 'lucas.contato.fenix@gmail.com',
    'approval_email' => 'contato@fusioncore.com.br',
    'timezone' => 'America/Sao_Paulo',
    'weekday_start' => '10:00',
    'weekday_end' => '18:00',
    'date_overrides' => [
        '2026-05-17' => [
            'start' => '12:00',
            'end' => '18:00',
            'label' => 'Janela excepcional de domingo para call comercial',
        ],
    ],
    'slot_minutes' => 30,
    'meeting_minutes' => 30,
    'duration_options_minutes' => [15, 30, 45, 60, 90, 120],
    'min_notice_hours' => 2,
    'days_ahead' => 21,
    'title' => 'Call FusionCore',
    'location' => 'Google Meet',
    'meeting_provider' => 'jitsi',
    'meeting_base_url' => 'https://meet.jit.si',
    'default_meet_url' => '',
    'google_calendar' => [
        'enabled' => true,
        'client_id' => $envValue('GOOGLE_CALENDAR_CLIENT_ID'),
        'client_secret' => $envValue('GOOGLE_CALENDAR_CLIENT_SECRET'),
        'calendar_id' => $envValue('GOOGLE_CALENDAR_ID', 'primary'),
        'redirect_path' => 'agendador/google_callback',
    ],
];

Agenda técnica

Vamos revisar arquitetura, escopo e próximos passos.

Uma conversa curta para avaliar WordPress como camada de aplicação, integrações, infraestrutura e responsabilidade de produção.

Agendar