/** * Value Object: LiveryDecal * Represents a decal/logo placed on a livery */ export type DecalType = 'sponsor' | 'user'; export interface LiveryDecalProps { id: string; imageUrl: string; x: number; y: number; width: number; height: number; zIndex: number; type: DecalType; } export class LiveryDecal { readonly id: string; readonly imageUrl: string; readonly x: number; readonly y: number; readonly width: number; readonly height: number; readonly zIndex: number; readonly type: DecalType; private constructor(props: LiveryDecalProps) { this.id = props.id; this.imageUrl = props.imageUrl; this.x = props.x; this.y = props.y; this.width = props.width; this.height = props.height; this.zIndex = props.zIndex; this.type = props.type; } static create(props: LiveryDecalProps): LiveryDecal { this.validate(props); return new LiveryDecal(props); } private static validate(props: LiveryDecalProps): void { if (!props.id || props.id.trim().length === 0) { throw new Error('LiveryDecal ID is required'); } if (!props.imageUrl || props.imageUrl.trim().length === 0) { throw new Error('LiveryDecal imageUrl is required'); } if (props.x < 0 || props.x > 1) { throw new Error('LiveryDecal x coordinate must be between 0 and 1 (normalized)'); } if (props.y < 0 || props.y > 1) { throw new Error('LiveryDecal y coordinate must be between 0 and 1 (normalized)'); } if (props.width <= 0 || props.width > 1) { throw new Error('LiveryDecal width must be between 0 and 1 (normalized)'); } if (props.height <= 0 || props.height > 1) { throw new Error('LiveryDecal height must be between 0 and 1 (normalized)'); } if (!Number.isInteger(props.zIndex) || props.zIndex < 0) { throw new Error('LiveryDecal zIndex must be a non-negative integer'); } if (!props.type) { throw new Error('LiveryDecal type is required'); } } /** * Move decal to new position */ moveTo(x: number, y: number): LiveryDecal { return LiveryDecal.create({ ...this, x, y, }); } /** * Resize decal */ resize(width: number, height: number): LiveryDecal { return LiveryDecal.create({ ...this, width, height, }); } /** * Change z-index */ setZIndex(zIndex: number): LiveryDecal { return LiveryDecal.create({ ...this, zIndex, }); } /** * Check if this decal overlaps with another */ overlapsWith(other: LiveryDecal): boolean { const thisRight = this.x + this.width; const thisBottom = this.y + this.height; const otherRight = other.x + other.width; const otherBottom = other.y + other.height; return !( thisRight <= other.x || this.x >= otherRight || thisBottom <= other.y || this.y >= otherBottom ); } }