◓ Today

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

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴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();
})();