Implement package limit notification system

This commit is contained in:
Codex Agent
2025-11-01 13:19:07 +01:00
parent 81cdee428e
commit 2c14493604
87 changed files with 4557 additions and 290 deletions

View File

@@ -133,217 +133,361 @@ export function clampElement(element: LayoutElement): LayoutElement {
}
const DEFAULT_TYPE_STYLES: Record<LayoutElementType, { width: number; height: number; fontSize?: number; align?: LayoutTextAlign; locked?: boolean }> = {
headline: { width: 620, height: 200, fontSize: 68, align: 'left' },
subtitle: { width: 580, height: 140, fontSize: 34, align: 'left' },
description: { width: 620, height: 280, fontSize: 28, align: 'left' },
link: { width: 400, height: 110, fontSize: 28, align: 'center' },
badge: { width: 280, height: 80, fontSize: 24, align: 'center' },
logo: { width: 240, height: 180, align: 'center' },
cta: { width: 400, height: 110, fontSize: 26, align: 'center' },
qr: { width: 520, height: 520 },
text: { width: 560, height: 200, fontSize: 26, align: 'left' },
headline: { width: 900, height: 240, fontSize: 82, align: 'left' },
subtitle: { width: 760, height: 170, fontSize: 40, align: 'left' },
description: { width: 920, height: 340, fontSize: 32, align: 'left' },
link: { width: 520, height: 130, fontSize: 30, align: 'center' },
badge: { width: 420, height: 100, fontSize: 26, align: 'center' },
logo: { width: 320, height: 220, align: 'center' },
cta: { width: 520, height: 130, fontSize: 28, align: 'center' },
qr: { width: 640, height: 640 },
text: { width: 720, height: 260, fontSize: 28, align: 'left' },
};
const DEFAULT_PRESET: LayoutPreset = [
{ id: 'badge', type: 'badge', x: 120, y: 140, width: 320, height: 80, align: 'center', fontSize: 24 },
{ id: 'headline', type: 'headline', x: 120, y: 260, width: 620, height: 200, fontSize: 68, align: 'left' },
{ id: 'subtitle', type: 'subtitle', x: 120, y: 440, width: 600, height: 140, fontSize: 34, align: 'left' },
{ id: 'description', type: 'description', x: 120, y: 600, width: 620, height: 280, fontSize: 28, align: 'left' },
{ id: 'badge', type: 'badge', x: 140, y: 160, width: 440, height: 100, align: 'center', fontSize: 28 },
{
id: 'headline',
type: 'headline',
x: 140,
y: 300,
width: (context) => context.canvasWidth - 280,
height: 240,
fontSize: 84,
align: 'left',
},
{
id: 'subtitle',
type: 'subtitle',
x: 140,
y: 560,
width: (context) => context.canvasWidth - 280,
height: 170,
fontSize: 42,
align: 'left',
},
{
id: 'description',
type: 'description',
x: 140,
y: 750,
width: (context) => context.canvasWidth - 280,
height: 340,
fontSize: 32,
align: 'left',
},
{
id: 'qr',
type: 'qr',
x: (context) => context.canvasWidth - context.qrSize - 140,
x: (context) => context.canvasWidth - Math.min(context.qrSize, 680) - 180,
y: 360,
width: (context) => context.qrSize,
height: (context) => context.qrSize,
width: (context) => Math.min(context.qrSize, 680),
height: (context) => Math.min(context.qrSize, 680),
},
{
id: 'link',
type: 'link',
x: (context) => context.canvasWidth - 420,
y: (context) => 400 + context.qrSize,
width: 400,
height: 110,
x: (context) => context.canvasWidth - 540,
y: (context) => 420 + Math.min(context.qrSize, 680),
width: 520,
height: 130,
fontSize: 28,
align: 'center',
},
{
id: 'cta',
type: 'cta',
x: (context) => context.canvasWidth - 420,
y: (context) => 420 + context.qrSize + 140,
width: 400,
height: 110,
fontSize: 26,
x: (context) => context.canvasWidth - 540,
y: (context) => 460 + Math.min(context.qrSize, 680) + 160,
width: 520,
height: 130,
fontSize: 30,
align: 'center',
},
];
const evergreenVowsPreset: LayoutPreset = [
{ id: 'logo', type: 'logo', x: 120, y: 140, width: 240, height: 180 },
{ id: 'badge', type: 'badge', x: 400, y: 160, width: 320, height: 80, align: 'center', fontSize: 24 },
{ id: 'headline', type: 'headline', x: 120, y: 360, width: 620, height: 220, fontSize: 70, align: 'left' },
{ id: 'subtitle', type: 'subtitle', x: 120, y: 560, width: 600, height: 140, fontSize: 34, align: 'left' },
{ id: 'description', type: 'description', x: 120, y: 720, width: 620, height: 280, fontSize: 28, align: 'left' },
{ id: 'logo', type: 'logo', x: 160, y: 140, width: 340, height: 240 },
{ id: 'badge', type: 'badge', x: 540, y: 160, width: 420, height: 100, align: 'center', fontSize: 28 },
{
id: 'headline',
type: 'headline',
x: 160,
y: 360,
width: (context) => context.canvasWidth - 320,
height: 250,
fontSize: 86,
align: 'left',
},
{
id: 'subtitle',
type: 'subtitle',
x: 160,
y: 630,
width: (context) => context.canvasWidth - 320,
height: 180,
fontSize: 42,
align: 'left',
},
{
id: 'description',
type: 'description',
x: 160,
y: 840,
width: (context) => context.canvasWidth - 320,
height: 360,
fontSize: 34,
align: 'left',
},
{
id: 'qr',
type: 'qr',
x: (context) => context.canvasWidth - context.qrSize - 160,
y: 460,
width: (context) => context.qrSize,
height: (context) => context.qrSize,
x: (context) => context.canvasWidth - Math.min(context.qrSize, 640) - 200,
y: 420,
width: (context) => Math.min(context.qrSize, 640),
height: (context) => Math.min(context.qrSize, 640),
},
{
id: 'link',
type: 'link',
x: (context) => context.canvasWidth - 420,
y: (context) => 500 + context.qrSize,
width: 400,
height: 110,
x: (context) => context.canvasWidth - 560,
y: (context) => 480 + Math.min(context.qrSize, 640),
width: 520,
height: 130,
align: 'center',
},
{
id: 'cta',
type: 'cta',
x: (context) => context.canvasWidth - 420,
y: (context) => 520 + context.qrSize + 150,
width: 400,
height: 110,
x: (context) => context.canvasWidth - 560,
y: (context) => 520 + Math.min(context.qrSize, 640) + 180,
width: 520,
height: 130,
align: 'center',
},
];
const midnightGalaPreset: LayoutPreset = [
{ id: 'badge', type: 'badge', x: 360, y: 160, width: 520, height: 90, align: 'center', fontSize: 26 },
{ id: 'headline', type: 'headline', x: 220, y: 300, width: 800, height: 220, fontSize: 76, align: 'center' },
{ id: 'subtitle', type: 'subtitle', x: 260, y: 520, width: 720, height: 140, fontSize: 36, align: 'center' },
{ id: 'badge', type: 'badge', x: (context) => context.canvasWidth / 2 - 300, y: 180, width: 600, height: 120, align: 'center', fontSize: 32 },
{
id: 'headline',
type: 'headline',
x: (context) => context.canvasWidth / 2 - (context.canvasWidth - 220) / 2,
y: 340,
width: (context) => context.canvasWidth - 220,
height: 260,
fontSize: 90,
align: 'center',
},
{
id: 'subtitle',
type: 'subtitle',
x: (context) => context.canvasWidth / 2 - (context.canvasWidth - 320) / 2,
y: 640,
width: (context) => context.canvasWidth - 320,
height: 200,
fontSize: 46,
align: 'center',
},
{
id: 'qr',
type: 'qr',
x: (context) => (context.canvasWidth - context.qrSize) / 2,
y: 700,
width: (context) => context.qrSize,
height: (context) => context.qrSize,
x: (context) => (context.canvasWidth - Math.min(context.qrSize, 640)) / 2,
y: 880,
width: (context) => Math.min(context.qrSize, 640),
height: (context) => Math.min(context.qrSize, 640),
},
{
id: 'link',
type: 'link',
x: (context) => (context.canvasWidth - 420) / 2,
y: (context) => 740 + context.qrSize,
width: 420,
height: 120,
x: (context) => (context.canvasWidth - 560) / 2,
y: (context) => 940 + Math.min(context.qrSize, 640),
width: 560,
height: 140,
align: 'center',
},
{
id: 'cta',
type: 'cta',
x: (context) => (context.canvasWidth - 420) / 2,
y: (context) => 770 + context.qrSize + 150,
width: 420,
height: 120,
x: (context) => (context.canvasWidth - 560) / 2,
y: (context) => 980 + Math.min(context.qrSize, 640) + 200,
width: 560,
height: 140,
align: 'center',
},
{
id: 'description',
type: 'description',
x: (context) => context.canvasWidth / 2 - (context.canvasWidth - 240) / 2,
y: 1250,
width: (context) => context.canvasWidth - 240,
height: 360,
fontSize: 34,
align: 'center',
},
{ id: 'description', type: 'description', x: 200, y: 1040, width: 840, height: 260, fontSize: 28, align: 'center' },
];
const gardenBrunchPreset: LayoutPreset = [
{ id: 'badge', type: 'badge', x: 160, y: 160, width: 360, height: 80, align: 'center', fontSize: 24 },
{ id: 'headline', type: 'headline', x: 160, y: 300, width: 560, height: 200, fontSize: 66, align: 'left' },
{ id: 'description', type: 'description', x: 160, y: 520, width: 560, height: 260, fontSize: 28, align: 'left' },
{ id: 'badge', type: 'badge', x: 180, y: 180, width: 500, height: 110, align: 'center', fontSize: 30 },
{ id: 'headline', type: 'headline', x: 180, y: 340, width: (context) => context.canvasWidth - 360, height: 260, fontSize: 86, align: 'left' },
{ id: 'description', type: 'description', x: 180, y: 630, width: (context) => context.canvasWidth - 360, height: 360, fontSize: 34, align: 'left' },
{
id: 'qr',
type: 'qr',
x: 160,
y: 840,
width: (context) => Math.min(context.qrSize, 520),
height: (context) => Math.min(context.qrSize, 520),
x: 180,
y: 1000,
width: (context) => Math.min(context.qrSize, 660),
height: (context) => Math.min(context.qrSize, 660),
},
{
id: 'link',
type: 'link',
x: 160,
y: (context) => 880 + Math.min(context.qrSize, 520),
width: 420,
height: 110,
x: 180,
y: (context) => 1060 + Math.min(context.qrSize, 660),
width: 520,
height: 140,
align: 'center',
},
{
id: 'cta',
type: 'cta',
x: 160,
y: (context) => 910 + Math.min(context.qrSize, 520) + 140,
width: 420,
height: 110,
x: 180,
y: (context) => 1100 + Math.min(context.qrSize, 660) + 190,
width: 520,
height: 140,
align: 'center',
},
{ id: 'subtitle', type: 'subtitle', x: 780, y: 320, width: 320, height: 140, fontSize: 32, align: 'left' },
{ id: 'text-strip', type: 'text', x: 780, y: 480, width: 320, height: 320, fontSize: 24, align: 'left' },
{ id: 'subtitle', type: 'subtitle', x: (context) => context.canvasWidth - 460, y: 360, width: 420, height: 200, fontSize: 38, align: 'left' },
{ id: 'text-strip', type: 'text', x: (context) => context.canvasWidth - 460, y: 620, width: 420, height: 360, fontSize: 28, align: 'left' },
];
const sparklerSoireePreset: LayoutPreset = [
{ id: 'badge', type: 'badge', x: 360, y: 150, width: 520, height: 90, align: 'center', fontSize: 26 },
{ id: 'headline', type: 'headline', x: 200, y: 300, width: 840, height: 220, fontSize: 72, align: 'center' },
{ id: 'subtitle', type: 'subtitle', x: 260, y: 520, width: 720, height: 140, fontSize: 34, align: 'center' },
{ id: 'description', type: 'description', x: 220, y: 680, width: 800, height: 240, fontSize: 28, align: 'center' },
{ id: 'badge', type: 'badge', x: (context) => context.canvasWidth / 2 - 320, y: 200, width: 640, height: 120, align: 'center', fontSize: 32 },
{
id: 'headline',
type: 'headline',
x: (context) => context.canvasWidth / 2 - (context.canvasWidth - 220) / 2,
y: 360,
width: (context) => context.canvasWidth - 220,
height: 280,
fontSize: 94,
align: 'center',
},
{
id: 'subtitle',
type: 'subtitle',
x: (context) => context.canvasWidth / 2 - (context.canvasWidth - 320) / 2,
y: 660,
width: (context) => context.canvasWidth - 320,
height: 210,
fontSize: 46,
align: 'center',
},
{
id: 'description',
type: 'description',
x: (context) => context.canvasWidth / 2 - (context.canvasWidth - 320) / 2,
y: 920,
width: (context) => context.canvasWidth - 320,
height: 380,
fontSize: 34,
align: 'center',
},
{
id: 'qr',
type: 'qr',
x: (context) => (context.canvasWidth - context.qrSize) / 2,
y: 960,
width: (context) => context.qrSize,
height: (context) => context.qrSize,
x: (context) => (context.canvasWidth - Math.min(context.qrSize, 680)) / 2,
y: 1200,
width: (context) => Math.min(context.qrSize, 680),
height: (context) => Math.min(context.qrSize, 680),
},
{
id: 'link',
type: 'link',
x: (context) => (context.canvasWidth - 420) / 2,
y: (context) => 1000 + context.qrSize,
width: 420,
height: 110,
x: (context) => (context.canvasWidth - 580) / 2,
y: (context) => 1260 + Math.min(context.qrSize, 680),
width: 580,
height: 150,
align: 'center',
},
{
id: 'cta',
type: 'cta',
x: (context) => (context.canvasWidth - 420) / 2,
y: (context) => 1030 + context.qrSize + 140,
width: 420,
height: 110,
x: (context) => (context.canvasWidth - 580) / 2,
y: (context) => 1300 + Math.min(context.qrSize, 680) + 200,
width: 580,
height: 150,
align: 'center',
},
];
const confettiBashPreset: LayoutPreset = [
{ id: 'badge', type: 'badge', x: 140, y: 180, width: 360, height: 90, align: 'center', fontSize: 24 },
{ id: 'headline', type: 'headline', x: 140, y: 320, width: 520, height: 220, fontSize: 68, align: 'left' },
{ id: 'subtitle', type: 'subtitle', x: 140, y: 520, width: 520, height: 140, fontSize: 34, align: 'left' },
{ id: 'description', type: 'description', x: 140, y: 680, width: 520, height: 240, fontSize: 26, align: 'left' },
{ id: 'badge', type: 'badge', x: 180, y: 220, width: 520, height: 120, align: 'center', fontSize: 32 },
{
id: 'headline',
type: 'headline',
x: 180,
y: 380,
width: (context) => context.canvasWidth - 360,
height: 260,
fontSize: 90,
align: 'left',
},
{
id: 'subtitle',
type: 'subtitle',
x: 180,
y: 660,
width: (context) => context.canvasWidth - 360,
height: 200,
fontSize: 46,
align: 'left',
},
{
id: 'description',
type: 'description',
x: 180,
y: 910,
width: (context) => context.canvasWidth - 360,
height: 360,
fontSize: 34,
align: 'left',
},
{
id: 'qr',
type: 'qr',
x: (context) => context.canvasWidth - context.qrSize - 200,
y: 360,
width: (context) => context.qrSize,
height: (context) => context.qrSize,
x: (context) => context.canvasWidth - Math.min(context.qrSize, 680) - 200,
y: 460,
width: (context) => Math.min(context.qrSize, 680),
height: (context) => Math.min(context.qrSize, 680),
},
{
id: 'link',
type: 'link',
x: (context) => context.canvasWidth - 420,
y: (context) => 400 + context.qrSize,
width: 400,
height: 110,
x: (context) => context.canvasWidth - 560,
y: (context) => 520 + Math.min(context.qrSize, 680),
width: 520,
height: 140,
align: 'center',
},
{
id: 'cta',
type: 'cta',
x: (context) => context.canvasWidth - 420,
y: (context) => 430 + context.qrSize + 140,
width: 400,
height: 110,
x: (context) => context.canvasWidth - 560,
y: (context) => 560 + Math.min(context.qrSize, 680) + 200,
width: 520,
height: 140,
align: 'center',
},
{ id: 'text-strip', type: 'text', x: 140, y: 960, width: 860, height: 220, fontSize: 26, align: 'left' },
{
id: 'text-strip',
type: 'text',
x: 180,
y: 1220,
width: (context) => context.canvasWidth - 360,
height: 360,
fontSize: 30,
align: 'left',
},
];
const LAYOUT_PRESETS: Record<string, LayoutPreset> = {
@@ -513,6 +657,7 @@ export function elementsToPayload(elements: LayoutElement[]): LayoutElementPaylo
}));
}
export function normalizeElements(elements: LayoutElement[]): LayoutElement[] {
const seen = new Set<string>();
return elements