Better Daymap

Modern redesign, customization, transparency, hidden soccer game, and more for Daymap.

  1. // ==UserScript==
  2. // @name Better Daymap
  3. // @namespace Better Daymap
  4. // @version 4.1.1
  5. // @description Modern redesign, customization, transparency, hidden soccer game, and more for Daymap.
  6. // @author LiamGo
  7. // @match https://*.daymap.net/*
  8. // @icon https://lh3.googleusercontent.com/_Jt3LvQt0VV4wQkW6brDIvKNCQMSWgzbE_ofiwnWCgWTw4pUv4HsLX0AH8PpNEde85jt8XPWyXQo91d4MEYqZZgm-k4=s60
  9. // @grant GM_registerMenuCommand
  10. // @grant GM_getValue
  11. // @grant GM_setValue
  12. // @grant GM_addStyle
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17.  
  18. // --- PROFILE/BACKGROUND & SECTION CONFIGURATION ---
  19. const DEFAULTS = {
  20. backgroundColor: '#000000',
  21. backgroundImage: '',
  22. profileImage: '',
  23. transparency: 0.0,
  24. blur: 0,
  25. disabledSections: {
  26. indicators: false,
  27. diaryNotes: false,
  28. homework: false,
  29. currentTasks: false,
  30. messages: false,
  31. bulletins: false,
  32. newsletters: false,
  33. rcFactSheets: false
  34. },
  35. hiddenGameEnabled: true,
  36. hiddenGameURL: 'https://geography-lessons.github.io/Vafor_IT/soccer-random'
  37. };
  38.  
  39. const SECTIONS = [
  40. { id: 'indicators', name: 'Indicators', selector: '#pnlMid > div.card.expWindow:nth-of-type(1)' },
  41. { id: 'diaryNotes', name: 'My Diary Notes', selector: '#pnlMid > div.card.expWindow:nth-of-type(2)' },
  42. { id: 'homework', name: 'Homework', selector: '#pnlMid > div.card.expWindow:nth-of-type(3)' },
  43. { id: 'currentTasks', name: 'Current Tasks', selector: '#pnlMid > div.card.expWindow:nth-of-type(4)' },
  44. { id: 'messages', name: 'Messages', selector: '#pnlRight > div.card.expWindow:nth-of-type(1)' },
  45. { id: 'bulletins', name: 'Bulletins', selector: '#pnlRight > div.card.expWindow:nth-of-type(2)' },
  46. { id: 'newsletters', name: 'Newsletters', selector: '#pnlRight > div.card.expWindow:nth-of-type(3)' },
  47. { id: 'rcFactSheets', name: 'RC Fact Sheets', selector: '#pnlRight > div.card.expWindow:nth-of-type(4)' }
  48. ];
  49.  
  50. // --- OUTLINE AROUND TEXT ---
  51. GM_addStyle(`
  52. body, body * {
  53. text-shadow:
  54. -1px -1px 0 #000,
  55. 1px -1px 0 #000,
  56. -1px 1px 0 #000,
  57. 1px 1px 0 #000,
  58. 0 -1px 0 #000,
  59. 0 1px 0 #000,
  60. -1px 0 0 #000,
  61. 1px 0 0 #000;
  62. }
  63. `);
  64.  
  65. // --- TRANSPARENCY/BLUR FUNCTIONALITY ---
  66. function applyTransBlur() {
  67. const transparency = parseFloat(GM_getValue('transparency', DEFAULTS.transparency));
  68. const blur = parseInt(GM_getValue('blur', DEFAULTS.blur), 10);
  69.  
  70. let dark = 0;
  71. if(document.cookie && document.cookie.length > 0) {
  72. const lastChar = document.cookie.substr(document.cookie.length - 1, 1);
  73. dark = lastChar === "1" ? 1 : 0;
  74. }
  75. const lightBg = "237,235,233";
  76. const darkBg = "37,37,37";
  77. const bg = dark ? darkBg : lightBg;
  78.  
  79. function setStyle(selector, bgColor, transparency, blur) {
  80. document.querySelectorAll(selector).forEach(el => {
  81. el.style.background = `rgba(${bgColor},${transparency})`;
  82. el.style.backdropFilter = `blur(${blur}px)`;
  83. if (transparency == 0) {
  84. el.style.boxShadow = 'none';
  85. }
  86. });
  87. }
  88.  
  89. setStyle(
  90. ".card, .msg, .ditm, .Toolbar, .ditm .t, .ditm .c, .hasDatepicker, #tblTt tbody tr td, .item-container, #bCalendar, #btnDiary, .itm",
  91. bg,
  92. transparency,
  93. blur
  94. );
  95.  
  96. // Navigation containers
  97. setStyle(
  98. ".nav-container, .nav-user-container",
  99. bg,
  100. transparency * 0.7,
  101. blur
  102. );
  103. }
  104.  
  105. // Initial apply
  106. window.setTimeout(applyTransBlur, 0);
  107.  
  108. // --- MutationObserver to reapply styles on dynamic content changes ---
  109. let applyTimeout = null;
  110. const DEBOUNCE_DELAY = 200;
  111.  
  112. const observer = new MutationObserver(mutations => {
  113. if (applyTimeout) clearTimeout(applyTimeout);
  114. applyTimeout = setTimeout(() => {
  115. applyTransBlur();
  116. }, DEBOUNCE_DELAY);
  117. });
  118.  
  119. observer.observe(document.body, {
  120. childList: true,
  121. subtree: true,
  122. attributes: false
  123. });
  124.  
  125. // --- PROFILE/BACKGROUND/SECTION FUNCTIONALITY ---
  126. function applySettings() {
  127. const bgColor = GM_getValue('backgroundColor', DEFAULTS.backgroundColor);
  128. const bgImage = GM_getValue('backgroundImage', DEFAULTS.backgroundImage);
  129. const profileImage = GM_getValue('profileImage', DEFAULTS.profileImage);
  130. const disabledSections = GM_getValue('disabledSections', DEFAULTS.disabledSections);
  131.  
  132. document.body.style.backgroundColor = bgColor;
  133.  
  134. if (bgImage) {
  135. document.body.style.backgroundImage = `url(${bgImage})`;
  136. document.body.style.backgroundSize = '100% 100%';
  137. document.body.style.backgroundRepeat = 'no-repeat';
  138. document.body.style.backgroundPosition = 'top left';
  139. document.body.style.backgroundAttachment = 'fixed';
  140. } else {
  141. document.body.style.backgroundImage = '';
  142. }
  143.  
  144. // --- NEW PROFILE IMAGE LOGIC ---
  145. // Clear previous interval if exists
  146. if (window.navUserImageInterval) clearInterval(window.navUserImageInterval);
  147.  
  148. if (profileImage) {
  149. // Reapply .nav-user-image every 100ms
  150. window.navUserImageInterval = setInterval(() => {
  151. const navUser = document.querySelector('.nav-user-image');
  152. if (navUser) {
  153. navUser.style.backgroundImage = `url(${profileImage})`;
  154. }
  155. }, 100);
  156. window.navUserImageTimeout = setTimeout(() => {
  157. clearInterval(window.navUserImageInterval);
  158. }, 10000);
  159.  
  160. // Apply to .photoThumb only once
  161. const photoThumb = document.querySelector('.photoThumb');
  162. if (photoThumb && !photoThumb.dataset.profileSet) {
  163. photoThumb.style.backgroundImage = `url(${profileImage})`;
  164. photoThumb.dataset.profileSet = "true";
  165. }
  166. } else {
  167. // If no profile image, clear interval and remove images
  168. if (window.navUserImageInterval) clearInterval(window.navUserImageInterval);
  169. const navUser = document.querySelector('.nav-user-image');
  170. if (navUser) navUser.style.backgroundImage = '';
  171. const photoThumb = document.querySelector('.photoThumb');
  172. if (photoThumb) photoThumb.style.backgroundImage = '';
  173. }
  174.  
  175. SECTIONS.forEach(section => {
  176. document.querySelectorAll(section.selector).forEach(el => {
  177. el.style.display = disabledSections[section.id] ? 'none' : '';
  178. });
  179. });
  180.  
  181. // Re-apply transparency/blur when settings change
  182. applyTransBlur();
  183. }
  184.  
  185. function expandContentHeight() {
  186. const currentTasks = document.querySelector('#pnlMid > div.card.expWindow:nth-of-type(3) .expContent');
  187. const messages = document.querySelector('#pnlRight > div.card.expWindow:nth-of-type(1) .expContent');
  188. if (currentTasks) currentTasks.style.minHeight = '572px';
  189. if (messages) messages.style.minHeight = '572px';
  190. }
  191.  
  192. // --- SETTINGS PAGE ---
  193. function createSettingsPage() {
  194. const modal = document.createElement('div');
  195. modal.style.cssText = `
  196. position: fixed;
  197. top: 50%;
  198. left: 50%;
  199. transform: translate(-50%, -50%);
  200. background: black;
  201. padding: 20px;
  202. border-radius: 8px;
  203. box-shadow: 0 0 20px rgba(0,0,0,0.3);
  204. z-index: 9999;
  205. min-width: 400px;
  206. max-width: 95vw;
  207. max-height: 90vh;
  208. overflow: auto;
  209. font-family: Arial, sans-serif;
  210. `;
  211. const heading = document.createElement('h2');
  212. heading.textContent = 'Better Daymap Settings';
  213. heading.style.marginTop = '0';
  214.  
  215. const form = document.createElement('div');
  216. form.style.display = 'grid';
  217. form.style.gap = '15px';
  218.  
  219. const colorLabel = createLabel('Background Color:', 'color');
  220. const colorInput = createInput('color', 'backgroundColor', GM_getValue('backgroundColor', DEFAULTS.backgroundColor));
  221.  
  222. const bgImageLabel = createLabel('Background Image URL:', 'bgImage');
  223. const bgImageInput = createInput('text', 'bgImage', GM_getValue('backgroundImage', DEFAULTS.backgroundImage));
  224. bgImageInput.placeholder = 'https://example.com/image.jpg';
  225.  
  226. const profileLabel = createLabel('Profile Image URL:', 'profile');
  227. const profileInput = createInput('text', 'profile', GM_getValue('profileImage', DEFAULTS.profileImage));
  228. profileInput.placeholder = 'https://example.com/avatar.jpg';
  229.  
  230. // --- TRANSPARENCY/BLUR CONTROLS ---
  231. const transparencyLabel = createLabel('Transparency:', 'transparency');
  232. const transparencyValue = document.createElement('span');
  233. transparencyValue.style.marginLeft = '10px';
  234. transparencyValue.style.fontWeight = 'bold';
  235. const transparencyInput = document.createElement('input');
  236. transparencyInput.type = 'range';
  237. transparencyInput.id = 'transparency';
  238. transparencyInput.min = 0;
  239. transparencyInput.max = 1;
  240. transparencyInput.step = 0.01;
  241. transparencyInput.value = GM_getValue('transparency', DEFAULTS.transparency);
  242. transparencyValue.textContent = transparencyInput.value;
  243. transparencyInput.addEventListener('input', () => {
  244. transparencyValue.textContent = transparencyInput.value;
  245. });
  246.  
  247. const blurLabel = createLabel('Blur (px):', 'blur');
  248. const blurValue = document.createElement('span');
  249. blurValue.style.marginLeft = '10px';
  250. blurValue.style.fontWeight = 'bold';
  251. const blurInput = document.createElement('input');
  252. blurInput.type = 'range';
  253. blurInput.id = 'blur';
  254. blurInput.min = 0;
  255. blurInput.max = 20;
  256. blurInput.step = 1;
  257. blurInput.value = GM_getValue('blur', DEFAULTS.blur);
  258. blurValue.textContent = blurInput.value;
  259. blurInput.addEventListener('input', () => {
  260. blurValue.textContent = blurInput.value;
  261. });
  262.  
  263. // Section toggles
  264. const sectionLabel = document.createElement('div');
  265. sectionLabel.textContent = 'Disable Sections:';
  266. sectionLabel.style.fontWeight = 'bold';
  267. sectionLabel.style.marginTop = '15px';
  268.  
  269. const togglesContainer = document.createElement('div');
  270. togglesContainer.style.display = 'flex';
  271. togglesContainer.style.gap = '20px';
  272.  
  273. const leftColumn = document.createElement('div');
  274. leftColumn.style.display = 'flex';
  275. leftColumn.style.flexDirection = 'column';
  276. leftColumn.style.gap = '8px';
  277.  
  278. const rightColumn = document.createElement('div');
  279. rightColumn.style.display = 'flex';
  280. rightColumn.style.flexDirection = 'column';
  281. rightColumn.style.gap = '8px';
  282.  
  283. const disabledSections = GM_getValue('disabledSections', DEFAULTS.disabledSections);
  284. SECTIONS.forEach((section, index) => {
  285. const wrapper = document.createElement('div');
  286. wrapper.style.display = 'flex';
  287. wrapper.style.alignItems = 'center';
  288. wrapper.style.gap = '8px';
  289.  
  290. const checkbox = document.createElement('input');
  291. checkbox.type = 'checkbox';
  292. checkbox.id = `section-${section.id}`;
  293. checkbox.checked = disabledSections[section.id];
  294.  
  295. const label = document.createElement('label');
  296. label.htmlFor = `section-${section.id}`;
  297. label.textContent = section.name;
  298. label.style.cursor = 'pointer';
  299.  
  300. wrapper.appendChild(checkbox);
  301. wrapper.appendChild(label);
  302.  
  303. if (index < 4) leftColumn.appendChild(wrapper);
  304. else rightColumn.appendChild(wrapper);
  305. });
  306.  
  307. togglesContainer.appendChild(leftColumn);
  308. togglesContainer.appendChild(rightColumn);
  309.  
  310. // --- HIDDEN GAME TOGGLE & URL ---
  311. const hiddenGameEnabled = GM_getValue('hiddenGameEnabled', DEFAULTS.hiddenGameEnabled);
  312. const hiddenGameURL = GM_getValue('hiddenGameURL', DEFAULTS.hiddenGameURL);
  313.  
  314. const gameToggleLabel = createLabel('Show Hidden Game Button:', 'hiddenGameEnabled');
  315. const gameToggleInput = document.createElement('input');
  316. gameToggleInput.type = 'checkbox';
  317. gameToggleInput.id = 'hiddenGameEnabled';
  318. gameToggleInput.checked = hiddenGameEnabled;
  319.  
  320. const gameUrlLabel = createLabel('Hidden Game URL:', 'hiddenGameURL');
  321. const gameUrlInput = createInput('text', 'hiddenGameURL', hiddenGameURL);
  322. gameUrlInput.placeholder = 'https://example.com/game';
  323.  
  324. // --- BUTTONS ---
  325. const buttonContainer = document.createElement('div');
  326. buttonContainer.style.display = 'flex';
  327. buttonContainer.style.gap = '10px';
  328. buttonContainer.style.marginTop = '15px';
  329.  
  330. const saveButton = createButton('Save', () => {
  331. const newDisabled = {};
  332. SECTIONS.forEach(section => {
  333. newDisabled[section.id] = document.getElementById(`section-${section.id}`).checked;
  334. });
  335. GM_setValue('backgroundColor', colorInput.value);
  336. GM_setValue('backgroundImage', bgImageInput.value);
  337. GM_setValue('profileImage', profileInput.value);
  338. GM_setValue('transparency', transparencyInput.value);
  339. GM_setValue('blur', blurInput.value);
  340. GM_setValue('disabledSections', newDisabled);
  341. GM_setValue('hiddenGameEnabled', gameToggleInput.checked);
  342. GM_setValue('hiddenGameURL', gameUrlInput.value);
  343. applySettings();
  344. expandContentHeight();
  345. if (location.pathname.match(/\/daymap\/timetable\/timetable\.aspx$/)) {
  346. removeHiddenGameElements();
  347. if (gameToggleInput.checked) addHiddenGameButton();
  348. }
  349. modal.remove();
  350. });
  351.  
  352. const resetButton = createButton('Reset to Defaults', () => {
  353. GM_setValue('backgroundColor', DEFAULTS.backgroundColor);
  354. GM_setValue('backgroundImage', DEFAULTS.backgroundImage);
  355. GM_setValue('profileImage', DEFAULTS.profileImage);
  356. GM_setValue('transparency', DEFAULTS.transparency);
  357. GM_setValue('blur', DEFAULTS.blur);
  358. GM_setValue('disabledSections', DEFAULTS.disabledSections);
  359. GM_setValue('hiddenGameEnabled', DEFAULTS.hiddenGameEnabled);
  360. GM_setValue('hiddenGameURL', DEFAULTS.hiddenGameURL);
  361. applySettings();
  362. expandContentHeight();
  363. if (location.pathname.match(/\/daymap\/timetable\/timetable\.aspx$/)) {
  364. removeHiddenGameElements();
  365. if (DEFAULTS.hiddenGameEnabled) addHiddenGameButton();
  366. }
  367. modal.remove();
  368. });
  369.  
  370. const closeButton = createButton('Close', () => modal.remove());
  371.  
  372. // Append in desired order
  373. form.append(
  374. colorLabel, colorInput,
  375. bgImageLabel, bgImageInput,
  376. profileLabel, profileInput,
  377. transparencyLabel, transparencyInput, transparencyValue,
  378. blurLabel, blurInput, blurValue,
  379. sectionLabel
  380. );
  381. form.append(togglesContainer);
  382.  
  383. // Insert hidden game controls
  384. form.append(gameToggleLabel, gameToggleInput, gameUrlLabel, gameUrlInput);
  385.  
  386. buttonContainer.append(saveButton, resetButton, closeButton);
  387. modal.append(heading, form, buttonContainer);
  388.  
  389. return modal;
  390. }
  391. function createLabel(text, forId) {
  392. const label = document.createElement('label');
  393. label.textContent = text;
  394. label.htmlFor = forId;
  395. label.style.fontWeight = 'bold';
  396. return label;
  397. }
  398. function createInput(type, id, value) {
  399. const input = document.createElement('input');
  400. input.type = type;
  401. input.id = id;
  402. input.value = value;
  403. input.style.padding = '5px';
  404. input.style.width = '100%';
  405. if (type === 'color') input.style.cursor = 'pointer';
  406. return input;
  407. }
  408. function createButton(text, onClick) {
  409. const button = document.createElement('button');
  410. button.textContent = text;
  411. button.onclick = onClick;
  412. button.style.cssText = `
  413. padding: 8px 15px;
  414. border: none;
  415. border-radius: 4px;
  416. cursor: pointer;
  417. background: #007bff;
  418. color: white;
  419. transition: background 0.3s;
  420. `;
  421. button.addEventListener('mouseover', () => button.style.background = '#0056b3');
  422. button.addEventListener('mouseout', () => button.style.background = '#007bff');
  423. return button;
  424. }
  425. // Initial setup
  426. applySettings();
  427. expandContentHeight();
  428. GM_registerMenuCommand('Open Better Daymap Settings', () => {
  429. document.body.appendChild(createSettingsPage());
  430. });
  431.  
  432. // --- HIDDEN GAME BUTTON/IFRAME LOGIC ---
  433. function removeHiddenGameElements() {
  434. const btn = document.getElementById('soccerToggleBtn');
  435. const cont = document.getElementById('soccerEmbedContainer');
  436. if (btn) btn.remove();
  437. if (cont) cont.remove();
  438. }
  439. function addHiddenGameButton() {
  440. // Remove if already present
  441. removeHiddenGameElements();
  442.  
  443. const hiddenGameURL = GM_getValue('hiddenGameURL', DEFAULTS.hiddenGameURL);
  444.  
  445. const soccerContainer = document.createElement('div');
  446. soccerContainer.id = 'soccerEmbedContainer';
  447. soccerContainer.style.cssText = 'width:100%; overflow:hidden; max-height:0; transition:max-height 0.4s ease;';
  448. const soccerFrame = document.createElement('iframe');
  449. soccerFrame.src = hiddenGameURL;
  450. soccerFrame.style.cssText = 'width:100%; height:100vh; border:none;';
  451. soccerContainer.appendChild(soccerFrame);
  452. document.body.appendChild(soccerContainer);
  453.  
  454. const soccerBtn = document.createElement('button');
  455. soccerBtn.id = 'soccerToggleBtn';
  456. soccerBtn.textContent = '▶️';
  457. soccerBtn.style.cssText = 'position:fixed; bottom:0px; right:0px; z-index:10000; padding:5px 5px; border:none; border-radius:4px; background:#000000; color:#fff; cursor:pointer; font-size:16px;';
  458. soccerBtn.addEventListener('click', () => {
  459. if (soccerContainer.style.maxHeight === '0px' || !soccerContainer.style.maxHeight) {
  460. soccerContainer.style.maxHeight = '100vh';
  461. soccerBtn.textContent = '◀️';
  462. } else {
  463. soccerContainer.style.maxHeight = '0';
  464. soccerBtn.textContent = '▶️';
  465. }
  466. });
  467. document.body.appendChild(soccerBtn);
  468. }
  469.  
  470. // --- TIMETABLE PAGE ENHANCEMENTS ---
  471. if (location.pathname.match(/\/daymap\/timetable\/timetable\.aspx$/)) {
  472. GM_addStyle(`
  473. /* Main layout improvements */
  474. .main-layout {
  475. padding: 20px;
  476. max-width: 1440px;
  477. margin: 0 auto;
  478. }
  479. .grid { gap: 0px; }
  480. .card {
  481. border-radius: 10px;
  482. box-shadow: 0 4px 12px rgba(0,0,0,0.1);
  483. border: none;
  484. overflow: hidden;
  485. }
  486. /* Timetable styling */
  487. .tt {
  488. border-collapse: separate;
  489. border-spacing: 0;
  490. width: 100%;
  491. }
  492. .tt th {
  493. background: #1888C9;
  494. color: white;
  495. padding: 10px;
  496. text-align: center;
  497. font-weight: 500;
  498. }
  499. .tt td {
  500. padding: 0;
  501. border: 1px solid #e9ecef;
  502. }
  503. .ttCell {
  504. padding: 8px;
  505. height: 80px;
  506. display: flex;
  507. flex-direction: column;
  508. justify-content: space-between;
  509. transition: all 0.2s ease;
  510. }
  511. .ttCell:hover {
  512. filter: brightness(95%);
  513. transform: translateY(-2px);
  514. }
  515. .ttSubject {
  516. font-weight: 600;
  517. font-size: 0.9rem;
  518. margin-bottom: 5px;
  519. }
  520. .ttTeacher, .ttRoom {
  521. font-size: 0.8rem;
  522. color: white;
  523. }
  524. .Period {
  525. background: #f8f9fa;
  526. font-weight: 500;
  527. padding: 8px;
  528. white-space: nowrap;
  529. }
  530. /* Task list improvements */
  531. .feed {
  532. width: 100%;
  533. border-collapse: collapse;
  534. }
  535. .feed tr {
  536. border-bottom: 1px solid #e9ecef;
  537. }
  538. .feed td {
  539. padding: 12px;
  540. }
  541. .feed .cap {
  542. width: 120px;
  543. font-weight: 500;
  544. color: #2c3e50;
  545. vertical-align: top;
  546. }
  547. .feed .itm {
  548. cursor: pointer;
  549. transition: background 0.2s ease;
  550. }
  551. .feed .itm:hover {
  552. background: #f8f9fa;
  553. }
  554. .Caption {
  555. font-size: 0.8rem;
  556. color: #6c757d;
  557. white-space: normal !important;
  558. overflow-wrap: break-word;
  559. word-break: break-word;
  560. }
  561. /* Message list improvements */
  562. .msgList {
  563. padding: 0;
  564. }
  565. daymap-list-item {
  566. padding: 12px 15px;
  567. border-bottom: 1px solid #e9ecef;
  568. display: block;
  569. transition: background 0.2s ease;
  570. }
  571. daymap-list-item:hover {
  572. background: #f8f9fa;
  573. }
  574. /* Button improvements */
  575. .btn {
  576. border-radius: 6px;
  577. padding: 8px 16px;
  578. transition: all 0.2s ease;
  579. }
  580. .btn:hover {
  581. transform: translateY(-1px);
  582. }
  583. /* Responsive adjustments */
  584. @media (max-width: 768px) {
  585. .grid > div {
  586. width: 100% !important;
  587. }
  588. .ttCell {
  589. height: auto;
  590. min-height: 60px;
  591. }
  592. }
  593. `);
  594.  
  595. window.addEventListener('load', function() {
  596. setTimeout(() => {
  597. // Timetable cell improvements
  598. const cells = document.querySelectorAll('.ttCell');
  599. cells.forEach(cell => {
  600. cell.style.cursor = 'pointer';
  601. const subject = cell.querySelector('.ttSubject');
  602. if (subject) {
  603. const text = subject.textContent.trim();
  604. if (text.length > 25) {
  605. subject.textContent = text.substring(0, 22) + '...';
  606. }
  607. }
  608. });
  609. // --- Timetable TH day text coloring (all except today are gray, today is blue) ---
  610. (function highlightTimetableDays() {
  611. const ths = document.querySelectorAll('.tt th');
  612. if (!ths.length) return;
  613.  
  614. const now = new Date();
  615. const todayDay = now.getDate();
  616. const todayMonth = now.getMonth() + 1;
  617.  
  618. ths.forEach(th => {
  619. const text = th.textContent.replace(/\s+/g, ' ').trim();
  620. const match = text.match(/([A-Za-z]{3})\s*(\d{1,2})\/(\d{1,2})/);
  621. if (!match) return;
  622.  
  623. const day = parseInt(match[2], 10);
  624. const month = parseInt(match[3], 10);
  625.  
  626. if (month === todayMonth && day === todayDay) {
  627. // Current day: blue and bold
  628. th.style.color = '#1888C9';
  629. th.style.fontWeight = 'bold';
  630. } if (month < todayMonth || (month === todayMonth && day < todayDay)) {
  631. // Past day: gray
  632. th.style.color = '#e0e0e0';
  633. th.style.color = '#888';
  634. }
  635. });
  636. })();
  637.  
  638. // Task list color coding
  639. const tasks = document.querySelectorAll('.feed .itm');
  640. tasks.forEach(task => {
  641. task.style.transition = 'all 0.2s ease';
  642. if (task.innerHTML.includes('Overdue') || task.innerHTML.includes('Uh did you submit on Turnitin or something?')) {
  643. task.style.borderLeft = '3px solid #e81123';
  644. } else if (task.innerHTML.includes('Grade:') || task.innerHTML.includes('Mark:') || task.innerHTML.includes('Work has been received')) {
  645. task.style.borderLeft = '3px solid #2ecc40';
  646. } else {
  647. task.style.borderLeft = '3px solid #ffb900';
  648. }
  649. });
  650.  
  651. // --- Task list: Show due/completed/overdue days for "Set on ... and due on ..." style ---
  652. tasks.forEach(task => {
  653. let match = task.innerText.match(/due on \w{3} (\d{1,2}) (\w{3})(?: (\d{4}))?/i);
  654. if (match) {
  655. let day = parseInt(match[1], 10);
  656. let monthStr = match[2].toLowerCase();
  657. let year = match[3] ? parseInt(match[3], 10) : (new Date()).getFullYear();
  658.  
  659. const months = {
  660. jan:0, feb:1, mar:2, apr:3, may:4, jun:5,
  661. jul:6, aug:7, sep:8, oct:9, nov:10, dec:11
  662. };
  663. let month = months[monthStr];
  664. if (month === undefined) return;
  665.  
  666. let dueDate = new Date(year, month, day);
  667. let now = new Date();
  668. now.setHours(0,0,0,0);
  669. dueDate.setHours(0,0,0,0);
  670. if (!match[3] && dueDate < now) {
  671. }
  672.  
  673. let diffMs = dueDate - now;
  674. let diffDays = Math.round(diffMs / (1000 * 60 * 60 * 24));
  675. let label = '', color = '';
  676.  
  677. // COMPLETED
  678. if (
  679. task.innerText.includes('Work has been received') ||
  680. task.innerText.includes('Grade:') ||
  681. task.innerText.includes('Mark:')
  682. ) {
  683. color = '#2ecc40'; // green
  684. if (diffDays < 0) {
  685. label = ` (completed ${-diffDays} days ago)`;
  686. } else if (diffDays === 0) {
  687. label = ` (completed today)`;
  688. } else {
  689. label = ` (completed early)`;
  690. }
  691. }
  692. // OVERDUE
  693. else if (
  694. task.innerText.includes('Overdue') ||
  695. task.innerText.includes('Uh did you submit on Turnitin or something?')
  696. ) {
  697. if (diffDays < 0) {
  698. color = '#e81123'; // red
  699. label = ` (overdue by ${-diffDays} day${-diffDays === 1 ? '' : 's'})`;
  700. }
  701. }
  702. // NORMAL
  703. else {
  704. if (diffDays > 1) {
  705. color = '#ffb900'; // amber/yellow
  706. label = ` (due in ${diffDays} days)`;
  707. }
  708. else if (diffDays === 1) {
  709. color = '#ffb900';
  710. label = ` (due in 1 day)`;
  711. }
  712. else if (diffDays === 0) {
  713. color = '#ffb900';
  714. label = ` (due today)`;
  715. }
  716. }
  717.  
  718. // Only add if not already present and label is not empty
  719. if (
  720. label &&
  721. !task.innerText.includes('due in') &&
  722. !task.innerText.includes('overdue') &&
  723. !task.innerText.includes('completed')
  724. ) {
  725. task.innerHTML = task.innerHTML.replace(
  726. /(due on \w{3} \d{1,2} \w{3}(?: \d{4})?)/i,
  727. `$1<br><span style="color:${color}; font-weight:bold;">${label}</span>`
  728. );
  729. }
  730. }
  731. });
  732. // --- End of Task list due/completed/overdue days feature ---
  733.  
  734. // --- Messages section: Show how long ago each message was sent ---
  735. (function enhanceMessages() {
  736. // Find the messages section (adjust selector if needed for your Daymap)
  737. const messageCards = document.querySelectorAll('#pnlRight > div.card.expWindow:nth-of-type(1) .msgList, #pnlRight > div.card.expWindow:nth-of-type(1) daymap-list-item');
  738. if (!messageCards.length) return;
  739.  
  740. // Helper: parse message date string to Date object
  741. function parseMessageDate(str) {
  742. const now = new Date();
  743. // Format 1: "Fri May 30" (older than a week)
  744. let match = str.match(/(\w{3}) (\w{3}) (\d{1,2})/);
  745. if (match) {
  746. // e.g., "Fri May 30" => this year
  747. let year = now.getFullYear();
  748. let month = ["jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec"].indexOf(match[2].toLowerCase());
  749. let day = parseInt(match[3], 10);
  750. let date = new Date(year, month, day);
  751. // If in the future (e.g., Dec 31 when now is Jan), assume last year
  752. if (date > now) date.setFullYear(year - 1);
  753. return date;
  754. }
  755. // Format 2: "Fri 2:11 PM" (this week)
  756. match = str.match(/(\w{3}) (\d{1,2}):(\d{2})\s*(AM|PM)/i);
  757. if (match) {
  758. // Find the most recent day matching the given weekday
  759. let targetDay = ["sun","mon","tue","wed","thu","fri","sat"].indexOf(match[1].toLowerCase());
  760. let hours = parseInt(match[2], 10);
  761. let minutes = parseInt(match[3], 10);
  762. if (match[4].toUpperCase() === "PM" && hours !== 12) hours += 12;
  763. if (match[4].toUpperCase() === "AM" && hours === 12) hours = 0;
  764. let date = new Date(now);
  765. date.setHours(hours, minutes, 0, 0);
  766. // Move back to the correct weekday
  767. let diff = (date.getDay() - targetDay + 7) % 7;
  768. date.setDate(date.getDate() - diff);
  769. // If the calculated date is in the future, subtract a week
  770. if (date > now) date.setDate(date.getDate() - 7);
  771. return date;
  772. }
  773. return null;
  774. }
  775.  
  776. // Helper: format how long ago
  777. function timeAgo(date) {
  778. const now = new Date();
  779. const diffMs = now - date;
  780. const diffSec = Math.floor(diffMs / 1000);
  781. const diffMin = Math.floor(diffSec / 60);
  782. const diffHr = Math.floor(diffMin / 60);
  783. const diffDay = Math.floor(diffHr / 24);
  784. if (diffDay > 0) return diffDay === 1 ? "1 day ago" : `${diffDay} days ago`;
  785. if (diffHr > 0) return diffHr === 1 ? "1 hour ago" : `${diffHr} hours ago`;
  786. if (diffMin > 0) return diffMin === 1 ? "1 minute ago" : `${diffMin} minutes ago`;
  787. return "just now";
  788. }
  789.  
  790. // Enhance each message
  791. messageCards.forEach(card => {
  792. // Find all message rows/items (adjust selector as needed for your Daymap)
  793. let items = card.querySelectorAll('.msg, daymap-list-item');
  794. if (!items.length) items = [card]; // fallback for flat list
  795.  
  796. items.forEach(item => {
  797. // Try to find the date string in the message (adjust selector if needed)
  798. let dateNode = item.querySelector('.date, .msgDate, .Caption') || item;
  799. let dateText = dateNode.textContent || '';
  800. let msgDate = parseMessageDate(dateText.trim());
  801. if (msgDate) {
  802. // Remove old "ago" label if present
  803. let old = item.querySelector('.msg-ago-label');
  804. if (old) old.remove();
  805. // Add new label
  806. let ago = document.createElement('span');
  807. ago.className = 'msg-ago-label';
  808. ago.style.color = '#888';
  809. ago.style.fontSize = '0.9em';
  810. ago.style.marginLeft = '8px';
  811. ago.textContent = `(${timeAgo(msgDate)})`;
  812. dateNode.appendChild(ago);
  813. }
  814. });
  815. });
  816. })();
  817. // --- End of messages section enhancement ---
  818.  
  819. // === NEW FEATURE: Color task subject text by timetable class color ===
  820. (function colorTasksByClass() {
  821. // 1. Build a mapping: subjectName → backgroundColor
  822. const subjectColorMap = {};
  823. document.querySelectorAll('.ttCell').forEach(cell => {
  824. // Get subject name (e.g., "12 RESEARCH PROJECTB")
  825. const subjDiv = cell.querySelector('.ttSubject');
  826. if (!subjDiv) return;
  827. const subjectName = subjDiv.textContent.trim().toUpperCase();
  828. // Get background color
  829. const bgColor = cell.style.backgroundColor || window.getComputedStyle(cell).backgroundColor;
  830. if (subjectName && bgColor) subjectColorMap[subjectName] = bgColor;
  831. });
  832.  
  833. // 2. Color task list items
  834. document.querySelectorAll('.feed .cap').forEach(capTd => {
  835. // Try to extract subject name from the task cell
  836. // The subject is usually after the <div class="Caption">SACE</div>
  837. // and before a <br>
  838. let html = capTd.innerHTML;
  839. let match = html.match(/<\/div>([^<]+?)<br/i);
  840. if (!match) return;
  841. let taskSubject = match[1].trim().toUpperCase();
  842. // Find color for this subject
  843. let color = subjectColorMap[taskSubject];
  844. if (color) {
  845. // Set color on all direct children (and <div class="Caption"> optionally)
  846. capTd.style.color = color;
  847. // Optionally: make the "Caption" div (e.g., "SACE") less saturated for contrast
  848. let captionDiv = capTd.querySelector('.Caption');
  849. if (captionDiv) captionDiv.style.color = '#666';
  850. }
  851. });
  852. })();
  853. // === END NEW FEATURE ===
  854.  
  855. });
  856. });
  857.  
  858. // --- HIDDEN GAME BUTTON ---
  859. if (GM_getValue('hiddenGameEnabled', DEFAULTS.hiddenGameEnabled)) {
  860. addHiddenGameButton();
  861. }
  862. }
  863. })();