/* eslint-disable */
;(function(){
// יפן 2026 — shared trip-album ingestion and surfaces (DL-031).
const DSalbum = window.Ds2026DesignSystem_dc290c;
const { Polaroid, Washi } = DSalbum;
const AlbumStore = window.JP2026Store;
const {
  Store, useStore, albumSettings, tripSettings, resolveAlbumUrl, revokeDocUrl,
  deleteAlbumPhoto, setAlbumPublic, enqueueAlbumUpload, retryAlbumUpload,
  discardAlbumUpload, refreshAlbumUploads, subscribeAlbumUploads, getAlbumUploadSnapshot,
} = AlbumStore;
const { ChibiLightbox, OverlayPortal, TapChibi } = window.JP2026Detail;
const ASSET_ALBUM = './assets';

const CITY_ALBUM = {
  tokyo:{ color:'var(--city-tokyo)', tint:'var(--city-tokyo-tint)', deep:'var(--city-tokyo-deep)', name:'טוקיו' },
  hakone:{ color:'var(--city-hakone)', tint:'var(--city-hakone-tint)', deep:'var(--city-hakone-deep)', name:'האקונה' },
  kyoto:{ color:'var(--city-kyoto)', tint:'var(--city-kyoto-tint)', deep:'var(--city-kyoto-deep)', name:'קיוטו' },
  nara:{ color:'var(--city-kyoto)', tint:'var(--city-kyoto-tint)', deep:'var(--city-kyoto-deep)', name:'נארה' },
  osaka:{ color:'var(--city-osaka)', tint:'var(--city-osaka-tint)', deep:'var(--city-osaka-deep)', name:'אוסקה' },
};
const cityAlbum = (key) => CITY_ALBUM[key] || CITY_ALBUM.tokyo;

/* PURE_ALBUM_LOGIC_START */
function readAscii(view, offset, length) {
  let output = '';
  for (let index=0; index<length && offset+index<view.byteLength; index += 1) {
    const byte = view.getUint8(offset + index);
    if (!byte) break;
    output += String.fromCharCode(byte);
  }
  return output.trim();
}
function parseTiffDate(view, tiffOffset) {
  if (tiffOffset < 0 || tiffOffset + 8 > view.byteLength) return '';
  const marker = readAscii(view, tiffOffset, 2);
  const little = marker === 'II';
  if (!little && marker !== 'MM') return '';
  const u16 = (offset) => view.getUint16(offset, little);
  const u32 = (offset) => view.getUint32(offset, little);
  if (u16(tiffOffset + 2) !== 42) return '';
  const seen = new Set();
  const scanIfd = (relativeOffset) => {
    const base = tiffOffset + relativeOffset;
    if (seen.has(base) || base < 0 || base + 2 > view.byteLength) return '';
    seen.add(base);
    const count = u16(base);
    for (let index=0; index<count; index += 1) {
      const entry = base + 2 + index * 12;
      if (entry + 12 > view.byteLength) break;
      const tag = u16(entry), type = u16(entry + 2), size = u32(entry + 4);
      const valueOffset = size <= 4 ? entry + 8 : tiffOffset + u32(entry + 8);
      if ((tag === 0x9003 || tag === 0x9004 || tag === 0x0132) && type === 2) {
        const value = readAscii(view, valueOffset, size);
        if (/^\d{4}:\d{2}:\d{2} \d{2}:\d{2}:\d{2}$/.test(value)) return value;
      }
      if (tag === 0x8769 && type === 4) {
        const nested = scanIfd(u32(entry + 8));
        if (nested) return nested;
      }
    }
    return '';
  };
  return scanIfd(u32(tiffOffset + 4));
}

/* JPEG and HEIC/HEIF both carry TIFF EXIF. HEIC wraps it in ISO-BMFF, so search
   for the Exif signature instead of assuming a JPEG APP1 offset. */
function parseExifCaptureTime(buffer) {
  if (!buffer) return '';
  const view = new DataView(buffer);
  for (let offset=0; offset+10<view.byteLength; offset += 1) {
    if (readAscii(view, offset, 4) !== 'Exif') continue;
    for (let probe=offset+4; probe<Math.min(view.byteLength-8, offset+24); probe += 1) {
      const value = parseTiffDate(view, probe);
      if (value) return value;
    }
  }
  // Some phone encoders expose the timestamp text while omitting the conventional
  // Exif\0\0 prefix. This bounded fallback is still metadata-only, never filename parsing.
  const sample = readAscii(view, 0, Math.min(view.byteLength, 1024 * 1024));
  const match = sample.match(/\b\d{4}:\d{2}:\d{2} \d{2}:\d{2}:\d{2}\b/);
  return match ? match[0] : '';
}

async function readCaptureTime(file, now=new Date()) {
  try {
    const exif = parseExifCaptureTime(await file.arrayBuffer());
    if (exif) return { value:exif, source:'exif' };
  } catch (error) {}
  if (Number(file && file.lastModified) > 0) return { value:new Date(file.lastModified), source:'last_modified' };
  return { value:now, source:'uploaded' };
}

function dateParts(value, timeZone) {
  const exif = /^(\d{4}):(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})$/.exec(String(value || ''));
  if (exif) return { year:+exif[1], month:+exif[2], day:+exif[3], hour:+exif[4], minute:+exif[5], second:+exif[6] };
  const instant = value instanceof Date ? value : new Date(value);
  if (Number.isNaN(instant.getTime())) return null;
  const parts = new Intl.DateTimeFormat('en-CA', {
    timeZone, year:'numeric', month:'2-digit', day:'2-digit', hour:'2-digit', minute:'2-digit', second:'2-digit', hourCycle:'h23',
  }).formatToParts(instant);
  const get = (type) => Number(parts.find(part => part.type === type)?.value);
  return { year:get('year'), month:get('month'), day:get('day'), hour:get('hour'), minute:get('minute'), second:get('second') };
}
const isoDateKey = (parts) => parts && `${parts.year}-${String(parts.month).padStart(2,'0')}-${String(parts.day).padStart(2,'0')}`;
function shiftedTripDateKey(value, timeZone, boundaryHour) {
  const parts = dateParts(value, timeZone);
  if (!parts) return '';
  const shifted = new Date(Date.UTC(parts.year, parts.month-1, parts.day, parts.hour - boundaryHour, parts.minute, parts.second));
  return shifted.toISOString().slice(0, 10);
}
function dayDateKey(day, fallbackYear) {
  const exact = /^\d{4}-\d{2}-\d{2}$/.test(String(day && day.iso_date || '')) ? day.iso_date : '';
  if (exact) return exact;
  const match = /^(\d{1,2})\.(\d{1,2})(?:\.(\d{4}))?$/.exec(String(day && day.date || ''));
  return match ? `${match[3] || fallbackYear}-${String(match[2]).padStart(2,'0')}-${String(match[1]).padStart(2,'0')}` : '';
}
function deriveAlbumDay(value, days, settings=albumSettings) {
  const parts = dateParts(value, settings.timeZone || tripSettings.timeZone);
  if (!parts) return null;
  const key = shiftedTripDateKey(value, settings.timeZone || tripSettings.timeZone, settings.dayBoundaryHour);
  const startYear = /^\d{4}/.exec(settings.tripStartDate || '');
  const year = startYear ? startYear[0] : String(parts.year);
  return (days || []).find(day => dayDateKey(day, year) === key) || null;
}
function albumDateLabel(value, timeZone) {
  const parts = dateParts(value, timeZone);
  return parts ? `${parts.day}.${parts.month}` : '';
}
function dayThreeHasPassed(day, now=new Date(), settings=albumSettings) {
  if (!day || Number(day.n) !== 3) return true;
  const parts = dateParts(now, settings.timeZone || tripSettings.timeZone);
  if (!parts) return false;
  const shifted = shiftedTripDateKey(now, settings.timeZone || tripSettings.timeZone, settings.dayBoundaryHour);
  const startYear = /^\d{4}/.exec(settings.tripStartDate || '');
  return shifted > dayDateKey(day, startYear ? startYear[0] : String(parts.year));
}

function zonedDateTimeToIso(dateKey, hour, timeZone, minute=0, second=0) {
  const [year, month, day] = String(dateKey).split('-').map(Number);
  let candidate = Date.UTC(year, month-1, day, hour, minute, second);
  for (let pass=0; pass<3; pass += 1) {
    const actual = dateParts(new Date(candidate), timeZone);
    const wanted = Date.UTC(year, month-1, day, hour, minute, second);
    const observed = Date.UTC(actual.year, actual.month-1, actual.day, actual.hour, actual.minute, actual.second);
    candidate += wanted - observed;
  }
  return new Date(candidate).toISOString();
}
function computeAlbumShareExpiry(tripEndDate, settings=albumSettings) {
  if (settings.shareExpiry && !Number.isNaN(Date.parse(settings.shareExpiry))) return new Date(settings.shareExpiry).toISOString();
  if (!/^\d{4}-\d{2}-\d{2}$/.test(String(tripEndDate || ''))) return '';
  const [year, month, day] = tripEndDate.split('-').map(Number);
  const next = new Date(Date.UTC(year, month-1, day + 1)).toISOString().slice(0,10);
  return zonedDateTimeToIso(next, settings.dayBoundaryHour, settings.timeZone || tripSettings.timeZone);
}
function captureToIso(value, timeZone) {
  const exif = /^(\d{4}):(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})$/.exec(String(value || ''));
  if (exif) return zonedDateTimeToIso(`${exif[1]}-${exif[2]}-${exif[3]}`, Number(exif[4]), timeZone, Number(exif[5]), Number(exif[6]));
  const date = value instanceof Date ? value : new Date(value);
  return date.toISOString();
}
function parsePublicAlbumRoute(pathname, basePath=albumSettings.publicBasePath) {
  const base = String(basePath || '/album').replace(/\/+$/, '');
  const path = String(pathname || '').replace(/\/+$/, '') || '/';
  if (path === base) return { type:'album' };
  const escaped = base.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  let match = new RegExp(`^${escaped}/day-(\\d+)-([a-z0-9-]+)$`, 'i').exec(path);
  if (match) return { type:'day', dayN:Number(match[1]), city:match[2].toLowerCase() };
  match = new RegExp(`^${escaped}/p/([a-f0-9-]+)$`, 'i').exec(path);
  return match ? { type:'photo', id:match[1] } : null;
}
/* PURE_ALBUM_LOGIC_END */

