◓ 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.

За да инсталирате този скрипт, трябва да инсталирате разширение, като например Tampermonkey .

За да инсталирате този скрипт, трябва да имате инсталирано разширение като 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();
})();