{"always_run_in_app": false, "icon": {"color": "deep-green", "glyph": "bus.fill"}, "name": "ASR 班车", "script": "// ASR 班车 · iOS 桌面小组件（Scriptable）\n// 安装：Scriptable App → ➕ 新建脚本 → 粘贴本文件全部内容 → 运行一次预览 →\n//      主屏长按 → 加小组件 → 选 Scriptable → 编辑小组件 → Script 选本脚本（建议 When Interacting: Run Script / Medium 尺寸）。\n// 数据内嵌、离线可用。来源：管理处 Shuttle bus timing。\n// 去程＝在 ASR 上车 → 地铁；返程＝在地铁站上车 → ASR（环线同车，上车时间≈到站时间）。周日停运。\n\nconst SCHED = {\n  weekday: [\n    [\"07:20\",\"o\",[\"07:26\",\"07:28\"]],[\"07:40\",\"o\",[\"07:46\",\"07:48\"]],[\"08:00\",\"o\",[\"08:06\",\"08:08\"]],\n    [\"08:20\",\"o\",[\"08:26\",\"08:28\"]],[\"08:40\",\"o\",[\"08:46\",\"08:48\"]],[\"09:00\",\"o\",[\"09:06\",\"09:08\"]],\n    [\"09:30\",\"o\",[\"09:36\",\"09:38\"]],[\"10:00\",\"h\",[\"10:10\"]],[\"10:30\",\"o\",[\"10:36\",\"10:38\"]],\n    [\"11:00\",\"h\",[\"11:10\"]],[\"11:30\",\"o\",[\"11:36\",\"11:38\"]],\n    [\"13:00\",\"h\",[\"13:10\"]],[\"13:30\",\"o\",[\"13:36\",\"13:38\"]],[\"14:00\",\"h\",[\"14:10\"]],\n    [\"14:30\",\"o\",[\"14:36\",\"14:38\"]],[\"15:00\",\"h\",[\"15:10\"]],\n    [\"16:30\",\"o\",[\"16:36\",\"16:38\"]],[\"17:00\",\"h\",[\"17:10\"]],[\"17:30\",\"o\",[\"17:36\",\"17:38\"]],\n    [\"18:00\",\"h\",[\"18:10\"]],[\"18:30\",\"o\",[\"18:36\",\"18:38\"]],[\"19:00\",\"h\",[\"19:10\"]],\n    [\"19:30\",\"o\",[\"19:36\",\"19:38\"]],[\"20:00\",\"h\",[\"20:10\"]],\n  ],\n  saturday: [\n    [\"09:00\",\"o\",[\"09:06\",\"09:08\"]],[\"09:30\",\"h\",[\"09:40\"]],[\"10:00\",\"o\",[\"10:06\",\"10:08\"]],\n    [\"10:30\",\"h\",[\"10:40\"]],[\"11:00\",\"o\",[\"11:06\",\"11:08\"]],[\"11:30\",\"h\",[\"11:40\"]],\n    [\"12:00\",\"o\",[\"12:06\",\"12:08\"]],[\"12:30\",\"h\",[\"12:40\"]],\n    [\"14:00\",\"o\",[\"14:06\",\"14:08\"]],[\"14:30\",\"h\",[\"14:40\"]],[\"15:00\",\"o\",[\"15:06\",\"15:08\"]],\n    [\"15:30\",\"h\",[\"15:40\"]],[\"16:00\",\"o\",[\"16:06\",\"16:08\"]],[\"16:30\",\"h\",[\"16:40\"]],\n    [\"18:00\",\"o\",[\"18:06\",\"18:08\"]],[\"18:30\",\"h\",[\"18:40\"]],[\"19:00\",\"o\",[\"19:06\",\"19:08\"]],\n    [\"19:30\",\"h\",[\"19:40\"]],[\"20:00\",\"o\",[\"20:06\",\"20:08\"]],[\"20:30\",\"h\",[\"20:40\"]],\n  ],\n};\nconst DEST = { o:{name:\"Outram\",stop:\"Exit 6/7\"}, h:{name:\"Harbourfront\",stop:\"Exit D\"} };\nconst LAST = { weekday:\"20:15\", saturday:\"20:45\" };\n\nfunction nowSGT(){\n  const p = new Intl.DateTimeFormat(\"en-GB\",{timeZone:\"Asia/Singapore\",weekday:\"short\",hour:\"2-digit\",minute:\"2-digit\",hour12:false}).formatToParts(new Date());\n  const g = ty => p.find(x=>x.type===ty).value;\n  return { wd:g(\"weekday\"), mins:(+g(\"hour\"))*60+(+g(\"minute\")), hm:`${g(\"hour\")}:${g(\"minute\")}` };\n}\nfunction keyFor(wd){ return wd===\"Sat\" ? \"saturday\" : (wd===\"Sun\" ? null : \"weekday\"); }\nfunction toMin(t){ const [h,m]=t.split(\":\").map(Number); return h*60+m; }\nfunction cd(min){ if(min<1) return \"即将\"; if(min<60) return min+\" 分钟\"; const h=Math.floor(min/60),m=min%60; return m? `${h}时${m}分` : `${h}小时`; }\nfunction nextBy(key, now, mode){ // mode \"go\": 按发车; \"ret\": 按到站(=上车)\n  const nx = {};\n  for(const r of SCHED[key]){ const t = mode===\"go\" ? toMin(r[0]) : toMin(r[2][0]); if(t>=now && !(r[1] in nx)) nx[r[1]]=r; }\n  return nx;\n}\n\n// ---------- 颜色 / 字体 ----------\nconst C = {\n  fg:  Color.dynamic(new Color(\"#15161a\"), new Color(\"#eceef4\")),\n  mut: Color.dynamic(new Color(\"#646878\"), new Color(\"#9298a8\")),\n  bg:  Color.dynamic(new Color(\"#ffffff\"), new Color(\"#11141c\")),\n  acc: new Color(\"#10b981\"),\n};\n\nconst now = nowSGT();\nconst key = keyFor(now.wd);\nconst fam = config.widgetFamily || \"medium\";\n\nconst w = new ListWidget();\nw.backgroundColor = C.bg;\nw.setPadding(15, 17, 15, 17);\nw.url = \"https://asrlife.vip/bus.html\";\nw.refreshAfterDate = new Date(Date.now() + 2*60*1000);   // ~2 分钟刷新（iOS 实际节奏由系统控制）\n\n// 头部\nconst wmap = {Mon:\"周一\",Tue:\"周二\",Wed:\"周三\",Thu:\"周四\",Fri:\"周五\",Sat:\"周六\",Sun:\"周日\"};\nconst hd = w.addStack(); hd.centerAlignContent();\nconst ti = hd.addText(\"🚌  ASR 班车\"); ti.font = Font.boldSystemFont(15); ti.textColor = C.fg;\nhd.addSpacer();\nconst tt = hd.addText(`${now.hm} ${wmap[now.wd]}`); tt.font = Font.systemFont(11); tt.textColor = C.mut;\nw.addSpacer(fam===\"small\" ? 8 : 11);\n\nfunction busLine(arrow, name, timeText, mins){\n  const s = w.addStack(); s.centerAlignContent();\n  const a = s.addText(arrow); a.font = Font.systemFont(12); a.textColor = C.mut;\n  s.addSpacer(5);\n  const nm = s.addText(name); nm.font = Font.systemFont(12.5); nm.textColor = C.mut;\n  s.addSpacer();\n  if(timeText){\n    const tm = s.addText(timeText); tm.font = Font.boldSystemFont(14); tm.textColor = (mins!=null && mins<=15) ? C.acc : C.fg;\n    if(mins!=null){ s.addSpacer(6); const c = s.addText(cd(mins)); c.font = Font.systemFont(11); c.textColor = C.mut; }\n  } else {\n    const o = s.addText(\"今日收班\"); o.font = Font.systemFont(12); o.textColor = C.mut;\n  }\n}\nfunction section(title, nx, mode){\n  const h = w.addText(title); h.font = Font.mediumSystemFont(11); h.textColor = C.acc;\n  w.addSpacer(4);\n  for(const d of [\"o\",\"h\"]){\n    const r = nx[d];\n    if(r){ const tx = mode===\"go\" ? r[0] : r[2][0]; busLine(mode===\"go\"?\"→\":\"←\", DEST[d].name, tx, toMin(tx)-now.mins); }\n    else  { busLine(mode===\"go\"?\"→\":\"←\", DEST[d].name, null); }\n  }\n}\n\nif(key === null){\n  w.addSpacer();\n  const c = w.addText(\"🛌  周日停运\"); c.font = Font.boldSystemFont(19); c.textColor = C.mut; c.centerAlignText();\n  const sub = w.addText(\"今日无班车服务\"); sub.font = Font.systemFont(12); sub.textColor = C.mut; sub.centerAlignText();\n  w.addSpacer();\n} else {\n  const go = nextBy(key, now.mins, \"go\");\n  if(fam === \"small\"){\n    section(\"去程 → 地铁\", go, \"go\");\n  } else {\n    const ret = nextBy(key, now.mins, \"ret\");\n    section(\"去程  ASR → 地铁\", go, \"go\");\n    w.addSpacer(9);\n    section(\"返程  地铁 → ASR\", ret, \"ret\");\n  }\n  if(fam !== \"small\"){\n    w.addSpacer(8);\n    const f = w.addText(`末班 ${LAST[key]} · 点按看完整时刻表`); f.font = Font.systemFont(10); f.textColor = C.mut;\n  }\n}\n\nScript.setWidget(w);\nif(config.runsInApp){ fam===\"small\" ? w.presentSmall() : (fam===\"large\" ? w.presentLarge() : w.presentMedium()); }\nScript.complete();\n"}