async function decodeAndResizeImage(file, settings=albumSettings) {
  let source, cleanup = () => {};
  try {
    if (typeof createImageBitmap === 'function') {
      source = await createImageBitmap(file, { imageOrientation:'from-image' });
    } else {
      const url = URL.createObjectURL(file);
      cleanup = () => URL.revokeObjectURL(url);
      source = await new Promise((resolve, reject) => {
        const image = new Image(); image.onload = () => resolve(image); image.onerror = () => reject(new Error('לא הצלחנו לפתוח את התמונה')); image.src = url;
      });
    }
    const sourceWidth = source.width || source.naturalWidth, sourceHeight = source.height || source.naturalHeight;
    if (!sourceWidth || !sourceHeight) throw new Error('לתמונה אין מידות תקינות');
    const scale = Math.min(1, settings.maxDimensionPx / Math.max(sourceWidth, sourceHeight));
    const width = Math.max(1, Math.round(sourceWidth * scale)), height = Math.max(1, Math.round(sourceHeight * scale));
    const canvas = document.createElement('canvas'); canvas.width = width; canvas.height = height;
    const context = canvas.getContext('2d', { alpha:false });
    context.fillStyle = '#fff'; context.fillRect(0, 0, width, height); context.drawImage(source, 0, 0, width, height);
    const blob = await new Promise((resolve, reject) => canvas.toBlob(value => value ? resolve(value) : reject(new Error('המרת התמונה נכשלה')), 'image/jpeg', settings.jpegQuality));
    return { blob, width, height };
  } finally {
    cleanup();
    if (source && typeof source.close === 'function') source.close();
  }
}

