mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 06:43:06 +00:00
A home abre o mesmo compositor do /cards ali mesmo (dialog no desktop, drawer no mobile) e perde o CTA: os três modelos viram botões e nada navega. A seção sobe para imediatamente antes da captura de novidades. O nome no card e no tile alinha à esquerda com o SOU (x=213) via leftX + drawCardName extraído; o tile passa a desenhar SEU NOME com o próprio pipeline real (canvas transparente sobre a base). Modal sem o vão interno (p-0 sm:p-0) e ajuda legível no tema campaign-site (--muted-foreground). Remove a variante link e o cardModelHref sem call site. Unit atualizado (+ drawCardName) e e2e com alinhamento por pixel; sem migration, sem PII em rede.
90 lines
2.7 KiB
TypeScript
90 lines
2.7 KiB
TypeScript
/**
|
||
* S13 — the three personalized-card models of the campaign site. Pure data so
|
||
* the home section, the `/cards` gallery and the canvas renderer share one
|
||
* catalog (no second editor, no CMS): each entry carries the master asset, the
|
||
* real output size in pixels and, for photo models, the transparent window the
|
||
* visitor's photo must cover.
|
||
*
|
||
* Geometry comes from the master files (pixel-measured, keep in sync with
|
||
* `docs/plans/cards-personalizados-campanha-impl.md`): the photo frames are
|
||
* 1000×1000 and 1000×1440 despite the `1080x` in the original file names.
|
||
*/
|
||
|
||
const CARD_MODEL_IDS = ['eu-sou-solla', 'perfil-quadrado', 'perfil-retangular'] as const
|
||
|
||
export type CardModelId = (typeof CARD_MODEL_IDS)[number]
|
||
type CardModelKind = 'name' | 'photo'
|
||
|
||
export type CardRect = {
|
||
x: number
|
||
y: number
|
||
width: number
|
||
height: number
|
||
}
|
||
|
||
export type CardModel = {
|
||
id: CardModelId
|
||
kind: CardModelKind
|
||
label: string
|
||
assetSrc: string
|
||
width: number
|
||
height: number
|
||
/** Photo models only: the transparent window the photo must cover. */
|
||
photoWindow?: CardRect
|
||
}
|
||
|
||
export const CARD_MODELS: readonly CardModel[] = [
|
||
{
|
||
id: 'eu-sou-solla',
|
||
kind: 'name',
|
||
label: 'Card com seu nome',
|
||
assetSrc: '/cards/name-card-base.jpg',
|
||
width: 1080,
|
||
height: 1440,
|
||
},
|
||
{
|
||
id: 'perfil-quadrado',
|
||
kind: 'photo',
|
||
label: 'Moldura quadrada',
|
||
assetSrc: '/cards/photo-square-frame.png',
|
||
width: 1000,
|
||
height: 1000,
|
||
photoWindow: { x: 0, y: 0, width: 1000, height: 740 },
|
||
},
|
||
{
|
||
id: 'perfil-retangular',
|
||
kind: 'photo',
|
||
label: 'Moldura vertical',
|
||
assetSrc: '/cards/photo-portrait-frame.png',
|
||
width: 1000,
|
||
height: 1440,
|
||
photoWindow: { x: 0, y: 0, width: 1000, height: 1044 },
|
||
},
|
||
]
|
||
|
||
/**
|
||
* Name model slot, pixel-measured from the filled master (`FULANO` example):
|
||
* the `#SOU` line and the visitor's name share the same left border at x=213
|
||
* (S14 — the name is left-aligned, not centered); the cap band tops at y=430
|
||
* with a 106px cap height and the master's ink spans x[214..925] (712px), so a
|
||
* fitted name never crosses 714px. The visitor's name is the only thing drawn
|
||
* over the base; the fixed lines already live in the art.
|
||
*/
|
||
export const NAME_CARD_SLOT = {
|
||
leftX: 213,
|
||
capTop: 430,
|
||
capHeight: 106,
|
||
maxInkWidth: 714,
|
||
maxBlockBottom: 630,
|
||
minCapHeight: 48,
|
||
fill: '#ffec01',
|
||
maxLines: 2,
|
||
} as const
|
||
|
||
export const BRAND_FALLBACK_FONT = 'sans-serif'
|
||
|
||
export const isCardModelId = (value: unknown): value is CardModelId =>
|
||
typeof value === 'string' && (CARD_MODEL_IDS as readonly string[]).includes(value)
|
||
|
||
export const getCardModel = (id: CardModelId): CardModel | undefined =>
|
||
CARD_MODELS.find((model) => model.id === id)
|