(function(){ "use strict"; var HOLIDAYS = [{"d": "2026-01-01", "n": "元旦", "w": "Thu"}, {"d": "2026-02-17", "n": "農曆年初一", "w": "Tue"}, {"d": "2026-02-18", "n": "農曆年初二", "w": "Wed"}, {"d": "2026-02-19", "n": "農曆年初三", "w": "Thu"}, {"d": "2026-04-03", "n": "清明節", "w": "Fri"}, {"d": "2026-04-04", "n": "耶穌受難節", "w": "Sat"}, {"d": "2026-04-06", "n": "復活節星期一", "w": "Mon"}, {"d": "2026-05-01", "n": "勞動節", "w": "Fri"}, {"d": "2026-05-25", "n": "佛誕", "w": "Mon"}, {"d": "2026-06-19", "n": "端午節", "w": "Fri"}, {"d": "2026-07-01", "n": "香港特別行政區成立紀念日", "w": "Wed"}, {"d": "2026-09-26", "n": "中秋節翌日", "w": "Sat"}, {"d": "2026-10-01", "n": "國慶日", "w": "Thu"}, {"d": "2026-10-19", "n": "重陽節", "w": "Mon"}, {"d": "2026-12-25", "n": "聖誕節", "w": "Fri"}, {"d": "2026-12-26", "n": "聖誕節後第一個周日", "w": "Sat"}, {"d": "2027-01-01", "n": "元旦", "w": "Fri"}, {"d": "2027-02-06", "n": "農曆年初一", "w": "Sat"}, {"d": "2027-02-08", "n": "農曆年初三", "w": "Mon"}, {"d": "2027-03-26", "n": "耶穌受難節", "w": "Fri"}, {"d": "2027-03-27", "n": "耶穌受難節翌日", "w": "Sat"}, {"d": "2027-03-29", "n": "復活節星期一", "w": "Mon"}, {"d": "2027-04-05", "n": "清明節", "w": "Mon"}, {"d": "2027-05-01", "n": "勞動節", "w": "Sat"}, {"d": "2027-05-13", "n": "佛誕", "w": "Thu"}, {"d": "2027-06-09", "n": "端午節", "w": "Wed"}, {"d": "2027-07-01", "n": "香港特別行政區成立紀念日", "w": "Thu"}, {"d": "2027-09-15", "n": "中秋節翌日", "w": "Wed"}, {"d": "2027-10-01", "n": "國慶日", "w": "Fri"}, {"d": "2027-10-08", "n": "重陽節", "w": "Fri"}, {"d": "2027-12-25", "n": "聖誕節", "w": "Sat"}, {"d": "2027-12-27", "n": "聖誕節後第一個周日", "w": "Mon"}]; var HOLIDAY_MAP = {}; HOLIDAYS.forEach(function(h){ HOLIDAY_MAP[h.d] = h; }); // ===== v6 calc (preserve) ===== window.lvCalc = function() { var start = document.getElementById('lv-start').value; var end = document.getElementById('lv-end').value; var daysPerWeek = parseInt(document.getElementById('lv-days').value); var taken = parseInt(document.getElementById('lv-taken').value || 0); var out = document.getElementById('lv-out'); if (!start || !end) { out.innerHTML = '
請填齊入職日期同計算至日期
'; return; } var s = new Date(start), e = new Date(end); if (e <= s) { out.innerHTML = '
計算至日期要晚過入職日期
'; return; } var years = (e - s) / (365.25 * 24 * 3600 * 1000); var entitled; if (years < 2) entitled = 7; else if (years < 5) entitled = 7; else if (years < 9) entitled = 11; else entitled = 14; if (daysPerWeek === 6) entitled = Math.round(entitled * 6 / 5); else if (daysPerWeek === 4) entitled = Math.round(entitled * 4 / 5); var remaining = Math.max(0, entitled - taken); var fmt = function(d){ return d.toISOString().slice(0,10); }; out.innerHTML = '
' + '
年資' + years.toFixed(1) + ' 年
' + '
應得年假' + entitled + ' 天
' + '
已放' + taken + ' 天
' + '
剩餘可放' + remaining + ' 天
' + '
'; }; // ===== v7: HK Holidays + Smart Leave Planner ===== function fmtDate(d){ var m = String(d.getMonth()+1).padStart(2,'0'); var day = String(d.getDate()).padStart(2,'0'); return d.getFullYear()+'-'+m+'-'+day; } function parseDate(s){ var p = s.split('-'); return new Date(+p[0], +p[1]-1, +p[2]); } function addDays(d, n){ var r = new Date(d); r.setDate(r.getDate()+n); return r; } function dayName(d){ return ['日','一','二','三','四','五','六'][d.getDay()]; } function isWeekend(d){ return d.getDay() === 0 || d.getDay() === 6; } function isHoliday(d){ return HOLIDAY_MAP[fmtDate(d)]; } function isOff(d){ return isWeekend(d) || isHoliday(d); } // Find best leave windows: scan 365 days, find longest off-streak reachable with N leave days function planLeave(annualDays) { var start = new Date(); var plans = []; var horizon = 365; for (var i = 0; i < horizon; i++) { var anchor = addDays(start, i); // Expand forward as long as consecutive off-days (must include at least 1 holiday) var streakEnd = anchor; var streakLen = 0; var hasHoliday = false; var leaveNeeded = 0; var cursor = anchor; while (cursor <= addDays(start, horizon)) { if (isOff(cursor)) { if (isHoliday(cursor)) hasHoliday = true; streakLen++; } else { if (hasHoliday) { leaveNeeded++; } else break; } cursor = addDays(cursor, 1); if (streakLen >= 14) break; // cap } if (hasHoliday && streakLen >= 4) { var offDays = 0; for (var j = 0; j < streakLen; j++) if (isOff(addDays(anchor, j))) offDays++; var leaveDays = streakLen - offDays; if (leaveDays > 0 && leaveDays <= annualDays) { var efficiency = streakLen / leaveDays; if (efficiency >= 2.0) { plans.push({ start: fmtDate(anchor), end: fmtDate(addDays(anchor, streakLen - 1)), total: streakLen, leave: leaveDays, eff: efficiency.toFixed(1) }); } } } } // Dedupe + sort by efficiency desc, top 8 var seen = {}; var uniq = []; plans.forEach(function(p){ if (!seen[p.start]) { seen[p.start] = 1; uniq.push(p); } }); uniq.sort(function(a,b){ return b.eff - a.eff; }); return uniq.slice(0, 8); } window.lvPlan = function() { var taken = parseInt(document.getElementById('lv-taken').value || 0); var entitledEl = document.querySelector('[data-entitled]'); var entitled = entitledEl ? parseInt(entitledEl.dataset.entitled) : 14; var remaining = entitled - taken; var out = document.getElementById('lv-plan-out'); if (remaining <= 0) { out.innerHTML = '
你今年年假已用晒,等下年度再 plan 😢
'; return; } var plans = planLeave(remaining); if (!plans.length) { out.innerHTML = '
未搵到 high-efficiency long weekend,考慮連住放假 4-5 天自己 trip
'; return; } var html = '
'; plans.forEach(function(p){ var color = p.eff >= 5 ? '#10b981' : p.eff >= 3 ? '#ff6210' : '#0891b2'; html += '
' + '
' + '
'+p.start+' 至 '+p.end+'
' + '
'+p.total+' 天連假
' + '
放 '+p.leave+' 天
' + '
效率 '+p.eff+'x
' + '
'; }); html += '
'; out.innerHTML = html; }; window.lvRenderHolidays = function() { var year = document.getElementById('lv-year').value; var list = HOLIDAYS.filter(function(h){ return h.d.indexOf(year) === 0; }); var html = '
'; list.forEach(function(h){ var d = parseDate(h.d); var isLong = (h.n.indexOf('農曆年初') === 0 || h.n === '清明節' || h.n === '聖誕節' || h.n === '國慶日' || h.n === '勞動節'); var bg = isLong ? '#fef3c7' : '#f0f9ff'; var fg = isLong ? '#78350f' : '#075985'; html += '
' + '
'+h.n+'
' + '
'+h.d.slice(5)+' ('+dayName(d)+')
' + '
'; }); html += '
'; document.getElementById('lv-hol-list').innerHTML = html; }; // Auto-init document.addEventListener('DOMContentLoaded', function(){ var today = new Date(); var oneYr = new Date(today); oneYr.setFullYear(oneYr.getFullYear()+1); var sd = document.getElementById('lv-start'); var ed = document.getElementById('lv-end'); if (sd && !sd.value) sd.value = fmtDate(addDays(today, -365)); if (ed && !ed.value) ed.value = fmtDate(today); var yr = document.getElementById('lv-year'); if (yr) { yr.value = today.getFullYear(); lvRenderHolidays(); } }); })();