function useAlbumUploads() {
  React.useEffect(() => { refreshAlbumUploads && refreshAlbumUploads(); }, []);
  return React.useSyncExternalStore(subscribeAlbumUploads, getAlbumUploadSnapshot, getAlbumUploadSnapshot);
}
function statusCopy(job) {
  if (job.status === 'processing') return 'מעבדת…';
  if (job.status === 'uploading') return `מעלה… ${job.progress || 0}%`;
  if (job.status === 'completed') return 'הועלתה ✓';
  if (job.status === 'failed') return job.error || 'נכשלה';
  return 'בהמתנה';
}
function UploadProgress({ transient=[] }) {
  const durable = useAlbumUploads();
  const jobs = transient.concat(durable);
  if (!jobs.length) return null;
  const done = jobs.filter(job => job.status === 'completed').length;
  const active = jobs.filter(job => job.status !== 'completed');
  if (!active.length) return null;
  return (
    <section role="status" style={{ marginTop:12, padding:'12px', borderRadius:'var(--r-md)', background:'var(--surface-card)' }}>
      <div style={{ display:'flex', justifyContent:'space-between', fontFamily:'var(--font-ui)', fontSize:13, color:'var(--ink-700)' }}>
        <span>העלאת תמונות</span><span>{done} מתוך {jobs.length}</span>
      </div>
      <div style={{ height:7, margin:'7px 0 10px', borderRadius:999, overflow:'hidden', background:'var(--paper-cream-deep)' }}>
        <div style={{ width:`${jobs.length ? Math.round(done/jobs.length*100) : 0}%`, height:'100%', background:'var(--matcha-green)' }} />
      </div>
      {!navigator.onLine && <div style={{ color:'var(--sky-deep)', fontFamily:'var(--font-ui)', fontSize:12, marginBottom:8 }}>☁️ אין חיבור — נשלים אוטומטית כשנתחבר</div>}
      <div style={{ display:'flex', flexDirection:'column', gap:7 }}>
        {active.slice(-6).map(job => (
          <div key={job.id} style={{ display:'flex', alignItems:'center', gap:8, fontFamily:'var(--font-ui)', fontSize:12.5 }}>
            <span style={{ flex:1, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap', direction:'ltr', unicodeBidi:'isolate' }}>{job.original_name || 'photo.jpg'}</span>
            <span style={{ color:job.status==='failed'?'var(--peach-deep)':'var(--ink-500)' }}>{statusCopy(job)}</span>
            {job.status === 'failed' && <button type="button" onClick={()=>retryAlbumUpload(job.id)} style={{ border:0, background:'transparent', color:'var(--peach-deep)', fontFamily:'var(--font-ui)', cursor:'pointer' }}>↻ נסו שוב</button>}
            {(job.status === 'failed' || job.status === 'completed') && <button type="button" aria-label="הסרה מהרשימה" onClick={()=>discardAlbumUpload(job.id)} style={{ border:0, background:'transparent', color:'var(--ink-300)', cursor:'pointer' }}>✕</button>}
          </div>
        ))}
      </div>
    </section>
  );
}

function AlbumUploader({ days, defaultDayN, accent, buttonLabel='העלו תמונות' }) {
  const input = React.useRef(null);
  const [processing, setProcessing] = React.useState([]);
  const update = (id, patch) => setProcessing(rows => rows.map(row => row.id === id ? { ...row, ...patch } : row));
  const choose = () => input.current?.click();
  const onFiles = async (event) => {
    const files = Array.from(event.target.files || []); event.target.value = '';
    for (const file of files) {
      const id = `${Date.now()}-${Math.random()}`;
      setProcessing(rows => rows.concat([{ id, original_name:file.name, status:'processing', progress:0 }]));
      try {
        const capture = await readCaptureTime(file);
        const detectedDay = deriveAlbumDay(capture.value, days, { ...albumSettings, timeZone:tripSettings.timeZone });
        const day = detectedDay || (defaultDayN == null ? null : (days || []).find(item => Number(item.n) === Number(defaultDayN)));
        const normalized = await decodeAndResizeImage(file);
        const city = cityAlbum(day?.city);
        const metadata = {
          day_n:day?.n ?? null, city_key:day?.city || '', city_label:day ? (day.cityName || city.name) : '',
          date_label:day?.date || albumDateLabel(capture.value, tripSettings.timeZone), taken_at:captureToIso(capture.value, tripSettings.timeZone),
          detected_at:new Date().toISOString(), date_source:capture.source,
          original_name:file.name || '', width:normalized.width, height:normalized.height,
        };
        await enqueueAlbumUpload({ blob:normalized.blob, metadata, original_name:file.name || '', photo_id:crypto.randomUUID ? crypto.randomUUID() : undefined });
        setProcessing(rows => rows.filter(row => row.id !== id));
      } catch (error) { update(id, { status:'failed', error:String(error && error.message || error) }); }
    }
  };
  return (
    <>
      <input ref={input} type="file" accept="image/*,.heic,.heif" multiple hidden onChange={onFiles} />
      <button type="button" onClick={choose} style={{ appearance:'none', border:'none', width:'100%', padding:'12px 16px', borderRadius:'var(--r-pill)', background:'var(--ink-900)', color:'var(--paper-cream)', fontFamily:'var(--font-ui)', fontSize:15, cursor:'pointer' }}>{buttonLabel}</button>
      <UploadProgress transient={processing} />
    </>
  );
}

function ResolvedPhoto({ photo, onOpen, selected=false, onSelect, onLongPress, style={} }) {
  const [src, setSrc] = React.useState(null), [missing, setMissing] = React.useState(false);
  const pressTimer = React.useRef(0), didLongPress = React.useRef(false);
  React.useEffect(() => {
    let alive = true, owned = null;
    resolveAlbumUrl(photo).then(url => { if (!alive) { revokeDocUrl(url); return; } owned=url; setSrc(url); setMissing(!url); }).catch(()=>setMissing(true));
    return () => { alive=false; revokeDocUrl(owned); };
  }, [photo.id, photo.storage_path]);
  const stopPress = () => { clearTimeout(pressTimer.current); pressTimer.current=0; };
  const startPress = () => {
    if (!onLongPress) return;
    didLongPress.current=false;
    pressTimer.current=setTimeout(()=>{ didLongPress.current=true; onLongPress(photo); }, 450);
  };
  return (
    <button type="button" onPointerDown={startPress} onPointerUp={stopPress} onPointerCancel={stopPress} onPointerLeave={stopPress} onClick={()=>{ if(didLongPress.current){didLongPress.current=false;return;} onSelect ? onSelect(photo) : onOpen?.(photo); }} aria-pressed={onSelect ? selected : undefined}
      style={{ appearance:'none', border:selected?'3px solid var(--sky-deep)':'none', padding:0, cursor:'pointer', overflow:'hidden', position:'relative', background:'var(--paper-cream-deep)', ...style }}>
      {src ? <img src={src} alt="תמונה מהטיול" loading="lazy" decoding="async" style={{ width:'100%', height:'100%', objectFit:'cover', display:'block' }} />
        : <span style={{ position:'absolute', inset:0, display:'grid', placeItems:'center', color:'var(--ink-500)', fontFamily:'var(--font-ui)', fontSize:12 }}>{missing ? 'לא זמינה אופליין' : 'טוענת…'}</span>}
      {onSelect && <span aria-hidden="true" style={{ position:'absolute', top:6, insetInlineEnd:6, width:24, height:24, borderRadius:'50%', background:selected?'var(--ink-900)':'rgba(255,255,255,.82)', border:selected?'none':'1px solid var(--hairline)', color:'var(--paper-cream)', display:'grid', placeItems:'center' }}>{selected?<svg width="12" height="12" viewBox="0 0 12 12"><path d="M2 6.5 L5 9 L10 3" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/></svg>:null}</span>}
    </button>
  );
}
function AddPhotoTile({ onClick, accent, size=104 }) {
  return <button type="button" onClick={onClick} aria-label="הוספת תמונות" style={{ flex:'0 0 auto', width:size, height:size, borderRadius:'var(--r-md)', border:`2px dashed ${accent}`, background:'transparent', color:accent, fontFamily:'var(--font-ui)', cursor:'pointer', display:'grid', placeItems:'center' }}><span><span style={{ display:'block', fontSize:28 }}>＋</span>הוספה</span></button>;
}

function PhotoViewer({ photo, onClose, withSelectionBar=false }) {
  const [src, setSrc] = React.useState(null);
  React.useEffect(() => { let alive=true, owned=null; resolveAlbumUrl(photo).then(url=>{ if(alive){ owned=url; setSrc(url); } }); return ()=>{alive=false; revokeDocUrl(owned);}; }, [photo.id]);
  return src ? <ChibiLightbox src={src} alt="תמונה מהטיול" filename={photo.original_name || 'trip-photo.jpg'} variant="reference" hideDownload={withSelectionBar} reserveBottom={withSelectionBar?'112px':0} onClose={onClose} /> : null;
}
function ReassignSheet({ photo, photos, days, onClose }) {
  const rows = photos || (photo ? [photo] : []);
  const [dayN, setDayN] = React.useState(rows[0]?.day_n);
  const save = async () => {
    const day = days.find(item => item.n === dayN); if (!day) return;
    const city = cityAlbum(day.city);
    await Promise.all(rows.map(row=>Store.upsert(albumSettings.table, { ...row, day_n:day.n, city_key:day.city || '', city_label:day.cityName || city.name, date_label:day.date || '', date_source:'manual' })));
    onClose();
  };
  return <OverlayPortal onClose={onClose} ariaLabel="שיוך תמונה ליום" zIndex={140}>
    <div onClick={onClose} style={{ position:'absolute', inset:0, background:'rgba(74,64,58,.42)', display:'flex', alignItems:'flex-end' }}>
      <section onClick={event=>event.stopPropagation()} style={{ width:'100%', maxHeight:'76%', overflowY:'auto', background:'var(--surface-card)', borderRadius:'var(--r-xl) var(--r-xl) 0 0', padding:'18px 20px calc(22px + env(safe-area-inset-bottom))', boxShadow:'var(--shadow-sheet)' }}>
        <div style={{ width:40, height:4, borderRadius:999, background:'var(--ink-300)', margin:'0 auto 14px' }} />
        <h2 style={{ fontFamily:'var(--font-display)', fontSize:30, color:'var(--ink-900)', margin:0 }}>{rows.length>1?'היום של התמונות':'היום של התמונה'}</h2>
        <div style={{ fontFamily:'var(--font-ui)', fontSize:12.5, color:'var(--sky-deep)', margin:'6px 0 12px' }}>🕓 זוהתה אוטומטית; אפשר לשייך ליום אחר</div>
        <div style={{ display:'flex', gap:8, overflowX:'auto', paddingBottom:8 }}>
          {days.map(day => { const city=cityAlbum(day.city), active=day.n===dayN; return <button key={day.n} type="button" onClick={()=>setDayN(day.n)} style={{ flex:'0 0 auto', minWidth:88, padding:'9px 12px', borderRadius:'var(--r-md)', border:`1.5px solid ${active?city.color:'var(--hairline)'}`, background:active?city.tint:'var(--surface-card)', color:active?city.deep:'var(--ink-700)', fontFamily:'var(--font-ui)' }}>יום {day.n}<span className="date-ltr" style={{ display:'block', fontSize:11 }}>{day.date}</span></button>; })}
        </div>
        <button type="button" onClick={save} style={{ width:'100%', border:0, borderRadius:'var(--r-pill)', padding:13, marginTop:12, background:'var(--ink-900)', color:'var(--paper-cream)', fontFamily:'var(--font-ui)' }}>שמירה · יום {dayN}</button>
      </section>
    </div>
  </OverlayPortal>;
}

function AlbumStrip({ day, title='התמונות של היום' }) {
  const photos = useStore(albumSettings.table).filter(photo => Number(photo.day_n) === Number(day.n)).sort((a,b)=>String(a.taken_at).localeCompare(String(b.taken_at)));
  const days = useStore('days');
  const [viewer, setViewer] = React.useState(null), uploader = React.useRef(null);
  const city = cityAlbum(day.city);
  return <section aria-label={title} style={{ padding:'10px 0 4px', borderTop:'1px solid var(--hairline)', marginTop:14 }}>
    <div style={{ padding:'0 20px', display:'flex', alignItems:'baseline', justifyContent:'space-between' }}><h2 style={{ fontFamily:'var(--font-display)', fontSize:30, color:'var(--ink-900)', margin:0 }}>{title}</h2><span style={{ fontFamily:'var(--font-ui)', fontSize:13, color:'var(--ink-500)' }}>{photos.length} תמונות</span></div>
    <div style={{ display:'flex', gap:12, overflowX:'auto', padding:'12px 20px 16px', alignItems:'flex-start', scrollbarWidth:'none' }}>
      <AddPhotoTile accent={city.color} onClick={()=>uploader.current?.querySelector('input')?.click()} />
      {photos.map((photo,index) => <Polaroid key={photo.id} variant="frame" aspect="1 / 1" width={114} rotate={index%2?2.2:-2.2} washi={index%3===0} washiColor={city.color}><ResolvedPhoto photo={photo} onOpen={setViewer} style={{ position:'absolute', inset:0, width:'100%', height:'100%', borderRadius:'var(--r-photo)' }} /></Polaroid>)}
    </div>
    <div ref={uploader} style={{ display:'none' }}><AlbumUploader days={days} defaultDayN={day.n} accent={city.color} /></div>
    <UploadProgress />
    {viewer && <PhotoViewer photo={viewer} onClose={()=>setViewer(null)} />}
  </section>;
}

function TodayAlbumCard({ day }) {
  const photos = useStore(albumSettings.table).filter(photo => Number(photo.day_n) === Number(day.n)).sort((a,b)=>String(b.taken_at).localeCompare(String(a.taken_at)));
  const days = useStore('days'), city = cityAlbum(day.city);
  const [viewer, setViewer] = React.useState(null);
  return <section style={{ margin:'8px 20px 0', padding:'16px', borderRadius:'var(--r-xl)', background:city.tint, borderInlineStart:`4px solid ${city.color}`, boxShadow:'var(--shadow-card)' }}>
    <div style={{ display:'flex', justifyContent:'space-between', alignItems:'baseline' }}><h2 style={{ fontFamily:'var(--font-display)', fontSize:29, color:'var(--ink-900)', margin:0 }}>התמונות של היום</h2><span style={{ fontFamily:'var(--font-ui)', fontSize:13, color:city.deep }}>{photos.length} היום 📷</span></div>
    <div style={{ fontFamily:'var(--font-ui)', fontSize:13.5, color:'var(--ink-700)', marginTop:3 }}>מוסיפים עכשיו — יסתדרו לבד ליום הנכון</div>
    {!!photos.length && <div style={{ display:'flex', gap:7, overflowX:'auto', margin:'12px 0' }}>{photos.slice(0,6).map(photo=><ResolvedPhoto key={photo.id} photo={photo} onOpen={setViewer} style={{ flex:'0 0 auto', width:64, height:64, borderRadius:'var(--r-sm)', boxShadow:'var(--shadow-sm)' }} />)}</div>}
    <div style={{ marginTop:12 }}><AlbumUploader days={days} defaultDayN={day.n} accent={city.color} buttonLabel="העלו את התמונות של היום" /></div>
    <div style={{ fontFamily:'var(--font-ui)', fontSize:12, color:navigator.onLine?'var(--matcha-deep)':'var(--sky-deep)', textAlign:'center', marginTop:10 }}>{navigator.onLine?'✓ מסונכרן בין שני המטיילים':'☁️ אופליין — התמונות ממתינות במכשיר'}</div>
    {viewer && <PhotoViewer photo={viewer} onClose={()=>setViewer(null)} />}
  </section>;
}

async function photoToFile(photo) {
  const url = await resolveAlbumUrl(photo); if (!url) throw new Error('התמונה אינה זמינה');
  try { const blob = await (await fetch(url)).blob(); return new File([blob], photo.original_name || `${photo.id}.jpg`, { type:blob.type || 'image/jpeg' }); }
  finally { revokeDocUrl(url); }
}
async function downloadFiles(files) {
  for (const file of files) { const url=URL.createObjectURL(file), anchor=document.createElement('a'); anchor.href=url; anchor.download=file.name; document.body.appendChild(anchor); anchor.click(); anchor.remove(); setTimeout(()=>URL.revokeObjectURL(url), 4000); }
}
async function sharePhotoFiles(photos) {
  const files = await Promise.all((photos || []).map(photoToFile));
  if (navigator.canShare && navigator.canShare({ files })) {
    try { await navigator.share({ files }); return; } catch (error) { if (error && error.name === 'AbortError') return; }
  }
  await downloadFiles(files);
}
const safeCitySlug = (value) => String(value || 'trip').toLowerCase().replace(/[^a-z0-9-]/g, '') || 'trip';
function AlbumGroupHeader({ day, count }) {
  if (day.outside) return <div style={{ margin:'4px 20px 8px', padding:'10px 14px', borderRadius:'var(--r-md)', background:'var(--surface-card-2)', display:'flex', alignItems:'center', gap:10 }}><div style={{ flex:1, fontFamily:'var(--font-display)', fontSize:27, color:'var(--ink-900)' }}>מחוץ לימי הטיול</div><span style={{ fontFamily:'var(--font-ui)', fontSize:13, color:'var(--ink-500)' }}>{count} · <span className="date-ltr">{day.date}</span></span></div>;
  const city=cityAlbum(day.city);
  return <div style={{ position:'relative', margin:'4px 20px 8px', padding:'10px 14px', borderRadius:'var(--r-md)', background:city.tint, display:'flex', alignItems:'center', gap:10 }}>
    <Washi color={city.color} width={70} height={20} rotate={-4} style={{ position:'absolute', top:-9, insetInlineStart:20 }} />
    <span style={{ width:12, height:12, borderRadius:'50%', background:city.color }} /><div style={{ flex:1, fontFamily:'var(--font-display)', fontSize:27, color:'var(--ink-900)' }}>{day.n===3?'🎂 ':''}יום {day.n} · {day.cityName || city.name}</div><span style={{ fontFamily:'var(--font-ui)', fontSize:13, color:city.deep }}>{count} · <span className="date-ltr">{day.date}</span></span>
  </div>;
}

function AlbumDeleteDialog({ count, busy, error, onConfirm, onClose }) {
  return <OverlayPortal onClose={busy?()=>{}:onClose} ariaLabel="אישור מחיקת תמונות" zIndex={150}><div style={{ position:'absolute', inset:0, background:'rgba(0,0,0,.55)', display:'grid', placeItems:'center', padding:24 }}><section role="alertdialog" aria-modal="true" aria-labelledby="album-delete-title" style={{ background:'var(--surface-card)', borderRadius:'var(--r-lg)', padding:'20px 22px', maxWidth:320, width:'100%', boxShadow:'var(--shadow-lift)', textAlign:'center' }}><h2 id="album-delete-title" style={{ fontFamily:'var(--font-display)', fontSize:29, fontWeight:400, color:'var(--ink-900)', margin:'0 0 6px' }}>{count===1?'למחוק את התמונה?':`למחוק ${count} תמונות?`}</h2><div style={{ fontFamily:'var(--font-ui)', fontSize:'var(--fs-sm)', color:'var(--ink-500)', marginBottom:16 }}>האם למחוק?</div>{error&&<div role="alert" style={{ color:'var(--peach-deep)', fontFamily:'var(--font-ui)', fontSize:'var(--fs-2xs)', marginBottom:10 }}>{error}</div>}<div style={{ display:'flex', gap:10, justifyContent:'center' }}><button type="button" disabled={busy} onClick={onConfirm} style={{ appearance:'none', border:'none', background:'var(--peach-deep)', color:'#fff', borderRadius:'var(--r-md)', padding:'9px 18px', fontFamily:'var(--font-ui)', fontSize:'var(--fs-sm)', cursor:busy?'wait':'pointer', opacity:busy?.6:1 }}>{busy?'מוחקים…':'מחיקה'}</button><button type="button" disabled={busy} onClick={onClose} style={{ appearance:'none', border:'var(--border-card)', background:'var(--surface-card-2)', color:'var(--ink-900)', borderRadius:'var(--r-md)', padding:'9px 18px', fontFamily:'var(--font-ui)', fontSize:'var(--fs-sm)', cursor:busy?'default':'pointer' }}>ביטול</button></div></section></div></OverlayPortal>;
}

function SelectionAction({ label, color='var(--paper-cream)', onClick, disabled, children }) {
  return <button type="button" onClick={onClick} disabled={disabled} style={{ appearance:'none', border:0, background:'transparent', color, flex:1, display:'flex', flexDirection:'column', alignItems:'center', gap:5, padding:'3px 2px', fontFamily:'var(--font-ui)', fontSize:11, cursor:disabled?'wait':'pointer', opacity:disabled?.58:1 }}>{children}<span>{label}</span></button>;
}
function AlbumSelectionBar({ count, busy, onCancel, onShare, onAssign, onDownload, onDelete }) {
  return <div aria-label="פעולות לתמונות נבחרות" style={{ position:'relative', zIndex:130, flex:'0 0 auto', width:'100%', boxSizing:'border-box', background:'var(--ink-900)', padding:'12px 16px calc(12px + env(safe-area-inset-bottom))', boxShadow:'0 -8px 24px rgba(74,64,58,.28)' }}><div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom:10 }}><span style={{ fontFamily:'var(--font-display)', fontSize:22, color:'var(--paper-cream)' }}>{count} נבחרו</span><button type="button" onClick={onCancel} disabled={busy} style={{ appearance:'none', border:0, background:'transparent', fontFamily:'var(--font-ui)', fontSize:14, color:'var(--paper-cream)', opacity:.8, cursor:'pointer' }}>בטל</button></div><div style={{ display:'flex', justifyContent:'space-between' }}>
    <SelectionAction label="שיתוף" onClick={onShare} disabled={busy}><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><path d="M8.6 10.5 15.4 6.5M8.6 13.5 15.4 17.5"/></svg></SelectionAction>
    <SelectionAction label="שיוך ליום" onClick={onAssign} disabled={busy}><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="4.5" width="18" height="16" rx="2.5"/><path d="M3 9h18M8 2.5v4M16 2.5v4"/></svg></SelectionAction>
    <SelectionAction label="הורדה" onClick={onDownload} disabled={busy}><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M12 3v11M7.5 9.5 12 14l4.5-4.5M4 20h16"/></svg></SelectionAction>
    <SelectionAction label="מחיקה" color="var(--blossom-pink)" onClick={onDelete} disabled={busy}><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M4 7h16M9 7V4.5h6V7M6 7l1 13h10l1-13"/></svg></SelectionAction>
  </div></div>;
}

