feat(home): prova no topo, contato sem atrito e conversão medida
Home mais curta (hero, vitrine de clientes, sobre, serviços, IA resumida, depoimentos e contato), faixa de clientes e telas reais no hero, sem números do GitHub. Barra de cookies fina, WhatsApp fixo no celular, formulário com e-mail opcional e alternativa por e-mail, página /ia, eventos de e-mail, redes e currículo e conversão do Ads opcional por variável. Rodapé em 3 colunas. Remove componentes órfãos (LighthouseMetrics, Skills, Experience, contagem do GitHub).
This commit is contained in:
1 parent
6e1022b58c
commit
8d07b8c6bb
60 files changed
+4392
-2124
No files matched your search
+108
-144
@@ -7,11 +7,13 @@ import {
|
||||
CONSENT_STORAGE_KEY,
|
||||
GA_DISABLE_KEY,
|
||||
GA_MEASUREMENT_ID,
|
||||
GOOGLE_ADS_ID,
|
||||
type Gtag,
|
||||
OPEN_COOKIE_PREFERENCES_EVENT,
|
||||
readConsent,
|
||||
} from "@/lib/analytics";
|
||||
import "./conversion.css";
|
||||
|
||||
const GOOGLE_ADS_ID = "AW-18432694647";
|
||||
const GA_SCRIPT_SRC = `https://www.googletagmanager.com/gtag/js?id=${GA_MEASUREMENT_ID}`;
|
||||
const SITE_DOMAIN = "evandro.dev.br";
|
||||
|
||||
@@ -76,17 +78,17 @@ function expireAnalyticsCookies() {
|
||||
}
|
||||
}
|
||||
|
||||
const panelClassName =
|
||||
"fixed bottom-sp-4 left-sp-4 right-sp-4 z-[9980] ml-auto max-w-[560px] border border-[var(--slash)] bg-[var(--ink)] p-sp-5 text-white shadow-[8px_8px_0_var(--slash)] sm:left-auto";
|
||||
const buttonClassName =
|
||||
"inline-flex min-h-11 min-w-11 items-center justify-center border px-sp-4 py-sp-2 font-mono text-[11px] font-bold uppercase tracking-[0.08em] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--slash)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ink)]";
|
||||
const barButtonClassName =
|
||||
"cv-btn inline-flex min-h-10 min-w-11 items-center justify-center border px-[10px] py-sp-1 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ink)] motion-reduce:transition-none";
|
||||
|
||||
function GoogleAnalyticsConsent() {
|
||||
const [consent, setConsent] = useState<AnalyticsConsent | null>();
|
||||
const [preferencesOpen, setPreferencesOpen] = useState(false);
|
||||
const [consentError, setConsentError] = useState<string | null>(null);
|
||||
const configuredRef = useRef(false);
|
||||
const preferencesButtonRef = useRef<HTMLButtonElement>(null);
|
||||
const barRef = useRef<HTMLElement>(null);
|
||||
const acceptRef = useRef<HTMLButtonElement>(null);
|
||||
const openerRef = useRef<HTMLElement | null>(null);
|
||||
|
||||
const enableAnalytics = useCallback(() => {
|
||||
const gtag = getGtag();
|
||||
@@ -123,12 +125,56 @@ function GoogleAnalyticsConsent() {
|
||||
}
|
||||
}, [consent]);
|
||||
|
||||
// O link "Preferências de cookies" do rodapé reabre a barra; quem pediu recebe o foco de volta ao fechar.
|
||||
useEffect(() => {
|
||||
const reopen = () => {
|
||||
openerRef.current =
|
||||
document.activeElement instanceof HTMLElement
|
||||
? document.activeElement
|
||||
: null;
|
||||
setPreferencesOpen(true);
|
||||
};
|
||||
window.addEventListener(OPEN_COOKIE_PREFERENCES_EVENT, reopen);
|
||||
return () =>
|
||||
window.removeEventListener(OPEN_COOKIE_PREFERENCES_EVENT, reopen);
|
||||
}, []);
|
||||
|
||||
const barOpen = consent === null || preferencesOpen;
|
||||
|
||||
// Enquanto a barra está na tela, publica a altura dela para o botão flutuante do WhatsApp subir.
|
||||
useEffect(() => {
|
||||
const bar = barRef.current;
|
||||
if (!barOpen || !bar) return;
|
||||
const root = document.documentElement;
|
||||
const publish = () =>
|
||||
root.style.setProperty("--cookie-bar-height", `${bar.offsetHeight}px`);
|
||||
publish();
|
||||
root.setAttribute("data-cookie-bar", "open");
|
||||
const observer = new ResizeObserver(publish);
|
||||
observer.observe(bar);
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
root.style.removeProperty("--cookie-bar-height");
|
||||
root.removeAttribute("data-cookie-bar");
|
||||
};
|
||||
}, [barOpen]);
|
||||
|
||||
// Reaberta pelo rodapé, a barra está no começo do documento: leva o foco para ela.
|
||||
useEffect(() => {
|
||||
if (preferencesOpen) acceptRef.current?.focus();
|
||||
}, [preferencesOpen]);
|
||||
|
||||
function closePreferences() {
|
||||
setPreferencesOpen(false);
|
||||
preferencesButtonRef.current?.focus();
|
||||
openerRef.current?.focus();
|
||||
openerRef.current = null;
|
||||
}
|
||||
|
||||
function acceptAnalytics() {
|
||||
if (consent === "granted") {
|
||||
closePreferences();
|
||||
return;
|
||||
}
|
||||
if (!persistConsent("granted")) {
|
||||
setConsentError(
|
||||
"Não foi possível salvar sua aceitação. Tente novamente para concluir.",
|
||||
@@ -142,92 +188,27 @@ function GoogleAnalyticsConsent() {
|
||||
}
|
||||
|
||||
function denyAnalytics() {
|
||||
const revoking = consent === "granted";
|
||||
if (!persistConsent("denied")) {
|
||||
setConsentError(
|
||||
"Não foi possível salvar sua recusa. Tente novamente para concluir.",
|
||||
revoking
|
||||
? "Não foi possível salvar a revogação. Tente novamente para concluir."
|
||||
: "Não foi possível salvar sua recusa. Tente novamente para concluir.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
setConsentError(null);
|
||||
setConsent("denied");
|
||||
closePreferences();
|
||||
}
|
||||
|
||||
function revokeAnalytics() {
|
||||
if (!persistConsent("denied")) {
|
||||
setConsentError(
|
||||
"Não foi possível salvar a revogação. Tente novamente para concluir.",
|
||||
);
|
||||
return;
|
||||
if (revoking) {
|
||||
window[GA_DISABLE_KEY] = true;
|
||||
window.gtag?.("consent", "update", deniedConsent);
|
||||
expireAnalyticsCookies();
|
||||
}
|
||||
setConsentError(null);
|
||||
window[GA_DISABLE_KEY] = true;
|
||||
window.gtag?.("consent", "update", deniedConsent);
|
||||
expireAnalyticsCookies();
|
||||
setConsent("denied");
|
||||
closePreferences();
|
||||
}
|
||||
|
||||
if (consent === undefined) return null;
|
||||
|
||||
if (consent === null) {
|
||||
return (
|
||||
<section
|
||||
role="dialog"
|
||||
aria-modal="false"
|
||||
aria-labelledby="analytics-consent-title"
|
||||
aria-describedby="analytics-consent-description"
|
||||
className={panelClassName}
|
||||
>
|
||||
<p
|
||||
id="analytics-consent-title"
|
||||
className="font-mono text-[10px] font-bold uppercase tracking-[0.16em] text-[var(--slash)]"
|
||||
>
|
||||
{"Privacidade // Analytics"}
|
||||
</p>
|
||||
<p
|
||||
id="analytics-consent-description"
|
||||
className="mt-sp-2 max-w-[48ch] text-[13px] leading-6 text-white/85"
|
||||
>
|
||||
Você decide se permite métricas de navegação com a tag compartilhada
|
||||
do Google Analytics e Google Ads. Cookies de publicidade e
|
||||
personalização continuam desativados.
|
||||
</p>
|
||||
{consentError && (
|
||||
<p
|
||||
role="alert"
|
||||
aria-label="Falha ao salvar o consentimento"
|
||||
className="mt-sp-3 text-[13px] leading-6 text-[var(--slash)]"
|
||||
>
|
||||
{consentError}
|
||||
</p>
|
||||
)}
|
||||
<div className="mt-sp-4 flex flex-wrap gap-sp-2">
|
||||
<button
|
||||
type="button"
|
||||
className={`${buttonClassName} border-[var(--slash)] bg-[var(--slash)] text-[var(--ink)]`}
|
||||
onClick={acceptAnalytics}
|
||||
>
|
||||
Aceitar analytics
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`${buttonClassName} border-white/40 bg-transparent text-white`}
|
||||
onClick={denyAnalytics}
|
||||
>
|
||||
Recusar analytics
|
||||
</button>
|
||||
</div>
|
||||
<a
|
||||
className="ds-link ds-link-accent mt-sp-3 inline-block"
|
||||
href="/privacidade"
|
||||
>
|
||||
Ler política de privacidade
|
||||
</a>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{consent === "granted" && (
|
||||
@@ -238,82 +219,65 @@ function GoogleAnalyticsConsent() {
|
||||
strategy="afterInteractive"
|
||||
/>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
ref={preferencesButtonRef}
|
||||
aria-expanded={preferencesOpen}
|
||||
aria-controls="analytics-preferences"
|
||||
className={`${buttonClassName} fixed bottom-sp-4 right-sp-4 z-[9980] border-white/25 bg-[var(--ink)] text-white shadow-[4px_4px_0_var(--slash)]`}
|
||||
onClick={() => setPreferencesOpen((open) => !open)}
|
||||
>
|
||||
<span className="max-sm:hidden">Preferências de analytics</span>
|
||||
<span aria-hidden="true" className="sm:hidden">
|
||||
Cookies
|
||||
</span>
|
||||
</button>
|
||||
{preferencesOpen && (
|
||||
{barOpen && (
|
||||
<section
|
||||
id="analytics-preferences"
|
||||
ref={barRef}
|
||||
role="dialog"
|
||||
aria-modal="false"
|
||||
aria-labelledby="analytics-preferences-title"
|
||||
aria-describedby="analytics-preferences-description"
|
||||
className={panelClassName}
|
||||
aria-label="Cookies e privacidade"
|
||||
aria-describedby="analytics-consent-description"
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Escape" && preferencesOpen) closePreferences();
|
||||
}}
|
||||
className="fixed inset-x-0 bottom-0 z-[9980] border-t border-white/20 bg-[var(--ink)] px-sp-4 pt-sp-2 pb-[max(var(--sp-2),env(safe-area-inset-bottom))] text-white shadow-[0_-8px_24px_rgba(0,0,0,0.35)] sm:px-sp-6"
|
||||
>
|
||||
<p
|
||||
id="analytics-preferences-title"
|
||||
className="font-mono text-[10px] font-bold uppercase tracking-[0.16em] text-[var(--slash)]"
|
||||
>
|
||||
{"Preferências // Analytics"}
|
||||
</p>
|
||||
<p
|
||||
id="analytics-preferences-description"
|
||||
className="mt-sp-2 text-[13px] leading-6 text-white/85"
|
||||
>
|
||||
Analytics está {consent === "granted" ? "ativo" : "desativado"}.
|
||||
Cookies de publicidade e personalização permanecem negados.
|
||||
</p>
|
||||
<div className="mx-auto flex max-w-[1100px] items-center gap-sp-3 sm:gap-sp-6">
|
||||
<p
|
||||
id="analytics-consent-description"
|
||||
className="min-w-0 flex-1 font-display text-[13px] leading-[1.3] text-white/85 sm:text-sm"
|
||||
>
|
||||
Usamos métricas de navegação do Google só se você permitir.{" "}
|
||||
<a
|
||||
className="whitespace-nowrap underline underline-offset-2 hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white"
|
||||
href="/privacidade"
|
||||
>
|
||||
Política de privacidade
|
||||
</a>
|
||||
{preferencesOpen && consent && (
|
||||
<span className="sr-only">
|
||||
{" "}
|
||||
Escolha atual:{" "}
|
||||
{consent === "granted" ? "permitido" : "recusado"}.
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
<div className="flex shrink-0 gap-sp-2">
|
||||
<button
|
||||
type="button"
|
||||
ref={acceptRef}
|
||||
className={`${barButtonClassName} border-[#f4f2ed] bg-[#f4f2ed] text-[var(--ink)] hover:bg-white`}
|
||||
onClick={acceptAnalytics}
|
||||
>
|
||||
Aceitar
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`${barButtonClassName} border-white/45 bg-transparent text-white hover:border-white`}
|
||||
onClick={denyAnalytics}
|
||||
>
|
||||
Recusar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{consentError && (
|
||||
<p
|
||||
role="alert"
|
||||
aria-label="Falha ao salvar o consentimento"
|
||||
className="mt-sp-3 text-[13px] leading-6 text-[var(--slash)]"
|
||||
className="mx-auto mt-sp-1 max-w-[1100px] font-display text-[13px] leading-5 text-[#ff9d94]"
|
||||
>
|
||||
{consentError}
|
||||
</p>
|
||||
)}
|
||||
<div className="mt-sp-4 flex flex-wrap gap-sp-2">
|
||||
{consent === "granted" ? (
|
||||
<button
|
||||
type="button"
|
||||
className={`${buttonClassName} border-[var(--slash)] bg-[var(--slash)] text-[var(--ink)]`}
|
||||
onClick={revokeAnalytics}
|
||||
>
|
||||
Revogar analytics
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className={`${buttonClassName} border-[var(--slash)] bg-[var(--slash)] text-[var(--ink)]`}
|
||||
onClick={acceptAnalytics}
|
||||
>
|
||||
Aceitar analytics
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className={`${buttonClassName} border-white/40 bg-transparent text-white`}
|
||||
onClick={closePreferences}
|
||||
>
|
||||
Fechar preferências
|
||||
</button>
|
||||
</div>
|
||||
<a
|
||||
className="ds-link ds-link-accent mt-sp-3 inline-block"
|
||||
href="/privacidade"
|
||||
>
|
||||
Ler política de privacidade
|
||||
</a>
|
||||
</section>
|
||||
)}
|
||||
</>
|
||||
|
||||
Reference in new issue
Block a user