SonicHackingContest Navbar Scroll

Avoids navbar dropdown menus from being cut-off and inaccessible by adding scrolling.

As of 2020-12-25. See the latest version.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         SonicHackingContest Navbar Scroll
// @version      2020.12.24
// @description  Avoids navbar dropdown menus from being cut-off and inaccessible by adding scrolling.
// @author       Obsidian
// @namespace    https://greasyfork.org/en/users/318252-obsidian
// @include      /^https?://(www\.)?sonichacking\.org//
// @include      /^https?://(www\.)?shc\.zone//
// @match        http*://*.sonichacking.org/*
// @match        http*://*.shc.zone/*
// @exclude      /^https?://(www\.)?(shc\.zone|sonichacking\.org)/vault//
// @grant        none
// @run-at       document-end
// @icon         data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAwUExURUdwTKfH4uzz+dfl8+70+nWn0hpqswpfrfD1+glfrXeo0v///wBbqxpstHCj0EWHwjcCEG0AAAALdFJOUwD49/Oay6syPmGDWhDOJgAABMNJREFUeNrtWtu2oyAMbUgVNRL+/28PoLa1AoRael7cD3NbM7O3uRO43S5cuHDhwoULFyQYhvsTw/Breq3oHQqdkN8pmEhZM3sYY6xlBlSrjB+p6AgXAU94IRBU3O8/UQBzDMYyBhHNLQHEcwKLiOYaMK3Ai/AacGicCmbOwQKRGpqmAuYVzIZVUwmpQHyT0NARkA2DTQLR/d/CYJEA7fwwlZ3gwYruzZxgJQraGWFUyswiI5C6N8oEFgmYDbZxg5aaILjhX00Q3DC0iAKQCpjtNxSEYQw93M/TpDWQESsw5/LRUavHBLb+Sikly8TToTgEcmC2ZvtiYywDFlvSVxQMSIRc8ak5BfVxcFek2MzfQb2CQRF+jT5EoqrzPdE36UM2VoSBo4fv0juA2AlDZYzLw0DIT/I628IJ9yafL3cCNvB+RSYMinhuBy6ZYCCyc0ugKvCrtvwz0z3Pb+bGyKXiL/hzqahVY/+XogB/wp9OBGiaf6+1ABP1D+bfIFEOFSEAsG0fhnM8DOExbwI0VgHpTJymCcLODVuKKNbjaerCKNwsKRJhuD/5TECkGvVF4Wg0dkhtWrN4OtTeDC0kSHywScDaBgmoFBZUA8nnY90pkhvBwHaK5LwPag4pI4iHRPtyo5AtrHWLG90J3WB3lxn8JR949LLN4NuNis0ORsvtT9g3lBfsA1K5VRl8v89Jizbvf3X4wkENDjdKGSdYtsaYbeHA2XF9kKWBOfATVrSH7OezrL4eYb4gQHpQixig4oiZzgrxQY1jAqRDliE4fVCDMwKAphS/uA/E+KVRaAn0LT4nivmjIUBKusccz/KfEgDUJeK/4qBgj+Y3s0Ehv46fFGsOCgcLhBUbi/ghwV+3qInGv+B2LcV/Q6obxTBWgYo1xCDF7e8MUHlShDf/L3OHKQ4wCf6bql0VcKQHmIIVmSjFP1UflU2kBXBeAJDqUy1A1e9K8DCImHwhAYIxfTVavyvgiAGOAszz3iPDX50Cu4nwxQAHAaxI8SZvSo/BHy0reN8Cw4jIB/5NlFFKn70aTkQBv+jhyLHBZBvAsi7DT/i3RDAv9oCjwrVGpgrgpyH40pHgpS7BgX+JECY15i7nPz0Q83outBiZSOB5WslVgE9D8CXK4nOxfQSIQcI++z4iE4LGWHawNrFDej8ewT5AeHnm0/X55xHR/9swAL49pYPIxRrv/xJY5kcCcBAI/Zg9C0c84MjVsjzrPPrwI4Q/i8SrsQygdn542B+py9NHPGAYiQgdbz+Oo35gHPu+S5f78OBv0x0MZkP2j+UHKjuzepcRePJY3uq+1LYeLWLhhyK/3r1MYCRKsi+rg9Lywj5nFAn/rgr51pFlX2xQqFu8roz8+FXmv3UPp670umSyvnR8h7A08+OXgP+G9Eo/askCBwpLsa34SfjXEGAk1Yn+wbJTVYSFSGBmkOzGQhWwVfTBDR0JXhsA0SCIQYtEVfTBCL4kEMSee7zIsuX785HUB/TeCE4CLPUZ2PoN1FKJYBei5T25/ox+dYQrjfDeDxGfa38reMbRf0q/GNCVZy9jg/9NWPsjMAOS6sufcf6hm28TK7ReA2SZhrpTX3fq6Vm/4L/4V8Po24ULFy5cuHDhQhp/KJVML/pyu60AAAAASUVORK5CYII=
// @noframes
// ==/UserScript==

/* jshint esversion: 6 */