function SharePanel({ photos, selected, onClose }) {
  const selectedPhotos = photos.filter(photo=>selected.has(photo.id));
  const publicOn = photos.length > 0 && photos.every(photo => photo.public_until && Date.parse(photo.public_until) > Date.now());
  const [busy,setBusy]=React.useState(false), [error,setError]=React.useState(''), [copied,setCopied]=React.useState('');
  const shareSelected = async () => {
    setBusy(true); setError('');
    try { await sharePhotoFiles(selectedPhotos); }
    catch (cause) { setError(String(cause.message || cause)); }
    finally { setBusy(false); }
  };
  const ensurePublic = async () => {
    if (!publicOn) {
      const config = await AlbumStore.getAlbumConfig();
      const expiry = computeAlbumShareExpiry(config.tripEndDate, { ...albumSettings, timeZone:config.timeZone });
      if (!expiry) throw new Error('חסר תאריך סיום טיול בהגדרות');
      await setAlbumPublic(photos.map(photo=>photo.id), true, expiry);
    }
  };
  const example = selectedPhotos[0] || photos[0];
  const hasTripDay = example && example.day_n != null && Number.isInteger(Number(example.day_n));
  const dayPath = hasTripDay ? `${albumSettings.publicBasePath}/day-${example.day_n}-${safeCitySlug(example.city_key)}` : albumSettings.publicBasePath;
  const photoPath = example ? `${albumSettings.publicBasePath}/p/${example.id}` : albumSettings.publicBasePath;
  const oneTripDay = selectedPhotos.length>1 && hasTripDay && selectedPhotos.every(photo=>Number(photo.day_n)===Number(example.day_n)&&safeCitySlug(photo.city_key)===safeCitySlug(example.city_key));
  const sharePath = selectedPhotos.length===1 ? photoPath : oneTripDay ? dayPath : albumSettings.publicBasePath;
  const shareUrl = new URL(sharePath, window.location.origin).href;
  const photoUrl = new URL(photoPath, window.location.origin).href;
  const copyPath = async (path, key) => {
    setBusy(true); setError('');
    try {
      await ensurePublic();
      await navigator.clipboard.writeText(path);
      setCopied(key); setTimeout(()=>setCopied(''), 1800);
    } catch (cause) { setError(String(cause.message || 'לא הצלחנו להעתיק את הקישור')); }
    finally { setBusy(false); }
  };
  return <OverlayPortal onClose={onClose} ariaLabel="שיתוף תמונות" zIndex={140}><div onClick={onClose} style={{ position:'absolute', inset:0, background:'rgba(74,64,58,.42)', display:'flex', alignItems:'flex-end' }}><section onClick={event=>event.stopPropagation()} style={{ width:'100%', background:'var(--surface-card)', borderRadius:'var(--r-xl) var(--r-xl) 0 0', padding:'18px 20px calc(22px + env(safe-area-inset-bottom))', boxShadow:'var(--shadow-sheet)' }}>
    <div style={{ width:40, height:4, borderRadius:999, background:'var(--ink-300)', margin:'0 auto 14px' }} />
    <h2 style={{ fontFamily:'var(--font-display)', fontSize:28, margin:0, color:'var(--ink-900)', fontWeight:400 }}>לשתף עם חברים</h2>
    <div style={{ fontFamily:'var(--font-ui)', fontSize:13, color:'var(--ink-500)', marginTop:2, marginBottom:14 }}>האפליקציה נעולה מאחורי כניסה — לחברים אין גישה ישירה</div>

    <button type="button" disabled={!selectedPhotos.length||busy} onClick={shareSelected} style={{ appearance:'none', width:'100%', textAlign:'start', borderRadius:'var(--r-lg)', border:'1px solid var(--hairline)', background:'var(--surface-card-2)', padding:'14px 16px', cursor:selectedPhotos.length&&!busy?'pointer':'default', opacity:selectedPhotos.length?1:.58 }}>
      <span style={{ display:'flex', alignItems:'center', gap:8 }}><span style={{ fontSize:22 }}>📤</span><span style={{ fontFamily:'var(--font-display)', fontSize:24, color:'var(--ink-900)' }}>שיתוף תמונות נבחרות</span></span>
    </button>

    <div style={{ borderRadius:'var(--r-lg)', border:'1px solid var(--hairline)', background:'var(--surface-card)', padding:'14px 16px', marginTop:12 }}>
      <div style={{ display:'flex', alignItems:'center', gap:8 }}><span style={{ fontSize:22 }}>🔗</span><span style={{ fontFamily:'var(--font-display)', fontSize:24, color:'var(--ink-900)' }}>קישור צפייה ציבורי</span></div>
      <div style={{ display:'flex', alignItems:'center', gap:8, marginTop:10, padding:'9px 12px', borderRadius:'var(--r-md)', background:'var(--surface-card-2)', border:'1px solid var(--hairline)' }}><code dir="ltr" style={{ flex:1, minWidth:0, fontFamily:'var(--font-mono)', fontSize:12, color:'var(--ink-700)', whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis' }}>{shareUrl}</code><button type="button" disabled={busy||!photos.length} onClick={()=>copyPath(shareUrl,'scope')} style={{ border:0, padding:0, background:'transparent', fontFamily:'var(--font-ui)', fontSize:12.5, color:'var(--sky-deep)', cursor:busy?'wait':'pointer' }}>{copied==='scope'?'הועתק ✓':'העתקה 📋'}</button></div>
      {selectedPhotos.length>1&&example&&<div style={{ fontFamily:'var(--font-ui)', fontSize:11.5, color:'var(--ink-500)', marginTop:5 }}>תמונה בודדת: <button type="button" disabled={busy} onClick={()=>copyPath(photoUrl,'photo')} dir="ltr" style={{ border:0, padding:0, background:'transparent', fontFamily:'var(--font-mono)', fontSize:'inherit', color:'inherit', cursor:busy?'wait':'pointer' }}>{copied==='photo'?'הועתק ✓':photoUrl}</button> · פג תוקף אחרי המסע</div>}
    </div>
    {error&&<div role="alert" style={{ color:'var(--peach-deep)', fontFamily:'var(--font-ui)', fontSize:12.5, marginTop:8 }}>{error}</div>}
  </section></div></OverlayPortal>;
}

function AlbumScreen({ onSelectionBar }) {
  const albumRows = useStore(albumSettings.table);
  const photos = React.useMemo(()=>albumRows.slice().sort((a,b)=>(b.day_n==null?-1:Number(b.day_n))-(a.day_n==null?-1:Number(a.day_n))||String(a.taken_at).localeCompare(String(b.taken_at))),[albumRows]);
  const days = useStore('days').filter(day=>day.n!=null).sort((a,b)=>b.n-a.n);
  const [mode,setMode]=React.useState('days'), [selection,setSelection]=React.useState(false), [selected,setSelected]=React.useState(new Set()), [viewer,setViewer]=React.useState(null), [sharing,setSharing]=React.useState(false), [assigning,setAssigning]=React.useState(false), [confirming,setConfirming]=React.useState(false), [actionBusy,setActionBusy]=React.useState(false), [message,setMessage]=React.useState(null), [headerLightbox,setHeaderLightbox]=React.useState(null);
  const selectedPhotos = React.useMemo(()=>photos.filter(photo=>selected.has(photo.id)),[photos,selected]);
  const clearSelection = React.useCallback(()=>{setSelected(new Set());setSelection(false);setViewer(null);},[]);
  const toggle = React.useCallback((photo) => setSelected(current=>{const next=new Set(current); next.has(photo.id)?next.delete(photo.id):next.add(photo.id); if(!next.size)setSelection(false); else setSelection(true); return next;}),[]);
  const downloadSelected = React.useCallback(async()=>{setActionBusy(true);setMessage(null);try{await downloadFiles(await Promise.all(selectedPhotos.map(photoToFile)));setMessage({kind:'success',text:'התמונות הורדו למכשיר.'});}catch(cause){setMessage({kind:'error',text:String(cause.message||cause)});}finally{setActionBusy(false);}},[selectedPhotos]);
  const deleteSelected = React.useCallback(async()=>{setActionBusy(true);setMessage(null);try{await Promise.all(selectedPhotos.map(deleteAlbumPhoto));setConfirming(false);clearSelection();}catch(cause){setMessage({kind:'error',text:String(cause.message||cause)});setActionBusy(false);}},[selectedPhotos,clearSelection]);
  React.useEffect(()=>{
    if(!onSelectionBar)return;
    if(sharing||assigning||confirming){onSelectionBar({hidden:true});return()=>onSelectionBar(null);}
    if(!selection||!selectedPhotos.length){onSelectionBar(null);return;}
    onSelectionBar({count:selectedPhotos.length,busy:actionBusy,onCancel:clearSelection,onShare:()=>setSharing(true),onAssign:()=>setAssigning(true),onDownload:downloadSelected,onDelete:()=>setConfirming(true)});
    return()=>onSelectionBar(null);
  },[onSelectionBar,selection,selectedPhotos,actionBusy,clearSelection,downloadSelected,sharing,assigning,confirming]);
  const outsideGroups = Object.entries(photos.filter(photo=>photo.day_n==null).reduce((out,photo)=>{const key=photo.date_label||'—';(out[key]||(out[key]=[])).push(photo);return out;},{})).map(([date,rows])=>({ key:`outside-${date}`, day:{ outside:true, n:null, city:'', cityName:'מחוץ לימי הטיול', date }, photos:rows }));
  const groups = mode==='cities'
    ? Object.entries(photos.reduce((out,photo)=>{const key=photo.day_n==null?'outside':(photo.city_key||'outside');(out[key]||(out[key]=[])).push(photo);return out;},{})).map(([city,rows])=>({ key:city, day:{ outside:city==='outside', n:null, city, cityName:city==='outside'?'מחוץ לימי הטיול':rows[0].city_label, date:'' }, photos:rows }))
    : outsideGroups.concat(days.map(day=>({ key:day.n, day, photos:photos.filter(photo=>photo.day_n!=null&&Number(photo.day_n)===Number(day.n)) })).filter(group=>group.photos.length));
  return <div style={{ paddingBottom:28 }}><header style={{ position:'relative', padding:'4px 20px 12px', minHeight:76 }}><TapChibi src={`${ASSET_ALBUM}/album-hero.jpg`} alt="משה וליעוז עוברים יחד על תמונות מהטיול" onOpen={setHeaderLightbox} style={{ position:'absolute', top:4, insetInlineEnd:12, width:70, height:70, borderRadius:'50%' }} /><div style={{ fontFamily:'var(--font-ui)', fontSize:13, color:'var(--ink-500)' }}>עוד · כל המסע במקום אחד</div><h1 style={{ fontFamily:'var(--font-display)', fontSize:43, lineHeight:.9, color:'var(--ink-900)', margin:'2px 0' }}>אלבום הטיול</h1><div style={{ fontFamily:'var(--font-ui)', fontSize:14, color:'var(--ink-500)' }}>{photos.length} תמונות</div></header>
    <div style={{ padding:'0 20px 14px' }}><AlbumUploader days={days.slice().reverse()} accent="var(--city-osaka)" buttonLabel="הוספת תמונות לאלבום" /></div>
    <div style={{ margin:'0 20px 16px', display:'flex', gap:8 }}><button type="button" onClick={()=>setMode('days')} style={{ flex:1,padding:9,borderRadius:999,border:'1px solid var(--hairline)',background:mode==='days'?'var(--surface-card)':'transparent',fontFamily:'var(--font-ui)' }}>לפי ימים</button><button type="button" onClick={()=>setMode('cities')} style={{ flex:1,padding:9,borderRadius:999,border:'1px solid var(--hairline)',background:mode==='cities'?'var(--surface-card)':'transparent',fontFamily:'var(--font-ui)' }}>לפי ערים</button><button type="button" onClick={()=>selection?clearSelection():setSelection(true)} style={{padding:'9px 13px',borderRadius:999,border:'1px solid var(--hairline)',background:selection?'var(--sky-tint)':'transparent',fontFamily:'var(--font-ui)'}}>{selection?'בטל':'בחירה'}</button></div>
    {message&&<div role={message.kind==='error'?'alert':'status'} aria-live="polite" style={{margin:'0 20px 12px',padding:'10px 12px',borderRadius:'var(--r-md)',background:message.kind==='error'?'var(--peach-tint)':'var(--vegan-full-tint)',color:message.kind==='error'?'var(--peach-deep)':'var(--matcha-deep)',fontFamily:'var(--font-ui)',fontSize:12.5}}>{message.text}</div>}
    {!photos.length && <div style={{ margin:'20px', padding:'28px 18px', textAlign:'center', border:'2px dashed var(--hairline)', borderRadius:'var(--r-xl)', color:'var(--ink-500)', fontFamily:'var(--font-ui)' }}>📷 האלבום מחכה לתמונה הראשונה</div>}
    {groups.map(group=><section key={group.key}>{mode==='days'?<AlbumGroupHeader day={group.day} count={group.photos.length}/>:<h2 style={{fontFamily:'var(--font-display)',fontSize:29,margin:'8px 20px',color:group.day.outside?'var(--ink-700)':cityAlbum(group.day.city).deep}}>{group.day.cityName} · {group.photos.length}</h2>}<div style={{display:'grid',gridTemplateColumns:'repeat(3,1fr)',gap:6,padding:'0 20px 20px'}}>{group.photos.map(photo=><ResolvedPhoto key={photo.id} photo={photo} onOpen={(row)=>{setSelected(new Set([row.id]));setSelection(true);setViewer(row);}} onSelect={selection?toggle:null} onLongPress={!selection?(row)=>{setSelection(true);setSelected(new Set([row.id]));}:null} selected={selected.has(photo.id)} style={{aspectRatio:'1 / 1',borderRadius:'var(--r-sm)',boxShadow:'var(--shadow-sm)'}} />)}</div></section>)}
    {viewer&&<PhotoViewer photo={viewer} withSelectionBar onClose={()=>setViewer(null)}/>} {sharing&&<SharePanel photos={photos} selected={selected} onClose={()=>setSharing(false)}/>} {assigning&&<ReassignSheet photos={selectedPhotos} days={days.slice().reverse()} onClose={()=>setAssigning(false)}/>} {confirming&&<AlbumDeleteDialog count={selectedPhotos.length} busy={actionBusy} error={message?.kind==='error'?message.text:''} onConfirm={deleteSelected} onClose={()=>setConfirming(false)}/>} {headerLightbox&&<ChibiLightbox src={headerLightbox} alt="משה וליעוז עוברים יחד על תמונות מהטיול" variant="reference" filename="album-hero.jpg" onClose={()=>setHeaderLightbox(null)}/>}</div>;
}

function PublicAlbum() {
  const route = parsePublicAlbumRoute(window.location.pathname);
  const [state,setState]=React.useState({loading:true,photos:[],urls:{},error:''});
  React.useEffect(()=>{let alive=true,owned=[];(async()=>{try{
    const sb=window.JP2026Supabase; if(!sb||!route) throw new Error('הקישור אינו תקין');
    let query=sb.from(albumSettings.table).select('*');
    if(route.type==='day') query=query.eq('data->>day_n',String(route.dayN)).eq('data->>city_key',route.city);
    if(route.type==='photo') query=query.eq('id',route.id);
    const {data,error}=await query; if(error) throw error;
    const photos=(data||[]).map(row=>({...(row.data||{}),id:row.id,created_at:row.created_at}));
    const {data:bucket,error:bucketError}=await sb.rpc('album_bucket_name'); if(bucketError||!bucket) throw bucketError||new Error('האלבום אינו זמין');
    const urls={}; for(const photo of photos){const {data:signed}=await sb.storage.from(bucket).createSignedUrl(photo.storage_path,300);if(signed){urls[photo.id]=signed.signedUrl;owned.push(signed.signedUrl);}}
    if(alive)setState({loading:false,photos,urls,error:''});
  }catch(error){if(alive)setState({loading:false,photos:[],urls:{},error:String(error.message||error)});}})();return()=>{alive=false;};},[window.location.pathname]);
  if(state.loading)return <div style={{padding:40,textAlign:'center',fontFamily:'var(--font-ui)'}}>טוענים את האלבום…</div>;
  return <div className="phone" style={{height:'100svh'}}><header className="appbar jp-day-title">אלבום הטיול</header><main className="scroll"><div style={{padding:'24px 20px'}}><h1 style={{fontFamily:'var(--font-display)',fontSize:44,margin:0,color:'var(--ink-900)'}}>רגעים מהמסע</h1><div style={{fontFamily:'var(--font-ui)',fontSize:13,color:'var(--ink-500)',marginBottom:18}}>קישור צפייה בלבד · פג אוטומטית</div>{(state.error||!state.photos.length)&&<div style={{padding:24,textAlign:'center',border:'1px solid var(--hairline)',borderRadius:'var(--r-lg)',fontFamily:'var(--font-ui)',color:'var(--ink-500)'}}>הקישור פג או שאין בו תמונות</div>}<div style={{display:'grid',gridTemplateColumns:'repeat(3,1fr)',gap:7}}>{state.photos.map(photo=><div key={photo.id} style={{aspectRatio:'1 / 1',borderRadius:'var(--r-sm)',overflow:'hidden',background:'var(--paper-cream-deep)'}}>{state.urls[photo.id]&&<img src={state.urls[photo.id]} alt="תמונה מהטיול" style={{width:'100%',height:'100%',objectFit:'cover'}}/>}</div>)}</div></div></main></div>;
}

Object.assign(window, {
  JP2026Album:{ AlbumUploader, UploadProgress, AlbumStrip, TodayAlbumCard, AlbumScreen, AlbumSelectionBar, PublicAlbum,
    parseExifCaptureTime, readCaptureTime, shiftedTripDateKey, deriveAlbumDay, albumDateLabel, dayThreeHasPassed,
    computeAlbumShareExpiry, parsePublicAlbumRoute, decodeAndResizeImage, sharePhotoFiles },
  JP2026AlbumScreen:AlbumScreen, JP2026AlbumSelectionBar:AlbumSelectionBar, JP2026PublicAlbum:PublicAlbum,
});
})();
