{"release_id":"01a1c502-4ad1-0000-ec62-adc54d3c69aa","chunk_id":"01a1c502-4c31-7120-adaf-f35011c25e5f","file":"DC0z2WD-.mjs","mappings":"8ZAUO,MAAMA,EAAiB,iBAEvB,SAASC,EAAgBC,EAA2C,CAC1E,MAAM,GAAKA,GAAS,IAAI,KAAA,EAAO,YAAA,EAC/B,OAAO,IAAM,IAAM,IAAM,YAAc,IAAMF,CAC9C,CAwGO,SAASG,EAAWC,EAAqB,CAC/C,MAAMC,EAAI,KAAK,IAAI,EAAG,KAAK,MAAMD,CAAG,CAAC,EACrC,OAAOC,EAAI,GAAK,GAAGA,CAAC,IAAM,GAAG,KAAK,MAAMA,EAAI,EAAE,CAAC,MAChD,CAOO,SAASC,EACfC,EACS,CACT,MAAMC,EAAS,KAAK,IAAI,EAAGD,EAAM,WAAaA,EAAM,UAAU,EACxDE,EAAiB,CAAA,EACvB,OAAIF,EAAM,WAAa,IACtBE,EAAK,KAAK,GAAGF,EAAM,UAAU,IAAIA,EAAM,aAAe,EAAI,OAAS,OAAO,EAAE,EACxEA,EAAM,UAAUE,EAAK,KAAKN,EAAWI,EAAM,QAAQ,CAAC,GAErDC,EAAS,GAAGC,EAAK,KAAK,GAAGD,CAAM,IAAIA,IAAW,EAAI,QAAU,QAAQ,EAAE,EACnEC,EAAK,KAAK,KAAK,GAAK,UAC5B,qdC5IA,CASE,IAAAC,eAAO,OAAO,EACdC,oBAAY,IAAI,EAChBC,mBAAW,EAAE,EACbC,cAAM,EAAE,EACRC,EAAKC,EAAAC,EAAA,QAAA,EAAG,EAAA,8DAcPC,EAAGC,EAAA,SAAHC,EAAAF,QAASN,GAAS,EAAlBQ,EAAAF,QAAqBJ,GAAG,EAAxBM,EAAAF,EAAG,UAAgCH,EAAK,EAAG,QAAU,MAAM,EAA3DK,EAAAF,EAAG,gBAAyEH,EAAK,EAAG,OAAS,MAAM,EAAnGM,EAAAH,MAA6HL,EAAQ,CAAA,CAAA,QAArIK,CAAG,aAGHI,EAAKC,EAAA,EAALD,EAAK,MAAA,YAALA,EAAK,MAAA,GAAAL,EAAA,KAAA,EAAA,QAAA,EAALI,EAAAC,MAAoET,EAAQ,CAAA,CAAA,QAA5ES,CAAK,WAJFV,EAAS,EAAAY,EAAAC,CAAA,EAAAD,EAAAE,EAAA,EAAA,2BAcbC,EAAGC,EAAA,WAAHD,EAAG,MAAAV,EAAA,GAAA,EAAHG,EAAAO,QAAeb,GAAG,EAAlBM,EAAAO,EAAG,UAA0BZ,EAAK,EAAG,QAAU,MAAM,EAArDK,EAAAO,EAAG,gBAAmEZ,EAAK,EAAG,OAAS,MAAM,EAA7FM,EAAAM,MAAuHd,EAAQ,CAAA,CAAA,QAA/Hc,CAAG,WAfAhB,EAAI,IAAK,QAAOa,EAAAK,CAAA,EAAAL,EAAAM,EAAA,EAAA,UAFb","names":["UNTITLED_ALBUM","isUntitledAlbum","title","clipLength","sec","s","albumCountLabel","album","photos","bits","kind","posterUrl","imgClass","alt","eager","$.prop","$$props","img","root_2","$.set_attribute","$.set_class","video","root_3","$$render","consequent","alternate","img_1","root_4","consequent_1","alternate_1"],"sources":["../../../../../../src/lib/types/album.ts","../../../../../../src/lib/components/album/AlbumCoverMedia.svelte"],"sourcesContent":["// Paid photo/video album types — shared between server load and components.\n\nexport type AlbumStatus = 'draft' | 'published' | 'archived';\n\n/** Media kind for an album item. Videos reuse the same bucket + unlock gate. */\nexport type AlbumMediaKind = 'image' | 'video';\n\n/** The title a new album starts with. Publishing needs a real one (RAT-605):\n *  177 albums went live as \"untitled album\", which is what the card, the\n *  feed post and search all showed. */\nexport const UNTITLED_ALBUM = 'untitled album';\n\nexport function isUntitledAlbum(title: string | null | undefined): boolean {\n\tconst t = (title ?? '').trim().toLowerCase();\n\treturn t === '' || t === 'untitled' || t === UNTITLED_ALBUM;\n}\n\n/** Compact creator identity carried on every album surface. avatarPath is\n *  resolved to a URL client-side via getAvatarUrl (public avatars bucket). */\nexport interface AlbumCreator {\n\tid: string;\n\tnickname: string | null;\n\tavatarPath: string | null;\n\tisRater?: boolean;\n\tisCreator?: boolean;\n\tisVerified?: boolean;\n\tisSubscriber?: boolean;\n}\n\n/** Grid / feed card. coverUrl is a signed URL (private bucket) or null. */\nexport interface AlbumCard {\n\tid: string;\n\ttitle: string;\n\tpriceTokens: number;\n\tphotoCount: number;\n\tpreviewCount: number;\n\tunlockCount: number;\n\tstatus: AlbumStatus;\n\t/** Fixed-vocabulary content tags (album-tags.ts), creator-picked, max 4. */\n\ttags: string[];\n\t/** How many of photoCount are video clips (photoCount counts BOTH). */\n\tvideoCount: number;\n\t/** The clips' running time added up, in seconds (RAT-621); 0 when a\n\t *  clip's length isn't known. */\n\tvideoSec: number;\n\tcoverUrl: string | null;\n\t/** Kind of the cover media — 'video' covers render a poster frame + play badge. */\n\tcoverKind: AlbumMediaKind;\n\t/** Signed poster frame for a video cover (instant thumbnail). Null otherwise. */\n\tcoverPosterUrl: string | null;\n\tcreator: AlbumCreator;\n\t/** Viewer has unlocked this album (or owns it). */\n\tunlocked: boolean;\n\t/** Viewer is the creator. */\n\tisOwner: boolean;\n}\n\n/** A single photo in the detail view. `url` is null for locked (non-preview,\n *  not-unlocked) photos — the gate is real, no URL is ever emitted. */\nexport interface AlbumPhotoView {\n\tid: string;\n\turl: string | null;\n\t/** Signed poster frame for video items (instant thumbnail). Null for images. */\n\tposterUrl: string | null;\n\tkind: AlbumMediaKind;\n\t/** Clip length in seconds (video only). */\n\tdurationSec: number | null;\n\twidth: number | null;\n\theight: number | null;\n\tisPreview: boolean;\n\t/** True when this photo is gated for the viewer (no url emitted). */\n\tlocked: boolean;\n\t/** A blurred stand-in can be drawn for it (/album/[id]/blur/[photo].jpg):\n\t *  every image, and a clip with a poster. */\n\tblurrable?: boolean;\n}\n\n/** Full album viewer payload (/album/[id]). */\nexport interface AlbumDetail extends AlbumCard {\n\tdescription: string | null;\n\tstatus: AlbumStatus;\n\tcreatedAt: string;\n\t/** First publish; null for a draft. */\n\tpublishedAt: string | null;\n\t/** The creator is banned or under review: only past buyers still see it. */\n\toffShelf: boolean;\n\tphotos: AlbumPhotoView[];\n}\n\n/** Owner-side editor row — every photo always carries a signed url. */\nexport interface AlbumEditorPhoto {\n\tid: string;\n\turl: string | null;\n\tkind: AlbumMediaKind;\n\tdurationSec: number | null;\n\twidth: number | null;\n\theight: number | null;\n\tisPreview: boolean;\n\tdisplayOrder: number;\n\tisCover: boolean;\n}\n\nexport interface AlbumEditor {\n\tid: string;\n\ttitle: string;\n\tdescription: string | null;\n\tpriceTokens: number;\n\ttags: string[];\n\t/** The creator set the tags by hand: the AI tagger leaves them alone. */\n\ttagsByUser: boolean;\n\tstatus: AlbumStatus;\n\tphotoCount: number;\n\tpreviewCount: number;\n\tunlockCount: number;\n\tcoverPath: string | null;\n\tphotos: AlbumEditorPhoto[];\n}\n\n/** \"45s\", \"12 min\": how long an album's clips run together. */\nexport function clipLength(sec: number): string {\n\tconst s = Math.max(0, Math.round(sec));\n\treturn s < 60 ? `${s}s` : `${Math.round(s / 60)} min`;\n}\n\n/**\n * \"2 clips · 12 min · 9 photos\": photoCount counts every item, videoCount the\n * clips. The clips lead (RAT-621), so a set with video says so on the card\n * even when its cover is a photo.\n */\nexport function albumCountLabel(\n\talbum: Pick<AlbumCard, 'photoCount' | 'videoCount'> & { videoSec?: number }\n): string {\n\tconst photos = Math.max(0, album.photoCount - album.videoCount);\n\tconst bits: string[] = [];\n\tif (album.videoCount > 0) {\n\t\tbits.push(`${album.videoCount} ${album.videoCount === 1 ? 'clip' : 'clips'}`);\n\t\tif (album.videoSec) bits.push(clipLength(album.videoSec));\n\t}\n\tif (photos > 0) bits.push(`${photos} ${photos === 1 ? 'photo' : 'photos'}`);\n\treturn bits.join(' · ') || '0 photos';\n}\n","<script lang=\"ts\">\n\timport type { AlbumMediaKind } from '$lib/types/album';\n\n\t// Renders an album cover that may be an image OR a video clip. Video covers\n\t// show the first frame as a static poster (#t=0.1 forces it to decode) with a\n\t// play badge so the card reads as \"this set has clips\". The class is shared\n\t// with the <img> so the cover fills the card identically either way.\n\tlet {\n\t\turl,\n\t\tkind = 'image',\n\t\tposterUrl = null,\n\t\timgClass = '',\n\t\talt = '',\n\t\teager = false\n\t}: {\n\t\turl: string;\n\t\tkind?: AlbumMediaKind;\n\t\tposterUrl?: string | null;\n\t\timgClass?: string;\n\t\talt?: string;\n\t\t/** First card(s) of a rail: load now, at high priority, instead of lazily. */\n\t\teager?: boolean;\n\t} = $props();\n</script>\n\n{#if kind === 'video'}\n\t{#if posterUrl}\n\t\t<img src={posterUrl} {alt} loading={eager ? 'eager' : 'lazy'} fetchpriority={eager ? 'high' : 'auto'} decoding=\"async\" class={imgClass} />\n\t{:else}\n\t\t<!-- svelte-ignore a11y_media_has_caption -->\n\t\t<video src=\"{url}#t=0.1\" preload=\"metadata\" muted playsinline class={imgClass}></video>\n\t{/if}\n\t<span class=\"pointer-events-none absolute inset-0 grid place-items-center\" aria-hidden=\"true\">\n\t\t<span class=\"grid h-10 w-10 place-items-center rounded-full bg-black/55 backdrop-blur-sm\">\n\t\t\t<svg class=\"ml-0.5 h-5 w-5 text-white\" viewBox=\"0 0 24 24\" fill=\"currentColor\"\n\t\t\t\t><path d=\"M8 5v14l11-7z\" /></svg\n\t\t\t>\n\t\t</span>\n\t</span>\n{:else}\n\t<img src={url} {alt} loading={eager ? 'eager' : 'lazy'} fetchpriority={eager ? 'high' : 'auto'} decoding=\"async\" class={imgClass} />\n{/if}\n"],"version":3}