Komica 定時功能與鎖定畫面

提供懸浮計時功能,並在超時後鎖定畫面提示休息。

  1. // ==UserScript==
  2. // @name Komica 定時功能與鎖定畫面
  3. // @namespace http://komica.org
  4. // @version 1.0
  5. // @description 提供懸浮計時功能,並在超時後鎖定畫面提示休息。
  6. // @author Yun
  7. // @match https://gita.komica1.org/*
  8. // @icon https://i.ibb.co/bscXhHh/icon.png
  9. // @license GNU GPLv3
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14.  
  15. // 插入CSS樣式
  16. const style = document.createElement('style');
  17. style.textContent = `
  18. @keyframes fadeIn {
  19. from { opacity: 0; }
  20. to { opacity: 1; }
  21. }
  22. @keyframes fadeOut {
  23. from { opacity: 1; }
  24. to { opacity: 0; }
  25. }
  26. .fade-in {
  27. animation: fadeIn 0.5s ease-in-out forwards;
  28. }
  29. .fade-out {
  30. animation: fadeOut 0.5s ease-in-out forwards;
  31. }
  32. .timer-button {
  33. width: 24px;
  34. height: 24px;
  35. background: rgba(68, 68, 68, 0.5);
  36. border: none;
  37. border-radius: 50%;
  38. color: #fff;
  39. font-size: 12px;
  40. cursor: pointer;
  41. display: flex;
  42. align-items: center;
  43. justify-content: center;
  44. margin-left: 4px;
  45. transition: background 0.3s ease;
  46. }
  47. .timer-button:hover {
  48. background: rgba(85, 85, 85, 0.7);
  49. }
  50. `;
  51. document.head.appendChild(style);
  52.  
  53. // 計時相關變數
  54. const TIMER_KEY = 'komica-timer-seconds';
  55. const WARNING_INDEX_KEY = 'komica-warning-index';
  56. const LOCK_STATE_KEY = 'komica-lock-state';
  57. const INITIAL_TIME = 600; // 10分鐘 = 600秒
  58. let seconds = parseInt(localStorage.getItem(TIMER_KEY), 10) || INITIAL_TIME;
  59. let timerInterval = null;
  60. let isPaused = false;
  61.  
  62. // 插入懸浮計時器
  63. const container = document.createElement('div');
  64. Object.assign(container.style, {
  65. position: 'fixed',
  66. top: '40px',
  67. right: '10px',
  68. display: 'flex',
  69. flexDirection: 'row',
  70. alignItems: 'center',
  71. background: 'rgba(30, 30, 30, 0.4)',
  72. padding: '6px 10px',
  73. borderRadius: '8px',
  74. boxShadow: '0 2px 6px rgba(0, 0, 0, 0.2)',
  75. zIndex: '10000',
  76. color: '#ffffff',
  77. fontFamily: 'Arial, sans-serif',
  78. fontSize: '12px',
  79. backdropFilter: 'blur(5px)'
  80. });
  81.  
  82. const timerDiv = document.createElement('div');
  83. timerDiv.id = 'komica-timer';
  84. Object.assign(timerDiv.style, {
  85. fontWeight: 'bold',
  86. marginRight: '6px'
  87. });
  88.  
  89. // 更新計時器顯示
  90. const updateTimerDisplay = () => {
  91. const minutes = Math.floor(seconds / 60).toString().padStart(2, '0');
  92. const secs = (seconds % 60).toString().padStart(2, '0');
  93. timerDiv.textContent = `${minutes}:${secs}`;
  94. };
  95.  
  96. // 計時器核心邏輯
  97. const tick = () => {
  98. if (seconds <= 0) {
  99. clearInterval(timerInterval);
  100. triggerSiteblock();
  101. return;
  102. }
  103. seconds--;
  104. localStorage.setItem(TIMER_KEY, seconds);
  105. updateTimerDisplay();
  106. };
  107.  
  108. const startTimer = () => {
  109. if (timerInterval) clearInterval(timerInterval);
  110. timerInterval = setInterval(tick, 1000);
  111. };
  112.  
  113. const stopTimer = () => {
  114. if (timerInterval) {
  115. clearInterval(timerInterval);
  116. timerInterval = null;
  117. }
  118. };
  119.  
  120. // 重置按鈕
  121. const resetButton = document.createElement('button');
  122. resetButton.innerHTML = '⟳';
  123. resetButton.className = 'timer-button';
  124. resetButton.title = '重置你無謂的人生';
  125.  
  126. // 播放/暫停按鈕
  127. const playPauseButton = document.createElement('button');
  128. playPauseButton.innerHTML = '⏸︎';
  129. playPauseButton.className = 'timer-button';
  130. playPauseButton.title = '暫停你的墮落時光';
  131.  
  132. resetButton.addEventListener('click', () => {
  133. if (confirm('真的要重置?反正你的人生也就這樣了!')) {
  134. seconds = INITIAL_TIME;
  135. localStorage.setItem(TIMER_KEY, seconds);
  136. updateTimerDisplay();
  137. if (!isPaused) {
  138. startTimer();
  139. }
  140. }
  141. });
  142.  
  143. playPauseButton.addEventListener('click', () => {
  144. isPaused = !isPaused;
  145. if (isPaused) {
  146. playPauseButton.innerHTML = '▶︎';
  147. playPauseButton.title = '繼續你可悲的瀏覽';
  148. stopTimer();
  149. } else {
  150. playPauseButton.innerHTML = '⏸︎';
  151. playPauseButton.title = '暫停你的墮落時光';
  152. startTimer();
  153. }
  154. });
  155.  
  156. container.appendChild(timerDiv);
  157. container.appendChild(playPauseButton);
  158. container.appendChild(resetButton);
  159. document.body.appendChild(container);
  160.  
  161. // 警告訊息
  162. const warningMessages = [
  163. '哈!又在這裡浪費生命?真有你的!',
  164. '媽媽知道你在這邊當廢物嗎?',
  165. '看來你是打算繼續當個魯蛇了?',
  166. '這就是你的人生價值?在這裡虛度光陰?',
  167. '你以為躲在這裡就能逃避現實?',
  168. '真替你感到可悲,繼續宅下去吧!',
  169. '又在這裡當個失敗者了?',
  170. '外面的世界很可怕?還是你太廢了?',
  171. '這就是你的極限了?真可憐!',
  172. '看來你是打算一輩子當個廢物了!'
  173. ];
  174.  
  175. const getNextWarning = () => {
  176. let currentIndex = parseInt(localStorage.getItem(WARNING_INDEX_KEY), 10) || 0;
  177. const warning = warningMessages[currentIndex];
  178. currentIndex = (currentIndex + 1) % warningMessages.length;
  179. localStorage.setItem(WARNING_INDEX_KEY, currentIndex);
  180. return warning;
  181. };
  182.  
  183. // 鎖定畫面處理
  184. const triggerSiteblock = () => {
  185. localStorage.setItem(LOCK_STATE_KEY, 'locked');
  186. stopTimer();
  187.  
  188. const oldOverlay = document.getElementById('komica-siteblock');
  189. if (oldOverlay) {
  190. oldOverlay.remove();
  191. }
  192.  
  193. const overlay = document.createElement('div');
  194. overlay.id = 'komica-siteblock';
  195. overlay.classList.add('fade-in');
  196.  
  197. Object.assign(overlay.style, {
  198. position: 'fixed',
  199. top: '0',
  200. left: '0',
  201. width: '100%',
  202. height: '100%',
  203. background: 'rgba(0, 0, 0, 0.85)',
  204. backdropFilter: 'blur(12px)',
  205. display: 'flex',
  206. flexDirection: 'column',
  207. alignItems: 'center',
  208. justifyContent: 'center',
  209. color: '#ffffff',
  210. zIndex: '10001',
  211. fontFamily: 'Arial, sans-serif'
  212. });
  213.  
  214. const warningText = document.createElement('div');
  215. warningText.textContent = getNextWarning();
  216. Object.assign(warningText.style, {
  217. fontSize: '24px',
  218. fontWeight: 'bold',
  219. marginBottom: '20px',
  220. textAlign: 'center',
  221. padding: '0 20px'
  222. });
  223.  
  224. const unlockButton = document.createElement('button');
  225. Object.assign(unlockButton.style, {
  226. padding: '12px 24px',
  227. fontSize: '18px',
  228. background: '#ffffff',
  229. color: '#000000',
  230. border: 'none',
  231. borderRadius: '8px',
  232. cursor: 'pointer',
  233. fontWeight: 'bold',
  234. position: 'relative',
  235. overflow: 'hidden'
  236. });
  237.  
  238. const progressIndicator = document.createElement('div');
  239. Object.assign(progressIndicator.style, {
  240. position: 'absolute',
  241. top: '0',
  242. left: '0',
  243. height: '100%',
  244. width: '0%',
  245. background: 'rgba(0, 128, 0, 0.5)',
  246. borderRadius: '8px',
  247. zIndex: '1',
  248. transition: 'width 0.1s linear'
  249. });
  250.  
  251. const buttonText = document.createElement('span');
  252. buttonText.textContent = '堅持要繼續浪費生命?按住 5 秒!';
  253. Object.assign(buttonText.style, {
  254. position: 'relative',
  255. zIndex: '2'
  256. });
  257.  
  258. unlockButton.appendChild(progressIndicator);
  259. unlockButton.appendChild(buttonText);
  260.  
  261. let pressStartTime = 0;
  262. let isPressed = false;
  263. let progressInterval;
  264.  
  265. const startUnlockProcess = () => {
  266. isPressed = true;
  267. pressStartTime = Date.now();
  268. progressInterval = setInterval(() => {
  269. if (!isPressed) return;
  270. const progress = Math.min((Date.now() - pressStartTime) / 5000 * 100, 100);
  271. progressIndicator.style.width = `${progress}%`;
  272.  
  273. if (progress >= 100) {
  274. clearInterval(progressInterval);
  275. overlay.classList.add('fade-out');
  276. setTimeout(() => {
  277. overlay.remove();
  278. localStorage.removeItem(LOCK_STATE_KEY);
  279. seconds = INITIAL_TIME;
  280. localStorage.setItem(TIMER_KEY, seconds);
  281. updateTimerDisplay();
  282. if (!isPaused) {
  283. startTimer();
  284. }
  285. }, 500);
  286. }
  287. }, 100);
  288. };
  289.  
  290. const stopUnlockProcess = () => {
  291. isPressed = false;
  292. clearInterval(progressInterval);
  293. progressIndicator.style.width = '0%';
  294. };
  295.  
  296. unlockButton.addEventListener('mousedown', startUnlockProcess);
  297. unlockButton.addEventListener('mouseup', stopUnlockProcess);
  298. unlockButton.addEventListener('mouseleave', stopUnlockProcess);
  299. unlockButton.addEventListener('touchstart', startUnlockProcess);
  300. unlockButton.addEventListener('touchend', stopUnlockProcess);
  301.  
  302. overlay.appendChild(warningText);
  303. overlay.appendChild(unlockButton);
  304. document.body.appendChild(overlay);
  305. };
  306.  
  307. // 初始化
  308. updateTimerDisplay();
  309. if (localStorage.getItem(LOCK_STATE_KEY) === 'locked') {
  310. triggerSiteblock();
  311. } else {
  312. startTimer();
  313. }
  314. })();