<a id="top"></a>
<a id="skip-to-main-content" href="#main">Skip to main content.</a>
<div id="rangeBox"></div>
<header>
<div class="container">
<div class="row">
<div class="col-12 content">
<div id="nav-wrapper">
<nav id="ddmenu-extra" role="navigation">
<a id="hamburger" role="button" aria-pressed="false" aria-expanded="false" aria-haspopup="true" aria-label="Open Menu" href="#">
<span class="title" aria-hidden="true">Menu</span>
<span class="line"></span>
<span class="line"></span>
<span class="line"></span>
</a>
<a class="logo ir" title="Click to go to Placeholder Company home page." href="./">
<h1>Welcome to the Placeholder Company Website</h1>
</a>
</nav>
<nav id="ddmenu-main" class="ddmenu" role="navigation">
<a id="hamburger-close" role="button" aria-label="Close Menu" href="#">
<span class="title" aria-hidden="true">Close</span>
<span class="line one"></span>
<span class="line two"></span>
</a>
<ul>
<li><a href="./">Home</a></li>
<li><a href="#about">About</a></li>
<li>
<a href="#">Products</a>
<ul>
<li>
<a href="#">For the Home</a>
<ul>
<li><a href="#">Bath</a></li>
<li>
<a href="#">Kitchen</a>
<ul>
<li><a href="#">Widgets</a></li>
<li><a href="#">Cutlery</a></li>
<li><a href="#">Thermometers</a></li>
<li><a href="#">Scales</a></li>
<li><a href="#">Storage</a></li>
</ul>
</li>
<li><a href="#">Exterior</a></li>
<li><a href="#">Other</a></li>
</ul>
</li>
<li>
<a href="#">Corporate</a>
<ul>
<li><a href="#">B2B</a></li>
<li><a href="#">Turnkey</a></li>
<li><a href="#">Franchises</a></li>
<li><a href="#">Other</a></li>
</ul>
</li>
<li><a href="#">Non Profits</a></li>
</ul>
</li>
<li>
<a href="#">Services</a>
<ul>
<li><a href="#">Cleanup</a></li>
<li><a href="#">Washup</a></li>
<li><a href="#">Tidying</a></li>
</ul>
</li>
<li><a href="#faq">F.A.Q.</a></li>
<li><a href="#contact">Contact</a></li>
</ul>
</nav>
</div><!-- .nav-wrapper -->
</div><!-- .col-12 content -->
</div><!-- .row -->
</div><!-- .container -->
</header>
<main id="main">
<div class="container">
<div class="row">
<div class="col-12 content">
<h2>An Excerpt from the book "Alice in Wonderland"</h2>
<p>
"That's very important," the King said, turning to the jury. They were just beginning to write this down on their slates, when the White Rabbit interrupted: "Unimportant, your Majesty means, of course," he said in a very respectful tone, but frowning and making faces at him as he spoke.
</p>
<p>
"Unimportant, of course, I meant," the King hastily said, and went on to himself in an undertone,
</p>
<img class="example" src="https://miro.medium.com/max/1716/1*TgvUcuCDGvYcv425gZ1ODg.jpeg" alt="Alice in the Court of the King and Queen, being lectured by the Queen." />
<p>
"important-unimportant-unimportant-important-" as if he were trying which word sounded best.
</p>
<p>
Some of the jury wrote it down "important," and some "unimportant." Alice could see this, as she was near enough to look over their slates; "but it doesn't matter a bit," she thought to herself.
</p>
<p>
At this moment the King, who had been for some time busily writing in his note-book, cackled out "Silence!" and read out from his book, "Rule Forty-two. All persons more than a mile high to leave the court."
</p>
<a id="about" href="#">Just a dummy About link</a>
<p>
Everybody looked at Alice.
</p>
<p>
"I'm not a mile high," said Alice.
</p>
<p>
"You are," said the King.
</p>
<a id="faq" href="#">Just a dummy FAQ link</a>
<p>
"Nearly two miles high," added the Queen.
</p>
<p>
"Well, I shan't go, at any rate," said Alice: "besides, that's not a regular rule: you invented it just now."
</p>
<p>
"It's the oldest rule in the book," said the King.
</p>
<p>
"Then it ought to be Number One," said Alice.
</p>
<p>
The King turned pale, and shut his note-book hastily. "Consider your verdict," he said to the jury, in a low, trembling voice.
</p>
<p>
"There's more evidence to come yet, please your Majesty," said the White Rabbit, jumping up in a great hurry; "this paper has just been picked up."
</p>
<a id="contact" href="#">Just a dummy Contact link</a>
<br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />
<br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />
</div><!-- .col-12 content -->
</div><!-- .row -->
</div><!-- .container -->
</main>
<footer>
<div class="container">
<div class="row">
<div class="col-12 content">
Footer Content
<p>
<a class="rt" href="#top">Back to Top</a><br /><br />
</p>
</div><!-- .col-12 content -->
</div><!-- .row -->
</div><!-- .container -->
</footer>
// THIS IS THE SCSS FILE
//begin colors
$color1: #fff; //white
$color2: #000; //black
$color3: #20145f; //dark blue
$color4: #00a1e1; //light blue
$color5: #ea7207; //dark orange
//end colors
//begin media query breakpoints
//matches https://getbootstrap.com/docs/5.0/layout/breakpoints/
$XS: 576px;
$S: 768px;
$M: 992px;
$L: 1200px;
$XL: 1400px;
//end media query breakpoints
//path to images
$ip: "../img/";
//end setup basic variables
//custom easings
$easeInOutBack: cubic-bezier(0.68, -0.55, 0.265, 1.55);
//begin mixins
// Begin Media Query mixin, original idea from
// Sass and Compass for Designers by Ben Frain
// https://www.packtpub.com/web-development/sass-and-compass-designers
// example breakpoints:
//$XS: 576px;
//$S: 768px;
//$M: 992px;
//$L: 1200px;
//$XL: 1400px;
@mixin MQ($canvas) {
@if $canvas == XS {
@media only screen and (min-width: 0) and (max-width: $XS - 1) {
@content;
}
} @else if $canvas == S {
@media only screen and (min-width: $XS) and (max-width: $S - 1) {
@content;
}
} @else if $canvas == M {
@media only screen and (min-width: $S) and (max-width: $M - 1) {
@content;
}
} @else if $canvas == L {
@media only screen and (min-width: $M) and (max-width: $L - 1) {
@content;
}
} @else if $canvas == XL {
@media only screen and (min-width: $L) and (max-width: $XL - 1) {
@content;
}
} @else if $canvas == XXL {
@media only screen and (min-width: $XL) {
@content;
}
} @else if $canvas == XSplus {
@media only screen and (min-width: 0) {
@content;
}
} @else if $canvas == Splus {
@media only screen and (min-width: $XS) {
@content;
}
} @else if $canvas == Mplus {
@media only screen and (min-width: $S) {
@content;
}
} @else if $canvas == Lplus {
@media only screen and (min-width: $M) {
@content;
}
} @else if $canvas == XLplus {
@media only screen and (min-width: $L) {
@content;
}
} @else if $canvas == XXLplus {
@media only screen and (min-width: $XL) {
@content;
}
} @else if $canvas == XSneg {
@media only screen and (max-width: $XS) {
@content;
}
} @else if $canvas == Sneg {
@media only screen and (max-width: $S) {
@content;
}
} @else if $canvas == Mneg {
@media only screen and (max-width: $M) {
@content;
}
} @else if $canvas == Lneg {
@media only screen and (max-width: $L) {
@content;
}
} @else if $canvas == XLneg {
@media only screen and (max-width: $XL) {
@content;
}
} @else if $canvas == XXLneg {
@media only screen and (min-width: 0) {
@content;
}
} @else if $canvas == XStoXL {
@media only screen and (min-width: 0) and (max-width: $XL - 1) {
@content;
}
}
@if $canvas == XSpor {
@media only screen and (min-width: 0) and (max-width: $XS - 1) and (orientation: portrait) {
@content;
}
} @else if $canvas == Spor {
@media only screen and (min-width: $XS) and (max-width: $S - 1) and (orientation: portrait) {
@content;
}
} @else if $canvas == Mpor {
@media only screen and (min-width: $S) and (max-width: $M - 1) and (orientation: portrait) {
@content;
}
} @else if $canvas == Lpor {
@media only screen and (min-width: $M) and (max-width: $L - 1) and (orientation: portrait) {
@content;
}
} @else if $canvas == XLpor {
@media only screen and (min-width: $XL) {
@content;
}
}
@if $canvas == XSlan {
@media only screen and (min-width: 0) and (max-width: $XS - 1) and (orientation: landscape) {
@content;
}
} @else if $canvas == Slan {
@media only screen and (min-width: $XS) and (max-width: $S - 1) and (orientation: landscape) {
@content;
}
} @else if $canvas == Mlan {
@media only screen and (min-width: $S) and (max-width: $M - 1) and (orientation: landscape) {
@content;
}
} @else if $canvas == Llan {
@media only screen and (min-width: $M) and (max-width: $L - 1) and (orientation: landscape) {
@content;
}
} @else if $canvas == XLlan {
@media only screen and (min-width: $XL) and (orientation: landscape) {
@content;
}
}
}
// end Media Query mixin
/* THIS IS THE GENERATED CSS FILE */
/* begin general css */
html {
//https://snook.ca/archives/html_and_css/font-size-with-rem
font-size: 62.5%;
--scroll-behavior: smooth; //for use with the polyfill
scroll-behavior: smooth;
}
body {
position: relative;
font-family: "MYRIADPROREGULAR", sans-serif !important;
line-height: 1.5;
hyphens: auto;
color: $color2;
//background: image-url('login-bg.jpg') no-repeat left center orange !important;
background-size: cover;
@include MQ(XS) {
//use 90% of base font size for XS viewport
font-size: 1.44rem; //14.4px
}
@include MQ(Splus) {
//use base font size for S and larger viewports
//https://snook.ca/archives/html_and_css/font-size-with-rem
font-size: 1.6rem; //16px
}
} //body
* {
// https://stackoverflow.com/questions/16587723/embedding-youtube-video-has-scrolling-issues-on-iphone
-webkit-overflow-scrolling: touch;
box-sizing: border-box;
}
.i {
font-style: italic;
}
.ni {
font-style: normal !important;
}
.b {
font-weight: bold;
}
.bni {
font-weight: normal !important;
}
.ir,
.ir:hover,
.ir:focus,
.ir:focus-within {
// image replacement
// http://nicolasgallagher.com/another-css-image-replacement-technique/
// https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757c9e03dda4e463fb0d4db5a5f82d7
font: 0/0 a;
text-shadow: none;
color: transparent;
background-color: transparent;
border: 0;
}
.rt {
float: right;
}
.lt {
float: left;
}
.cb {
clear: both !important;
}
.all-caps {
text-transform: uppercase;
}
.no-caps {
text-transform: lowercase;
}
.first-caps {
text-transform: capitalize;
}
.npr {
padding-right: 0px !important;
}
.npl {
padding-left: 0px !important;
}
.nmr {
margin-right: 0px !important;
}
.nml {
margin-left: 0px !important;
}
// ie9 gradient fix
.ie9 * {
/* http://www.colorzilla.com/gradient-editor/ */
filter: none;
}
a:active {
// no outline for clicked links
// https://www.456bereastreet.com/archive/200910/remove_the_outline_from_links_on_active_only/
outline: none !important;
}
:focus:not(:focus-visible) {
// no outline for not focus-visible elements
// https://twitter.com/LeaVerou/status/1045768279753666562
outline: none !important;
outline-color: transparent !important;
}
a#top {
//style the empty back to top anchor
position: absolute;
top: -10px;
left: 0px;
}
//begin standard anchor links
a.std {
//standard anchor links in content
color: $color4;
text-decoration: underline;
&:visited,
&:active {
color: $color4;
}
&:hover {
color: $color5;
font-style: italic;
text-decoration: none !important;
}
}
//end standard anchor links
hr {
height: 1px;
width: 100%;
background-color: $color3;
}
/* forces default iOS telephone link styling back to original styling */
a[href^="tel"] {
color: inherit;
text-decoration: none;
}
a#skip-to-main-content {
position: fixed;
z-index: 10000;
display: inline-block;
left: 50%;
top: 0;
transform: translate(-50%, -100vh);
background-color: rgba(0, 0, 0, 0.75);
color: white;
padding: 20px;
opacity: 0;
transition: opacity ease-in-out 0.5s, transform ease-in-out 0.2s,
color ease-in-out 0.5s, outline-offset ease-in-out 0.5s;
cursor: pointer;
text-decoration: none;
text-align: center;
box-shadow: 0px 5px 7px rgba(0, 0, 0, 0.5);
outline-offset: -40px;
&:hover {
color: cyan;
text-decoration: underline;
}
&:focus-visible {
transform: translate(-50%, 0vh);
opacity: 1;
outline-offset: -4px;
outline-color: white;
outline-style: dashed;
} //&:focus-visible
} //a.skip-to-main-content
/* begin custom scrollbar */
// https://alligator.io/css/css-scrollbars/
// https://www.w3schools.com/howto/tryit.asp?filename=tryhow_css_custom_scrollbar
// https://scotch.io/tutorials/customize-the-browsers-scrollbar-with-css
/* width */
//begin webkit browsers
::-webkit-scrollbar {
width: 8px;
}
/* Track */
::-webkit-scrollbar-track {
background-color: rgba(0, 0, 0, 0.05);
&:hover {
background-color: WhiteSmoke;
}
} //::-webkit-scrollbar-track
/* Handle */
::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.5);
border-radius: 4px;
border: 2px solid CornSilk;
} //::-webkit-scrollbar-thumb
/* Handle on hover */
::-webkit-scrollbar-thumb:hover {
background: DarkMagenta;
//border: 2px solid WhiteSmoke;
border: none;
}
//end webkit browsers
//begin firefox only
* {
scrollbar-width: thin;
scrollbar-color: DarkMagenta rgba(0, 0, 0, 0.1);
}
//end firefox only
/* end custom scrollbar */
/* begin readout */
div#readout {
display: none;
position: fixed;
z-index: 10000;
bottom: 0px;
left: 0px;
width: 200px;
height: 300px;
color: #fff;
background-color: rgba(0, 0, 0, 0.5);
overflow: auto;
font-size: 1em;
&::before {
color: yellow;
display: block;
clear: both;
}
@include MQ(XXL) {
&::before {
content: "350px / XXL";
}
}
@include MQ(XL) {
&::before {
content: "300px / XL";
}
}
@include MQ(L) {
&::before {
content: "250px / L";
}
}
@include MQ(M) {
&::before {
content: "200px / M";
}
}
@include MQ(S) {
&::before {
content: "150px / S";
}
}
@include MQ(XS) {
&::before {
content: "100px / XS";
}
}
@media only screen and (max-width: 450px) {
&::before {
content: "50px / 450";
}
}
} // div.readout
/* end readout */
//begin div#rangebox
// height state lets javascript test for state of the viewport
div#rangeBox {
position: fixed;
overflow: hidden;
top: 100px;
left: -20px;
width: 20px;
min-height: 10px;
color: $color1;
background-color: rgba(0, 0, 0, 0.5);
z-index: 10000;
//heights are arbitrary, but have to be different per the device state (if changed here, please change all if-checks in the JS)
@include MQ(XXL) {
height: 350px;
}
@include MQ(XL) {
height: 300px;
}
@include MQ(L) {
height: 250px;
}
@include MQ(M) {
height: 200px;
}
@include MQ(S) {
height: 150px;
}
@include MQ(XS) {
height: 100px;
}
@media only screen and (max-width: 450px) {
height: 50px;
}
} //end div#rangebox
/* end general css */
/* begin site styles */
body {
background-color: CornSilk;
overflow-x: hidden;
}
body.mobile-menu-opened,
html.mobile-menu-opened {
overflow: hidden;
}
//begin sticky menu
header {
@include MQ(Mplus) {
position: relative;
transform: translate(0%, 0%);
box-shadow: 0px 0px 0px 0px rgba(0, 0, 0, 0); //fadeout
transition: box-shadow 0.4s linear !important;
} //MQ(Mplus)
&.sticky {
@include MQ(Mplus) {
background-color: CornSilk;
width: 100%;
display: flex;
justify-content: center;
} //MQ(Mplus)
@include MQ(XLplus) {
margin: 0 auto !important;
}
@include MQ(L) {
width: 100% !important;
max-width: 100% !important;
}
@include MQ(M) {
width: 100% !important;
max-width: 100% !important;
}
@include MQ(Mplus) {
position: relative;
transform: translate(0%, calc(-100% - 10px)); //-10px hides the box-shadow
transition: transform 0.4s ease-in-out !important; //controls vertical animation
}
& div#nav-wrapper {
@include MQ(Mplus) {
margin: 0px !important;
}
}
& nav#ddmenu-extra {
margin: 2px !important;
} //& nav#ddmenu-extra
& a.logo {
@include MQ(Mplus) {
width: 167px !important;
height: 35px !important;
margin: 0 !important;
}
} //& a.logo
} //&.sticky
&.sticky.in {
&::before {
@include MQ(Mplus) {
content: "";
display: block;
position: absolute;
z-index: -1;
width: 100%;
height: 100%;
}
}
@include MQ(Mplus) {
position: fixed;
transform: translate(0%, 0%);
box-shadow: 0px 2px 2px 1px rgba(0, 0, 0, 0.1);
}
} //&.sticky.in
&.sticky.in.out {
@include MQ(Mplus) {
transform: translate(0%, calc(-100% - 10px)); //-10px hides the box-shadow
}
&::before {
@include MQ(Mplus) {
opacity: 0;
}
}
} //&.sticky.in.out
//reset padding for the first bootstrap .container in main for MQ(Sneg) and smaller
& + main > .container {
@include MQ(Sneg) {
padding-top: 0px !important;
}
}
} //header
//end sticky menu
main {
& h2 {
@include MQ(Splus) {
text-align: left;
}
@include MQ(XSneg) {
text-align: center;
}
}
& img.example {
width: 100%;
height: auto;
display: block;
@include MQ(Mplus) {
margin: 10px;
margin-left: 0px;
float: left;
}
@include MQ(Splus) {
max-width: 429px;
}
@include MQ(S) {
margin: 10px;
float: none;
margin: 0 auto;
}
@include MQ(XSneg) {
max-width: 320px;
margin: 0px;
float: none;
margin: 0 auto;
}
} //& img.example
} //main
//dummy links in MAIN element
a {
color: DarkMagenta;
transition: all ease 0.5s;
&:hover {
color: Teal;
}
&:focus {
outline-color: black;
outline-style: dashed;
outline-offset: 2px;
outline-width: 1px;
}
} //dummy links
//headings
h2,
h3,
h4,
h5,
h6 {
font-weight: bold;
}
:root {
//sizing variables for menu items
--ddbaseHeight: 40px; //base height of menu items
--ddbaseWidth: 150px; //base width of menu items
//background colors for menu items
--ddnavBGColor: transparent;
--ddnavMobileBGColor: MediumSeaGreen;
--ddnavMobileBGGradientStart: MediumSeaGreen;
--ddnavMobileBGGradientMid: LightGreen;
--ddnavMobileBGGradientEnd: CornSilk;
--ddanchorBGHover: MediumBlue;
--ddanchorBGSelected: MediumBlue;
--ddfirstLvlBG: DarkMagenta;
--ddsecondLvlBG: DodgerBlue;
--ddthirdLvlBG: SteelBlue;
--ddfourthLvlBG: DarkOliveGreen;
//text colors for menu items
--ddanchorTextColor: white;
--ddanchorTextHoverColor: CornSilk;
--ddanchorTxtSelectedColor: CornSilk;
//outline
--ddoutlineColor: white;
--ddoutlineColorLogo: black;
--ddoutlineOffset: -4px;
} //:root
div#nav-wrapper {
position: relative;
display: flex;
flex-wrap: wrap;
align-items: center;
width: 100%;
min-height: var(--ddbaseHeight);
margin-top: 15px;
margin-bottom: 15px;
transition: margin 0.4s ease-in-out; //for use when header is .sticky
@include MQ(XLplus) {
justify-content: flex-end;
}
@include MQ(L) {
justify-content: center;
}
} //div#nav-wrapper
nav#ddmenu-extra {
position: relative;
display: flex;
align-self: center;
@include MQ(XLplus) {
position: absolute !important;
left: 0px;
}
@include MQ(Lneg) {
width: 100%;
margin-bottom: 10px;
}
@include MQ(L) {
justify-content: center;
}
@include MQ(M) {
justify-content: center;
}
@include MQ(Sneg) {
flex-direction: row-reverse;
justify-content: flex-start;
}
& a#hamburger {
position: relative;
display: block;
overflow: hidden;
padding: 0;
font-size: 0rem;
text-align: right;
border: none;
background-color: transparent;
@include MQ(Mplus) {
display: none;
}
@include MQ(Sneg) {
align-self: center;
width: 40px;
height: 40px;
margin-left: auto;
}
&:focus-visible {
outline-offset: calc(var(--ddoutlineOffset) * -1);
outline-color: $color2;
}
.desktop &:hover {
& span.title {
color: DarkMagenta;
}
& span.line {
border-color: DarkMagenta;
}
& span.line::after {
transform: translate(-100%, 0);
}
} //.desktop &:hover
& span.title {
display: inline-block;
position: absolute;
bottom: 0px;
width: 100%;
font-size: 1rem;
text-align: center;
color: $color2;
transform: translate(-50%, 2px);
transition: transform 0.25s ease-in-out, color 0.5s ease-in-out;
} //& span.title
& span.line {
display: inline-block;
position: relative;
top: 4px;
height: 6px;
margin-bottom: 3px;
border-top: 1px solid $color2;
transition: border-color 0.25s ease-in-out;
&:nth-of-type(2) {
width: 50%;
}
&:nth-of-type(3) {
width: 75%;
&::after {
transition-delay: 0.25s;
}
}
&:nth-of-type(4) {
width: 100%;
margin-bottom: 0px;
&::after {
transition-delay: 0.5s;
}
}
&::after {
content: "";
display: inline-block;
position: absolute;
width: 100%;
height: 100%;
background-color: DarkMagenta;
transition: transform 0.5s ease-in-out;
}
} //& span.line
} //& a#hamburger
& a.logo {
display: block;
position: relative;
z-index: 1;
float: left;
width: 250px;
height: 52px;
overflow: hidden;
line-height: 0;
background-image: url("https://placeholder.com/wp-content/uploads/2018/10/placeholder.com-logo1.png"); //original 885x183
background-repeat: no-repeat;
background-size: 100% auto;
background-position: top left;
transition: outline-offset 0.4s ease-in-out, width 0.4s ease-in-out,
height 0.4s ease-in-out; //for use when header is .sticky
&::before,
&::after {
display: none !important;
}
&:focus-visible {
outline-offset: calc(var(--ddoutlineOffset) * -1);
outline-color: var(--ddoutlineColorLogo);
}
} //& a.logo
} //nav#ddmenu-extra
nav#ddmenu-main {
@include MQ(Sneg) {
position: fixed;
z-index: 9000;
display: flex;
justify-content: center;
align-items: flex-start;
visibility: hidden;
opacity: 0;
overflow: auto;
width: 100%;
height: 100%;
top: 0;
bottom: 0;
left: 0;
right: 0;
padding: 15px;
background-color: var(--ddnavMobileBGColor);
background: linear-gradient(
180deg,
var(--ddnavMobileBGGradientStart) 0%,
var(--ddnavMobileBGGradientStart) 35%,
var(--ddnavMobileBGGradientMid) 80%,
var(--ddnavMobileBGGradientEnd) 100%
);
background-position: center top;
background-repeat: no-repeat;
background-size: cover;
transform: translate(0px, -100%);
@media (prefers-reduced-motion: reduce) {
animation: none;
transition: none;
}
@media (prefers-reduced-motion: no-preference) {
//shorthand style:
//property duration ease delay
transition: visibility 0s ease-in-out 0.5s, opacity 0.5s ease-in-out 0s,
transform 0.5s ease-in-out 0s;
} //prefers-reduced-motion: no-preference
} //MQ(Sneg)
&.opened {
@include MQ(Sneg) {
opacity: 1;
visibility: visible;
//the transition delay for visibility (the first value, in the transition-delay, below) is set to zero so the menu and its animation may immediately be seen
transition-delay: 0s, 0s, 0s;
transform: translate(0px, 0px);
} //MQ(Sneg)
//overall ul
& > ul::before {
width: 100%;
height: 100%;
visibility: visible;
opacity: 1;
box-shadow: 0px 2px 2px 1px rgba(0, 0, 0, 0.25);
//shorthand style:
//property duration ease delay
transition: visibility 0s ease-in-out 1.5s, opacity 0.75s ease-in-out 1.5s,
transform 0.75s ease-in-out 1.5s;
} //& > ul::before
//first level nav items
& > ul > li {
opacity: 1;
transform: rotateX(0deg) rotateY(0deg) rotateZ(0deg) translate(0px, 0px)
scale(1);
}
} //&.opened
& a#hamburger-close {
@include MQ(Mplus) {
display: none;
}
@include MQ(Sneg) {
position: absolute;
display: flex;
justify-content: center;
align-items: center;
z-index: 10;
top: 10px;
right: 10px;
width: 40px;
height: 40px;
font-size: 0rem;
text-decoration: none;
color: $color2;
.desktop &:hover span.title {
color: DarkMagenta;
}
.desktop &:hover span.line {
background-color: DarkMagenta;
&.one {
transform: rotate(225deg);
}
&.two {
transform: rotate(135deg);
}
} //.desktop &:hover span.line
} //MQ(Sneg)
& span.title {
@include MQ(Sneg) {
position: absolute;
bottom: 0px;
font-size: 1rem;
transition: color 0.4s ease-in-out;
} //MQ(Sneg)
} //& span.title
& span.line {
@include MQ(Sneg) {
position: absolute;
top: 26%;
left: 27%;
display: block;
width: 20px;
height: 4px;
background-color: $color2;
transform-origin: 50% 50%;
@media (prefers-reduced-motion: reduce) {
animation: none;
transition: none;
}
@media (prefers-reduced-motion: no-preference) {
transition: background-color 0.4s ease-in-out, transform 0.4s ease-in-out;
} //prefers-reduced-motion: no-preference
&.one {
transform: rotate(45deg);
}
&.two {
transform: rotate(-45deg);
}
} //MQ(Sneg)
} //& span.line
} //& a#hamburger-close
} //div#ddmenu-main
//begin dropdown
nav.ddmenu {
@include MQ(Mplus) {
display: flex;
align-self: center;
align-items: center;
position: relative;
min-height: var(--ddbaseHeight);
background-color: var(--ddnavBGColor);
} //MQ(Mplus)
@include MQ(XLplus) {
justify-content: flex-end;
width: calc(100% - 250px);
}
@include MQ(Lneg) {
width: 100%;
}
@include MQ(L) {
justify-content: center;
}
@include MQ(M) {
justify-content: center;
}
& li a {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
min-height: var(--ddbaseHeight);
padding-left: 10px;
padding-right: 10px;
padding-top: 10px;
padding-bottom: 10px;
font-family: verdana, sans-serif;
hyphens: auto;
text-decoration: none;
line-height: 1.25;
color: var(--ddanchorTextColor);
text-align: center;
//https://www.cloudhadoop.com/sass-calc-function/
//yields initial outline-offset to be at center of anchor.
//comment out the following line if close to standard is desired
outline-offset: calc(max(var(--ddbaseHeight) * -1, var(--ddbaseWidth) * -1));
outline: 1px dashed transparent;
@media (prefers-reduced-motion: reduce) {
animation: none;
transition: none;
}
@media (prefers-reduced-motion: no-preference) {
transition: color ease-in-out 0.5s, background-color ease-in-out 0.5s,
outline-offset ease-in-out 0.25s, outline-color ease-in-out 0.25s;
}
.desktop &:hover {
background-color: var(--ddanchorBGHover);
color: var(--ddanchorTextHoverColor);
}
&:focus-visible {
outline-offset: var(--ddoutlineOffset);
outline: 1px dashed var(--ddoutlineColor);
background-color: var(--ddanchorBGSelected);
color: var(--ddanchorTxtSelectedColor);
} //&:focus-visible
//menu open/closed indicators
&.hasSub::before,
&.hasSub::after {
content: "\25BC"; //downward solid triangle
font-size: 0.75em;
margin-left: 0.25em;
@media (prefers-reduced-motion: reduce) {
animation: none;
transition: none;
}
@media (prefers-reduced-motion: no-preference) {
transition: transform ease-in-out 0.25s, color ease-in-out 0.25s;
}
} //&.hasSub::after
&.hasSub::before {
display: none; //will be overridden by ul.shiftLeft
@include MQ(Sneg) {
display: none !important; //never show for this MQ
}
} //&.hasSub::before
&.hasSub::after {
@include MQ(Sneg) {
display: block !important; //always show for this MQ
}
} //&.hasSub::before
&.hasSub.opened::after {
//content: '\25B2'; //upward solid triangle
transform: rotate(180deg);
}
//menu open/closed indicators
} //& li a
& ul,
& li {
margin: 0;
padding: 0;
list-style: none;
}
& > ul {
//overall UL
position: relative;
@include MQ(Mplus) {
display: flex;
flex-wrap: wrap;
width: 100%;
height: 100%;
box-shadow: none !important; //prevents box shadow from .opened state and browser width change from bleeding over into this MQ
transition: all 0s; //prevents any transition from MQ(Sneg) from bleeding over into this MQ
} //MQ(Mplus)
//begin submenu on left side, as required
& ul.shiftLeft a.hasSub::after {
display: none;
}
& ul.shiftLeft a.hasSub::before {
display: block;
margin-left: 0 !important;
margin-right: 0.25em;
transform: rotate(90deg);
}
& ul.shiftLeft a.hasSub.opened::before {
transform: rotate(-90deg);
}
& ul.shiftLeft > li > ul {
@include MQ(Mplus) {
left: calc(-100% - 5px) !important;
}
//menu arrow
&::before {
@include MQ(Mplus) {
//horizontal
left: calc(100% - 6px);
transform: rotate(135deg);
} //MQ(Mplus)
}
} //& ul.shiftLeft > li > ul
//end submenu on left side, as required
@include MQ(XLplus) {
justify-content: flex-end;
}
@include MQ(L) {
justify-content: center;
}
@include MQ(M) {
justify-content: center;
}
@include MQ(Sneg) {
justify-content: center;
width: calc(100% - 100px); //accounts for close button
min-width: calc(var(--ddbaseWidth) * 0.9);
max-width: calc(var(--ddbaseWidth) * 2.5);
&::before {
content: "";
position: absolute;
visibillity: hidden;
opacity: 0;
width: 0;
height: 0;
box-shadow: 0px 0px 0px 0px rgba(0, 0, 0, 0.25); //this preparatory for animating in box-shadow on menu being opened
@media (prefers-reduced-motion: reduce) {
animation: none;
transition: none;
}
} //&::before
} //MQ(Sneg)
//home link for mobile menu
& > li:first-of-type {
@include MQ(Mplus) {
display: none;
}
@include MQ(Sneg) {
display: block;
}
} //& > li:first-of-type
& li {
position: relative;
display: block;
@include MQ(Lplus) {
width: var(--ddbaseWidth);
}
@include MQ(M) {
width: calc(0.9 * var(--ddbaseWidth));
}
@include MQ(Sneg) {
width: 100%;
}
} //& li
//default submenu state
& li ul {
//hide all sub nav below first level
//https://web.dev/prefers-reduced-motion/
//if the user has set a preference for no animations
@media (prefers-reduced-motion: reduce) {
animation: none;
transition: none;
}
@include MQ(Mplus) {
visibility: hidden;
opacity: 0;
transform: scale(0.5);
transform-origin: 50% calc(50% - var(--ddbaseHeight));
//https://web.dev/prefers-reduced-motion/
//If the browser understands the following media query and the user explicitly hasn't set a preference, allow animations
@media (prefers-reduced-motion: no-preference) {
//https://css-tricks.com/snippets/css/toggle-visibility-when-hiding-elements/
//http://jsfiddle.net/YZvdm/29/
//set transition-delay of visibility so that, on menu close, the submenu animation may be seen due to the visibility transition-delay being set to the longest animation duration of anything that needs to animate. The visibility transition-duration should be set to 0 for this to work. Other items' transition delay should be set so the animations occur immediately. This ability to nicely transition out doesn't seem to work for named keyframe animations for the submenus 2 levels deeper than where you are currently clicking, hence why the named animations are commented out.
//use longhand style, below, or shorthand style
//transition-property: visibility, opacity, transform;
//transition-duration: 0s, .4s, 0.4s;
//transition-timing-function: ease-in-out, ease-in-out, ease-in-out;
//transition-delay: 0.4s, 0s, 0s;
//shorthand style:
//property duration ease delay
transition: visibility 0s ease-in-out 0.4s, opacity 0.4s ease-in-out 0s,
transform 0.4s ease-in-out 0s;
//animation: ddClipOut ease-out 0.4s;
} //prefers-reduced-motion: no-preference
} //MQ(Mplus)
@include MQ(Sneg) {
overflow: hidden;
max-height: 0px;
opacity: 0;
visibility: hidden;
//https://web.dev/prefers-reduced-motion/
//If the browser understands the following media query and the user explicitly hasn't set a preference, allow animations
@media (prefers-reduced-motion: no-preference) {
//https://css-tricks.com/snippets/css/toggle-visibility-when-hiding-elements/
//http://jsfiddle.net/YZvdm/29/
//set transition-delay of visibility so that, on menu close, the submenu animation may be seen due to the visibility transition-delay being set to the longest animation duration of anything that needs to animate. The visibility transition-duration should be set to 0 for this to work. Other items' transition delay should be set so the animations occur immediately. This ability to nicely transition out doesn't seem to work for named keyframe animations for the submenus 2 levels deeper than where you are currently clicking, hence why the named animations are commented out.
//shorthand style:
//property duration ease delay
transition: visibility 0s ease-in-out 0.4s, opacity 0.4s ease-in-out 0s,
max-height 0.4s ease-in-out 0s;
} //prefers-reduced-motion: no-preference
} //MQ(Sneg)
//menu arrow to parent menu item, common properties
&::before {
position: absolute;
//z-index: -1;
display: block;
content: "";
width: 10px;
height: 10px;
clip-path: polygon(0% 0%, 100% 0px, 0px 100%); //form a triangle
} //&::before
} //& li ul
} // & > ul //overall UL
//first level nav
& > ul > li {
background-color: var(--ddfirstLvlBG);
@include MQ(Sneg) {
opacity: 0;
transform-origin: center bottom;
transform: rotateX(-90deg) rotateY(0deg) rotateZ(-20deg)
translate(0px, -200%) scale(0.05);
@media (prefers-reduced-motion: reduce) {
animation: none;
transition: none;
}
@media (prefers-reduced-motion: no-preference) {
transition: opacity 1s ease-in-out, transform 0.75s $easeInOutBack;
//give transition delays to fill up from bottom. use :nth-of-type(#{10-$i}) to fill down from top
@for $i from 1 through 10 {
//10 is arbitrary but should be set to reasonable number, equal to the maximum relative number of first level navigation items
&:nth-of-type(#{$i}) {
transition-delay: #{0.1 * (10-$i)}s, #{0.1 * (10-$i)}s;
}
} //@for
} //prefers-reduced-motion: no-preference
} //MQ(Sneg)
} //& > ul > li
//first level nav
//second level nav
& > ul > li > ul {
@include MQ(Mplus) {
position: absolute;
z-index: 1; //shows this and subsequent navigation levels overtop of first level nav
top: calc(var(--ddbaseHeight) + 5px);
} //MQ(Mplus)
& > li {
background-color: var(--ddsecondLvlBG);
}
& a {
&.hasSub::after {
@include MQ(Mplus) {
transform: rotate(-90deg);
}
}
&.hasSub.opened::after {
//content: '\25C4'; //left facing triangle
//content: '\25BA'; //right facing triangle
//transform-origin: 0% 60%;
//transform: scaleY(2) scaleX(1.25);
@include MQ(Mplus) {
transform: rotate(90deg);
}
} //&.hasSub.opened::after
} //& > a
//menu arrow
&::before {
background-color: var(--ddsecondLvlBG);
margin-left: calc(50% - 5px);
transform: rotate(45deg);
@include MQ(Mplus) {
//vertical
top: -3px;
left: 0px;
}
@include MQ(Sneg) {
//vertical
top: calc(var(--ddbaseHeight) - 3px) !important;
}
} //&::before
} //& > ul > li > ul
//second level nav
//third level nav +
& > ul > li > ul > li ul {
@include MQ(Mplus) {
position: absolute;
top: 0px;
//give horizontal gap between second and third level menu items
left: calc(100% + 5px); //submenu on right side by default
} //MQ(Mplus)
//menu arrow
&::before {
//arrow points to left by default
background-color: var(--ddthirdLvlBG);
@include MQ(Mplus) {
//horizontal
top: calc(var(--ddbaseHeight) / 2 - 3px);
left: -3px;
transform: rotate(-45deg);
} //MQ(Mplus)
@include MQ(Sneg) {
//vertical
top: calc(var(--ddbaseHeight) - 3px) !important;
left: calc(50% - 3px);
transform: rotate(45deg);
} //MQ(Sneg)
} //&::before
}
//third level nav+
//third level nav
& > ul > li > ul > li > ul > li {
background-color: var(--ddthirdLvlBG);
}
//third level nav
//fourth level nav
& > ul > li > ul > li > ul > li > ul::before {
background-color: var(--ddfourthLvlBG) !important;
}
& > ul > li > ul > li > ul > li > ul > li {
background-color: var(--ddfourthLvlBG);
}
//fourth level nav
} //nav.ddmenu
.ddmenu:not(.js-enabled) li:hover > ul, //only for when JS doesn't provide the js-enabled class to the menu
.ddmenu ul.selected {
//the transition delay for visibility (the first value, in the transition-delay, below) is set to zero so the submenu and its animation may immediately be seen
transition-delay: 0s, 0s, 0s;
@include MQ(Mplus) {
visibility: visible;
opacity: 1;
transform: scale(1);
transform-origin: 50% 50%;
box-shadow: 0px 2px 2px 1px rgba(0, 0, 0, 0.25);
//https: //stackoverflow .com/questions/ 22444440px /how to make pure css dropdown menu appear with a fade in slowly
//animation: ddClipIn ease-out 0.4s;
} //MQ(Mplus)
@include MQ(Sneg) {
visibility: visible;
opacity: 1;
max-height: 500px; //good for approx 10 relative first level menu items
overflow: initial;
} //MQ(Sneg)
} //ul.selected
//begin keyframe based animations
//https://stackoverflow.com/questions/22444440/how-to-make-pure-css-dropdown-menu-appear-with-a-fade-in-slowly
//https://css-tricks.com/animating-with-clip-path/
//https://codepen.io/geoffgraham/pen/xojrNG
@keyframes ddClipIn {
//the +/-5px values give a little more space so that arrows in gap are visible
0% {
clip-path: polygon(
-5px -5px,
calc(100% + 5px) -5px,
calc(100% + 5px) -5px,
-5px -5px
);
}
100% {
clip-path: polygon(
-5px -5px,
calc(100% + 5px) -5px,
calc(100% + 5px) calc(100% + 5px),
-5px 100%
);
}
}
@keyframes ddClipOut {
//the +/-5px values give a little more space so that arrows in gap are visible
0% {
clip-path: polygon(
-5px -5px,
calc(100% + 5px) -5px,
calc(100% + 5px) calc(100% + 5px),
-5px 100%
);
}
100% {
clip-path: polygon(
-5px -5px,
calc(100% + 5px) -5px,
calc(100% + 5px) -5px,
-5px -5px
);
}
}
//end keyframe based animations
//end dropdown
/* end site styles */
View Compiled
/*
This is a WIP.
Features:
1. Click based dropdown menu.
2. Plain vanilla JavaScript.
3. Mobile menu enabled, with opening animation.
4. Submenus account for right edge of browser.
5. Submenus have arrow indicators that point appropriately.
6. Scrolling disabled when mobile menu is opened.
7. Menu is directionally scroll-aware for MQ(M) or larger.
Items to improve-
A. add js disabled styles for mobile view, MQ(S) and smaller
*/
(function () {
//begin Self-Executing Anonymous Function
"use strict";
//equivalent to jQuery document ready event
document.addEventListener("DOMContentLoaded", domContentLoaded);
function domContentLoaded() {
//setup keys to trigger a click on skip to main content link
keysTriggerClick(document.getElementById("skip-to-main-content"));
//overall drop down menu
let ddmenuMain = document.getElementById("ddmenu-main");
//setup main navigation dropdown
initDdmenu(ddmenuMain);
} //end fxn domContentLoaded
function getOffset(el) {
//https://stackoverflow.com/questions/442404/retrieve-the-position-x-y-of-an-html-element-relative-to-the-browser-window#11396681
//usage: xCoord = getOffset( document.getElementById('yourElId') ).left
let _x = 0;
let _y = 0;
while (el && !isNaN(el.offsetLeft) && !isNaN(el.offsetTop)) {
_x += el.offsetLeft - el.scrollLeft;
_y += el.offsetTop - el.scrollTop;
el = el.offsetParent;
}
return { top: _y, left: _x };
} //end fxn getOffset
function initDdmenu(ddMenu) {
//begin setup hamburger menu anchors
let hamburger = document.getElementById("hamburger");
let hamburgerClose = document.getElementById("hamburger-close");
let body = document.getElementsByTagName("body")[0];
let html = document.getElementsByTagName("html")[0];
let skipToMain = document.getElementById("skip-to-main-content");
keysTriggerClick(hamburger);
keysTriggerClick(hamburgerClose, ["Escape"]); //allow default keys and escape key to trigger click
hamburger.addEventListener("click", function (e) {
e.preventDefault();
if (!ddMenu.classList.contains("opened")) {
ddMenu.classList.add("opened");
this.setAttribute("aria-pressed", "true");
this.setAttribute("aria-expanded", "true");
//set timeout to be no smaller than 50 to allow time for visibility of menu to change
window.setTimeout(function () {
hamburgerClose.focus();
}, 50);
//give class to prevent scrolling while mobile menu is open
body.classList.add("mobile-menu-opened");
html.classList.add("mobile-menu-opened");
skipToMain.style.visibility = "hidden";
} //end if
}); //end hamburger click
hamburgerClose.addEventListener("click", function (e) {
e.preventDefault();
if (ddMenu.classList.contains("opened")) {
ddMenu.classList.remove("opened");
hamburger.setAttribute("aria-pressed", "false");
hamburger.setAttribute("aria-expanded", "false");
//set timeout to be no smaller than 50 to allow time for visibility of menu to change
window.setTimeout(function () {
hamburger.focus();
}, 50);
body.classList.remove("mobile-menu-opened");
html.classList.remove("mobile-menu-opened");
skipToMain.style.visibility = "visible";
} //end if
}); //end hamburgerClose click
//variables for window events
let windowWidth = window.outerWidth;
let windowHeight = window.outerHeight;
let resizeTimer; //for window resize event setTimeout
let resizeDelay = 20; //throttling delay for window resize event in milliseconds
let scrollTimer; //for window scroll event setTimeout
let scrollDelay; //throttling delay for window scroll event in milliseconds
let yStart = window.pageYOffset; //initial scroll position
let rangeBox = document.getElementById("rangeBox"); //el that changes height based on different MQs
let rangeBoxHeight; //default heights for rangeBox at various media queries is within its definition in the stylesheet
if (html.classList.contains("ff") && html.classList.contains("desktop")) {
scrollDelay = 50; //ff for desktop has a problem with really low scrollDelay values
} else if (
html.classList.contains("safari") &&
html.classList.contains("mobile")
) {
scrollDelay = 0; //safari under ios seems to require this
} else {
scrollDelay = 20;
}
window.addEventListener("load", function () {
// get initial window dimensions for resize event check
windowWidth = window.outerWidth;
windowHeight = window.outerHeight;
});
//end window load event
window.addEventListener(
"resize",
function () {
/* https://stackoverflow.com/questions/8898412/iphone-ipad-triggering-unexpected-resize-events */
// Check window width has actually changed and it's not just a buggy iOS triggering a resize event on scroll
if (
window.outerWidth != windowWidth ||
window.outerHeight != windowHeight
) {
/* https://css-tricks.com/snippets/jquery/done-resizing-event/ */
//run code after a time once resizing is done
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function () {
// Run code here, resizing has "stopped"
//update the window dimensions for next use
windowWidth = window.outerWidth;
windowHeight = window.outerHeight;
rangeBoxHeight = rangeBox.offsetHeight;
if (ddMenu.classList.contains("opened")) {
if (rangeBoxHeight > 150) {
//MQ(M) or larger
body.classList.remove("mobile-menu-opened");
html.classList.remove("mobile-menu-opened");
} else {
//MQ(S) or smaller
body.classList.add("mobile-menu-opened");
html.classList.add("mobile-menu-opened");
} //end if
} //end if
checkSubMenuBounds(subMenuUL);
}, resizeDelay);
} //end if window dimensions check
},
true
);
//end window resize event
window.addEventListener(
"scroll",
function () {
let yEnd; //ending scroll position after timeout has finished
let scrollDir; //scrolling direction. >0 = scroll down, <0 = scroll up
let header = document.getElementsByTagName("header");
let main = document.getElementsByTagName("main");
let cont = main[0].getElementsByClassName("container")[0]; //first bootstrap container within the main tag that gets paddingTop adjusted on scroll
/* https://css-tricks.com/snippets/jquery/done-resizing-event/ */
//run code after a time once resizing is done
clearTimeout(scrollTimer);
scrollTimer = setTimeout(function () {
//Run code here, scrolling has "stopped"
rangeBoxHeight = rangeBox.offsetHeight;
if (rangeBoxHeight > 150) {
//MQ(M) or larger
yEnd = window.pageYOffset;
scrollDir = yEnd - yStart;
yStart = yEnd;
if (yEnd > 0) {
header[0].classList.add("sticky");
resetDdmenu(ddMenu);
if (scrollDir < 0) {
//scroll direction is up
cont.style.paddingTop = header[0].offsetHeight + "px"; //give padding equal to height of header
header[0].classList.add("in");
header[0].classList.remove("out");
} else {
//scroll direction is down
header[0].classList.add("out");
}
} else {
resetStickyMenu(header[0], cont);
} //end if
} else {
//MQ(S) or smaller, don't use sticky menu as it takes up too much of the screen
resetStickyMenu(header[0], cont);
} //end if
}, scrollDelay);
},
true
);
//end window scroll event
//begin window orientation change event
window.matchMedia("(orientation: portrait)").addListener(function (m) {
if (m.matches) {
//portrait
window.dispatchEvent(new Event("resize"));
} else {
//landscape
window.dispatchEvent(new Event("resize"));
}
});
// end window orientation change event
//add class that overrides default, CSS based hover behavior
ddMenu.classList.add("js-enabled");
//submenu ULs
let subMenuUL = ddMenu.querySelectorAll("ul a + ul");
//handle clicks outside of menu
document.body.addEventListener("click", function (e) {
if (
!e.target.closest("#" + ddMenu.getAttribute("id")) ||
e.target.tagName.toLowerCase() != "a"
) {
//if out of menu, or if within menu but not on an anchor
//this last check is because the entire menu can span from an apparent logo as the first menu item, for menus having the class of "with-logo", to the last apparent menu item
resetDdmenu(ddMenu);
}
}); //end handle clicks outside of menu
//https://stackoverflow.com/questions/3369593/how-to-detect-escape-key-press-with-pure-js-or-jquery
//handle escape key being pressed
document.body.addEventListener("keydown", function (e) {
e = e || window.e;
if (e.key === "Escape") {
resetDdmenu(ddMenu);
}
});
//end handle escape key being pressed
//begin menu items with internal links
//get all links with hashtag
let hashLinks = ddMenu.querySelectorAll('a[href^="#"]'); //href starting with #
for (let hashLink of hashLinks) {
if (hashLink.getAttribute("href").toString().length > 1) {
//hashlink is internal link
//allow internal links to respond to spacebar
keysTriggerClick(hashLink);
hashLink.addEventListener("click", function (e) {
//reset menu
resetDdmenu(ddMenu);
//provide proper hash for URL
let hashTarget = document.getElementById(
hashLink.getAttribute("href").substring(1)
);
window.location.hash = hashTarget;
hamburgerClose.click();
window.setTimeout(function () {
hashTarget.focus();
}, 60); //set timeout to be larger than timeout for hamburger focus timeout
});
} //end if
}
//end menu items with internal links
//provide initial aria for subMenuUL
subMenuUL.forEach(function (item) {
item.setAttribute("aria-hidden", "true");
item.setAttribute("aria-label", "submenu");
});
for (let subMenu of subMenuUL) {
//get the anchor followed by a sub menu
let subLink = subMenu.parentNode.querySelector("a:first-of-type");
subLink.classList.add("hasSub");
//setup initial aria for each subLink
subLink.setAttribute("role", "button");
subLink.setAttribute("aria-haspopup", "true");
subLink.setAttribute("aria-pressed", "false");
subLink.setAttribute("aria-expanded", "false");
//setup keys to trigger a click
keysTriggerClick(subLink);
subLink.addEventListener(
"click",
function (e) {
e.preventDefault();
//handle open / closed glyphs for subLink, and aria
if (this.classList.contains("opened")) {
resetDdmenuSublink(this);
} else {
//if closed
resetDdmenuSublink(this);
this.classList.add("opened");
this.setAttribute("aria-pressed", "true");
this.setAttribute("aria-expanded", "true");
} //end if
//begin check to see if submenu UL is of class selected when clicked
if (
subLink.parentNode
.querySelector("ul:first-of-type")
.classList.contains("selected")
) {
//if selected, the submenu is opened, so close it
resetDdmenuSubMenu(subLink);
subLink.parentNode
.querySelector("ul:first-of-type")
.classList.remove("selected");
subLink.parentNode
.querySelector("ul:first-of-type")
.setAttribute("aria-hidden", "true"); //hide from screen readers
} else {
//if NOT selected, the submenu is closed, so open it
resetDdmenuSubMenu(subLink);
subLink.parentNode
.querySelector("ul:first-of-type")
.classList.add("selected");
subLink.parentNode
.querySelector("ul:first-of-type")
.setAttribute("aria-hidden", "false"); //show to screen readers
} //end if
},
true
); //subLink.addEventListener('click')
} //for(let subMenu of subMenuUL)
checkSubMenuBounds(subMenuUL);
return;
} //end fxn initDdmenu
function resetStickyMenu(header, cont) {
//removes sticky menu related classes from header and resets the padding of the first bootstrap element in main tag
header.classList.remove("sticky");
header.classList.remove("in");
header.classList.remove("out");
cont.style.paddingTop = "0px"; //reset
return;
} //end fxn resetStickyMenu
function checkSubMenuBounds(subMenuUL) {
//adjust class of subMenuUL if it extends beyond the right edge of browser window
for (let subMenu of subMenuUL) {
if (subMenu.classList.contains("shiftLeft")) {
subMenu.classList.remove("shiftLeft"); //remove class that shifts subMenu to left side
}
let depth = 1;
let currentMenu = subMenu;
while (
currentMenu.parentNode
.querySelector("a:first-of-type")
.classList.contains("hasSub")
) {
depth++;
currentMenu = currentMenu.parentNode.parentNode; //walk up the subMenu tree
}
if (depth > 2) {
//subMenu in question normally appears on right side
//check its bounds
let x = getOffset(subMenu).left; //left x coordinate of subMenu
let w = subMenu.offsetWidth;
let rc = x + w; //right x coordinate of subMenu
let windowWidth = window.innerWidth;
if (rc >= windowWidth - w / 2) {
// the w/2 is arbitrary but serves to ensure that the submenus shift to the left properly for all MQ > MQ(s)
//add class to shift subMenu to left side
subMenu.classList.add("shiftLeft");
subMenu.parentNode.parentNode.classList.add("shiftLeft");
}
} //end if
} //for(let subMenu of subMenuUL)
return;
} //end fxn checkSubMenuBounds
function resetDdmenuSublink(subLink) {
//resets current sublink aria and class states
subLink.parentNode.parentNode.querySelectorAll("a").forEach(function (item) {
item.classList.remove("opened");
item.setAttribute("aria-pressed", "false");
item.setAttribute("aria-expanded", "false");
});
return;
} //end fxn resetDdmenuSublink
function resetDdmenuSubMenu(subLink) {
//resets current submenu tree aria and class states
subLink.parentNode.parentNode
.querySelectorAll("li > a + ul:first-of-type")
.forEach(function (item) {
item.classList.remove("selected");
item.setAttribute("aria-hidden", "true"); //hide from screen readers
});
return;
} //end fxn resetDdmenuSubMenu
function resetDdmenu(menu) {
//resets aria attributes and classes for menu and link states
menu.querySelectorAll('[aria-hidden="false"]').forEach(function (item) {
item.setAttribute("aria-hidden", "true");
});
menu.querySelectorAll('[aria-expanded="true"]').forEach(function (item) {
item.setAttribute("aria-expanded", "false");
});
menu.querySelectorAll('[aria-pressed="true"]').forEach(function (item) {
item.setAttribute("aria-pressed", "false");
});
menu.querySelectorAll(".selected").forEach(function (item) {
item.classList.remove("selected");
});
menu.querySelectorAll(".opened").forEach(function (item) {
item.classList.remove("opened");
});
//https://stackoverflow.com/questions/2520650/how-do-you-clear-the-focus-in-javascript
//remove focus from menu
document.activeElement.blur();
return;
} //end fxn resetDdmenu
function keysTriggerClick(el, keys = [], stopEvent = true, stopProp = true) {
//allow for proper aria keydown events, by default, or other events to be triggered on keydown
//https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/button_role
//el is the element triggering the click event. Must have an ID attribute set for itself in the DOM.
//stopEvent & stopProp are booleans to stop event default or propagation
//keys are additional keys that trigger the events and must be passed as an array: ['1', '2', 'x', 'y']
//default keys to check:
keys.push(" ", "Enter", "Spacebar"); //"Spacebar" for IE11 support
el.addEventListener("keydown", function (e) {
if (keys.includes(e.key)) {
if (stopEvent) {
e.preventDefault();
}
if (stopProp) {
e.stopPropagation();
}
el.click();
} //end keydown event for el
return;
}); //end keydown event
} //end fxn keysTriggerClick
})(); //end Self-Executing Anonymous Function