feat(home): posicionamento com prova, menu sem recarregar e WhatsApp em todo o site
H1 'Sites e sistemas que trazem pedidos pelo WhatsApp e tiram sua operação da planilha.'; HeroWork maior com a SEV; menu Cases, Serviços, Como trabalho, Sobre e IA com next/link e pré-carregamento no hover; menu do celular em tela cheia; WhatsApp flutuante e no rodapé com a mensagem da rota; rodapé em todas as páginas; FAQ da home; /ia como oferta de revisão de app feito com IA; formulário com noscript, validação do mailto e plano B do pop-up; cartões sem foco indevido; sem a fonte Monument.
This commit is contained in:
1 parent
e80f73d075
commit
10f170abf4
56 files changed
+3099
-906
No files matched your search
+24
-43
@@ -23,28 +23,25 @@ const SOCIAL_LINKS = [
|
||||
];
|
||||
|
||||
/**
|
||||
* Números reais, calculados em app/page.tsx (nada digitado à mão): os anos vêm de public/portfolio-data.json.
|
||||
* Números reais, calculados em app/page.tsx (nada digitado à mão): os anos vêm de public/portfolio-data.json
|
||||
* e aparecem na mesma linha de "Já entregue para" (ClientStrip).
|
||||
*/
|
||||
export type HeroStats = {
|
||||
years?: number;
|
||||
};
|
||||
|
||||
type StatItem = {
|
||||
value: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
function statItems(stats?: HeroStats): StatItem[] {
|
||||
if (!stats?.years) return [];
|
||||
return [{ value: String(stats.years), label: "anos de experiência" }];
|
||||
}
|
||||
|
||||
/** O destaque em limão fica num trecho curto: "pedidos pelo WhatsApp" (palavras 5 a 7). */
|
||||
const ACCENT = new Set([5, 6, 7]);
|
||||
/** Título em palavras para a entrada em cascata; o texto segue inteiro para leitores de tela e buscadores. */
|
||||
const TITLE =
|
||||
"Sistemas sob medida para operações que não cabem em soluções prontas.".split(
|
||||
" ",
|
||||
);
|
||||
const ACCENT = new Set([1, 2]);
|
||||
"Sites e sistemas que trazem pedidos pelo WhatsApp e tiram sua operação da planilha."
|
||||
.split(" ")
|
||||
.map((word, index) => ({
|
||||
word,
|
||||
// a mesma palavra se repete ("e"), então a posição entra na chave
|
||||
key: `${word}-${index}`,
|
||||
accent: ACCENT.has(index),
|
||||
}));
|
||||
|
||||
/** O que cada camada da cena significa, em linguagem simples. */
|
||||
const CAPTIONS = [
|
||||
@@ -63,11 +60,9 @@ export default function Hero({
|
||||
}: {
|
||||
stats?: HeroStats;
|
||||
clients?: ClientLink[];
|
||||
/** Endereço mostrado na barra do navegador de HeroWork (o do site da Lavação 047). */
|
||||
/** Endereço mostrado na barra do navegador de HeroWork (o domínio próprio do site da SEV Moda Íntima). */
|
||||
workUrl?: HeroWorkUrl;
|
||||
}) {
|
||||
const items = statItems(stats);
|
||||
|
||||
return (
|
||||
<section
|
||||
id="home"
|
||||
@@ -94,12 +89,12 @@ export default function Hero({
|
||||
Evandro Fonseca Junior · Engenharia de software
|
||||
</p>
|
||||
{/* Sem opacity:0: as palavras pintam no primeiro quadro (LCP); a entrada anima só posição e foco. */}
|
||||
{/* No desktop o título para antes da coluna HeroWork (largura da coluna: clamp(300px,24vw,400px)). */}
|
||||
<h1 className="hero-title max-w-[12.5em] lg:max-w-[min(11em,54vw,calc(100%-clamp(300px,24vw,400px)-24px))] text-[clamp(38px,min(6vw,9svh),124px)] font-semibold leading-[0.98] tracking-[-0.05em]">
|
||||
{TITLE.map((word, index) => (
|
||||
<Fragment key={word}>
|
||||
{/* No desktop o título ocupa até 54% da largura; a coluna HeroWork fica com 40%, à direita. */}
|
||||
<h1 className="hero-title max-w-[16em] text-[clamp(34px,min(8.4vw,5.2svh+14px),84px)] font-semibold leading-[1.02] tracking-[-0.045em] lg:max-w-[54%]">
|
||||
{TITLE.map(({ word, key, accent }, index) => (
|
||||
<Fragment key={key}>
|
||||
<span
|
||||
className={`hero-word${ACCENT.has(index) ? " text-[var(--slash)]" : ""}`}
|
||||
className={`hero-word${accent ? " text-[var(--slash)]" : ""}`}
|
||||
style={{ "--i": index } as CSSProperties}
|
||||
>
|
||||
{word}
|
||||
@@ -107,10 +102,9 @@ export default function Hero({
|
||||
</Fragment>
|
||||
))}
|
||||
</h1>
|
||||
<p className="hero-in mt-sp-4 max-w-[34em] text-[clamp(16px,0.95rem+0.4vw,22px)] leading-[1.55] text-[rgba(244,242,237,.8)] [--d:.55s] lg:max-w-[min(40em,calc(100vw-clamp(300px,24vw,400px)-9rem))]">
|
||||
Para empresas que cresceram além das planilhas e dos sistemas
|
||||
prontos: sites, sistemas web, aplicativos e integrações, com contato
|
||||
direto com quem desenvolve.
|
||||
<p className="hero-in mt-sp-4 max-w-[34em] text-[clamp(16px,0.95rem+0.4vw,22px)] leading-[1.55] text-[rgba(244,242,237,.8)] [--d:.55s] lg:max-w-[min(36em,54%)]">
|
||||
Do catálogo com pedido no WhatsApp ao painel da sua equipe. Você
|
||||
fala direto com quem desenvolve.
|
||||
</p>
|
||||
|
||||
<div className="hero-in mt-sp-5 flex flex-col gap-sp-3 [--d:.65s] sm:flex-row sm:flex-wrap sm:items-center">
|
||||
@@ -141,7 +135,8 @@ export default function Hero({
|
||||
|
||||
<ClientStrip
|
||||
clients={clients}
|
||||
className="hero-in mt-sp-4 [--d:.75s] lg:max-w-[min(44rem,calc(100vw-clamp(300px,24vw,400px)-9rem))]"
|
||||
years={stats?.years}
|
||||
className="hero-in mt-sp-3 [--d:.75s]"
|
||||
/>
|
||||
|
||||
<HeroWork url={workUrl} />
|
||||
@@ -149,24 +144,10 @@ export default function Hero({
|
||||
|
||||
<div className="hero-foot relative mx-auto flex w-full max-w-[max(1400px,var(--shell))] flex-wrap items-end justify-between gap-sp-5 px-sp-6 pb-sp-5 lg:px-sp-12">
|
||||
<div className="flex flex-wrap items-end gap-x-sp-8 gap-y-sp-4 border-t border-white/10 pt-sp-4">
|
||||
{items.length > 0 && (
|
||||
<dl className="flex gap-x-sp-5 gap-y-sp-4 sm:flex-wrap sm:gap-x-sp-8">
|
||||
{items.map(({ value, label }) => (
|
||||
<div key={label} className="flex flex-col-reverse">
|
||||
<dt className="text-[13px] leading-[1.4] text-[rgba(244,242,237,.64)]">
|
||||
{label}
|
||||
</dt>
|
||||
<dd className="font-mono text-[clamp(22px,1.1rem+0.8vw,36px)] font-semibold leading-none tracking-[-0.03em] text-[var(--slash)]">
|
||||
{value}
|
||||
</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
)}
|
||||
{/* no celular, serviços e redes ficam no menu e no rodapé: assim o hero cabe numa tela e pode ficar fixo */}
|
||||
<div className="max-lg:hidden">
|
||||
<ul className="flex flex-wrap gap-x-sp-5 gap-y-sp-2 text-[14px] leading-[1.5] text-[rgba(244,242,237,.8)]">
|
||||
{["Sistemas web", "Aplicativos", "Integrações"].map(
|
||||
{["Sites", "Sistemas web", "Aplicativos", "Integrações"].map(
|
||||
(service) => (
|
||||
<li key={service} className="flex items-center gap-sp-2">
|
||||
<span
|
||||
|
||||
Reference in new issue
Block a user