{
  "schemaVersion": 2,
  "application": "Photo Slots",
  "lastVerified": "2026-07-31",
  "scope": "User-facing images, animated GIFs, audio, video, PWA icons, uploaded media, and server-generated media. Application source files are excluded.",
  "defaultPolicy": "A fresh instance uses complete shared application defaults. Per-instance uploads create overrides and never mutate shared defaults.",
  "roots": {
    "project": "Events/Weddings",
    "productionProject": "/home/airbridg/public_html/ceriapps/Airbridge/Events/Weddings",
    "bundledPublic": "apps/web/public",
    "defaultMediaStorage": "server/storage/media",
    "mediaStorageEnvironmentVariable": "MEDIA_STORAGE_ROOT",
    "dynamicDirectoryPattern": "<MEDIA_STORAGE_ROOT>/<wedding-public-id>/<asset-UUID>/",
    "dynamicPublicUrlPattern": "/media/<wedding-public-id>/<asset-UUID>/<filename>"
  },
  "routeMappings": [
    { "publicPrefix": "/images/", "repositoryDirectory": "apps/web/public/images" },
    { "publicPrefix": "/videos/", "repositoryDirectory": "apps/web/public/videos" },
    { "publicPrefix": "/GIFs/", "repositoryDirectory": "apps/web/public/GIFs" },
    { "publicPrefix": "/effects/", "repositoryDirectory": "apps/web/public/effects" },
    { "publicPrefix": "/airbridge/", "repositoryDirectory": "apps/web/public/airbridge" },
    { "publicPrefix": "/media/", "repositoryDirectory": "server/storage/media", "configurableRoot": true },
    { "publicPrefix": "/assets/demo/", "generator": "server/public/index.php", "storedFiles": false }
  ],
  "bundledAssets": {
    "pwaIcons": [
      { "name": "Generic 192px PWA icon", "file": "apps/web/public/icons/icon-192.png", "publicUrl": "/public/icons/icon-192.png" },
      { "name": "Generic 512px PWA icon", "file": "apps/web/public/icons/icon-512.png", "publicUrl": "/public/icons/icon-512.png" }
    ],
    "images": [
      { "name": "Default welcome poster", "file": "apps/web/public/images/default-welcome-poster.png", "publicUrl": "/images/default-welcome-poster.png" },
      { "name": "Default Guest-win poster", "file": "apps/web/public/images/default-guest-win-poster.png", "publicUrl": "/images/default-guest-win-poster.png" },
      { "name": "Fireworks animation", "file": "apps/web/public/images/fireworks.gif", "publicUrl": "/images/fireworks.gif" },
      { "name": "Legacy background", "file": "apps/web/public/images/background.png", "publicUrl": "/images/background.png" },
      { "name": "Legacy reel normal", "file": "apps/web/public/images/reel_normal.png", "publicUrl": "/images/reel_normal.png" },
      { "name": "Legacy reel blur", "file": "apps/web/public/images/reel_blur.png", "publicUrl": "/images/reel_blur.png" },
      { "name": "Slot background", "file": "apps/web/public/images/slot/background.png", "publicUrl": "/images/slot/background.png" },
      { "name": "Slot reel normal", "file": "apps/web/public/images/slot/reel_normal.png", "publicUrl": "/images/slot/reel_normal.png" },
      { "name": "Slot reel blur", "file": "apps/web/public/images/slot/reel_blur.png", "publicUrl": "/images/slot/reel_blur.png" },
      { "name": "The Voice normal", "file": "apps/web/public/images/slot/thevoice_normal.png", "publicUrl": "/images/slot/thevoice_normal.png" },
      { "name": "The Voice blur", "file": "apps/web/public/images/slot/thevoice_blur.png", "publicUrl": "/images/slot/thevoice_blur.png" },
      { "name": "Airbridge icon", "file": "apps/web/public/airbridge/Transceiver/abicon.png", "publicUrl": "/airbridge/Transceiver/abicon.png" },
      { "name": "Airbridge compatibility icon", "file": "apps/web/public/airbridge/abicon.png", "publicUrl": "/airbridge/abicon.png" }
    ],
    "videos": [
      { "name": "Default welcome video", "file": "apps/web/public/videos/default-welcome.mp4", "publicUrl": "/videos/default-welcome.mp4", "scope": "shared", "fallbackFor": "wedding.welcomeVideo" },
      { "name": "Default Guest-win video", "file": "apps/web/public/videos/default-guest-win.mp4", "publicUrl": "/videos/default-guest-win.mp4", "scope": "shared", "fallbackFor": "wedding.guestWinMessageVideos" }
    ],
    "presetGifs": {
      "repositoryDirectory": "apps/web/public/GIFs",
      "publicUrlPattern": "/GIFs/<filename>",
      "files": [
        "aura.gif", "beach.gif", "brainpump.gif", "couplemoonlight.gif", "cross.gif", "diamond.gif", "earthsmall.gif", "ekg.gif", "fallleaves.gif", "fireworks.gif", "gaydiamond.gif", "heart.gif", "jet.gif", "medical.gif", "moonsmall.gif", "puppypopup.gif", "stars.gif", "starwarp.gif", "sun.gif", "tvnoise.gif", "warp.gif"
      ]
    },
    "effects": {
      "primaryDirectory": "apps/web/public/effects",
      "publicUrlPattern": "/effects/<filename>",
      "compatibilityDirectory": "apps/web/public/airbridge/Transceiver/effects",
      "files": [
        "WeddingMarch.mp3", "WeddingMarch2.mp3", "WeddingMarchGoth.mp3", "abjingle.mp3", "aww1.mp3", "celticWedding.mp3", "clapping.mp3", "click.mp3", "easy.mp3", "golfClapping.mp3", "golftorpedo.mp3", "pulseeffect.mp3", "pulserifle.mp3", "rampdown.mp3", "rampup.mp3", "slot3.mp3", "slotHandle.mp3", "slotPull.mp3", "stumble.wav", "winning.mp3"
      ],
      "compatibilityOnlyFiles": ["slot1.mp3"],
      "runtimeDefaults": {
        "slotPull": "slotPull.mp3",
        "noMatch": "aww1.mp3",
        "twoMatch": "clapping.mp3",
        "guestWin": "winning.mp3",
        "defaultWin": "easy.mp3",
        "protectedCoupleWin": "WeddingMarchGoth.mp3",
        "lightboxOpen": "rampup.mp3",
        "lightboxClose": "rampdown.mp3"
      }
    }
  },
  "generatedDemoAssets": [
    { "name": "Default wedding background", "publicUrl": "/assets/demo/background.svg", "generator": "server/public/index.php" },
    { "name": "Default launch and animation heart", "publicUrl": "/assets/demo/icon.svg", "generator": "server/public/index.php" },
    { "name": "Default splash", "publicUrl": "/assets/demo/splash.svg", "generator": "server/public/index.php" },
    { "name": "Legacy reel-symbol placeholders", "publicUrlPattern": "/assets/demo/symbol-<1..18>.svg", "generator": "server/public/index.php" }
  ],
  "defaultOverrideSelection": [
    { "feature": "welcomeVideo", "sharedDefault": "/videos/default-welcome.mp4", "poster": "/images/default-welcome-poster.png", "overrideReference": "weddings[publicId].welcomeVideo", "defaultWhen": "field absent", "resetInput": "useDefaultWelcomeVideo" },
    { "feature": "guestWinMessageVideo", "sharedDefault": "/videos/default-guest-win.mp4", "poster": "/images/default-guest-win-poster.png", "overrideReference": "weddings[publicId].guestWinMessageVideos[0]", "defaultWhen": "array empty", "resetInput": "useDefaultGuestWinMessage" },
    { "feature": "reelSymbols", "sharedDefault": "/images/defaults/starter-<01..18>.webp", "overrideReference": "weddings[publicId].symbols[*].occupant", "instances": 18, "allReplaceable": true },
    { "feature": "protectedImages", "sharedDefault": "/images/defaults/protected-<left|center|right>.webp", "overrideReference": "weddings[publicId].theme.protectedImages", "instances": 3, "allReplaceableByPlatformHost": true }
  ],
  "dynamicRoles": [
    { "role": "launch_icon", "name": "Wedding launch icon", "databaseReferences": ["wedding.theme.launchIconUrl"], "outputs": ["reel.webp", "reel.gif"], "consumer": "Dynamic PWA manifest and Apple touch icon" },
    { "role": "background", "name": "Wedding background", "databaseReferences": ["wedding.theme.backgroundImageUrl"], "outputs": ["reel.webp", "reel.gif"], "consumer": "Guest app background" },
    { "role": "reel_background", "name": "Reel background", "databaseReferences": ["wedding.theme.reelBackgroundUrl"], "outputs": ["reel.webp", "reel.gif"], "consumer": "Slot machine panel" },
    { "role": "idle_gallery_background", "name": "Idle-gallery background", "databaseReferences": ["wedding.theme.idleGalleryBackgroundUrl"], "outputs": ["reel.webp", "reel.gif", "video.mp4"], "consumer": "Idle gallery" },
    { "role": "splash", "name": "Reserved splash upload role", "databaseReferences": ["new-wedding placeholder: wedding.theme.splashImageUrl"], "outputs": ["reel.webp", "reel.gif"], "consumer": null, "implementationStatus": "storage-recognized; no active admin-field or applySetupAsset mapping" },
    { "role": "protected_image", "name": "Protected couple image", "instances": 3, "databaseReferences": ["wedding.theme.protectedImages", "wedding.theme.protectedImageOriginals", "wedding.theme.protectedImageCaptions", "wedding.theme.protectedImageStatus"], "outputs": ["reel.webp", "reel.gif"], "consumer": "Protected overlays and gallery", "protectedFromSubmissionReset": true },
    { "role": "default_symbol", "name": "PlatformHost starter reel image", "instances": 18, "databaseReferences": ["wedding.symbols[*].occupant", "wedding.starterOccupants[*]"], "outputs": ["reel.webp", "reel.gif"], "consumer": "Reel sprite and starter gallery", "protectedFromSubmissionReset": true },
    { "role": "guest_replacement", "name": "Guest slot-win or wishing-well media", "databaseReferences": ["replacementUploads", "wedding.symbols[*].occupant", "wedding.galleryMedia", "galleryMedia[publicId]", "occupantHistory[publicId]"], "outputs": ["archive.webp", "reel.webp", "thumbnail.webp", "video.mp4", "reel-sticker.png"], "consumer": "Moderation, reel, popup, and gallery", "protectedFromSubmissionReset": false },
    { "role": "couple_welcome_video", "name": "Wedding-party welcome video", "databaseReferences": ["wedding.welcomeVideo"], "outputs": ["video.mp4"], "consumer": "Welcome-message player" },
    { "role": "guest_win_message_video", "name": "PlatformHost Guest-win override", "databaseReferences": ["wedding.guestWinMessageVideos[0]"], "outputs": ["video.mp4"], "consumer": "Winner-message player" },
    { "role": "animation_object", "name": "Uploaded event-animation object", "databaseReferences": ["wedding.animationObjects[]", "wedding.animationConfig.triggers[*].objectUrl"], "outputs": ["reel.webp", "reel.gif"], "consumer": "Event animation runtime" },
    { "role": "music", "name": "Reserved music role", "databaseReferences": [], "outputs": ["audio.<ext>"], "consumer": null, "implementationStatus": "storage-recognized only" },
    { "role": "spin_sound", "name": "Reserved custom spin-sound role", "databaseReferences": [], "outputs": ["audio.<ext>"], "consumer": null, "implementationStatus": "storage-recognized only" },
    { "role": "win_sound", "name": "Reserved custom win-sound role", "databaseReferences": [], "outputs": ["audio.<ext>"], "consumer": null, "implementationStatus": "storage-recognized only" },
    { "role": "protected_sound", "name": "Reserved Protected Couple Win sound role", "databaseReferences": [], "outputs": ["audio.<ext>"], "consumer": null, "implementationStatus": "storage-recognized only" },
    { "role": "arrival_sound", "name": "Reserved arrival-sound role", "databaseReferences": [], "outputs": ["audio.<ext>"], "consumer": null, "implementationStatus": "storage-recognized only" },
    { "role": "departure_sound", "name": "Reserved departure-sound role", "databaseReferences": [], "outputs": ["audio.<ext>"], "consumer": null, "implementationStatus": "storage-recognized only" },
    { "role": "sound", "name": "Reserved generic-sound role", "databaseReferences": [], "outputs": ["audio.<ext>"], "consumer": null, "implementationStatus": "storage-recognized only" }
  ],
  "submissionLayouts": [
    { "kind": "singlePhoto", "assets": 1, "outputsPerAsset": ["archive.webp", "reel.webp", "thumbnail.webp"] },
    { "kind": "video", "assets": 2, "outputs": ["picked-frame/archive.webp", "picked-frame/reel.webp", "picked-frame/thumbnail.webp", "companion/video.mp4"] },
    { "kind": "fourPhoto", "assets": 5, "outputs": ["four original image variant sets", "one composite image variant set"], "moderation": "four originals", "publicPresentation": "one composite", "reelFace": "first original when used for a slot win" }
  ],
  "serverGeneratedAssets": [
    { "name": "Current reel sprite", "storagePattern": "<MEDIA_STORAGE_ROOT>/<wedding-public-id>/reel-sprite/normal.png", "databaseReferences": ["wedding.theme.reelSpriteUrl"] },
    { "name": "Wedding-specific Apple touch icon", "publicUrlPattern": "/api/v1/weddings/<public-id>/apple-touch-icon.png", "persistentFile": false },
    { "name": "Wedding-specific web manifest", "publicUrlPattern": "/api/v1/weddings/<public-id>/manifest.webmanifest", "persistentFile": false },
    { "name": "Offline replay bundle assets", "storagePattern": "replay export below the wedding media root", "consumer": "Standalone replay bundle" }
  ],
  "videoNormalization": {
    "acceptedInputs": ["video/mp4", "video/webm", "video/quicktime"],
    "publishedContainer": "MP4",
    "videoCodec": "H.264/libx264",
    "audioCodec": "AAC",
    "audioSelection": "first standard audio track",
    "pixelFormat": "yuv420p",
    "h264Profile": "High",
    "h264Level": "4.0",
    "maximumWidth": 1280,
    "maximumHeight": 1280,
    "upscaling": false,
    "sourceMetadataStripped": true,
    "fastStart": true,
    "binaryEnvironmentVariable": "FFMPEG_BINARY"
  },
  "retention": {
    "backupRequired": ["server/storage/media", "production database"],
    "submissionResetPreserves": ["18 PlatformHost starter occupants", "3 protected couple images"],
    "submissionResetDeletes": ["guest replacement records", "unprotected guest media references", "guest submission history and related realtime records"],
    "immutableBlobRule": "The database reference, not file presence, determines which asset is current.",
    "deploymentRule": "Application-code deployment must not overwrite or delete server/storage/media."
  },
  "implementationAuthorities": [
    "server/src/Services/AssetService.php",
    "server/src/Services/WeddingService.php",
    "server/src/Services/ReplacementWorkflowService.php",
    "server/src/Services/ReelSpriteService.php",
    "server/src/Services/ReplayExportService.php",
    "server/src/Controllers/AssetController.php",
    "server/src/Controllers/BootstrapController.php",
    "server/public/index.php",
    "packages/ui/src/admin-setup.js",
    "packages/ui/src/index.js",
    "apps/web/src/main.js"
  ]
}
