Files
portifolio-evandrodevbr/app/components/contact-subject.ts
T
evandro c7d09d63d4
CI / audit (push) Successful in 10m38s
CI / verify (push) Successful in 13m10s
CI / deploy (push) Successful in 1m50s
CI / e2e (push) Successful in 18m48s
feat(site): blocos 3D da marca, novas ofertas e hero mais abrangente
Hero e movimento
- cena 3D em tempo real com os blocos da marca (kit feito no Blender, 3d/blocos.py):
  marca, tela, integrações e dados; segue a rolagem, tem vida em repouso, reage ao
  cursor e, parada no topo, passa sozinha pelos estados com legenda
- três níveis de aparelho (full, lite, static); sem WebGL fica o pôster
- título novo: "Sites e sistemas sob medida para o jeito que a sua empresa trabalha."
- faixa de clientes com o ramo, linha do primeiro passo, hero alinhado às seções
- rótulos digitados, entrada dos blocos, cortina de blocos entre páginas e capa
  compartilhada entre a lista de cases e o case

Ofertas
- atendimento no WhatsApp com IA e nota fiscal para a reforma tributária
  (páginas de serviço, cartões da home, formulário e aviso com data de validade)
- página /ia atualizada, com fontes
- peças 3D em loop nos cartões e topos de página (3d/pecas.py)

Design
- hover por papel (bloco nos botões, barra nos links), rótulos sem caixa alta
- um destino por rótulo: "Falar sobre meu projeto" leva ao formulário,
  "Chamar no WhatsApp" abre o WhatsApp
- logo montado em blocos, 404 redesenhado, grades sem célula vazia
- ícones do PWA e manifesto novos

Specs em specs/, contexto de design em DESIGN.md e PRODUCT.md.
2026-11-10 03:48:25 -03:00

70 lines
3.2 KiB
TypeScript

/**
* Tipos de projeto do formulário de contato (Contact.tsx) e o jeito de as ofertas da home escolherem um deles.
* Uma oferta abre o formulário com o tipo já marcado de dois jeitos: o endereço `/?tipo=<tipo>#contact-form` (vale ao
* chegar de outra página ou em um link copiado) e o evento CONTACT_KIND_EVENT (vale na própria home, onde o
* gerenciador de âncoras rola até o formulário sem recarregar a página, e ContactOfferLink deixa o endereço como `/#contact-form`).
* O destino é o cartão do formulário (`#contact-form`), e não a cena inteira (`#contact`): no celular o formulário fica
* abaixo do texto e dos contatos diretos, e rolar até o começo da cena parava acima do campo do tipo.
* O tipo vai na busca, antes do `#`: um `/#contact-form?tipo=...` faria o fragmento virar "contact-form?tipo=...",
* que não é o id de nenhum elemento, e a página não rolaria até o formulário.
*/
import { offerLabels } from "@/lib/offer-labels";
/** Primeiro e padrão: quem chega sem saber o que pedir não é empurrado para um tipo. */
export const DEFAULT_CONTACT_SUBJECT = "Ainda preciso definir";
/**
* As cinco ofertas entram com o nome que têm na home e em /servicos, que vem de `offerLabels` (lib/offer-labels.ts): o
* módulo pequeno que o navegador pode importar, ao contrário de lib/site-content.ts, que reúne o texto de todas as páginas.
*/
export const CONTACT_SUBJECT_OPTIONS = [
DEFAULT_CONTACT_SUBJECT,
offerLabels.site.name,
offerLabels.sistema.name,
offerLabels.whatsapp.name,
"Aplicativos",
"Integrações e automações",
"Evolução de um sistema existente",
offerLabels.revisao.name,
offerLabels.fiscal.name,
] as const;
/** Tipo que uma oferta pede -> opção do formulário (o nome da oferta). */
export const CONTACT_KINDS = {
site: offerLabels.site.name,
sistema: offerLabels.sistema.name,
whatsapp: offerLabels.whatsapp.name,
revisao: offerLabels.revisao.name,
fiscal: offerLabels.fiscal.name,
} as const satisfies Record<string, (typeof CONTACT_SUBJECT_OPTIONS)[number]>;
export type ContactKind = keyof typeof CONTACT_KINDS;
/** Parâmetro de busca do endereço: `/?tipo=site#contact-form`. */
export const CONTACT_KIND_PARAM = "tipo";
/** Evento da janela com o tipo escolhido em `detail`. */
export const CONTACT_KIND_EVENT = "contact:kind";
/** Opção do formulário para um tipo; `undefined` para um tipo desconhecido (inclusive nomes herdados, como "constructor"). */
export function contactSubjectForKind(kind: string | null | undefined) {
return kind && Object.hasOwn(CONTACT_KINDS, kind)
? CONTACT_KINDS[kind as ContactKind]
: undefined;
}
/** Id do cartão do formulário de contato: o destino das ofertas (o campo do tipo fica a poucos toques do topo dele). */
export const CONTACT_FORM_ID = "contact-form";
export const CONTACT_FORM_HASH = `#${CONTACT_FORM_ID}`;
export function contactKindHref(kind: ContactKind) {
return `/?${CONTACT_KIND_PARAM}=${kind}${CONTACT_FORM_HASH}`;
}
/** Oferta cujo nome é a opção do formulário; `undefined` para as outras opções (e para o padrão). */
export function contactKindForSubject(subject: string | null | undefined) {
return (Object.keys(CONTACT_KINDS) as ContactKind[]).find(
(kind) => CONTACT_KINDS[kind] === subject,
);
}