◓ Today

A visual birds eye view of today. Uses top of Browser as a timline of year. | NOTE: Select icon toggles outline.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

You will need to install an extension such as Tampermonkey to install this script.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         ◓ Today
// @namespace    qp5.progress.weekdot
// @version      0.2
// @description  A visual birds eye view of today. Uses top of Browser as a timline of year. | NOTE: Select icon toggles outline.
// @icon         data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 60'><path d='M10 50 A40 40 0 0 1 90 50' fill='none' stroke='white' stroke-width='6'/><line x1='10' y1='50' x2='90' y2='50' stroke='white' stroke-width='6'/></svg>
// @match        *://*/*
// @run-at       document-idle
// @license      free use
// @grant        none
// ==/UserScript==

// My other scripts:
// https://greasyfork.org/en/scripts/531444-tampermonkey-hide-header
// https://greasyfork.org/en/scripts/534417-refresh-script-qa-tool

(function () {
  const SIZE = 14; // px
  const DOT_ID = "__weekProgressDot__";

  if (document.getElementById(DOT_ID)) return;

  const dot = document.createElement("div");
  dot.id = DOT_ID;
  Object.assign(dot.style, {
    position: "fixed",
    top: "0px",
    left: "0px",
    width: SIZE + "px",
    height: Math.ceil(SIZE / 2) + "px",
    background: "#fff",
    borderTopLeftRadius: SIZE + "px",
    borderTopRightRadius: SIZE + "px",
    borderBottomLeftRadius: "0",
    borderBottomRightRadius: "0",
    boxSizing: "border-box",
    border: "1px solid #000",
    transform: "translateX(-50%)",
    zIndex: "2147483647",
    pointerEvents: "auto",
  });

  // toggle outline on click
  dot.addEventListener("click", (e) => {
    e.stopPropagation();
    dot.style.border = dot.style.border ? "" : "1px solid #000";
  });

  // place after body is ready
  const place = () => document.body && document.body.appendChild(dot);
  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", place);
  } else {
    place();
  }

  // helper: same calendar day
  function sameDay(a, b) {
    return (
      a.getFullYear() === b.getFullYear() &&
      a.getMonth() === b.getMonth() &&
      a.getDate() === b.getDate()
    );
  }

  // helper: US Thanksgiving (4th Thursday in November)
  function thanksgivingDate(year) {
    const d = new Date(year, 10, 1); // November 1
    const day = d.getDay(); // 0 = Sun ... 6 = Sat
    const offsetToThu = (4 - day + 7) % 7; // 4 = Thu
    d.setDate(1 + offsetToThu + 3 * 7); // 4th Thursday
    return d;
  }

  // list of holidays for a given year. //Month minuses 1 month
  function getHolidays(year) {
    return [
      new Date(year, 0, 1), // New Year’s
      new Date(year, 1, 14), // Valentine day
      new Date(year, 2, 17), // St. Patricks
      new Date(year, 3, 20), // Easter Sunday
      new Date(year, 4, 11), // Mother's Day
      new Date(year, 4, 26), // Mem Day
      new Date(year, 5, 15), // Father's Day
      new Date(year, 4, 26), // Mem Day
      new Date(year, 6, 4), //  Indepen Day
      new Date(year, 9, 31), // Halloween
      new Date(year, 10, 27), // Indepen Day
      new Date(year, 11, 25), // Christmas
    ];
  }

  // return info about holiday status for day 'd'
    // { color: "green"|"yellow"|null, kind: "holiday"|"pre"|null }
  function holidayInfo(d) {
    const year = d.getFullYear();
    const holidays = getHolidays(year);

    for (const h of holidays) {
      if (sameDay(d, h)) {
        return { color: "green", kind: "holiday" };
      }
      const prev = new Date(h);
      prev.setDate(prev.getDate() - 1);
      if (sameDay(d, prev)) {
        return { color: "yellow", kind: "pre" };
      }
    }

    return { color: null, kind: null };
  }

  // days until next holiday (this year or next)
  function daysUntilNextHoliday(d) {
    const start = new Date(d.getFullYear(), d.getMonth(), d.getDate());
    const msPerDay = 86400000;

    function scan(year) {
      let best = null;
      for (const h of getHolidays(year)) {
        const diff = h - start;
        if (diff >= 0 && (best === null || diff < best)) {
          best = diff;
        }
      }
      return best;
    }

    let diff = scan(start.getFullYear());
    if (diff === null) {
      diff = scan(start.getFullYear() + 1);
    }

    return diff === null ? null : Math.round(diff / msPerDay);
  }




  function weekOfYear(d) {
    // ISO-like: week starts Monday; simpler: use UTC to be stable
    const t = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
    const dayNum = t.getUTCDay() || 7;
    t.setUTCDate(t.getUTCDate() + 4 - dayNum);
    const yearStart = new Date(Date.UTC(t.getUTCFullYear(), 0, 1));
    const week = Math.floor(((t - yearStart) / 86400000 + 1) / 7) + 1;
    return Math.max(1, Math.min(week, 52)); // clamp to 1..52
  }

  function positionDot() {
    const today = new Date();
    const w = Math.max(window.innerWidth, 1);
    const wk = weekOfYear(today); // 1..52
    const frac = (wk - 0.5) / 52; // middle of each week slot
    const x = Math.round(frac * w);
    dot.style.left = x + "px";

    const info = holidayInfo(today);

    if (info.color) {
      // holiday or day before
      dot.style.background = info.color;
      if (info.kind === "Holiday") {    //yellow
        dot.title = "Holiday today";    //green
      } else {
        dot.title = "Holiday tomorrow!";
      }
    } else {
      // normal day: show countdown
      dot.style.background = "#fff";
      const days = daysUntilNextHoliday(today);
      if (days === null) {
        dot.title = "No holidays found";
      } else if (days === 0) {
        dot.title = "Holiday";
      } else if (days === 1) {
        dot.title = "1 day before holiday";
      } else {
        dot.title = days + "Days before next holiday";
      }
    }
  }

  function scheduleNextDay() {
    const now = new Date();
    const next = new Date(now);
    next.setHours(24, 0, 2, 0);
    setTimeout(() => {
      positionDot();
      scheduleNextDay();
    }, next - now);
  }

  positionDot();
  scheduleNextDay();
})();