mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 15:13:07 +00:00
Home ganha a seção Faça parte com os três modelos e a rota /cards concentra catálogo + compositor client-side (dialog desktop, bottom drawer mobile): nome no slot medido do mestre com Brexter 700 e foto full-bleed sob o overlay oficial (pan/zoom clampados), download PNG sem conta e sem enviar nome/foto. Núcleo puro em src/lib/card* com unit tests; e2e cobre home→deep-link, nome→PNG real, erro fail-closed, foto e drawer mobile. Sem migration/PII.
90 lines
2.6 KiB
TypeScript
90 lines
2.6 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):
|
||
* glyph cap band tops at y=430 with a 106px cap height, ink spans x[213..926]
|
||
* (714px) centered at x≈570 — not at the canvas center. 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 = {
|
||
centerX: 570,
|
||
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)
|
||
|
||
export const cardModelHref = (id: CardModelId): string => `/cards?model=${id}`
|