PageSourceSearch

https://2026djbridge.web.app/assets/stamp-rally-context-BoS8evRS.js

js 2026djbridge.web.app collected 2026-10-03 12:15:21 UTC 10,756 bytes, 3 lines download raw bytes

1import{r as i,j as tt}from"./chunk-UVKPFVEO-D9yzbXen.js";import{pushEvent as k,getTodayStr as _,getLatestEventTime as E,loadData as h,createDefaultData as q,saveData as B,clearData as et,STORAGE_KEY as st}from"./storage-DZSlhtS0.js";import{R as x,e as nt}from"./useFirestoreConfig-CJ5Wsy2e.js";import{COLLECTABLE_STAMP_IDS as at,STAMP_NAMES as ot,STAMP_LOCATIONS as S,STAMP_IDS as rt,STAMP_NAME_MAP as D,STAMPS as ct,refreshStampsFromFirestore as it}from"./stamp-data-d6wEFa4g.js";function C(t){return t?typeof t.status=="string"?t.status:t.collected?t.consumed?"consumed":"collected":"not_collected":"not_collected"}function O(t){return at.includes(t)}function lt(t){return C(t)!=="not_collected"}function K(t){return Object.entries(t.stamps).filter(([e,n])=>O(e)&&C(n)==="collected").length}function ut(t){return Object.entries(t.stamps).filter(([e,n])=>O(e)&&lt(n)).length}function ft(t){return K(t)}function dt(t){const e=_();let n=0;for(const[o,l]of Object.entries(t.stamps))O(o)&&C(l)==="collected"&&E(l,"collected")?.startsWith(e)&&n++;return n}function mt(t){return Object.entries(t.stamps).filter(([e,n])=>O(e)&&C(n)==="consumed").length}function F(t){return Math.floor(mt(t)/x)}function gt(t){const e=yt(t),n=new Date().toISOString();return e.forEach(o=>{k(t.stamps[o],"consumed",n)}),e}function yt(t){return Object.entries(t.stamps).filter(([e,n])=>O(e)&&C(n)==="collected").sort((e,n)=>{const o=E(e[1],"collected"),l=E(n[1],"collected"),f=o?new Date(o).getTime():0,c=l?new Date(l).getTime():0;return f-c}).slice(0,x).map(([e])=>e)}function z(){if(typeof window>"u"){console.log("log_stamp: 必須在瀏覽器環境執行");return}const t=h();console.groupCollapsed("log_stamp — Stamp Rally Summary"),console.log("Total collected:",t.totalCollected),console.log("Daily redeem entries:",t.dailyRedeem||{}),console.groupCollapsed("Stamps");const e=Object.keys(t.stamps).sort().map(n=>{const o=parseInt(n.split("_")[1],10)-1,l=ot[o]??n,f=S[n],c=t.stamps[n],p=E(c,"collected")??c.collectedAt??"-",d=E(c,"consumed")??c.consumedAt??"-",P=Array.isArray(c.history)&&c.history.length?c.history.map(A=>`${A.status}@${A.at}`):[];return{index:o+1,id:n,name:l,zone:f?.zone??"-",status:c.status,collectedAt:p,consumedAt:d,history:P.join(", ")}});console.table(e,["index","id","name","status","collectedAt","consumedAt","history"]),console.groupEnd(),console.groupCollapsed("Raw data (stamprally_data)"),console.log(t),console.groupEnd(),console.groupEnd()}function pt(){if(!(typeof window>"u"))try{Object.defineProperty(window,"log_stamp",{configurable:!0,enumerable:!1,writable:!0,value:z})}catch{window.log_stamp=z}}const ht=5e3;function vt(t,e,n,o){const f=(n-t)*Math.PI/180,c=(o-e)*Math.PI/180,p=Math.sin(f/2)**2+Math.cos(t*Math.PI/180)*Math.cos(n*Math.PI/180)*Math.sin(c/2)**2;return 6371e3*2*Math.atan2(Math.sqrt(p),Math.sqrt(1-p))}function St(t,e){const n=S[t];if(!n)return{distance:0,withinRange:!0};const o=vt(e.latitude,e.longitude,n.lat,n.lng);return{distance:o,withinRange:o<=ht}}async function Et(){if(typeof navigator>"u"||!navigator.geolocation)return"unsupported";if(!("permissions"in navigator)||!navigator.permissions?.query)return"unknown";try{return(await navigator.permissions.query({name:"geolocation"})).state}catch{return"unknown"}}function Q(t=9e3){return new Promise((e,n)=>{if(typeof navigator>"u"||!navigator.geolocation){n("UNAVAILABLE");return}navigator.geolocation.getCurrentPosition(o=>{e({latitude:o.coords.latitude,longitude:o.coords.longitude,accuracy:o.coords.accuracy})},o=>{if(o.code===o.PERMISSION_DENIED){n("DENIED");return}if(o.code===o.TIMEOUT){n("TIMEOUT");return}n("UNAVAILABLE")},{timeout:t,maximumAge:3e4,enableHighAccuracy:!0})})}function W(t,e,n,o=!1){const{distance:l,withinRange:f}=St(t,e);if(f)return n?.({status:"ok",message:`位置確認成功,距離據點約 ${Math.round(l)} 公尺`}),!0;throw n?.({status:"error",message:`距離太遠(${Math.round(l)} 公尺),請靠近據點後再試`}),"TOO_FAR"}function At(t,e,n={}){return new Promise(async(o,l)=>{if(o(!0),!S[t]){o(!0);return}if(n.manualCoords){e?.({status:"checking",message:"正在用手動座標驗證位置..."});try{o(W(t,n.manualCoords,e,n.forceValidation))}catch(c){l(c)}return}if(typeof navigator>"u"||!navigator.geolocation){o(!0);return}e?.({status:"checking",message:"正在確認你的位置..."});try{const c=await Q();o(W(t,c,e,n.forceValidation))}catch(c){e?.(c==="TIMEOUT"?{status:"error",message:"定位逾時,請重試"}:c==="UNAVAILABLE"?{status:"error",message:"此裝置不支援定位,請改用手動座標測試"}:{status:"error",message:"無法取得位置,請確認已開å•
1Ÿå®šä½æ¬Šé™"}),l(c)}})}function L(t,e=new Date){if(!t)return{isHomepageOpen:!1,isActivityOpen:!1,homepagePhase:"loading",activityPhase:"loading",message:"活動資訊載入中…"};const n=e>=t.webStart&&e<=t.webEnd,o=e>=t.eventStart&&e<=t.eventEnd,l=e<t.webStart?"before":e>t.webEnd?"after":"during",f=e<t.eventStart?"before":e>t.eventEnd?"after":"during";let c;return!n&&!o?e<t.webStart?c="活動尚未開始,敬請期待!":c="活動已結束,感謝您的參與!":n&&!o&&(e<t.eventStart?c="活動即將開始,目前僅開放瀏覽資訊。":c="集章活動已結束,感謝您的參與!"),{isHomepageOpen:n,isActivityOpen:o,homepagePhase:l,activityPhase:f,message:c}}const X=i.createContext(null);function Mt({children:t}){const[e,n]=i.useState(q),[o,l]=i.useState(!1),[f,c]=i.useState({visible:!1,icon:"",title:"",subtitle:"",visualStyle:"default",status:void 0}),[p,d]=i.useState(null),[P,A]=i.useState(null),[N,w]=i.useState("unknown"),{schedule:b,loading:$}=nt(),[v,I]=i.useState(()=>L(null));i.useEffect(()=>{if(I(L(b)),!b)return;const s=setInterval(()=>{const a=L(b);I(r=>r.isHomepageOpen===a.isHomepageOpen&&r.isActivityOpen===a.isActivityOpen&&r.homepagePhase===a.homepagePhase&&r.activityPhase===a.activityPhase&&r.message===a.message?r:a)},6e4);return()=>clearInterval(s)},[b]),i.useEffect(()=>{let s=!0,a=null;async function r(){await it(),s&&n(h())}return a=setTimeout(()=>{r()},1200),()=>{s=!1,a&&clearTimeout(a)}},[]),i.useEffect(()=>{const s=h(),a=_();let r=!1;for(const[u,y]of Object.entries(s.stamps))if(y.status==="consumed"){const m=E(y,"consumed");m&&!m.startsWith(a)&&(k(y,"repeatable"),r=!0)}n(s),l(!0),r&&B(s)},[]),i.useEffect(()=>{let s=!0,a=null;async function r(){if(!(typeof navigator>"u"||!navigator.permissions?.query))try{a=await navigator.permissions.query({name:"geolocation"}),s&&w(a.state),a.onchange=()=>{s&&w(a.state)}}catch{}}return r(),()=>{s=!1,a&&(a.onchange=null)}},[]),i.useEffect(()=>{function s(r){try{if(r.key!==st)return;n(h())}catch{}}window.addEventListener("storage",s);let a=null;try{typeof BroadcastChannel<"u"&&(a=new BroadcastChannel("stamprally_data"),a.addEventListener("message",r=>{try{const u=r.data;if(!u)return;(u.type==="update"||u.type==="clear")&&n(h())}catch{}}))}catch{}return()=>{window.removeEventListener("storage",s),a&&a.close()}},[]),i.useEffect(()=>{try{pt()}catch{}},[]);const M=i.useCallback(s=>{n(s),B(s)},[]),g=i.useCallback((s,a,r,u)=>{c({visible:!0,icon:s,title:a,subtitle:r,onClose:u?.onClose,actionLabel:u?.actionLabel,visualStyle:u?.visualStyle??"default",status:u?.status})},[]),V=i.useCallback(()=>{c(s=>({...s,visible:!1})),d(null)},[]),G=i.useCallback(async()=>{d({status:"checking",message:"正在請求定位權限..."});try{const s=await Q(5e3);return A(s),w("granted"),d({status:"ok",message:s.accuracy?`已取得目前位置,定位精度約 ${Math.round(s.accuracy)} 公尺`:"已取得目前位置"}),s}catch(s){console.error("GPS error:",s);const a=await Et();throw w(a==="denied"?"unknown":a),d(s==="TIMEOUT"?{status:"error",message:"定位逾時,請重試"}:s==="UNAVAILABLE"?{status:"error",message:"此裝置不支援定位,請改用手動座標測試"}:s==="DENIED"?{status:"error",message:"定位權限遭拒絕,請手動重置權限後再試"}:{status:"error",message:"無法取得位置,請確認已開啟定位權限"}),s}},[]),H=i.useCallback(async(s,a)=>{if(!rt.includes(s))return g("❌","無效的集章連結","æ­¤ QR Code 驗證失敗,請確認是否為活動官方立牌。",a?.overlayOptions),!1;const r=h(),u=r.stamps[s]?.status??"not_collected";if(u==="consumed"){const T=D[s]??s;return g(S[s]?.icon??"🏴","圖章已完成兌換!",`您今日已使用「${T}」圖章換取獎勵。此據點將在明日凌晨 00:00 重置狀態,歡迎再次回來收集!`,{...a?.overlayOptions,status:"consumed"}),!1}else if(u==="collected"){const T=D[s]??s;return g(S[s]?.icon??"🏴","此地已探索完成!",`您已持有「${T}」的圖章囉!打開地圖找找其他地點繼續收集吧!`,a?.overlayOptions),!1}if(a?.gpsOverride==="force_fail")return d({status:"error",message:"開發模式:已設定無視 GPS,強制收集失敗"}),g("❌","集章失敗","開發模式:已設定為無視 GPS 並強制失敗。",a?.overlayOptions),!1;try{a?.gpsOverride==="force_success"?d({status:"ok",message:"開發模式:已設定無視 GPS,強制收集成功"}):a?.skipGpsValidation?d(null):await At(s,d,{manualCoords:a?.manualCoords,forceValidation:a?.forceGpsValidation})}catch{}a?.manualCoords&&A(a.manualCoords);const y=new Date().toISOString(),m={...r};m.stamps={...m.stamps};const R={...m.stamps[s]??{status:"not_collected",history:[]}};m.stamps[s]=k(R,"collected",y),m.totalCollected=ut(m),M(m);const J=D[s]??s,Z=S[s]?.icon||"🏴";return g(Z,"探索成功!",`恭喜獲得「${J}」圖章,
2目前已收集 ${m.totalCollected} / ${ct.length} 個圖章囉!`,{...a?.overlayOptions,visualStyle:"stamp-success",status:"collected"}),!0},[M,g]),U=i.useCallback((s,a)=>
2{if(!s)return;const r=h(),u=gt(r);r.dailyRedeem[_()]=s,M(r);const y=F(r);a?.skipOverlay||g("🎉",`第 ${y} 次兌換成功!`,`恭喜你取得限定航海旗標誌扇子!
3已消耗 ${u.length} 個章,剩餘章繼續累積中。`)},[M,g]),j=i.useCallback(()=>{et(),n(q()),g("🗑","資料已清除","所有集章進度已重置。")},[g]),Y=i.useMemo(()=>{const s=_(),a=K(e),r=ft(e),u=dt(e),y=!!e.dailyRedeem[s],m=F(e),R=r>=x&&!y;return{data:e,hydrated:o,totalCollected:a,availableCount:r,dailyCount:u,todayRedeemed:y,redeemCount:m,canRedeem:R,overlay:f,gpsResult:p,currentPosition:P,locationPermission:N,isHomepageOpen:v.isHomepageOpen,isActivityOpen:v.isActivityOpen,activityMessage:v.message,homepagePhase:v.homepagePhase,activityPhase:v.activityPhase,scheduleLoading:$,collectStamp:H,requestLocationPermission:G,confirmRedeem:U,clearAllData:j,showOverlay:g,closeOverlay:V}},[e,o,f,p,P,N,v,$,H,G,U,j,g,V]);return tt.jsx(X.Provider,{value:Y,children:t})}function Tt(){const t=i.useContext(X);if(!t)throw new Error("useStampRally must be used within StampRallyProvider");return t}export{Mt as S,ut as g,Tt as u};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.