2026-08-03 22:58:30 +02:00
<!doctype html>
< html lang = "en" >
< head >
< meta charset = "utf-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1" >
< meta name = "color-scheme" content = "dark" >
< meta name = "theme-color" content = "#111827" >
< title > Immich Share Sync</ title >
< link rel = "stylesheet" href = "/styles.css" >
</ head >
< body >
< a class = "source-ribbon" href = "https://code.brunner.ninja/feedc0de/immich-sync" target = "_blank" rel = "noopener noreferrer" aria-label = "View and fork Immich Share Sync at code.brunner.ninja" >
< svg viewBox = "0 0 24 24" aria-hidden = "true" >
< path d = "m8.7 16.6-4.6-4.6 4.6-4.6 1.4 1.4L6.9 12l3.2 3.2-1.4 1.4Zm6.6 0-1.4-1.4 3.2-3.2-3.2-3.2 1.4-1.4 4.6 4.6-4.6 4.6ZM10.4 19l2.9-14 2 .4-2.9 14-2-.4Z" />
</ svg >
< span > Fork me at code.brunner.ninja</ span >
</ a >
< div class = "ambient ambient-one" ></ div >
< div class = "ambient ambient-two" ></ div >
< main >
< header class = "hero" >
< div class = "brand-mark" aria-hidden = "true" >< span ></ span >< span ></ span >< span ></ span >< span ></ span >< span ></ span >< span ></ span ></ div >
< div >
< p class = "eyebrow" > Independent Immich servers, one complete album</ p >
< h1 > Immich Share Sync</ h1 >
< p class = "lede" > Compare two public shares by checksum and stream only the missing originals. Nothing is deleted and nothing is stored here.</ p >
</ div >
< div id = "connection" class = "connection idle" >< i ></ i >< span > Idle</ span ></ div >
</ header >
2026-08-05 18:32:30 +02:00
< section class = "statistics" aria-labelledby = "statisticsTitle" >
< div class = "statistics-heading" >
< div >< p class = "eyebrow" > Shared service totals</ p >< h2 id = "statisticsTitle" > What we have synchronized</ h2 ></ div >
< span id = "statisticsStatus" > Loading…</ span >
</ div >
< div class = "statistics-grid" >
< div >< strong id = "statSyncs" > —</ strong >< span > completed syncs</ span ></ div >
< div >< strong id = "statAssets" > —</ strong >< span > assets synchronized</ span ></ div >
< div >< strong id = "statImages" > —</ strong >< span > photos</ span ></ div >
< div >< strong id = "statVideos" > —</ strong >< span > videos</ span ></ div >
</ div >
</ section >
2026-08-03 22:58:30 +02:00
< section class = "workspace" aria-label = "Share links" >
< article id = "leftCard" class = "share-card left" >
< div class = "card-heading" >
< span class = "side-label" > A</ span >
< div >< h2 > Left share</ h2 >< p id = "leftStatus" > Waiting for a link</ p ></ div >
< span id = "leftState" class = "state idle" > Idle</ span >
</ div >
< label > Immich share URL
< input id = "leftUrl" type = "url" autocomplete = "url" spellcheck = "false" placeholder = "https://photos.example/share/…" required >
</ label >
< label > Password < span > if protected</ span >
< input id = "leftPassword" type = "password" autocomplete = "off" placeholder = "Optional" >
</ label >
< div id = "leftDetails" class = "details hidden" ></ div >
</ article >
< div class = "exchange" aria-hidden = "true" >
< span > →</ span >< span > ←</ span >
</ div >
< article id = "rightCard" class = "share-card right" >
< div class = "card-heading" >
< span class = "side-label" > B</ span >
< div >< h2 > Right share</ h2 >< p id = "rightStatus" > Waiting for a link</ p ></ div >
< span id = "rightState" class = "state idle" > Idle</ span >
</ div >
< label > Immich share URL
< input id = "rightUrl" type = "url" autocomplete = "url" spellcheck = "false" placeholder = "https://photos.example/share/…" required >
</ label >
< label > Password < span > if protected</ span >
< input id = "rightPassword" type = "password" autocomplete = "off" placeholder = "Optional" >
</ label >
< div id = "rightDetails" class = "details hidden" ></ div >
</ article >
</ section >
< section class = "controls" >
< button id = "inspect" class = "primary" type = "button" > Inspect both shares</ button >
< p id = "inspectionMessage" > Connect both sides to discover available synchronization directions.</ p >
< div id = "syncOptions" class = "sync-options hidden" >
< button type = "button" data-direction = "left-to-right" >< strong > A → B</ strong >< span ></ span ></ button >
< button type = "button" data-direction = "bidirectional" >< strong > A ↔ B</ strong >< span ></ span ></ button >
< button type = "button" data-direction = "right-to-left" >< strong > B → A</ strong >< span ></ span ></ button >
</ div >
</ section >
< section id = "activity" class = "activity hidden" aria-live = "polite" >
< div class = "activity-heading" >
< div >< p class = "eyebrow" > Live WebSocket status</ p >< h2 id = "activityTitle" > Synchronization</ h2 ></ div >
< button id = "abort" class = "danger" type = "button" > Abort & disconnect</ button >
</ div >
< div class = "progress-track" >< div id = "progressBar" ></ div ></ div >
< div class = "progress-copy" >< span id = "progressText" > Preparing…</ span >< span id = "progressCount" > 0 / 0</ span ></ div >
< ol id = "events" class = "events" ></ ol >
</ section >
< footer >
< span > Ephemeral by design</ span >
< span > Checksum comparison</ span >
2026-08-05 18:32:30 +02:00
< span > One temporary asset at a time</ span >
2026-08-03 22:58:30 +02:00
< span > No deletion</ span >
</ footer >
</ main >
< script src = "/app.js" defer ></ script >
</ body >
</ html >