(function (global) { "use strict"; /* ---------------------------------------------------------------- helpers */ function number(value) { const parsed = Number(value); return Number.isFinite(parsed) ? parsed : 0; } function escapeHtml(value) { return String(value == null ? "" : value).replace(/[&<>"']/g, function (ch) { return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[ch]; }); } function formatNumber(value, digits) { return new Intl.NumberFormat("zh-CN", { minimumFractionDigits: digits, maximumFractionDigits: digits, }).format(number(value)); } function changeClass(value) { const n = number(value); return n > 0 ? "up" : n < 0 ? "down" : ""; } function streakLabel(streak) { const value = Math.max(1, number(streak)); return value === 1 ? "首板" : value + "板"; } function displayCompactDate(value) { const text = String(value || "").replaceAll("-", ""); if (text.length !== 8) return value || "--"; return text.slice(0, 4) + "-" + text.slice(4, 6) + "-" + text.slice(6, 8); } function localDateString(value) { const year = value.getFullYear(); const month = String(value.getMonth() + 1).padStart(2, "0"); const day = String(value.getDate()).padStart(2, "0"); return year + "-" + month + "-" + day; } function todayString() { return localDateString(new Date()); } function parseLocalDate(value) { const parts = String(value || "").split("-").map(Number); return new Date(parts[0], (parts[1] || 1) - 1, parts[2] || 1); } function addDays(value, delta) { const date = parseLocalDate(value); date.setDate(date.getDate() + delta); return localDateString(date); } function previousWeekday(value) { let date = parseLocalDate(value); date.setDate(date.getDate() - 1); while (date.getDay() === 0 || date.getDay() === 6) date.setDate(date.getDate() - 1); return localDateString(date); } const ICONS = { calendar: '', close: '', "chevron-left": '', "chevron-right": '', inbox: '', }; function icon(name, size) { const body = ICONS[name] || ""; return '"; } /* ---------------------------------------------------------------- state */ const state = { key: "", requestedDate: "", dashboard: null, popularity: null, popularitySource: "combined", seq: 0, calCursor: null, }; let sheetToken = 0; function pageConfig(key) { const cfg = global.MobileNav && global.MobileNav.tableColumns ? global.MobileNav.tableColumns[key] : null; return cfg || null; } function columnsFor(cfg) { if (cfg.columns) return cfg.columns[state.popularitySource] || cfg.columns.combined; return cfg; } /* ---------------------------------------------------------------- cells */ function columnWidth(col) { if (col.width) return col.width; if (col.type === "rank") return 40; if (col.type === "stock") return 96; if (col.wide) return 140; if (col.type === "text" || col.type === "concepts" || col.type === "outcome" || col.type === "dual") return 96; return 72; } function colAlign(col) { if (col.type === "rank") return "m-align-center"; if (["change", "price", "rate", "money", "gap", "int", "streak", "advance", "height", "move"].indexOf(col.type) >= 0) return "m-align-right"; return "m-align-left"; } function stockCell(row) { return '' + '' + escapeHtml(row.name || "--") + "" + '' + escapeHtml(row.code || "") + "" + ""; } function rankCell(row, index) { const value = number(row.rank) > 0 ? number(row.rank) : index + 1; const hot = index < 3 ? '' : ""; return '' + value + "" + hot + ""; } function streakCell(value) { const n = number(value); if (n <= 0) return ""; const high = n >= 4 ? " is-high" : ""; return '' + streakLabel(n) + ""; } function numCell(value, digits, signed) { if (value == null || value === "") return ''; const n = number(value); const sign = signed && n > 0 ? "+" : ""; return '' + sign + formatNumber(n, digits) + ""; } function changeCell(value) { if (value == null || value === "") return ''; const n = number(value); return '' + (n > 0 ? "+" : "") + formatNumber(n, 2) + ""; } function intCell(value, hideZero) { const n = Math.round(number(value)); if (hideZero && n === 0) return ''; if (value == null || value === "") return ''; return '' + n.toLocaleString("zh-CN") + ""; } function advanceCell(value) { const n = number(value); const cls = n === 0 ? "is-neutral" : n < 20 ? "is-warning" : "is-active"; return '' + formatNumber(n, 1) + ""; } function outcomeCell(value) { const map = { "晋级": "advance", "断板": "fail", "炸板": "broken", "跌停": "down" }; const cls = map[value] || "fail"; return '' + escapeHtml(value || "") + ""; } function heightCell(value) { const n = number(value); if (n <= 0) return ""; return '' + n + ""; } function moveCell(value) { const n = value == null ? null : number(value); if (n == null) return ''; if (n > 0) return '\u2191' + n + ""; if (n < 0) return '\u2193' + Math.abs(n) + ""; return '持平'; } function conceptsCell(value) { const list = Array.isArray(value) ? value : []; const text = list.slice(0, 3).join("、"); return '' + escapeHtml(text) + ""; } function dualCell(value) { const dual = Boolean(value); return '' + (dual ? "双榜共识" : "单榜入选") + ""; } function textCell(value) { const text = String(value == null ? "" : value); return '' + escapeHtml(text) + ""; } function cellHtml(col, row, index) { const value = row[col.key]; switch (col.type) { case "stock": return stockCell(row); case "rank": return rankCell(row, index); case "streak": return streakCell(value); case "change": return changeCell(value); case "price": return numCell(value, 2); case "rate": return numCell(value, 2); case "money": return numCell(value, 2); case "gap": return numCell(value, 2); case "int": return intCell(value, col.hideZero); case "advance": return advanceCell(value); case "outcome": return outcomeCell(value); case "height": return heightCell(value); case "move": return moveCell(value); case "concepts": return conceptsCell(value); case "dual": return dualCell(value); case "text": default: return textCell(value); } } /* ---------------------------------------------------------------- table */ function buildTable(cols, rows) { const frozen = cols.frozenColumns || []; const primary = cols.primaryColumns || []; const scroll = cols.scrollColumns || []; const ordered = frozen.concat(primary, scroll); let left = 0; const frozenLeft = frozen.map(function (col) { const offset = left; left += columnWidth(col); return offset; }); function cellOpen(col, index, isHead) { const isFrozen = index < frozen.length; const width = columnWidth(col); const clamp = col.type === "text" || col.type === "concepts" ? ";max-width:" + width + "px" : ""; const style = "min-width:" + width + "px" + clamp + (isFrozen ? ";width:" + width + "px;left:" + frozenLeft[index] + "px" : ""); const cls = (isHead ? "m-th" : "m-td") + " " + colAlign(col) + (isFrozen ? " m-frozen" : ""); return '<' + (isHead ? "th" : "td") + ' class="' + cls + '" style="' + style + '">'; } const head = ordered.map(function (col, i) { return cellOpen(col, i, true) + escapeHtml(col.label) + (i < frozen.length ? "" : ""); }).join(""); const body = rows.map(function (row, rIndex) { const cells = ordered.map(function (col, i) { return cellOpen(col, i, false) + cellHtml(col, row, rIndex) + ""; }).join(""); const code = /^\d{6}$/.test(String(row.code || "")) ? ' data-code="' + escapeHtml(row.code) + '"' : ""; return "" + cells + ""; }).join(""); return '' + head + "" + body + "
"; } function skeletonHtml(rowCount) { const rows = []; for (let i = 0; i < rowCount; i += 1) { rows.push('
'); } return '"; } function emptyHtml() { return '
' + '' + icon("inbox", 26) + "" + "

该交易日暂无相关数据

" + '可点右上角日期切换交易日' + "
"; } function errorHtml(message) { return '
' + '

' + escapeHtml(message || "数据加载失败") + "

" + '' + "
"; } /* ---------------------------------------------------------------- summary */ function summaryCard(label, value, tone) { const cls = tone === "up" ? " is-up" : tone === "down" ? " is-down" : ""; return '
' + escapeHtml(label) + "" + escapeHtml(value) + "
"; } function buildSummary(key, cfg) { const dash = state.dashboard || {}; const overview = dash.overview || {}; const cards = []; if (cfg.summary === "limit") { const limits = dash.limits || []; const maxStreak = limits.reduce(function (m, r) { return Math.max(m, number(r.streak)); }, 0); const seal = overview.seal_rate != null ? formatNumber(overview.seal_rate, 1) + "%" : "--"; cards.push(summaryCard("涨停家数", limits.length, "up")); cards.push(summaryCard("最高连板", maxStreak ? maxStreak + " 板" : "--")); cards.push(summaryCard("封板率", seal)); } else if (cfg.summary === "broken") { const broken = dash.broken || []; const limits = dash.limits || []; const seal = overview.seal_rate != null ? formatNumber(overview.seal_rate, 1) + "%" : "--"; cards.push(summaryCard("炸板家数", broken.length, "down")); cards.push(summaryCard("涨停家数", overview.limit_up_count != null ? overview.limit_up_count : limits.length, "up")); cards.push(summaryCard("封板率", seal)); } else if (cfg.summary === "down") { const downs = dash.down_limits || []; const maxStreak = downs.reduce(function (m, r) { return Math.max(m, number(r.streak)); }, 0); const cluster = downSectorCluster(downs); cards.push(summaryCard("跌停家数", downs.length, "down")); cards.push(summaryCard("连续跌停", maxStreak ? maxStreak + " 天" : "--")); cards.push(summaryCard("集中板块", cluster || "--")); } else if (cfg.summary === "yesterday") { const rows = dash.yesterday_limits || []; const total = rows.length; const advance = rows.filter(function (r) { return r.outcome === "晋级"; }).length; const positive = rows.filter(function (r) { return number(r.current_change) > 0; }).length; const risk = rows.filter(function (r) { return ["炸板", "跌停"].indexOf(r.outcome) >= 0; }).length; const rate = function (v) { return total ? formatNumber(v / total * 100, 1) + "%" : "--"; }; cards.push(summaryCard("晋级率", rate(advance), "up")); cards.push(summaryCard("兑现率", rate(positive), "up")); cards.push(summaryCard("亏钱效应", rate(risk), "down")); } else if (cfg.summary === "performance") { const up = number(overview.up_count); const down = number(overview.down_count); const ratio = up + down > 0 ? formatNumber(up / (up + down) * 100, 1) + "%" : "--"; cards.push(summaryCard("上涨", up + " 家", "up")); cards.push(summaryCard("下跌", down + " 家", "down")); cards.push(summaryCard("红盘率", ratio, "up")); } else { return ""; } if (!cards.length) return ""; return '
' + cards.join("") + "
"; } function downSectorCluster(rows) { const counts = {}; rows.forEach(function (row) { const sector = String(row.sector || "其他").trim() || "其他"; if (sector === "其他") return; counts[sector] = (counts[sector] || 0) + 1; }); let cluster = null; Object.keys(counts).forEach(function (sector) { if (!cluster || counts[sector] > cluster.count) cluster = { sector: sector, count: counts[sector] }; }); return cluster && cluster.count >= 2 ? cluster.sector + " \u00d7" + cluster.count : ""; } /* ---------------------------------------------------------------- rows */ function brokenLimitRate(row) { const name = String(row.name || "").toUpperCase(); const code = String(row.code || "").replace(/\D/g, ""); if (name.indexOf("ST") >= 0) return 10; if (/^(300|301|688|689)/.test(code)) return 20; if (/^(4|8|92)/.test(code)) return 30; return 10; } function prepareRows(key) { const dash = state.dashboard || {}; if (key === "market/limit-up") return dash.limits || []; if (key === "market/broken") { return (dash.broken || []).map(function (row) { row.limitGap = Math.max(0, brokenLimitRate(row) - number(row.change)); return row; }); } if (key === "market/limit-down") return dash.down_limits || []; if (key === "market/yesterday") return dash.yesterday_limits || []; if (key === "market/performance") return normalizePerformanceRows(dash.limit_performance || []); if (key === "market/popularity") { return state.popularity ? (state.popularity[state.popularitySource] || []) : []; } return []; } function normalizePerformanceRows(rows) { const groups = {}; (rows || []).forEach(function (row) { const level = Math.max(1, number(row.level)); const displayLevel = Math.min(level, 5); const group = groups[displayLevel] || { level: displayLevel, label: displayLevel === 1 ? "昨日首板" : displayLevel === 5 ? "昨日5板+" : "昨日" + displayLevel + "板", count: 0, advanced: 0, positive: 0, changeTotal: 0, }; const count = number(row.count); group.count += count; group.advanced += number(row.advanced); group.positive += count * number(row.positive_rate) / 100; group.changeTotal += count * number(row.average_change); groups[displayLevel] = group; }); return Object.keys(groups) .map(Number) .sort(function (a, b) { return b - a; }) .map(function (level) { const group = groups[level]; return { level: group.level, label: group.label, count: group.count, advanced: group.advanced, advance_rate: group.count ? group.advanced / group.count * 100 : 0, positive_rate: group.count ? group.positive / group.count * 100 : 0, average_change: group.count ? group.changeTotal / group.count : 0, }; }); } function performanceConclusion() { const overview = (state.dashboard && state.dashboard.overview) || {}; const up = number(overview.up_count); const down = number(overview.down_count); const breadthRate = up + down > 0 ? up / (up + down) * 100 : 50; const stance = breadthRate < 25 ? "宜守不宜攻" : breadthRate < 45 ? "控制仓位,聚焦核心" : "保持精选,跟随强势梯队"; const phase = overview.sentiment_phase || "观察"; return '
结论:' + escapeHtml(stance) + ",当前情绪周期「" + escapeHtml(phase) + "」。
"; } /* ---------------------------------------------------------------- render */ function updateHeader(title, dateText) { if (global.MobileRouter && global.MobileRouter.updateHeader) { global.MobileRouter.updateHeader({ title: title, back: true, actions: dateButtonHtml(dateText) }); } } function dateButtonHtml(dateText) { return '"; } function sourceTabsHtml(cfg) { if (!cfg.sources || !cfg.sources.length) return ""; const labels = { combined: "双榜综合", ths: "同花顺", dc: "东方财富" }; return '
' + cfg.sources.map(function (source) { const active = source === state.popularitySource; return '"; }).join("") + "
"; } function renderPage(key) { const cfg = pageConfig(key); if (!cfg) return; state.key = key; state.requestedDate = todayString(); state.dashboard = null; state.popularity = null; state.popularitySource = "combined"; state.calCursor = null; document.getElementById("m-view").classList.add("m-view-feature"); const title = findLabel(key) || key; updateHeader(title, state.requestedDate); const summary = buildSummary(key, cfg); const sourceTabs = sourceTabsHtml(cfg); const content = '
' + summary + sourceTabs + '
' + skeletonHtml(8) + "
" + "
"; document.getElementById("m-view").innerHTML = content; load(); } function findLabel(key) { const hubs = global.MobileNav && global.MobileNav.hubs ? global.MobileNav.hubs : {}; for (const hubKey in hubs) { const items = hubs[hubKey].items || []; for (const item of items) { if (item.key === key) return item.label; } } return key; } function load() { const key = state.key; const cfg = pageConfig(key); if (!cfg) return; const seq = ++state.seq; const requestedDate = state.requestedDate; const url = key === "market/popularity" ? "/api/popularity?trade_date=" + encodeURIComponent(requestedDate) : "/api/dashboard?trade_date=" + encodeURIComponent(requestedDate); global.MobileAPI.request(url).then(function (payload) { if (seq !== state.seq) return; if (key === "market/popularity") { state.popularity = payload; } else { state.dashboard = payload; } const meta = payload.meta || {}; state.requestedDate = displayCompactDate(meta.requested_date || meta.trade_date || requestedDate); const title = findLabel(key) || key; updateHeader(title, state.requestedDate); renderData(key, cfg); }).catch(function (error) { if (seq !== state.seq) return; renderError(error && error.message ? error.message : "数据加载失败"); }); } function renderData(key, cfg) { const cols = columnsFor(cfg); const rows = prepareRows(key); const scroll = document.getElementById("m-table-scroll"); if (!scroll) return; if (!rows.length) { scroll.innerHTML = emptyHtml(); } else { scroll.innerHTML = buildTable(cols, rows); } const page = document.querySelector(".m-page"); if (!page) return; let summary = buildSummary(key, cfg); if (key === "market/performance" && summary) summary += performanceConclusion(); const existingSummary = page.querySelector(".m-summary"); if (existingSummary) { existingSummary.outerHTML = summary || ""; } else if (summary) { const tabs = page.querySelector(".m-source-tabs"); const tmp = document.createElement("div"); tmp.innerHTML = summary; const node = tmp.firstChild; page.insertBefore(node, tabs || scroll); } scroll.classList.remove("m-motion-fade-in"); void scroll.offsetWidth; scroll.classList.add("m-motion-fade-in"); } function renderError(message) { const scroll = document.getElementById("m-table-scroll"); if (scroll) scroll.innerHTML = errorHtml(message); const page = document.querySelector(".m-page"); if (page) { const summary = page.querySelector(".m-summary"); if (summary) summary.remove(); } } /* ---------------------------------------------------------------- sheets */ function ensureSheetRoot() { let root = document.getElementById("m-sheet-root"); if (!root) { root = document.createElement("div"); root.id = "m-sheet-root"; root.className = "m-sheet-root"; document.getElementById("m-app").appendChild(root); } return root; } function openSheet(content, opts) { const root = ensureSheetRoot(); sheetToken += 1; root.innerHTML = '
' + '"; global.requestAnimationFrame(function () { root.classList.add("is-open"); }); bindSheetDrag(root); } function closeSheet() { const root = document.getElementById("m-sheet-root"); if (!root) return; const token = sheetToken; root.classList.remove("is-open"); global.setTimeout(function () { if (sheetToken === token && !root.classList.contains("is-open")) root.innerHTML = ""; }, 340); } function bindSheetDrag(root) { const sheet = root.querySelector(".m-sheet"); const handle = root.querySelector(".m-sheet-handle"); if (!sheet || !handle) return; let startY = 0; let dragging = false; handle.addEventListener("touchstart", function (event) { dragging = true; startY = event.touches[0].clientY; sheet.style.transition = "none"; }, { passive: true }); handle.addEventListener("touchmove", function (event) { if (!dragging) return; const dy = event.touches[0].clientY - startY; if (dy > 0) sheet.style.transform = "translateY(" + dy + "px)"; }, { passive: true }); handle.addEventListener("touchend", function (event) { if (!dragging) return; dragging = false; sheet.style.transition = ""; const dy = event.changedTouches[0].clientY - startY; if (dy > sheet.offsetHeight / 3) { closeSheet(); } else { sheet.style.transform = ""; } }); } function selectDate(dateStr) { if (!dateStr) return; state.requestedDate = dateStr; closeSheet(); load(); } function openDateSheet() { state.calCursor = { year: parseLocalDate(state.requestedDate).getFullYear(), month: parseLocalDate(state.requestedDate).getMonth(), }; openSheet( '
' + '

选择日期

' + '" + "
" + '
', { detail: false } ); renderDateSheetBody(); } function renderDateSheetBody() { const body = document.getElementById("m-date-sheet-body"); if (!body || !state.calCursor) return; const year = state.calCursor.year; const month = state.calCursor.month; const today = todayString(); const now = parseLocalDate(today); const currentMonth = now.getFullYear() * 12 + now.getMonth(); const cursorMonth = year * 12 + month; const prevDisabled = cursorMonth <= currentMonth - 12; const nextDisabled = cursorMonth >= currentMonth; const quick = [ { label: "今天", date: todayString() }, { label: "昨天", date: addDays(today, -1) }, { label: "前一交易日", date: previousTradeDate() }, ]; const quickHtml = quick.map(function (item) { return '"; }).join(""); body.innerHTML = '
' + quickHtml + "
" + '
' + '" + '' + year + " 年 " + (month + 1) + " 月" + '" + "
" + '
' + ["一", "二", "三", "四", "五", "六", "日"].map(function (d) { return "" + d + ""; }).join("") + "
" + '
' + calendarCells(year, month) + "
"; } function calendarCells(year, month) { const first = new Date(year, month, 1); const startWeekday = (first.getDay() + 6) % 7; const daysInMonth = new Date(year, month + 1, 0).getDate(); const today = todayString(); const cells = []; for (let i = 0; i < startWeekday; i += 1) cells.push(''); for (let d = 1; d <= daysInMonth; d += 1) { const date = new Date(year, month, d); const dateStr = localDateString(date); const dow = date.getDay(); const disabled = dow === 0 || dow === 6 || dateStr > today; const selected = dateStr === state.requestedDate; cells.push( '" ); } return cells.join(""); } function previousTradeDate() { const dash = state.dashboard || {}; if (dash.meta && dash.meta.previous_trade_date) return displayCompactDate(dash.meta.previous_trade_date); if (state.popularity && state.popularity.meta && state.popularity.meta.previous_trade_date) { return displayCompactDate(state.popularity.meta.previous_trade_date); } return previousWeekday(todayString()); } function openDetailSheet(code) { openSheet( '
' + '
' + escapeHtml(code) + '
' + '" + "
" + '
' + '
' + '
' + '
' + '
' + "
" + "
", { detail: true } ); loadDetail(code); } function loadDetail(code) { const token = sheetToken; global.MobileAPI.request("/api/stock/" + encodeURIComponent(code) + "/preview").then(function (payload) { if (token !== sheetToken) return; renderDetail(payload); }).catch(function (error) { if (token !== sheetToken) return; const body = document.getElementById("m-detail-sheet-body"); if (body) body.innerHTML = errorHtml(error && error.message ? error.message : "行情预览加载失败"); }); } function renderDetail(payload) { const body = document.getElementById("m-detail-sheet-body"); if (!body) return; const stock = payload.stock || {}; const prices = payload.prices || []; const meta = payload.meta || {}; const name = stock.name && stock.name !== "--" ? stock.name : "--"; const price = number(stock.price); const change = number(stock.change); const industry = stock.industry && stock.industry !== "其他" ? stock.industry : (stock.sector || "其他"); const spark = sparklineHtml(prices); body.innerHTML = '
' + '
' + escapeHtml(name) + '' + escapeHtml(stock.code || "") + "
" + '
' + '' + (price ? formatNumber(price, 2) : "--") + "" + '' + (change > 0 ? "+" : "") + formatNumber(change, 2) + "%" + "
" + '
' + escapeHtml(industry) + " · " + escapeHtml(displayCompactDate(meta.trade_date) || "--") + "
" + "
" + spark + '
' + escapeHtml(meta.notice || "数据来源:本地行情") + "
"; } function sparklineHtml(prices) { const closes = (prices || []).map(function (bar) { return number(bar.close); }).filter(function (v) { return v > 0; }); if (closes.length < 2) return ""; const min = Math.min.apply(null, closes); const max = Math.max.apply(null, closes); const range = max - min || 1; const width = 320; const height = 96; const step = width / (closes.length - 1); const points = closes.map(function (value, i) { const x = Math.round(i * step); const y = Math.round(height - (value - min) / range * height); return x + "," + y; }).join(" "); const up = closes[closes.length - 1] >= closes[0]; return '
"; } /* ---------------------------------------------------------------- events */ function bindEvents() { window.addEventListener("hashchange", closeSheet); document.addEventListener("click", function (event) { const dateBtn = event.target.closest("[data-action=date]"); if (dateBtn) { openDateSheet(); return; } const retry = event.target.closest("[data-action=retry]"); if (retry) { load(); return; } const sourceTab = event.target.closest("[data-action=source]"); if (sourceTab) { state.popularitySource = sourceTab.dataset.source || "combined"; const cfg = pageConfig(state.key); if (cfg) { const rows = prepareRows(state.key); const scroll = document.getElementById("m-table-scroll"); if (scroll) scroll.innerHTML = rows.length ? buildTable(columnsFor(cfg), rows) : emptyHtml(); document.querySelectorAll(".m-source-tab").forEach(function (tab) { const active = tab.dataset.source === state.popularitySource; tab.classList.toggle("active", active); tab.setAttribute("aria-selected", String(active)); }); } return; } const closeBtn = event.target.closest("[data-sheet-close]"); if (closeBtn) { closeSheet(); return; } const backdrop = event.target.closest("[data-sheet-backdrop]"); if (backdrop) { closeSheet(); return; } const quick = event.target.closest("[data-quick-date]"); if (quick) { selectDate(quick.dataset.quickDate); return; } const calNav = event.target.closest("[data-cal-nav]"); if (calNav) { if (calNav.disabled) return; const dir = calNav.dataset.calNav === "next" ? 1 : -1; const monthIndex = state.calCursor.year * 12 + state.calCursor.month + dir; state.calCursor = { year: Math.floor(monthIndex / 12), month: ((monthIndex % 12) + 12) % 12 }; renderDateSheetBody(); return; } const calCell = event.target.closest("[data-date]"); if (calCell) { if (calCell.disabled) return; selectDate(calCell.dataset.date); return; } const row = event.target.closest("[data-code]"); if (row) { const interactive = event.target.closest("button, a, input, select, textarea"); if (!interactive) openDetailSheet(row.dataset.code); } }); } /* ---------------------------------------------------------------- init */ bindEvents(); global.MobilePages = { render: renderPage, has: function (key) { return Boolean(pageConfig(key)); }, }; })(window);