PageSourceSearch

https://www.shinbu.com.tw/wp-content/themes/shinbu/assets/home.js?ver=1790229421

js shinbu.com.tw collected 2026-10-09 04:15:02 UTC 4,949 bytes, 136 lines download raw bytes

1/**
2 * 首頁數字看板:初次捲入視野才開始跳動,結尾漸慢停止。
3 * 目標數字直接讀自 .sb-stat-v 段落的文字(後台改數字即生效);
4 * 原文含千分位逗號就以千分位顯示,沒有(如年份)就純數字。
5 * 無 JS / reduced-motion 時保持原字不動畫。
6 */
7(function () {
8	'use strict';
9
10	var els = document.querySelectorAll('.sb-stat-v');
11	if (!els.length) return;
12
13	var reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
14	if (reduced || !('IntersectionObserver' in window)) return;
15
16	var items = [];
17	els.forEach(function (el) {
18		var raw = el.textContent.trim();
19		var target = parseInt(raw.replace(/[^\d]/g, ''), 10);
20		if (!isFinite(target) || target <= 0) return;
21		items.push({ el: el, target: target, grouped: raw.indexOf(',') !== -1 });
22	});
23	if (!items.length) return;
24
25	function fmt(n, grouped) {
26		return grouped ? n.toLocaleString('en-US') : String(n);
27	}
28
29	// 會動畫的情況下,先歸零,等捲入才起跑(避免先看到最終值再跳回 0)
30	items.forEach(function (it) { it.el.textContent = fmt(0, it.grouped); });
31
32	function animate(it) {
33		var dur = 1600;
34		var start = null;
35
36		function step(ts) {
37			if (start === null) start = ts;
38			var p = Math.min((ts - start) / dur, 1);
39			// 前段接近等速,尾段漸慢收停;指數 2.5 介於 quad 與 cubic,不會拖
40			var eased = 1 - Math.pow(1 - p, 2.5);
41			it.el.textContent = fmt(Math.round(it.target * eased), it.grouped);
42			if (p < 1) requestAnimationFrame(step);
43		}
44		requestAnimationFrame(step);
45	}
46
47	// 觀察整個看板:實際捲入視野(超出底緣 10% 的緩衝)才一起起跑,只跑一次
48	var container = document.querySelector('.sb-stats') || items[0].el;
49	var fired = false;
50	var io = new IntersectionObserver(function (entries) {
51		entries.forEach(function (entry) {
52			if (entry.isIntersecting && !fired) {
53				fired = true;
54				items.forEach(animate);
55				io.disconnect();
56			}
57		});
58	}, { threshold: 0.25, rootMargin: '0px 0px -10% 0px' });
59
60	io.observe(container);
61})();
62
63/**
64 * 服務網絡地圖:Leaflet + Carto 淺灰底圖,紅點+城市標籤。
65 * 支援多實例(據點頁=世界圖+台灣圖雙聯);loc.big=1 畫加大聚合點(世界圖上的台灣)。
66 */
67(function () {
68	'use strict';
69	if ( typeof L === 'undefined' ) return;
70
71	document.querySelectorAll( '.sb-map[data-locations]' ).forEach( function ( el ) {
72		var locations;
73		try { locations = JSON.parse( el.getAttribute( 'data-locations' ) ); } catch ( e ) { return; }
74
75		// data-interactive="1"(據點頁)開放拖曳縮放;首頁維持靜態版圖
76		var interactive = el.getAttribute( 'data-interactive' ) === '1';
77		var map = L.map( el, {
78			scrollWheelZoom: false,
79			zoomControl: interactive,
80			dragging: interactive,
81			doubleClickZoom: interactive,
82			boxZoom: false,
83			keyboard: interactive,
84			touchZoom: interactive,
85			attributionControl: true,
86			zoomSnap: 0 // 允許小數 zoom:fitBounds 剛好填滿容器,不因整數捨去留大片空白
87		} );
88
89		// 底圖(2026-09-24 換掉):CARTO 開始要金鑰,沒帶金鑰的圖磚會被打上
90		// 「API KEY REQUIRED」浮水印並整張模糊。改用 Esri 的淺灰底圖 —— 免金鑰、
91		// 全球涵蓋,而且只有陸塊沒有地名,紅點與我們自己的城市標籤更醒目。
92		// 萬一哪天 Esri 也擋,連續抓不到圖磚就自動退回 Wikimedia 的 OSM 圖磚。
93		var base = L.tileLayer(
94			'https://services.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/{z}/{y}/{x}',
95			{ attribution: '&copy; Esri &copy; OpenStreetMap contributors', maxZoom: 12 }
96		).addTo( map );
97
98		var tileErrors = 0;
99		base.on( 'tileerror', function () {
100			if ( ++tileErrors < 4 ) return;   // 零星失敗不動作,連續失敗才換
101			base.off( 'tileerror' );
102			map.removeLayer( base );
103			L.tileLayer( 'https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}.png', {
104				attribution: '&copy; OpenStreetMap contributors',
105				maxZoom: 12
106			} ).addTo( map );
107		} );
108
109		var bounds = [];
110		locations.forEach( function ( loc ) {
111			bounds.push( loc.c );
112			L.circleMarker( loc.c, {
113				radius: loc.big ? 10 : 6,
114				color: '#E60012',
115				weight: loc.big ? 3 : 2,
116				fillColor: '#E60012',
117				fillOpacity: loc.big ? 0.55 : 0.85
118			} ).addTo( map ).bindTooltip( loc.n, {
119				permanent: true,
120				// 密集區可用 d 欄位指定標籤方向錯開(left/right/top/bottom)
121				direction: loc.d || 'right',
122				offset: { left: [ -8, 0 ], right: [ 8, 0 ], top: [ 0, -8 ], bottom: [ 0, 8 ] }[ loc.d ] || [ 8, 0 ],
123				className: 'sb-map-label' + ( loc.big ? ' sb-map-label--big' : '' )
124			} );
125		} );
126
127		// data-bounds:固定視野(如台灣圖框住全島,不以據點為界)
128		var fixed = el.getAttribute( 'data-bounds' );
129		if ( fixed ) {
130			try { bounds = JSON.parse( fixed ); } catch ( e ) {}
131			map.fitBounds( bounds, { padding: [ 10, 10 ] } );
132		} else {
133			map.fitBounds( bounds, { padding: [ 36, 56 ] } );
134		}
135	} );
136})();

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.