(function(){
'use strict';
  
function GM_addStyle(aCss){
  var head = document.getElementsByTagName('head')[0];
  if(head){
    var style = document.createElement('style');
    style.setAttribute('type', 'text/css');
    style.textContent = aCss;
    head.appendChild(style);
    return style;
  }
  return null;
}

var strCss = `
@media (min-width: 992px) {
  .navbar-nav .dropdown-menu {
    max-height: calc(100vh - 100% - 2px);
    overflow-y: auto;
    scrollbar-width: thin;
    scrollbar-color: rgba(255,255,255,0.38) rgba(32,32,32,0.35);
    overscroll-behavior: contain;
  }
}
/* mobile style (max-width: 991.999) */
@media not all and (min-width: 992px) {
  .navbar-collapse {
    padding-top: 0.7px;
  }
  .navbar-collapse .navbar-nav {
    max-height: calc(100vh - 54px);
    overflow-y: auto;
    scrollbar-width: thin;
    scrollbar-color: rgba(255,255,255,0.38) rgba(32,32,32,0.35);
    overscroll-behavior: contain;
    min-width: max-content;
    max-width: 100vw;
    width: 24em;
  }
  .navbar {
    max-height: 50.6px;
    /*max-height: calc(3rem + 2px);*/
  }
  #entry_banner_image {
    width: 100%;
    max-width: 352px;
  }
  .shc-news-body iframe {
    max-width: 100%;
    max-height: calc((100vw - 4rem - 2px) * 0.5625); /* assume this is a 16x9 video */
  }
  .shc-news-body img {
    max-width: 100%;
    height: auto;
  }
}
/* constrain size of news post avatars on narrow screens */
@media only screen and (max-width: 480px) {
  .shc-news-header h2 {
    margin-right: 0px !important;
    font-size: 1.5rem;
  }
  .shc-news-header h2 a {
    font-size: 1.5rem;
  }
  .shc-news-header img {
    top: auto;
    bottom: -0.5em;
    right: 0.4rem;
    height: auto !important;
    width: 56px;
    width: 3.8rem;
  }
  /* trick for padding end of last-line */
  .shc-news-header h2::after {
    content: "\\00a0";
    display: inline;
    visibility: hidden;
    margin-left: -0.25em;
    padding-right: 54px;
    padding-right: calc(3.7rem - 1px);
  }
}
/* styles for all */
.navbar-nav > .nav-divider {
  flex-shrink: 0;
}
.navbar-nav > .nav-divider:first-child {
  display: none;
}
.navbar-backdrop {
  top: 50.4px !important;
  /*top: calc(3rem + 1px) !important;*/
}
.bg-shc-tails-light {
  background-color: #edd090 !important;
  background-color: var(--shc-tails-light, #edd090) !important;
}
.bg-shc-knuckles-light {
  background-color: #e0c0c6 !important;
  background-color: var(--shc-knuckles-light, #e0c0c6) !important;
}
/**/
.bg-shc-tails-light img[src*=\\/res\\/img\\/title] {
  filter: hue-rotate(calc(39deg - 207deg)) brightness(1.6);
}
.bg-shc-knuckles-light img[src*=\\/res\\/img\\/title] {
  filter: hue-rotate(calc(349deg - 207deg)) contrast(1.08);
}
/**/

/* non-standard thin scrollbar style */
.navbar-nav .dropdown-menu::-webkit-scrollbar, 
.navbar-collapse .navbar-nav::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}
.navbar-nav .dropdown-menu::-webkit-scrollbar-track, 
.navbar-collapse .navbar-nav::-webkit-scrollbar-track {
  background: rgba(32,32,32,0.35);
}
.navbar-nav .dropdown-menu::-webkit-scrollbar-track {
  margin: 0 1px 1px 1px; /* push away from menu border */
}
.navbar-nav .dropdown-menu::-webkit-scrollbar-thumb, 
.navbar-collapse .navbar-nav::-webkit-scrollbar-thumb {
  background: transparent linear-gradient(to right, rgba(248,248,248,0.35) 0%, rgba(248,248,248,0.35) 100%) 1.1px 0px/6.2px repeat-y;
}
.navbar-nav .dropdown-menu::-webkit-scrollbar-thumb:hover,
.navbar-collapse .navbar-nav::-webkit-scrollbar-thumb:hover {
  background-image: linear-gradient(to right, rgba(208,208,208,0.35) 0%, rgba(208,208,208,0.35) 100%);
}
.navbar-nav .dropdown-menu::-webkit-scrollbar-thumb:active,
.navbar-collapse .navbar-nav::-webkit-scrollbar-thumb:active {
  background-image: linear-gradient(to right, rgba(152,152,152,0.35) 0%, rgba(152,152,152,0.35) 100%);
}
.navbar-nav .dropdown-menu::-webkit-scrollbar-thumb:horizontal,
.navbar-collapse .navbar-nav::-webkit-scrollbar-thumb:horizontal {
  background-repeat: repeat-x;
  background-position: 0px 1.1px;
  background-size: auto 6.15px;
}
.navbar-nav .dropdown-menu::-webkit-scrollbar-corner,
.navbar-collapse .navbar-nav::-webkit-scrollbar-corner {
  background: transparent;
}`;

// Insert CSS styles
GM_addStyle(strCss);
// Add background color to menus
var bgTheme = document.querySelector(".navbar").className.match(/(bg-shc-[a-z]+)|$/)[1] || "bg-shc-sonic";
[].forEach.call(document.querySelectorAll(".navbar .navbar-nav"), function(navnode){navnode.classList.add(bgTheme);});
// Trigger backdrop event when clicking empty menu area
[].forEach.call(document.querySelectorAll(".navbar .navbar-collapse"), function(navnode){
  navnode.addEventListener("click", function(event){if(event.target===navnode){document.querySelector(".navbar-backdrop").click();}});
});
// Reset menu scroll position back to top
[].forEach.call(document.querySelectorAll(".navbar .nav-link.dropdown-toggle"), function(ddnode){
  ddnode.addEventListener("click", function(){setTimeout(function(){ddnode.parentElement.querySelector(".dropdown-menu").scrollTop=0;},100);});
});
// Reset mobile-view menu scroll position back to top
[].forEach.call(document.querySelectorAll(".navbar-toggler"), function(hbnode){
  hbnode.addEventListener("click", function(event){
    var n = document.querySelector(event.currentTarget.dataset.target);
    setTimeout(function(){n.scrollTop=0; if(!!n.firstElementChild){n.firstElementChild.scrollTop=0;}},80);
  });
});


// BONUS: Switching Color Themes

// Function for applying a theme
var cycleThemes = function(newTheme){
  var themes = ["shc-sonic","shc-tails","shc-knuckles"];
  var theme = document.querySelector(".navbar").className.match(/bg-(shc-[a-z]+)(?!\S)|$/)[1];
  var themeIndex = themes.indexOf(theme);
  var themeNext = newTheme || themes[(themeIndex+1)%themes.length];
  if(!theme){theme = themes[0];}
  if(theme===themeNext || !(/^shc-[a-z]+$/).test(themeNext)){return theme;}
  var themeExp = new RegExp("(bg-|border-|btn-)("+theme+")","g");
  [].forEach.call(document.querySelectorAll(".bg-"+theme+", .border-"+theme+", .btn-"+theme), function(node){
    node.className = node.className.replace(themeExp, "$1"+themeNext);
  });
  window.hapi_c.MODAL_THEME = window.hapi_c.MODAL_THEME.replace(/bg-shc-[a-z]+/, "bg-"+themeNext);

  // Some elements (footer, schedule, shc-entry-tags) are using CSS Var for color rather than class name.
  // Modify CSS Var "--shc-sonic" to match new theme:
  var cloneCssVar = function(fromVar, toVar, newOnly){
    if(!!getComputedStyle(document.documentElement).getPropertyValue("--"+fromVar)){
      if(!newOnly || !getComputedStyle(document.documentElement).getPropertyValue("--"+toVar)){
        document.documentElement.style.setProperty("--"+toVar, getComputedStyle(document.documentElement).getPropertyValue("--"+fromVar));
        return true;
      }
    }
    return false;
  };
  cloneCssVar("shc-sonic","shc-sonic-backup",true);
  if(themeNext==="shc-sonic"){
    cloneCssVar("shc-sonic-backup","shc-sonic");
  }else{
    cloneCssVar(themeNext,"shc-sonic");
  }

  // Set body background tint
  if(!document.querySelector(".alert")){
    document.body.className = document.body.className.replace(/(?:^|\s)bg-(shc-[a-z]+)-light(?!\S)/g, "").replace(/^\s/, "");
    document.body.classList.add("bg-"+themeNext+"-light");
  }
  return themeNext;
};
window.cycleThemes = cycleThemes;

// Insert a button for selecting theme
var strHtml = `<div id="theme-selector" class="dropup" style="position:absolute;display:inline-block;right:1em;margin-top:-2px;">
  <a id="theme-button" class="dropdown-toggle btn btn-shc-sonic" href="#" role="button" data-toggle="dropdown" style="display:block;padding:0 4px;font-size:1.1em;border:1px solid rgba(255,255,255,.4);">Theme</a>
  <ul role="menu" class="dropdown-menu dropdown-menu-right dropdown-pills dropdown-dark bg-shc-sonic" style="min-width:auto;">
    <li><a class="dropdown-item" role="menuitem" href="#">SHC-Sonic</a></li>
    <li><a class="dropdown-item" role="menuitem" href="#">SHC-Tails</a></li>
    <li><a class="dropdown-item" role="menuitem" href="#">SHC-Knuckles</a></li>
  </ul>
</div>`;
var footer = document.getElementsByTagName("footer")[0];
footer.insertAdjacentHTML("afterbegin", strHtml);
[].forEach.call(footer.querySelectorAll("#theme-selector a.dropdown-item"), function(node){
  node.addEventListener("click", function(event){
    var res=cycleThemes(event.currentTarget.innerText.toLowerCase()); event.preventDefault(); sessionStorage.setItem("theme",res); return false;
  }, false);
});

// Restore last theme
var savedTheme = sessionStorage.getItem("theme");
if(!!savedTheme){cycleThemes(savedTheme);}

})();