const RegistryApp = (() => { const files = ['visual_states_registry','fiqh_time_registry','worship_fiqh_registry','islamic_events_registry','religious_content_registry','contextual_knowledge_registry','on_this_day_registry','user_time_markers_registry','announcements_guidance_registry','content_channels_registry','astronomy_events_registry','external_sources_registry','sky_simulation_registry']; const R = {}; let ui, visual, layout, lastFocus, currentKind, view, initialScroll = false; const $ = id => document.getElementById(id); const registry = name => R[name + '_registry'] || {rules:[],boundaries:[],madhhabs:{},legacy_rule_map:[],items:[],events:[],channels:[],providers:[],event_types:[],marker_types:{}}; const array = value => Array.isArray(value) ? value : value ? [value] : []; const make = (tag,text) => {const e=document.createElement(tag);if(text!=null)e.textContent=text;return e;}; const title = x => x.display?.title || x.content?.title || x.title_ar || x.title || x.label_ar || x.label || x.name || ''; const screen = kind => ui.screensConfig.find(x=>x.id===kind); const noticeRoutes=()=>visual.runtime_styles.notice_screen_routes||{}; const safeScreen=kind=>screen(kind)||screen(noticeRoutes().default)||ui.screensConfig[0]; const reminderScreen=x=>noticeRoutes().reminder_types?.[x?.type]||noticeRoutes().default||'worship'; const eventScreen=x=>x?.screen_id||noticeRoutes().content_actions?.[x?.action]||noticeRoutes().default||'worship'; const rule = id => registry('worship_fiqh').rules.find(x=>x.id===id); const boundary = id => registry('fiqh_time').boundaries.find(x=>x.id===id); function icon(id){return visual.marker_icons?.[id]?.emoji || visual.worship_icons[id]?.emoji || visual.prayer_icons[id]?.emoji || visual.solar_icons[id]?.emoji || '';} function ruleTitle(x){return ui.ruleTitles[x.id] || visual.prayer_icons[x.id]?.label || visual.worship_icons[x.id]?.label || title(x) || ui.unknownTerm;} function term(value){ if(typeof value!=='string')return String(value); if(ui.terms[value])return ui.terms[value]; if(boundary(value))return boundary(value).label; // Translate identifier tokens even inside Arabic variant descriptions. return value.replace(/[a-z][a-z0-9]*(?:_[a-z0-9]+)+/g,key=>ui.terms[key]||boundary(key)?.label||ui.unknownTerm); } function textLines(data){ if(!data)return ''; if(typeof data==='string')return term(data); return Object.entries(data).filter(([k])=>ui.fields[k]).map(([k,v])=>ui.fields[k]+': '+term(v)).join('\n'); } function button(label,action){const b=make('button',label);b.type='button';b.addEventListener('click',action);return b;} function safeURL(raw){try{const u=new URL(raw,location.href);return ['http:','https:'].includes(u.protocol)?u.href:null;}catch{return null;}} function link(parent,label,url){const href=safeURL(url);if(!href)return;const a=make('a',label);a.href=href;a.target='_blank';a.rel='noopener noreferrer';parent.appendChild(a);} function para(text){if(text)$('adaptivePopupBody').appendChild(make('p',text));} function show(kind,data,render){ window.dispatchEvent(new Event('muslim-clock-popup-leave')); const overlay=$('adaptivePopup');if(overlay.hidden)lastFocus=document.activeElement; const target=safeScreen(kind);kind=target.id;currentKind=kind; view=()=>show(kind,data,render); $('adaptivePopupKind').textContent=target.title; $('adaptivePopupKind').hidden=data.compact===true; $('adaptivePopupTitle').textContent=data.title||''; $('adaptivePopupMedia').textContent=data.media||icon(target.icon); $('adaptivePopupMedia').hidden=data.compact===true; $('adaptivePopupBody').replaceChildren();$('adaptivePopupActions').replaceChildren(); para(data.text);if(render)render(); const n=$('adaptivePopupBody').textContent.length; $('adaptivePopupCard').dataset.size=n<90?'short':n<420?'medium':'long'; overlay.hidden=false;document.body.style.overflow='hidden';$('adaptivePopupClose').focus(); $('adaptivePopupCard').scrollTop=0;$('adaptivePopupBody').scrollTop=0; } function close(){ window.dispatchEvent(new Event('muslim-clock-popup-leave')); $('adaptivePopup').hidden=true;document.body.style.overflow='';lastFocus?.focus(); } function back(fn){$('adaptivePopupActions').appendChild(button(ui.back,fn));} function home(kind){show(kind,{title:screen(kind).title,text:ui.catalogue},()=>{ screen(kind).groups.forEach(group=>{ $('adaptivePopupBody').appendChild(button(ui.groupTitles[group],()=>list(kind,group))); // Radio corner sits right after the Quran corner, opened the same way. if(group==='quran'&&window.MuslimClockRadio)window.MuslimClockRadio.mountEntry($('adaptivePopupBody'),()=>home(kind)); }); });} function records(group){ switch(group){ case 'worship': return registry('worship_fiqh').rules; case 'events': return registry('islamic_events').events.filter(x=>x.enabled!==false); case 'time': return registry('fiqh_time').boundaries; case 'markers': return Object.entries(registry('user_time_markers').marker_types).map(([id,x])=>({id,...x})); case 'announcements': return registry('announcements_guidance').enabled===false?[]:registry('announcements_guidance').items.filter(x=>x.enabled!==false); case 'channels': return registry('content_channels').channels; case 'knowledge': return registry('contextual_knowledge').items; case 'history': return registry('on_this_day').events; case 'astronomy': return registry('astronomy_events').event_types.filter(x=>x.enabled!==false); case 'quran': return registry('external_sources').providers.filter(x=>array(x.capabilities).some(c=>/^(quran_text|ayah_audio|recitations|audio_editions)$/.test(c))); case 'providers': return registry('external_sources').providers; default:return []; } } function list(kind,group){ if(group==='quran'&&window.MuslimClockQuran){window.MuslimClockQuran.open({back:()=>home(kind)});return;} if(group==='markers'){remindersHome();return;} show(kind,{title:ui.groupTitles[group],text:group==='history'?ui.historyNote:group==='markers'?ui.markerNote:''},()=>{ const rows=records(group);if(!R[ui.groupRegistry[group]+'_registry'])para(ui.unavailable);else if(!rows.length)para(ui.empty); rows.forEach(x=>$('adaptivePopupBody').appendChild(button(group==='worship'?ruleTitle(x):title(x),()=>detail(kind,group,x)))); back(()=>home(kind)); }); } function ruleText(x){ const parts=[textLines(x.common),x.note,x.critical,textLines(x.trigger)]; Object.entries(x.madhhab||{}).forEach(([k,v])=>parts.push(registry('worship_fiqh').madhhabs[k]+':\n'+textLines(v))); array(x.windows).forEach(w=>parts.push(textLines(w))); return parts.filter(Boolean).map(term).join('\n\n'); } function related(kind,x){ const authority=x.authority_link||{}; const ruleIds=[...array(authority.rule_id),...array(authority.rule_ids)]; const timeIds=[...array(authority.boundary_id),...array(authority.boundary_ids),...array(authority.time_boundaries)]; registry('religious_content').legacy_rule_map.filter(m=>m.legacy_id===x.id).forEach(m=>{ ruleIds.push(...array(m.target_rule_id),...array(m.target_rule_ids),...array(m.worship_rule_id)); timeIds.push(...array(m.target_boundary_id),...array(m.time_boundaries)); }); if(rule(x.id)){ registry('religious_content').legacy_rule_map.filter(m=>[...array(m.target_rule_id),...array(m.target_rule_ids),...array(m.worship_rule_id)].includes(x.id)).forEach(m=>timeIds.push(...array(m.time_boundaries),...array(m.target_boundary_id))); } const returnView=view; [...new Set(ruleIds)].map(rule).filter(Boolean).forEach(r=>$('adaptivePopupBody').appendChild(button(ui.related+' — '+ruleTitle(r),()=>detail(kind,'worship',r,returnView)))); [...new Set(timeIds)].map(boundary).filter(Boolean).forEach(b=>$('adaptivePopupBody').appendChild(button(ui.related+' — '+title(b),()=>detail(kind,'time',b,returnView)))); } function detail(kind,group,x,returnView){ if(group==='events'&&isAdhkar(x)){adhkarSettings(x.id);return;} let text=x.display?.text||x.content?.text||x.text||x.summary_ar||x.note||''; if(group==='time'){boundaryInfo(x,null,returnView||(()=>list(kind,group)));return;} if(group==='worship')text=ruleText(x); if(group==='quran'||group==='providers')text=ui.providerNote; if(group==='channels')text=ui.channelNote; if(group==='astronomy')text=ui.astronomyNote+(x.religious_relation?.note?'\n\n'+x.religious_relation.note:''); if(group==='markers'){reminderChoose();return;} if(x.date_note_ar)text+='\n\n'+x.date_note_ar; show(kind,{title:group==='worship'?ruleTitle(x):title(x),text,media:icon(x.icon_id||x.id)||icon(visual.event_action_mapping[x.action])},()=>{ // References are links, never executable HTML from registries. if(group==='worship')array(x.sources).forEach(id=>{const s=registry('worship_fiqh').sources[id];if(s?.url)link($('adaptivePopupBody'),s.title,s.url);}); if(group==='quran'||group==='providers')link($('adaptivePopupBody'),ui.source+' — '+x.name,x.documentation_url||x.base_url); if(x.source?.url)link($('adaptivePopupBody'),x.source.name||ui.source,x.source.url); if(group==='events'){ if(x.calendar_rule?.requires_month_confirmation){para(ui.monthConfirmation.required);$('adaptivePopupActions').appendChild(button(ui.calendarSettings,calendarSettings));} if(x.user_schedule)$('adaptivePopupActions').appendChild(button(ui.seasonSchedule.title,()=>seasonSettings(x))); } array(x.references).forEach(r=>{if(typeof r==='string')para(r);else if(r.url)link($('adaptivePopupBody'),r.title||ui.source,r.url);}); related(kind,x);back(returnView||(()=>list(kind,group))); }); } let calculationMethod=null, prayerEngineReady=false; let observer=null, madhhab, solarDays=[], solarPeriods=[], timelineStart=0, timelineViewDate=null, lastSolarDay='', frameTimer; const getSaved=(key,fallback)=>{try{return JSON.parse(localStorage.getItem(key))??fallback;}catch{return fallback;}}; const save=(key,value)=>{try{localStorage.setItem(key,JSON.stringify(value));}catch{}}; function validLocation(x){return x&&Number.isFinite(x.lat)&&Number.isFinite(x.lng)&&Math.abs(x.lat)<=90&&Math.abs(x.lng)<=180;} function profile(){return registry('fiqh_time').calculation_profile;} function timeText(ms){return new Date(ms).toLocaleTimeString(document.documentElement.lang,{hour:'numeric',minute:'2-digit',hour12:true});} function dateKey(ms){const d=new Date(ms);return d.getFullYear()+'-'+d.getMonth()+'-'+d.getDate();} function calculationConfig(){return registry('fiqh_time').prayer_calculation;} function methodChoice(){const c=calculationConfig();return c?.methods.find(x=>x.id===calculationMethod);} function prayerParameters(lib,choice){ let parameters; if(choice.engine_mode==='custom_angles'){ parameters=lib.CalculationMethod.Other(); parameters.method=choice.id; parameters.fajrAngle=Number(choice.fajr_angle); parameters.ishaAngle=Number(choice.isha_angle); parameters.adjustments={...parameters.adjustments,...(choice.adjustments||{})}; }else{ const factory=lib.CalculationMethod[choice.id]; if(typeof factory!=='function')throw Error('unsupported prayer method'); parameters=factory(); } return parameters; } async function loadPrayerEngine(){ const c=calculationConfig();if(!c)return;calculationMethod=getSaved(c.storage_key,c.default_method); if(calculationMethod!==c.legacy_option.id&&!c.methods.some(x=>x.id===calculationMethod))calculationMethod=c.default_method; if(window.adhan){prayerEngineReady=true;return;} await new Promise(resolve=>{const script=document.createElement('script');let finished=false; const done=()=>{if(finished)return;finished=true;clearTimeout(timer);prayerEngineReady=Boolean(window.adhan);resolve();}; const timer=setTimeout(done,c.engine.timeout_ms);script.src=c.engine.url;script.onload=done;script.onerror=done;document.head.appendChild(script); }); } function applyPrayerMethod(days){ const choice=methodChoice();if(!choice)return; const c=calculationConfig(),lib=window.adhan; for(const d of days){ if(!prayerEngineReady){d.methodUnavailable=true;continue;} try{ const parameters=prayerParameters(lib,choice); const asrRule=rule('asr'),id=asrRule.madhhab?.[madhhab]?.start||asrRule.common?.start; const factor=profile().boundaries[id]?.factor,key=c.shadow_factor_to_madhab[String(factor)]; if(!key||!lib.Madhab?.[key])throw Error('unsupported madhhab binding'); parameters.madhab=lib.Madhab[key]; const schoolBinding=c.madhhab_engine_bindings?.[madhhab]; if(choice.id==='MoonsightingCommittee'&&schoolBinding?.moonsighting_shafaq&&lib.Shafaq?.[schoolBinding.moonsighting_shafaq])parameters.shafaq=lib.Shafaq[schoolBinding.moonsighting_shafaq]; const confirmation=getSaved(c.ramadan_interval.storage_key,null); if(choice.id===c.ramadan_interval.method&&confirmation?.enabled&&confirmation.day===dateKey(new Date(new Date(d.day).getUTCFullYear(),new Date(d.day).getUTCMonth(),new Date(d.day).getUTCDate()).getTime()))parameters.ishaInterval=c.ramadan_interval.minutes; const utc=new Date(d.day),date=new Date(utc.getUTCFullYear(),utc.getUTCMonth(),utc.getUTCDate()); const coordinates=new lib.Coordinates(observer.lat,observer.lng),times=new lib.PrayerTimes(coordinates,date,parameters); const next=new Date(date);next.setDate(next.getDate()+1);const tomorrow=new lib.PrayerTimes(coordinates,next,parameters); const number=value=>Number.isFinite(value?.getTime())?value.getTime():null; for(const [id,key] of Object.entries(c.output_bindings))d.boundaries[id]=number(times[key]); d.boundaries.true_dawn=d.boundaries.fajr;d.boundaries.next_true_dawn=number(tomorrow.fajr); for(const [id,def] of Object.entries(profile().boundaries))if(def.op==='fraction'){ const a=d.boundaries[def.from],b=d.boundaries[def.to];d.boundaries[id]=a!=null&&b!=null?a+def.fraction*(b-a):null; } d.calculationMethod=choice.id; }catch{d.methodUnavailable=true;} } } function recalculate(){ solarDays=[];solarPeriods=[]; if(observer&&profile()&®istry('worship_fiqh').rules.length){ for(const day of timelineCalculationDays())solarDays.push(SolarEngine.day(day,observer,profile(),registry('worship_fiqh'),madhhab)); applyPrayerMethod(solarDays); solarPeriods=SolarEngine.periods(solarDays,profile(),registry('worship_fiqh'),madhhab,visual.runtime_styles,methodChoice()?calculationConfig().period_bindings:null); } lastSolarDay=dateKey(Date.now());buildNotices();buildRuler();tickSolar(); } function timelineCalculationDays(){ const days=new Set(); for(const anchor of [Date.now(),timelineViewDate??Date.now()]){ const d=new Date(anchor),base=Date.UTC(d.getFullYear(),d.getMonth(),d.getDate()); for(let i=-2;i<=(window.MuslimNativeBridge?.active?8:3);i++)days.add(base+i*86400000); } return [...days].sort((a,b)=>a-b); } function configureLocation(){ show('worship',{title:ui.location,text:ui.deviceTime},()=>{ const body=$('adaptivePopupBody'); const p=profile();if(!p){para(ui.missingSolar);return;} const config=calculationConfig();if(config){ const method=make('select');[config.legacy_option,...config.methods].forEach(x=>{const opt=make('option',x.label);opt.value=x.id;method.appendChild(opt);});method.value=calculationMethod; const field=make('label',ui.calculationMethod);field.appendChild(method);body.appendChild(field); const methodDetails=make('div');body.appendChild(methodDetails); const describeMethod=()=>{methodDetails.replaceChildren();const choice=methodChoice();if(choice?.note_ar)methodDetails.appendChild(make('p',choice.note_ar));if(choice)methodDetails.appendChild(make('p',ui.madhhabCalculationActive+' — '+registry('worship_fiqh').madhhabs[madhhab]));if(choice?.solar_sunset_is_independent)methodDetails.appendChild(make('p',ui.sunsetSeparation));if(choice?.daily_reference_url)link(methodDetails,ui.officialDailyTimes||ui.source,choice.daily_reference_url);if(choice?.method_reference_url)link(methodDetails,ui.calculationReference||ui.source,choice.method_reference_url);}; method.addEventListener('change',()=>{calculationMethod=method.value;save(config.storage_key,calculationMethod);describeMethod();recalculate();});describeMethod(); const ramadan=make('input');ramadan.type='checkbox';const confirmation=getSaved(config.ramadan_interval.storage_key,null);ramadan.checked=confirmation?.enabled&&confirmation.day===dateKey(Date.now());const row=make('label',ui.ramadanInterval);row.appendChild(ramadan);body.appendChild(row); ramadan.addEventListener('change',()=>{save(config.ramadan_interval.storage_key,{enabled:ramadan.checked,day:dateKey(Date.now())});recalculate();}); para(ui.calculationMethodNote);if(!prayerEngineReady)para(ui.prayerEngineUnavailable); } body.appendChild(button(ui.gps,()=>{ if(!navigator.geolocation){para(ui.locationFailed);return;} const status=make('p',ui.locating);body.appendChild(status); navigator.geolocation.getCurrentPosition(pos=>{ observer={lat:pos.coords.latitude,lng:pos.coords.longitude,name:ui.gps};save('clock_observer_v1',observer);window.dispatchEvent(new CustomEvent('muslim-clock-location-change',{detail:{...observer}}));recalculate();close(); },()=>status.textContent=ui.locationFailed,{timeout:15000,maximumAge:300000}); })); p.locations.forEach(loc=>body.appendChild(button(loc.name,()=>{observer={...loc};save('clock_observer_v1',observer);window.dispatchEvent(new CustomEvent('muslim-clock-location-change',{detail:{...observer}}));recalculate();close();}))); const lat=make('input'),lng=make('input'); [[lat,ui.latitude,-90,90,observer?.lat],[lng,ui.longitude,-180,180,observer?.lng]].forEach(([e,label,min,max,val])=>{ e.type='number';e.step='any';e.min=min;e.max=max;e.value=val??'';const field=make('label',label);field.appendChild(e);body.appendChild(field); }); const select=make('select');Object.entries(registry('worship_fiqh').madhhabs||{}).forEach(([id,label])=>{const opt=make('option',label);opt.value=id;select.appendChild(opt);});select.value=madhhab; const field=make('label',ui.madhhab);field.appendChild(select);body.appendChild(field); select.addEventListener('change',()=>{madhhab=select.value;save('clock_madhhab_v1',madhhab);recalculate();}); body.appendChild(button(ui.apply,()=>{ if(lat.value===''||lng.value===''){para(ui.invalidLocation);return;} const loc={lat:Number(lat.value),lng:Number(lng.value)};if(!validLocation(loc)){para(ui.invalidLocation);return;} observer=loc;madhhab=select.value;save('clock_observer_v1',observer);window.dispatchEvent(new CustomEvent('muslim-clock-location-change',{detail:{...observer}}));save('clock_madhhab_v1',madhhab);recalculate();close(); })); para(p.note_ar); }); } function inHour(start){return solarPeriods.filter(p=>p.startstart||(p.point&&p.start>=start)));} function periodIcon(p){const key=visual.runtime_styles.rule_icons[p.id]||p.id;return icon(key)||visual.timeline_states[key]?.emoji||visual.timeline_states[p.state]?.emoji||'';} function boundaryKnowledge(id){ return registry('contextual_knowledge').items.filter(x=>{const a=x.authority_link||{};return [...array(a.boundary_id),...array(a.boundary_ids),...array(a.time_boundaries)].includes(id);}); } function boundaryKnowledgeScreen(def,returnView){ show('content',{title:title(def),text:ui.knowledgeTopics},()=>{ boundaryKnowledge(def.id).forEach(x=>$('adaptivePopupBody').appendChild(button(title(x),()=>detail('content','knowledge',x,()=>boundaryKnowledgeScreen(def,returnView))))); back(returnView); }); } function boundaryInfo(def,t,returnView){ const goBack=returnView||(()=>home('worship')); const summary=registry('contextual_knowledge').boundary_summaries?.[def.id]; const entryRule=!methodChoice()&&(visual.runtime_styles.boundary_entry_rule_ids||[]).map(rule).find(r=>(r?.madhhab?.[madhhab]?.start||r?.common?.start)===def.id); show('worship',{title:title(def),text:t==null?'':timeText(t),media:entryRule?icon(visual.runtime_styles.rule_icons[entryRule.id]||entryRule.id):icon(def.id)},()=>{ para(summary?.text||def.relation||def.note||ui.empty); if(summary?.estimate)para(summary.estimate); if(summary?.source_url)link($('adaptivePopupBody'),ui.source,summary.source_url); for(const id of visual.runtime_styles.boundary_entry_rule_ids||[]){const r=rule(id),choice=r?.madhhab?.[madhhab]; if(!methodChoice()&&(choice?.start||r?.common?.start)===def.id)para(icon(visual.runtime_styles.rule_icons[r.id]||r.id)+' '+ruleTitle(r)+' — '+registry('worship_fiqh').madhhabs[madhhab]); } if(boundaryKnowledge(def.id).length)$('adaptivePopupActions').appendChild(button(ui.moreKnowledge,()=>boundaryKnowledgeScreen(def,()=>boundaryInfo(def,t,goBack)))); back(goBack); }); } function untimedMarkers(){return array(registry('contextual_knowledge').untimed_boundary_markers);} function markerButton(m,returnView){const def=boundary(m.boundary_id);return def?button(m.caption,()=>boundaryInfo(def,null,returnView)):null;} function periodTitle(p){return (p.displayEstimate?.label_ar||ruleTitle(p.rule))+(p.preference?' — '+ui.preferredPeriod:'');} function periodTime(p){return p.point?ui.calculatedBoundary+' — '+timeText(p.start)+'\n'+ui.pointNote:ui.from+' '+timeText(p.start)+' '+ui.to+' '+timeText(p.end)+(p.displayEndEstimate?' — '+p.displayEndEstimate.end_label_ar:'')+(p.displayEstimate?'\n'+ui.estimatedWindow:'');} function applyStatePaint(el,stateId){ const s=visual.timeline_states[stateId];el.style.backgroundColor=s.color;el.style.color=s.text_color; if(s.pattern==='diagonal')el.style.backgroundImage='repeating-linear-gradient(135deg,transparent 0 6px,currentColor 6px 7px,transparent 7px 12px)'; } function hourSettings(start){ show('worship',{title:hourAccessibleLabel(new Date(start).getHours()),compact:true},()=>{ window.MuslimClockQuran?.mountEntry($('adaptivePopupBody'),()=>hourSettings(start)); window.MuslimClockRadio?.mountEntry($('adaptivePopupBody'),()=>hourSettings(start)); $('adaptivePopupBody').appendChild(button(ui.adhkar.title,()=>adhkarHome(start))); $('adaptivePopupBody').appendChild(button(ui.worshipSchedules,()=>list('worship','events'))); $('adaptivePopupActions').appendChild(button(ru().add,()=>reminderChoose(start))); $('adaptivePopupBody').appendChild(button(ru().settings,reminderSettings)); $('adaptivePopupBody').appendChild(button(ru().tools,remindersHome)); $('adaptivePopupBody').appendChild(button(ui.location,configureLocation));$('adaptivePopupBody').appendChild(button(ui.calendarSettings,calendarSettings)); const qiblaUI=registry('sky_simulation').qibla_accessibility?.ui; if(qiblaUI?.settings)$('adaptivePopupBody').appendChild(button(qiblaUI.settings,()=>qiblaSettings(start))); }); } function qiblaSettings(start){ const config=registry('sky_simulation').qibla_accessibility; if(!config?.ui)return; show('worship',{title:config.ui.settings,compact:true},()=>{ const host=make('div');host.id='celestialSettingsHost';$('adaptivePopupBody').appendChild(host); if(window.MuslimClockQibla)window.MuslimClockQibla.mountSettings(host); else para(config.ui.loading); back(()=>hourSettings(start)); }); } function periodDetails(p){ show('worship',{title:periodTitle(p),text:periodTime(p),media:periodIcon(p)},()=>{ para(visual.timeline_states[p.state].label+' — '+registry('worship_fiqh').madhhabs[madhhab]); if(p.conditional)para(p.activation==='after_prayer'?ui.conditionalWindow:ui.boundaryRestrictionWindow); para((boundary(p.startId)?.label||ui.terms[p.startId]||p.startId)+' ← '+(p.displayEndEstimate?.end_label_ar||boundary(p.endId)?.label||ui.terms[p.endId]||p.endId));if(p.calculationMethod)para(ui.methodTime+' — '+methodChoice()?.label); if(p.displayEstimate?.note_ar)para(p.displayEstimate.note_ar); if(p.rule.category===visual.runtime_styles.restriction_category)para(ui.solarRestrictionEstimate); $('adaptivePopupActions').appendChild(button(ui.fullRuling,()=>detail('content','worship',p.rule,()=>periodDetails(p)))); }); } function hourDetails(start){ const rows=inHour(start),end=start+3600000; show('worship',{title:ui.hour+' — '+timeText(start),text:new Date(start).toLocaleDateString(document.documentElement.lang)},()=>{ $('adaptivePopupActions').appendChild(button(ru().add,()=>reminderChoose(start))); if(!observer){para(ui.chooseLocation);$('adaptivePopupActions').appendChild(button(ui.location,configureLocation));return;} if(!profile()){para(ui.missingSolar);return;} para(ui.deviceTime+' · '+registry('worship_fiqh').madhhabs[madhhab]); const unique=new Map();rows.forEach(p=>unique.set(p.id+'-'+p.start+'-'+(p.preference||''),p)); if(!rows.length)para(ui.noPeriod); for(const p of unique.values()){ const state=visual.timeline_states[p.state]; const card=button(periodIcon(p)+' '+periodTitle(p)+'\n'+state.label+'\n'+periodTime(p),()=>{ show('worship',{title:periodTitle(p),text:periodTime(p),media:periodIcon(p)},()=>{ para(visual.timeline_states[p.state].label+' — '+registry('worship_fiqh').madhhabs[madhhab]); if(p.conditional)para(p.activation==='after_prayer'?ui.conditionalWindow:ui.boundaryRestrictionWindow); $('adaptivePopupActions').appendChild(button(ui.fullRuling,()=>detail('content','worship',p.rule,()=>hourDetails(start)))); back(()=>hourDetails(start)); }); });card.style.borderInlineStart='8px solid '+state.color;const swatch=make('span');swatch.className='state-swatch';swatch.setAttribute('aria-hidden','true');applyStatePaint(swatch,p.state);card.prepend(swatch);$('adaptivePopupBody').appendChild(card); } const points=[];for(const d of solarDays)for(const def of registry('fiqh_time').boundaries){const t=d.boundaries[def.id];if(t!=null&&t>=start&&ta.t-b.t).forEach(({def,t})=>{ untimedMarkers().filter(m=>m.before_boundary_id===def.id).forEach(m=>{const b=markerButton(m,()=>hourDetails(start));if(b)$('adaptivePopupBody').appendChild(b);}); const b=button(icon(def.id)+' '+def.label+' — '+timeText(t),()=>boundaryInfo(def,t,()=>hourDetails(start)));b.dataset.boundaryId=def.id;$('adaptivePopupBody').appendChild(b); }); matchingEvents(start,end).forEach(x=>$('adaptivePopupBody').appendChild(button(title(x),()=>detail('worship','events',x,()=>hourDetails(start))))); para(ui.conditionalNote);para(ui.estimated); }); } function hijriParts(ms){ const id=(visual.runtime_styles.current_time_calendars||[]).find(c=>c.adjustable)?.id||'islamic'; return HijriCalendarEngine.parts(ms,{calendarId:id,offsetDays:getSaved('clock_calendar_adjustment_v1',0),maxOffsetDays:visual.runtime_styles.calendar_adjustment_limit,confirmations:getSaved(registry('islamic_events').calendar_confirmation?.storage_key,[])}); } function eventCalendarMatches(event,ms,forBlocking=false){ return HijriCalendarEngine.matches(event,ms,{parts:hijriParts,forBlocking}); } function eventBlockedOn(event,ms){return (event.blocks_if||[]).some(id=>{const blocker=registry('islamic_events').events.find(e=>e.id===id&&e.enabled!==false);return blocker?eventCalendarMatches(blocker,ms,true):false;});} function eventEnabled(event){ if(event.enabled===false)return false;const activation=event.activation; if(isAdhkar(event))return adhkarPreference(event).enabled; if(activation?.mode==='user_configurable')return Boolean(getSaved(activation.storage_key,activation.default_enabled===true)); return true; } const adhkarConfig=()=>registry('islamic_events').reminder_preferences; const isAdhkar=event=>Boolean(adhkarConfig()&&event.action===adhkarConfig().event_action); const adhkarReady=()=>adhkarConfig()?.ui_contract_version===2; function seasonPreference(event){return getSaved(registry('islamic_events').scheduling_policy.season_preferences_key,{})[event.id]||{enabled:event.user_schedule?.default_enabled===true,weekdays:[],time:''};} function seasonSettings(event){ const labels=ui.seasonSchedule,p=seasonPreference(event); show('worship',{title:title(event),text:labels.note},()=>{ const enabled=reminderField(labels.enable,'checkbox','');enabled.checked=p.enabled; const weekdays=labels.weekdays.map((label,index)=>{const field=reminderField(label,'checkbox','');field.checked=p.weekdays.includes(index);return field;}); const time=reminderField(labels.time,'time',p.time); $('adaptivePopupActions').appendChild(button(ru().save,()=>{ const selected=weekdays.flatMap((field,index)=>field.checked?[index]:[]); if(enabled.checked&&(!selected.length||!/^([01]\d|2[0-3]):[0-5]\d$/.test(time.value))){para(labels.invalid);return;} const key=registry('islamic_events').scheduling_policy.season_preferences_key,state=getSaved(key,{}); state[event.id]={enabled:enabled.checked,weekdays:selected,time:time.value}; try{localStorage.setItem(key,JSON.stringify(state));}catch{para(ru().storageError);return;} buildNotices();buildRuler();detail(eventScreen(event),'events',event); }));back(()=>detail(eventScreen(event),'events',event)); }); } function adhkarPreference(event){ const c=adhkarConfig(),saved=getSaved(c.storage_key,{}),value=saved[event.id]; return {enabled:Boolean(getSaved(event.activation.storage_key,event.activation.default_enabled)),mode:c.default_mode,trigger:event.triggers[0].when,offset:0,time:'',sound:c.default_sound,...(value&&typeof value==='object'?value:{})}; } function adhkarHome(selectedHour){show('worship',{title:ui.adhkar.title,text:ui.adhkar.note},()=>{ if(!adhkarReady()){para(ui.adhkar.unavailable);return;} for(const event of registry('islamic_events').events.filter(isAdhkar)){ const p=adhkarPreference(event); $('adaptivePopupBody').appendChild(button(title(event)+' — '+(p.enabled?ui.adhkar.enabled:ui.adhkar.disabled),()=>adhkarSettings(event.id,selectedHour))); } $('adaptivePopupActions').appendChild(button(ui.adhkar.custom,()=>reminderForm('adhkar',selectedHour))); back(()=>remindersHome(selectedHour)); });} function adhkarSettings(id,selectedHour){ const event=registry('islamic_events').events.find(e=>e.id===id&&isAdhkar(e));if(!event)return; const c=adhkarConfig(),p=adhkarPreference(event),a=ui.adhkar; show('worship',{title:title(event),text:a.note},()=>{ const enabled=reminderField(a.enable,'checkbox','');enabled.checked=p.enabled; const mode=reminderSelect(a.mode,c.modes.map(id=>[id,a.modes[id]]),p.mode); const options=c.prayer_triggers.map(id=>[id,boundary(profile()?.notice_runtime?.trigger_bindings[id]?.boundary)?.label||id]); const trigger=reminderSelect(a.prayer,options,p.trigger),offset=reminderField(a.offset,'number',p.offset); offset.min=0;offset.max=c.offset_limit_minutes; const clock=reminderField(a.clock,'time',p.time),sound=reminderField(a.sound,'checkbox','');sound.checked=p.sound; const sync=()=>{const custom=mode.value==='clock';trigger.parentElement.hidden=custom;offset.parentElement.hidden=custom;clock.parentElement.hidden=!custom;};mode.addEventListener('change',sync);sync(); para(a.locationNote);para(ru().closedBrowserNote); $('adaptivePopupActions').appendChild(button(ru().save,()=>{ const n=Number(offset.value),validClock=/^([01]\d|2[0-3]):[0-5]\d$/.test(clock.value); if(enabled.checked&&((mode.value==='clock'&&!validClock)||(mode.value==='prayer_relative'&&(!c.prayer_triggers.includes(trigger.value)||!Number.isFinite(n)||n<0||n>c.offset_limit_minutes)))){para(a.invalid);return;} const state=getSaved(c.storage_key,{});state[id]={enabled:enabled.checked,mode:mode.value,trigger:trigger.value,offset:n,time:clock.value,sound:sound.checked}; try{localStorage.setItem(c.storage_key,JSON.stringify(state));}catch{para(a.storageError);return;} if(sound.checked)enableReminderSound();buildNotices();buildRuler();adhkarHome(selectedHour); }));back(()=>adhkarHome(selectedHour)); }); } function matchingEvents(start,end){ return registry('islamic_events').events.filter(e=>{ if(!eventEnabled(e))return false;const c=e.calendar_rule||{},t=e.time_rule||{}; if(e.display?.timeline===false||t.type==='conditional_prayer_relative'||t.type==='trigger_only'||!eventCalendarMatches(e,start)||eventBlockedOn(e,start))return false; if(['full_day_worship','full_day','season_window','month_window'].includes(t.type))return true; if(t.rule_id)return solarPeriods.some(p=>p.id===t.rule_id&&p.startstart); if(t.start&&t.end){return solarDays.some(d=>d.boundaries[t.start]!=null&&d.boundaries[t.end]!=null&&d.boundaries[t.start]start);} if(Array.isArray(t.windows))return t.windows.some(w=>solarDays.some(d=>d.boundaries[w.start]!=null&&d.boundaries[w.end]!=null&&d.boundaries[w.start]start)); return false; }); } function prayerEntries(){ const policy=visual.runtime_styles.prayer_entry_markers;if(!policy||policy.placement!=='start_only')return []; const ids=new Set(policy.rule_ids),seen=new Set(); return solarPeriods.filter(p=>{ if(!ids.has(p.id)||(!policy.include_preference_windows&&p.preference))return false; const key=p.id+':'+p.start;if(seen.has(key))return false;seen.add(key);return true; }); } function indicatorState(now){ const spec=visual.runtime_styles.solar_indicator;if(!spec)return null; if(!observer)return spec.unknown_state; const days=solarDays.filter(d=>dateKey(d.boundaries[spec.start_boundary])===dateKey(now)); if(!days.length)return spec.unknown_state; const b=days[0].boundaries,a=b[spec.start_boundary],z=b[spec.end_boundary]; if(a==null||z==null)return spec.unknown_state; return now>=a&&now{ const matching=rows.filter(p=>p.state===stateId);if(!matching.length)return; const track=make('span');track.className='time-track';track.style.top=(48+lane*21)+'px';track.dataset.state=stateId; const seen=new Set(); matching.forEach(p=>{ const key=p.start+':'+p.end;if(seen.has(key))return;seen.add(key); const part=button('',()=>periodDetails(p));part.className=p.point?'solar-point':'time-band';part.title=periodTitle(p)+' — '+periodTime(p); if(stateId==='prayer_time'){part.setAttribute('aria-hidden','true');part.tabIndex=-1;}else part.setAttribute('aria-label',periodTitle(p)+' — '+periodTime(p)); part.style.left=((Math.max(start,p.start)-start)/3600000*100)+'%'; if(!p.point)part.style.width=((Math.min(end,p.end)-Math.max(start,p.start))/3600000*100)+'%'; applyStatePaint(part,stateId);part.style.top=(48+lane*21)+'px';part.style.height='17px';part.dataset.timelineTime=String(Math.max(start,p.start));controls.push(part); }); }); const entries=prayerEntries().filter(p=>p.start>=start&&p.startperiodDetails(p));entry.setAttribute('aria-label',periodTitle(p)+' — '+timeText(p.start));entry.className='prayer-entry';entry.dataset.ruleId=p.id;entry.dataset.time=String(p.start);entry.dataset.timelineTime=String(p.start);entry.style.position='absolute';entry.style.top='6px';entry.style.left='4px';entry.style.zIndex='3';controls.push(entry);} } const marks=[]; for(const d of solarDays)for(const id of style.boundary_markers||[]){ const t=d.boundaries[id],def=boundary(id);if(!def||t==null||t=end)continue; const entry=button((icon(id)?icon(id)+' ':'')+def.label+' '+timeText(t),()=>boundaryInfo(def,t)); entry.dataset.boundaryId=id;entry.dataset.time=String(t);entry.dataset.timelineTime=String(t);entry.className='boundary-entry';marks.push(entry); } marks.sort((a,b)=>Number(a.dataset.time)-Number(b.dataset.time)); marks.forEach((entry,index)=>{entry.style.top=(164+index*40)+'px';controls.push(entry);}); const seenNotices=new Set(); for(const notice of timedNotices){ const event=notice.event,t=notice.at; const key=notice.key+':'+t; if(t=end||seenNotices.has(key)||!eventEnabled(event)||event.display?.timeline===false)continue; seenNotices.add(key); const symbol=icon(visual.event_action_mapping[event.action]); const entry=button((symbol?symbol+' ':'')+(notice.trigger.popup?.title||notice.trigger.title||title(event))+' '+timeText(t),()=>detail(eventScreen(event),'events',event)); entry.dataset.eventId=event.id;entry.dataset.time=String(t);entry.dataset.timelineTime=String(t);entry.className='event-marker';controls.push(entry); } controls.sort((a,b)=>Number(a.dataset.timelineTime)-Number(b.dataset.timelineTime)).forEach(x=>cell.appendChild(x)); } function calendarText(c,now){ try{const date=new Date(now),offset=Number(getSaved('clock_calendar_adjustment_v1',0)); if(c.adjustable){const h=hijriParts(now);if(h?.confirmed)return [h.day,h.month,h.year].join('/')+' '+c.label;} if(c.adjustable&&Number.isInteger(offset)&&Math.abs(offset)<=visual.runtime_styles.calendar_adjustment_limit)date.setDate(date.getDate()+offset); return new Intl.DateTimeFormat(document.documentElement.lang,{calendar:c.id,year:'numeric',month:'long',day:'numeric'}).format(date)+' '+c.label; }catch{return ui.calendarUnavailable;} } function calendarSettings(){show('worship',{title:ui.calendarSettings,text:ui.calendarNote},()=>{ const value=reminderField(ui.calendarAdjustment,'number',getSaved('clock_calendar_adjustment_v1',0));value.min=-visual.runtime_styles.calendar_adjustment_limit;value.max=visual.runtime_styles.calendar_adjustment_limit;value.step=1; $('adaptivePopupActions').appendChild(button(ui.apply,()=>{const n=Number(value.value);if(!Number.isInteger(n)||Math.abs(n)>visual.runtime_styles.calendar_adjustment_limit){para(ui.calendarInvalid);return;}save('clock_calendar_adjustment_v1',n);recalculate();close();})); $('adaptivePopupBody').appendChild(button(ui.monthConfirmation.title,monthConfirmation)); });} function monthConfirmation(){ const config=registry('islamic_events').calendar_confirmation,labels=ui.monthConfirmation; show('worship',{title:labels.title,text:labels.note},()=>{ const h=hijriParts(Date.now()),year=reminderField(labels.year,'number',h?.year||''),month=reminderField(labels.month,'number',h?.month||''); year.min=1;year.step=1;month.min=1;month.max=12;month.step=1; const start=reminderField(labels.start,'date',''),length=reminderSelect(labels.length,config.length_options.map(n=>[String(n),String(n)]),String(config.length_options[0])); const confirmations=getSaved(config.storage_key,[]); confirmations.forEach(item=>{ para(item.year+'/'+item.month+' — '+item.start+' — '+item.length); $('adaptivePopupBody').appendChild(button(labels.remove+' '+item.year+'/'+item.month,()=>{ save(config.storage_key,confirmations.filter(x=>x!==item));recalculate();monthConfirmation(); })); }); $('adaptivePopupActions').appendChild(button(labels.save,()=>{ const item={year:Number(year.value),month:Number(month.value),start:start.value,length:Number(length.value)}; if(!HijriCalendarEngine.validConfirmation(item)){para(labels.invalid);return;} const rest=confirmations.filter(x=>x.year!==item.year||x.month!==item.month),a=Date.parse(item.start+'T00:00:00Z'),z=a+item.length*86400000; if(rest.some(x=>{const b=Date.parse(x.start+'T00:00:00Z');return ab;})){para(labels.invalid);return;} try{localStorage.setItem(config.storage_key,JSON.stringify([...rest,item]));}catch{para(ru().storageError);return;} recalculate();monthConfirmation(); }));back(calendarSettings); }); } function updateCurrentMarker(now){ const width=Number($('ruler').dataset.cellWidth),marker=$('nowMarker'); if(marker){marker.style.left=((now-timelineStart)/3600000*width)+'px';marker.hidden=now=timelineStart+layout.total_hours*3600000; const state=indicatorState(now),paint=visual.timeline_states[state]; const label=$('nowMarkerLabel'); if(label){const calendars=visual.runtime_styles.current_time_calendars||[]; label.textContent=(observer&&paint?.emoji?paint.emoji+' ':'')+timeText(now)+'\n'+calendars.map(c=>calendarText(c,now)).join('\n'); const x=(now-timelineStart)/3600000*width,half=Math.min(135,$('shell').clientWidth/2); label.style.transform='translateX('+Math.max($('shell').scrollLeft-x,Math.min(-half,$('shell').scrollLeft+$('shell').clientWidth-2*half-x))+'px)'; }marker.style.color=paint?.text_color||'';marker.style.backgroundColor=paint?.color||''; } } function tickSolar(){ if(lastSolarDay!==dateKey(Date.now())){recalculate();return;} const now=Date.now();updateCurrentMarker(now); tickNotices(now);tickAdhan(now); const status=$('solarStatus'); if(!observer){status.textContent=ui.chooseLocation;return;} if(!profile()){status.textContent=ui.missingSolar;return;} if(!SolarEngine.ready()){status.textContent=ui.solarEngineUnavailable;return;} if(methodChoice()&&!prayerEngineReady){status.textContent=ui.prayerEngineUnavailable;return;} const upcoming=prayerEntries().filter(p=>p.start>now).sort((a,b)=>a.start-b.start)[0]; const altitude=solarAltitude(now); status.textContent=(observer.name||observer.lat.toFixed(3)+', '+observer.lng.toFixed(3))+' · '+ui.deviceTime+' · '+ui.estimated; if(Number.isFinite(altitude))status.textContent+='\n'+(ui.solarAltitude||'ارتفاع الشمس')+': '+altitude.toFixed(1)+'° · '+(ui.solarGeometrySource||'حساب هندسي شمسي محلي'); if(methodChoice())status.textContent+='\n'+ui.calculationMethod+': '+methodChoice().label; if(solarDays.some(d=>d.methodUnavailable))status.textContent+='\n'+ui.methodUnavailable; if(upcoming){const mins=Math.ceil((upcoming.start-now)/60000);status.textContent+='\n'+ui.nextPrayer+': '+ruleTitle(upcoming.rule)+' '+timeText(upcoming.start)+' — '+ui.remaining+' '+Math.floor(mins/60)+' '+ui.hours+' '+mins%60+' '+ui.minutes;} } function initSolar(){ const candidate=getSaved('clock_observer_v1',null);observer=validLocation(candidate)?candidate:null; madhhab=getSaved('clock_madhhab_v1',profile()?.default_madhhab); if(!registry('worship_fiqh').madhhabs?.[madhhab])madhhab=profile()?.default_madhhab; $('locationButton').textContent=ui.location;$('locationButton').addEventListener('click',configureLocation); recalculate();frameTimer=setInterval(tickSolar,1000); } let timedNotices=[];const pendingTimedNotices=new Map(); function buildNotices(){ timedNotices=[];const runtime=profile()?.notice_runtime;if(!runtime)return; const localToday=new Date();localToday.setHours(0,0,0,0); const days=timelineCalculationDays(); timedNotices=WorshipEventEngine.compile({events:registry('islamic_events').events,days,solarDays,periods:solarPeriods,runtime, policy:registry('islamic_events').scheduling_policy,enabled:eventEnabled,blocked:eventBlockedOn,matches:eventCalendarMatches,parts:hijriParts, preference:event=>isAdhkar(event)?adhkarPreference(event):null,seasonPreference, solarAvailable:Boolean(observer&&SolarEngine.ready()&&(!methodChoice()||prayerEngineReady)), defaultSound:getSaved(registry('user_time_markers').reminder_system?.storage_key,{})?.settings?.sound_enabled===true}); const end=new Date(localToday);end.setDate(end.getDate()+4); const start=new Date(localToday);start.setDate(start.getDate()-2); timedNotices.push(...WorshipEventEngine.eclipses({astronomy:registry('astronomy_events'),event:registry('islamic_events').events.find(e=>e.id==='eclipse_prayer'),location:observer,start:start.getTime(),end:end.getTime()})); timedNotices.sort((a,b)=>a.at-b.at||b.priority-a.priority); for(const [key,pending] of pendingTimedNotices)if(!timedNotices.some(n=>n.key===key&&n.at===pending.at))pendingTimedNotices.delete(key); } // Prayer-time (adhan) alert: tone or the user's chosen radio station. Settings live in alert_actions.js. function tickAdhan(now){ if(window.MuslimNativeBridge?.active)return; // Android app schedules these natively const A=window.MuslimClockAlerts;if(!A||!A.adhanEnabled())return; const ledger=getSaved('clock_adhan_fired_v1',{});let changed=false; for(const p of prayerEntries()){ if(now90000||!A.shouldFire(p.id))continue; const key=p.id+':'+p.start;if(ledger[key])continue; ledger[key]=now;changed=true;A.fire({id:p.id,title:ruleTitle(p.rule),time:timeText(p.start)}); } if(changed){for(const k of Object.keys(ledger))if(now-ledger[k]>86400000)delete ledger[k];save('clock_adhan_fired_v1',ledger);} } // Everything that should alert in the next hours, for background push (titles/bodies stay on the device). function upcomingAlerts(hoursAhead=60){ const now=Date.now(),end=now+hoursAhead*3600000,out=[],seen=new Set(),A=window.MuslimClockAlerts; const add=x=>{if(x.atend||seen.has(x.id))return;seen.add(x.id);out.push(x);}; for(const n of timedNotices){ if(!eventEnabled(n.event)||n.event.display?.timeline===false)continue; const popup=n.trigger.popup||{}; add({id:'ev:'+n.key+':'+n.at,at:n.at,title:popup.title||n.trigger.title||title(n.event),body:popup.text||n.trigger.popup_text||n.event.display?.text||'',data:{eventId:n.event.id,kind:eventScreen(n.event)}}); } if(A?.adhanEnabled())for(const p of prayerEntries()){ if(!A.shouldFire(p.id))continue;const t=ruleTitle(p.rule),time=timeText(p.start); add({id:'adhan:'+p.id+':'+p.start,at:p.start,title:t,body:t+' — '+time,requireInteraction:true,data:{adhan:true,prayerId:p.id,title:t,time}}); } if(personalReady){try{ for(const item of personal.read().items){ const type=registry('user_time_markers').marker_types[item.type]; for(const at of personal.timelineOccurrences(item,now,end))add({id:'rem:'+item.id+':'+at,at,title:type?.label_ar||ui.title,body:item.text,data:{reminderId:item.id,kind:reminderScreen(item)}}); } }catch{}} return out.sort((a,b)=>a.at-b.at); } function adhanPrayers(){const ids=visual?.runtime_styles?.prayer_entry_markers?.rule_ids||[];return ids.map(id=>({id,title:ruleTitle(rule(id)||{id})}));} function tickNotices(now){ const runtime=profile()?.notice_runtime;if(!runtime)return; const ledger=getSaved('clock_popup_impressions_v1',{}); for(const n of timedNotices)if(now>=n.at&&now-n.at=n.expires)pendingTimedNotices.delete(key); const due=[...pendingTimedNotices.values()].filter(n=>eventEnabled(n.event)&&(ledger[n.key]?.count||0)b.priority-a.priority||a.at-b.at)[0]; if(!due)return; pendingTimedNotices.delete(due.key); // This records display counts only; it is never worship completion tracking. for(const [key,value] of Object.entries(ledger))if(now-value.at>7*86400000)delete ledger[key]; ledger[due.key]={count:(ledger[due.key]?.count||0)+1,at:now};save('clock_popup_impressions_v1',ledger); const popup=due.trigger.popup||{}; const kind=eventScreen(due.event),noticeTitle=popup.title||due.trigger.title||title(due.event),noticeText=popup.text||due.trigger.popup_text||due.event.display?.text||''; if(due.sound!==false)playReminderSound();notifySystem(noticeTitle,noticeText,{kind,eventId:due.event.id}); if(!document.hidden&&$('adaptivePopup').hidden!==false)show(kind,{title:noticeTitle,text:noticeText,media:icon(visual.event_action_mapping[due.event.action])},()=>{ back(()=>detail(kind,'events',due.event)); }); } let personal=null,personalReady=false,reminderAudio=null,notificationRegistration=null,personalNoticeKeys=new Set(); async function enableReminderSound(){try{const Audio=window.AudioContext||window.webkitAudioContext;if(!Audio)return false;if(!reminderAudio)reminderAudio=new Audio();await reminderAudio.resume();return reminderAudio.state==='running';}catch{return false;}} async function playReminderSound(){try{if(window.MuslimClockAlerts?.hasCustomTone('general')&&await window.MuslimClockAlerts.playTone('general'))return true;}catch{}if(!reminderAudio||!visual.reminder_audio)return false;try{if(reminderAudio.state!=='running')await reminderAudio.resume();if(reminderAudio.state!=='running')return false;const a=visual.reminder_audio,o=reminderAudio.createOscillator(),g=reminderAudio.createGain();o.frequency.value=a.frequency_hz;g.gain.value=a.gain;o.connect(g);g.connect(reminderAudio.destination);o.start();g.gain.exponentialRampToValueAtTime(0.001,reminderAudio.currentTime+a.duration_seconds);o.stop(reminderAudio.currentTime+a.duration_seconds);return true;}catch{return false;}} async function initNotifications(){ if(window.MuslimNativeBridge?.active)return null; if(window.isSecureContext!==true||!('serviceWorker' in navigator))return null; try{notificationRegistration=await navigator.serviceWorker.register('./sw.js');navigator.serviceWorker.addEventListener('message',e=>openNotificationData(e.data));return notificationRegistration;}catch{return null;} } function openNotificationData(data){if(!data)return;if(data.adhan&&window.MuslimClockAlerts){window.MuslimClockAlerts.fire({id:data.prayerId,title:data.title,time:data.time},{test:true});return;}if(data.reminderId){const s=personal&&reminderAction(()=>personal.read()),x=s?.items.find(i=>i.id===data.reminderId);if(x)reminderDetail(x.id,undefined,data.kind||reminderScreen(x));return;}if(data.eventId){const x=registry('islamic_events').events.find(i=>i.id===data.eventId);if(x)detail(data.kind||eventScreen(x),'events',x);}} async function requestAlertPermission(){ const soundPromise=enableReminderSound();let permissionPromise=Promise.resolve('unsupported'); if('Notification' in window&&window.isSecureContext===true){try{permissionPromise=Notification.permission==='default'?Promise.resolve(Notification.requestPermission()).catch(()=>'error'):Promise.resolve(Notification.permission);}catch{permissionPromise=Promise.resolve('error');}} const [sound,permission]=await Promise.all([soundPromise,permissionPromise]);await initNotifications();return {sound,permission}; } async function notifySystem(titleText,body,data){ if(window.MuslimNativeBridge?.active)return false; // native alarms already notify if(!('Notification' in window)||Notification.permission!=='granted'||window.isSecureContext!==true)return false; const options={body:String(body||'').slice(0,240),lang:document.documentElement.lang,tag:'muslim-clock-'+(data?.reminderId||data?.eventId||Date.now()),renotify:true,data}; try{const reg=notificationRegistration||await initNotifications();if(reg){await reg.showNotification(titleText,options);return true;}new Notification(titleText,options);return true;}catch{return false;} } const ru=()=>ui.reminders; function reminderAction(fn){try{return fn();}catch(e){para(e.message==='invalid'?ru().invalid:ru().storageError);return null;}} function reminderDate(t){return new Date(t).toLocaleString(document.documentElement.lang,{dateStyle:'short',timeStyle:'short',hour12:true});} function localInput(t){const d=new Date(t),pad=x=>String(x).padStart(2,'0');return d.getFullYear()+'-'+pad(d.getMonth()+1)+'-'+pad(d.getDate())+'T'+pad(d.getHours())+':'+pad(d.getMinutes());} function reminderField(label,type,value){const wrap=make('label',label),input=make(type==='textarea'?'textarea':'input');if(type!=='textarea')input.type=type;input.value=value??'';wrap.appendChild(input);$('adaptivePopupBody').appendChild(wrap);return input;} function reminderSelect(label,options,value){const wrap=make('label',label),select=make('select');for(const [id,name] of options){const o=make('option',name);o.value=id;select.appendChild(o);}select.value=value;wrap.appendChild(select);$('adaptivePopupBody').appendChild(wrap);return select;} function reminderMoment(x){if(x.mode==='one_time')return x.target;if(x.mode==='recurring')return x.notice&&x.lastNotified!=null?x.lastNotified:personal.next(x,Date.now());if(x.mode==='pomodoro')return x.end;return null;} function reminderSummary(x){const u=ru();if(x.mode==='pomodoro')return (x.active?(x.phase==='waiting'?u.waitingPhase:x.phase==='work'?u.workPhase:u.breakPhase)+' · '+u.session+' '+x.session+' '+u.of+' '+x.total:u.finished)+'\n'+u.endAt+' '+reminderDate(x.end);const cadence=x.mode==='recurring'?u.repeatEvery+' '+personal.recurringSpec(x).every+' '+u.repeatUnits[personal.recurringSpec(x).unit]+' · ':'';return cadence+(x.notice?u.pending:u.scheduled)+' · '+reminderDate(reminderMoment(x));} function remindersHome(selectedHour,saved=false){ show('worship',{title:ru().title,text:ru().scope},()=>{ if(saved){const status=make('p',ru().saved);status.setAttribute('role','status');$('adaptivePopupBody').appendChild(status);} if(!personal){para(ru().missing);return;} const s=reminderAction(()=>personal.tick());if(!s)return;if(s.settings.sound_enabled)enableReminderSound(); $('adaptivePopupActions').appendChild(button(ru().add,()=>reminderChoose(selectedHour))); $('adaptivePopupActions').appendChild(button(ru().settings,reminderSettings)); $('adaptivePopupActions').appendChild(button(ui.adhkar.title,()=>adhkarHome(selectedHour))); $('adaptivePopupActions').appendChild(button(ui.worshipSchedules,()=>list('worship','events'))); if(s.items.length)$('adaptivePopupActions').appendChild(button(ru().removeSelected,()=>remindersRemoveSelected(selectedHour))); if(!s.items.length)para(ru().empty); s.items.slice().sort((a,b)=>Boolean(b.notice)-Boolean(a.notice)||reminderMoment(a)-reminderMoment(b)).forEach(x=>{ const type=registry('user_time_markers').marker_types[x.type];$('adaptivePopupBody').appendChild(button(icon(type.icon_id)+' '+type.label_ar+' — '+x.text.slice(0,100)+'\n'+reminderSummary(x),()=>reminderDetail(x.id,selectedHour))); }); }); } function remindersRemoveSelected(selectedHour){ const state=reminderAction(()=>personal.read());if(!state)return; show('worship',{title:ru().removeSelected,text:ru().selectToRemove},()=>{ const choices=state.items.map(item=>({id:item.id,input:reminderField(item.text,'checkbox','')})); $('adaptivePopupActions').appendChild(button(ru().delete,()=>{ const selected=new Set(choices.filter(x=>x.input.checked).map(x=>x.id)); if(!selected.size){para(ru().nothingSelected);return;} if(!window.confirm(ru().removeSelectedConfirm))return; const result=reminderAction(()=>{const current=personal.read();current.items=current.items.filter(x=>!selected.has(x.id));return personal.write(current);}); if(result){refreshPersonal();remindersHome(selectedHour);} }));back(()=>remindersHome(selectedHour)); }); } function reminderChoose(selectedHour){if(!personal){remindersHome(selectedHour);return;}show('worship',{title:ru().add},()=>{ for(const id of personal.config.enabled_types){const type=registry('user_time_markers').marker_types[id];$('adaptivePopupBody').appendChild(button(icon(type.icon_id)+' '+type.label_ar,()=>id==='adhkar'?adhkarHome(selectedHour):reminderForm(id,selectedHour)));}back(()=>remindersHome(selectedHour)); });} function reminderForm(type,selectedHour,id){ const s=reminderAction(()=>personal.read());if(!s)return; const old=s.items.find(x=>x.id===id),c=personal.config,types=registry('user_time_markers').marker_types; show('worship',{title:types[type].label_ar,media:icon(types[type].icon_id)},()=>{ const category=reminderSelect(ru().type,c.enabled_types.map(key=>[key,icon(types[key].icon_id)+' '+types[key].label_ar]),old?.type||type); const mode=reminderSelect(ru().mode,c.scheduling_modes.map(key=>[key,ru().modes[key]]),old?.mode||c.type_behaviors[type]); const text=reminderField(ru().text,'textarea',old?.text);text.maxLength=c.limits.text_length; const religious=make('p',ru().religiousNote),notes=make('p',ru().notesNote);$('adaptivePopupBody').append(religious,notes); const groups={}; const group=(key,fields,note)=>{const box=make('div');box.dataset.reminderMode=key;fields.forEach(field=>box.appendChild(field.parentElement));if(note)box.appendChild(make('p',note));$('adaptivePopupBody').appendChild(box);groups[key]=box;}; const target=reminderField(ru().when,'datetime-local',old?.mode==='one_time'?localInput(old.target):selectedHour>Date.now()?localInput(selectedHour):''); const lead=reminderField(ru().lead,'number',old?.lead??s.settings.lead_minutes);lead.min=0;lead.max=c.limits.lead_minutes; group('one_time',[target,lead]); const defaults=c.default_recurring,spec=old?.mode==='recurring'?personal.recurringSpec(old):{startAt:selectedHour>Date.now()?selectedHour:Date.now()+defaults.start_delay_minutes*60000,every:defaults.every,unit:defaults.unit}; const startAt=reminderField(ru().startsAt,'datetime-local',localInput(spec.startAt)),repeatEvery=reminderField(ru().repeatEvery,'number',spec.every);repeatEvery.min=1;repeatEvery.step=1;repeatEvery.max=c.limits.repeat_value; const repeatUnit=reminderSelect(ru().frequency,c.repeat_units.map(key=>[key,ru().repeatUnits[key]]),spec.unit);group('recurring',[startAt,repeatEvery,repeatUnit],ru().habitNote); const focus=old?.mode==='pomodoro'?old:c.default_focus; const focusAt=reminderField(ru().focusStartsAt,'datetime-local',old?.mode==='pomodoro'?(old.startAt>Date.now()?localInput(old.startAt):''):selectedHour>Date.now()?localInput(selectedHour):''); const work=reminderField(ru().work,'number',focus.work),rest=reminderField(ru().rest,'number',focus.rest),total=reminderField(ru().sessions,'number',focus.total); for(const e of [work,rest]){e.min=1;e.max=c.limits.duration_minutes;}total.min=1;total.step=1;total.max=c.limits.sessions; group('pomodoro',[focusAt,work,rest,total],ru().focusNote); const sound=reminderField(ru().sound,'checkbox','');sound.checked=old?.sound??c.default_alarm_sound; const sync=()=>{for(const [key,box]of Object.entries(groups))box.hidden=key!==mode.value;religious.hidden=!c.religious_types.includes(category.value);notes.hidden=category.value!=='note';$('adaptivePopupTitle').textContent=types[category.value].label_ar;$('adaptivePopupMedia').textContent=icon(types[category.value].icon_id);}; category.addEventListener('change',sync);mode.addEventListener('change',sync);sync(); $('adaptivePopupActions').appendChild(button(ru().save,()=>reminderAction(()=>{ const start=mode.value==='pomodoro'?(focusAt.value?new Date(focusAt.value).getTime():null):new Date(startAt.value).getTime(); const saved=personal.save({type:category.value,mode:mode.value,text:text.value,sound:sound.checked,target:new Date(target.value).getTime(),lead:lead.value,startAt:start,repeatEvery:repeatEvery.value,repeatUnit:repeatUnit.value,work:work.value,rest:rest.value,total:total.value},id); if(sound.checked)enableReminderSound();refreshPersonal();remindersHome(selectedHour,true);viewReminderOnTimeline(saved); })));back(()=>remindersHome(selectedHour)); }); } function reminderDetail(id,selectedHour,notificationKind,timelineAt){ const s=reminderAction(()=>personal.read()),x=s?.items.find(x=>x.id===id);if(!x)return; show(notificationKind||'worship',{title:registry('user_time_markers').marker_types[x.type].label_ar,text:x.text+'\n\n'+reminderSummary(x),media:icon(registry('user_time_markers').marker_types[x.type].icon_id)},()=>{ if(Number.isFinite(timelineAt))para(ru().when+' — '+reminderDate(timelineAt)); $('adaptivePopupActions').appendChild(button(ru().showOnTimeline,()=>{close();viewReminderOnTimeline(x,timelineAt);})); if(personal.tracksStreak(x))para(ru().streak+': '+x.streak+' '+ru().occurrences); const act=(label,fn)=>$('adaptivePopupActions').appendChild(button(label,()=>reminderAction(()=>{fn();refreshPersonal();remindersHome(selectedHour);}))); if(personal.tracksCompletion(x))act(ru().complete,()=>personal.complete(id)); const recurringDue=x.mode==='recurring'?personal.schedule(x,Date.now()).due:null; if(personal.tracksStreak(x)&&recurringDue!=null&&recurringDue>=x.createdAt&&x.lastCompleted!==recurringDue)act(ru().habitComplete,()=>personal.complete(id)); if(x.mode==='one_time')for(const o of personal.config.snooze_options)act(ru().snooze[o.id],()=>personal.snooze(id,o.id)); if(x.notice)act(ru().acknowledge,()=>personal.acknowledge(id)); $('adaptivePopupActions').appendChild(button(ru().edit,()=>reminderForm(x.type,selectedHour,id))); $('adaptivePopupActions').appendChild(button(ru().copy,async()=>{ try{await navigator.clipboard.writeText(x.text);para(ru().copied);}catch{para(ru().copyManual);const e=reminderField(ru().text,'textarea',x.text);e.readOnly=true;e.focus();e.select();} })); $('adaptivePopupActions').appendChild(button(x.mode==='pomodoro'?ru().cancel:ru().delete,()=>{if(window.confirm(ru().deleteAsk))reminderAction(()=>{personal.remove(id);refreshPersonal();remindersHome(selectedHour);});})); back(()=>remindersHome(selectedHour)); }); } function reminderSettings(){const s=reminderAction(()=>personal.read());if(!s)return;show('worship',{title:ru().settings,text:ru().scope},()=>{ window.MuslimClockQuran?.mountSettingsEntry($('adaptivePopupBody'),reminderSettings); window.MuslimClockRadio?.mountEntry($('adaptivePopupBody'),reminderSettings); window.MuslimClockPush?.mountSettingsEntry($('adaptivePopupBody'),reminderSettings); window.MuslimNativeBridge?.mountSettingsEntry?.($('adaptivePopupBody'),reminderSettings); window.MuslimClockAlerts?.mountSettingsEntry($('adaptivePopupBody'),reminderSettings); const sound=reminderField(ru().sound,'checkbox','');sound.checked=s.settings.sound_enabled===true; $('adaptivePopupBody').appendChild(button(ru().enableAlerts,async()=>{const result=await requestAlertPermission();if(result.permission==='denied')para(ru().notificationDenied);else if(result.sound&&result.permission==='granted')para(ru().alertsReady);else if(result.permission==='unsupported')para(ru().notificationUnavailable);else para(ru().alertsPartlyReady);})); $('adaptivePopupBody').appendChild(button(ru().testSound,async()=>{if(await enableReminderSound())playReminderSound();else para(ru().soundUnavailable);}));para(ru().closedBrowserNote); const lead=reminderField(ru().lead,'number',s.settings.lead_minutes),evening=reminderField(ru().evening,'time',s.settings.evening_time),tomorrow=reminderField(ru().tomorrow,'time',s.settings.tomorrow_time); $('adaptivePopupActions').appendChild(button(ru().save,()=>reminderAction(()=>{personal.settings({lead_minutes:lead.value,evening_time:evening.value,tomorrow_time:tomorrow.value,sound_enabled:sound.checked});if(sound.checked)enableReminderSound();remindersHome();})));back(remindersHome); });} let personalSignature='',personalTimelineRows=[]; function preparePersonalTimeline(items){ personalTimelineRows=[];const end=timelineStart+layout.total_hours*3600000; for(const item of items)for(const at of personal.timelineOccurrences(item,timelineStart,end))personalTimelineRows.push({item,at}); personalTimelineRows.sort((a,b)=>a.at-b.at); } function viewReminderOnTimeline(item,requestedAt){ const at=Number.isFinite(requestedAt)?requestedAt:item.mode==='recurring'?personal.next(item,Date.now()):item.mode==='pomodoro'?(item.startAt??item.end):item.target; if(!Number.isFinite(at))return; if(at=timelineStart+layout.total_hours*3600000){timelineViewDate=at;recalculate();} const width=Number($('ruler').dataset.cellWidth);$('shell').scrollLeft=Math.max(0,(Math.floor((at-timelineStart)/3600000)-1)*width); } function tickPersonal(){if(!personalReady)return;try{ const s=personal.tick(),count=s.items.filter(x=>x.notice).length,b=$('reminderNotice'); const pending=s.items.filter(x=>x.notice),key=x=>[x.id,x.notice,x.target,x.lastNotified,x.end].join(':'),currentKeys=new Set(pending.map(key)),fresh=pending.filter(x=>!personalNoticeKeys.has(key(x))); personalNoticeKeys=currentKeys; if(fresh.some(x=>x.sound??s.settings.sound_enabled))playReminderSound(); fresh.forEach(x=>notifySystem(registry('user_time_markers').marker_types[x.type].label_ar,x.text,{kind:reminderScreen(x),reminderId:x.id})); if(fresh.length&&!document.hidden&&$('adaptivePopup').hidden!==false)reminderDetail(fresh[0].id,undefined,reminderScreen(fresh[0])); const message=count?ru().notice+' ('+count+')':''; if(b.textContent!==message)b.textContent=message; const live=$('reminderDialogNotice');if(live){if(live.textContent!==message)live.textContent=message;live.hidden=!count;} b.hidden=!count; const signature=JSON.stringify([timelineStart,s.items]); if(signature!==personalSignature){personalSignature=signature;preparePersonalTimeline(s.items);const ruler=$('ruler');for(const cell of Array.from(ruler.children).filter(x=>x.className==='hour')){for(const row of Array.from(cell.children).filter(x=>x.className==='personal-hour'))row.remove();paintPersonal(cell,Number(cell.dataset.start));}positionTimelineRows();} }catch{$('reminderNotice').hidden=false;$('reminderNotice').textContent=ru().storageError;}} function refreshPersonal(){personalSignature='';tickPersonal();} function initPersonal(){const c=registry('user_time_markers').reminder_system; const live=button('',()=>remindersHome());live.id='reminderDialogNotice';live.hidden=true;live.setAttribute('aria-live','assertive');live.setAttribute('aria-atomic','true');$('adaptivePopupCard').prepend(live); $('reminderButton').textContent=ru().tools;$('reminderButton').addEventListener('click',()=>remindersHome());$('reminderNotice').addEventListener('click',()=>remindersHome()); const adhkarButton=$(adhkarConfig()?.entry_button_id||'adhkarButton'); if(adhkarButton){adhkarButton.textContent=ui.adhkar.button;adhkarButton.addEventListener('click',()=>adhkarHome());} if(!c)return;personal=new PersonalReminderEngine(c,localStorage);personalReady=true;tickPersonal();setInterval(tickPersonal,registry('user_time_markers').reminder_system.poll_interval_ms); window.addEventListener('focus',refreshPersonal);window.addEventListener('storage',refreshPersonal);document.addEventListener('visibilitychange',()=>{if(!document.hidden)refreshPersonal();}); document.addEventListener('pointerdown',()=>{try{if(personal?.read().settings.sound_enabled||registry('islamic_events').events.filter(isAdhkar).some(e=>{const p=adhkarPreference(e);return p.enabled&&p.sound;}))enableReminderSound();}catch{}},{passive:true}); } function paintPersonal(cell,start){if(!personalReady)return;try{ for(const {item:x,at} of personalTimelineRows.filter(row=>row.at>=start&&row.atreminderDetail(x.id,undefined,undefined,at)); row.className='personal-hour';row.dataset.reminderId=x.id;row.dataset.timelineTime=String(at);row.setAttribute('aria-label',type.label_ar+' — '+x.text+' — '+reminderDate(at));cell.appendChild(row); } Array.from(cell.children).filter(x=>x.dataset.timelineTime).sort((a,b)=>Number(a.dataset.timelineTime)-Number(b.dataset.timelineTime)).forEach(x=>cell.appendChild(x)); }catch{}} function twelveHourLabel(h){return (h%12||12)+':00 '+(h<12?ui.am:ui.pm);} function hourAccessibleLabel(h){return ui.hourOnly+' '+(h%12||12)+':00';} function buildRuler(){ const shell=$('shell'),ruler=$('ruler'),width=Math.max(layout.minimum_cell_width,shell.clientWidth/layout.visible_cells); const oldWidth=Number(ruler.dataset.cellWidth)||width;const offset=shell.scrollLeft/oldWidth; ruler.replaceChildren();ruler.dataset.cellWidth=width;ruler.style.width=(layout.total_hours*width)+'px'; const axis=make('div');axis.className='axis';axis.setAttribute('aria-hidden','true');ruler.appendChild(axis); const reference=new Date(timelineViewDate??Date.now()),start=new Date(reference);start.setHours(layout.start_hour,0,0,0);if(start>reference)start.setDate(start.getDate()-1);timelineStart=start.getTime(); if(personalReady)preparePersonalTimeline(personal.read().items); const endDate=new Date(timelineStart+(layout.total_hours-1)*3600000); $('timelineDates').textContent=new Date(timelineStart).toLocaleDateString(document.documentElement.lang)+' — '+endDate.toLocaleDateString(document.documentElement.lang); $('timelineToday').hidden=timelineViewDate==null; for(let i=0;ihourSettings(date.getTime()));hourButton.className='hour-hit';hourButton.setAttribute('aria-label',hourAccessibleLabel(date.getHours()));b.appendChild(hourButton); b.className='hour';b.dataset.start=String(date.getTime());b.style.left=(i*width)+'px';b.style.width=width+'px'; const tick=make('span');tick.className='tick';tick.setAttribute('aria-hidden','true'); const caption=make('span',label);caption.className='label';caption.setAttribute('aria-hidden','true');hourButton.append(tick,caption);paintCell(b,date.getTime());paintPersonal(b,date.getTime());ruler.appendChild(b); } positionTimelineRows(); const marker=make('div');marker.id='nowMarker';marker.className='now-marker';marker.setAttribute('aria-hidden','true');const markerLabel=make('span');markerLabel.id='nowMarkerLabel';markerLabel.className='now-marker-label';marker.appendChild(markerLabel);ruler.prepend(marker); shell.scrollLeft=initialScroll?offset*width:Math.max(0,Math.floor((Date.now()-start.getTime())/3600000)*width);initialScroll=true;updateCurrentMarker(Date.now()); } function positionTimelineRows(){ const ruler=$('ruler'),cells=[...ruler.querySelectorAll('.hour')]; const upperHeight=Math.max(270,...cells.map(cell=>164+cell.querySelectorAll('.boundary-entry').length*40)); let lowerRows=0; for(const cell of cells){ cell.style.height=upperHeight+'px'; const reminders=[...cell.querySelectorAll('.event-marker,.personal-hour')].sort((a,b)=>Number(a.dataset.timelineTime)-Number(b.dataset.timelineTime)); reminders.forEach((entry,index)=>{entry.style.top=(upperHeight+20+index*70)+'px';entry.style.bottom='auto';}); lowerRows=Math.max(lowerRows,reminders.length); } ruler.querySelector('.reminder-axis')?.remove(); if(lowerRows){const axis=make('div');axis.className='reminder-axis';axis.setAttribute('aria-hidden','true');axis.style.top=(95+upperHeight+8)+'px';ruler.prepend(axis);} ruler.style.height=(95+upperHeight+(lowerRows?20+lowerRows*70:25))+'px'; } async function start(){ // No embedded domain-data fallback: a missing registry must remain visible. const results=await Promise.allSettled(files.map(async name=>{ const controller=new AbortController(),timer=setTimeout(()=>controller.abort(),15000); try{const response=await fetch('./'+name+'.json',{cache:'no-store',signal:controller.signal});if(!response.ok)throw Error(name+'.json ('+response.status+')');R[name]=await response.json();} catch(e){throw Error(name+'.json: '+e.message);}finally{clearTimeout(timer);} })); visual=registry('visual_states');ui=visual?.ui;layout=visual?.ui_layout; if(visual?.ui_theme)Object.entries(visual.ui_theme).forEach(([key,val])=>document.documentElement.style.setProperty('--'+key,val)); const errors=results.filter(r=>r.status==='rejected'); if(!ui||!layout||!visual.runtime_styles){$('loadStatus').textContent=(ui?.loadError||'visual_states_registry.json')+'\n'+errors.map(r=>r.reason.message).join('\n');return;} document.title=ui.title;document.querySelectorAll('[data-ui]').forEach(e=>e.textContent=ui[e.dataset.ui]||''); document.querySelectorAll('[data-aria]').forEach(e=>e.setAttribute('aria-label',ui[e.dataset.aria]||'')); await loadPrayerEngine(); $('timelineToday').addEventListener('click',()=>{timelineViewDate=null;initialScroll=false;recalculate();}); $('loadStatus').textContent=errors.length?ui.partialLoad+'\n'+errors.map(r=>r.reason.message).join('\n'):'';await initNotifications();initSolar();initPersonal();const legend=make('div');legend.className='timeline-legend';for(const id of visual.runtime_styles.timeline_lanes||[]){const item=make('span',visual.timeline_states[id].label),swatch=make('span');swatch.setAttribute('aria-hidden','true');applyStatePaint(swatch,id);item.prepend(swatch);legend.appendChild(item);}$('shell').after(legend);const annotations=make('div');annotations.className='untimed-markers';untimedMarkers().forEach(m=>{const b=markerButton(m);if(b)annotations.appendChild(b);});legend.after(annotations);window.addEventListener('resize',()=>{buildRuler();tickSolar();});$('shell').addEventListener('scroll',tickSolar); $('adaptivePopupClose').addEventListener('click',close); $('adaptivePopup').addEventListener('click',e=>{if(e.target===$('adaptivePopup'))close();}); document.addEventListener('keydown',e=>{ if($('adaptivePopup').hidden)return; if(e.key==='Escape'){e.preventDefault();close();} if(e.key==='Tab'){ const nodes=[...$('adaptivePopup').querySelectorAll('button,a[href],input,select,textarea,summary,[tabindex="0"]')].filter(e=>!e.disabled&&!e.closest('[hidden]')&&e.tabIndex>=0&&(!e.closest('details:not([open])')||e.tagName==='SUMMARY')),first=nodes[0],last=nodes[nodes.length-1]; if(e.shiftKey&&document.activeElement===first){e.preventDefault();last.focus();} else if(!e.shiftKey&&document.activeElement===last){e.preventDefault();first.focus();} } }); } return {openPanel:show,start,home,close,notifySystem,adhanPrayers,ruleTitle,upcomingAlerts,openNotificationData,qiblaSettings,celestialRegistry:()=>registry('sky_simulation'),records,detail,boundaryInfo,ruleText,remindersHome,reminderChoose,reminderForm,reminderDetail,reminderSettings,prayerEntries,indicatorState,twelveHourLabel,hourDetails,hourSettings,periodDetails,recalculate,configureLocation,tickPersonal,calendarText,applyPrayerMethod,solarState:()=>({observer,madhhab,solarDays,solarPeriods}),resolveLegacy(id){return registry('religious_content').legacy_rule_map.find(x=>x.legacy_id===id);}}; })(); window.PopupManager={open:(kind)=>RegistryApp.home(kind),close:()=>RegistryApp.close()}; window.registryReady=RegistryApp.start();