(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(); }
});
})();