Sabrina-Online.com – Colored Hi-res [Ath]

Sabrina-Online.com enhancements for old strips: always load hi-res images, load enhanced fan-colored images when available, keyboard navigation.

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

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

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

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

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name           Sabrina-Online.com – Colored Hi-res [Ath]
// @description    Sabrina-Online.com enhancements for old strips: always load hi-res images, load enhanced fan-colored images when available, keyboard navigation.
// @namespace      athari
// @author         Athari (https://github.com/Athari)
// @copyright      © Prokhorov ‘Athari’ Alexander, 2025–2025
// @license        MIT
// @homepageURL    https://github.com/Athari/AthariUserJS
// @supportURL     https://github.com/Athari/AthariUserJS/issues
// @version        1.0.0
// @icon           https://www.google.com/s2/favicons?sz=64&domain=sabrina-online.com
// @match          *://*.sabrina-online.com/*
// @match          *://*.sabrinaonline2k.net/*
// @grant          unsafeWindow
// @grant          GM_info
// @run-at         document-start
// @require        https://cdn.jsdelivr.net/npm/[email protected]/dist/string.min.js
// @require        https://cdn.jsdelivr.net/npm/@athari/[email protected]/monkeyutils.u.min.js
// @tag            athari
// ==/UserScript==

(async () => {
  'use strict'

  const scrollOpts = { behavior: 'smooth' };
  const colorSrcBase = "http://www.sabrinaonline2k.net/MHA/bigstrips";

  const { waitForDocumentReady, h, u, f, attempt, els } =
    //require("../@athari-monkeyutils/monkeyutils.u"); // TODO
    athari.monkeyutils;

  const eld = doc => els(doc, {
    strip: "img:is([src^='strips/'], [src*='/strips/'], [src^='../strips/'])",
    linkedStrip: "a[href^='strips/']:has(> img:is([src^='strips/'], [src*='/strips/'])), a[href^='../bigstrips']:has(> img[src^='../strips/'])",
    lnkPrevPage: "a:has(img[alt='back' i])", lnkNextPage: "a:has(img[alt='next' i])",
  }), el = eld(document);

  S.extendPrototype();
  console.debug("GM info", GM_info);

  await waitForDocumentReady();

  el.tag.head.insertAdjacentHTML('beforeEnd', /*html*/`
    <style>
      :root {
        color-scheme: light dark;
      }
      .ath-strip {
        display: grid;
        margin: 0 auto;
        img {
          grid-area: 1 / 1;
          display: block;
          max-width: calc(100vw - 140px);
          &.ath-image-bw {
            filter: url(#filter-bw);
          }
          &.ath-image-color {
            filter: url(#filter-color);
            mix-blend-mode: multiply;
          }
        }
        &:hover img.ath-image-color {
          opacity: 0;
        }
        + br {
          display: none;
        }
      }
      .ath-warn {
        position: absolute;
        inset: 8px 8px auto auto;
        text-align: right;
        font-size: .8rem;
        font-weight: bold;
        color: red;
        em {
          opacity: 0.6;
        }
      }
    </style>`);
  el.tag.body.insertAdjacentHTML('beforeEnd', /*html*/`
    <svg xmlns="http://www.w3.org/2000/svg" style="display: none">
      <filter id="filter-bw">
      </filter>
      <filter id="filter-color">
        <feGaussianBlur stdDeviation="1" />
        <feMorphology operator="dilate" radius="1.5" />
        <feGaussianBlur stdDeviation="2" />
      </filter>
    </svg>`);

  if (el.linkedStrip && location.protocol === 'https:') {
    el.tag.body.insertAdjacentHTML('beforeBegin', /*html*/`
      <p class="ath-warn">
        <a href="${location.href.replace("https:", "http:")}">Switch to HTTP</a>
        to access fan-colored strips<br>
        <em>(or enable insecure content in site options)</em>
      </p>`);
  }

  const onKeyDown = {
    'ArrowLeft': e => {
      if (!el.lnkPrevPage)
        return;
      e.preventDefault();
      location = el.lnkPrevPage.href;
    },
    'ArrowRight': e => {
      if (!el.lnkNextPage)
        return;
      e.preventDefault();
      location = el.lnkNextPage.href;
    },
    'Space': e => {
      if (el.strip) {
        const strips = el.all.strip.map(el => ({ el, rect: el.getBoundingClientRect() }));
        const currentStripIndex = strips.findIndex(s => s.rect.top >= -10);
        const currentStrip = strips[currentStripIndex];
        const nextStrip = strips[currentStripIndex + 1];
        if (currentStrip.rect.bottom > window.innerHeight) {
          e.preventDefault();
          if (currentStrip.rect.top > 0)
            currentStrip.el.scrollIntoView(scrollOpts);
          else
            window.scrollBy({ top: window.innerHeight, ...scrollOpts });
          return;
        }
        else if (nextStrip) {
          e.preventDefault();
          nextStrip.el.scrollIntoView(scrollOpts);
          return;
        }
      }
      if (el.lnkNextPage) {
        if (window.innerHeight + window.pageYOffset >= document.body.offsetHeight - 50) {
          e.preventDefault();
          location = el.lnkNextPage.href;
        }
      }
    },
  };

  const modKeys = [ 'Meta', 'Ctrl', 'Alt', 'Shift' ].map(k => [ `${k.toLowerCase()}Key`, k ]);
  const getKeyCode = e => [ e.key, e.code ].map(c => modKeys.reduce((a, [k, p]) => e[k] && !e.code.startsWith(p) ? `${p}+${a}` : a, c));
  document.addEventListener('keydown', e => {
    const [ key, code ] = getKeyCode(e);
    onKeyDown[key]?.(e);
    if (code !== key)
      onKeyDown[code]?.(e);
  });

  attempt("color images", () => {
    for (const elwStrip of el.wrap.all.linkedStrip) {
      const [ elLink, elImageBw ] = [ elwStrip.self, elwStrip.tag.img ];
      const bwSrc = elLink.href;
      const bwFileName = elLink.pathname.split("/").at(-1).replace("OnXmas", "OnlineXmas");
      const colorSrc = `${colorSrcBase}/${bwFileName.replace(/\.(gif|jpg)$/i, ".jpg")}`;
      elImageBw.src = bwSrc;
      elImageBw.classList.add('ath-image-bw');
      elLink.classList.add('ath-strip');
      if (location.hostname.includes('sabrina-online.com'))
        elLink.insertAdjacentHTML('beforeEnd', /*html*/`
          <img class="ath-image-color" alt="${h(elImageBw.alt)}" src="${h(colorSrc)}" onerror="this.remove()">`);
    }
  });
})();