UNPKG

retroify.js

Version:

A library to apply retro 8-bit styling to web pages

251 lines (229 loc) 14.1 kB
/** * retroify.js v0.2.1 * A library to apply retro 8-bit styling to web pages * https://github.com/rbonestell/retroify.js * * @license * Copyright (c) 2025 Bobby Bonestell * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ (window=>{const features_imageRendering="imageRendering"in(test=document.createElement("div").style)?"imageRendering":"webkitImageRendering"in test?"webkitImageRendering":"mozImageRendering"in test?"mozImageRendering":"msImageRendering"in test?"msImageRendering":"imageRendering";let BLACKLISTED_TAGS=["abbr","acronym","b","bdo","big","br","cite","dfn","em","kbd","label","map","object","output","q","script","small","strong","sub","sup","time","var"];var test=` /* Basic root element styles */ [data-retroify-root="true"], [data-retroify="true"] code, [data-retroify="true"] pre, [data-retroify="true"] samp { font-family: 'Press Start 2P', monospace; font-size: 0.75em; --retroify-shadow-opacity: 0.3; } /* Modified heading sizes */ [data-retroify="true"] h1 { font-size: 2em; text-shadow: 2px 2px 0 rgb(var(--retroify-shadow-color, 0 0 0) / var(--retroify-shadow-opacity, 0.3)); } [data-retroify="true"] h2 { font-size: 1.75em; text-shadow: 2px 2px 0 rgb(var(--retroify-shadow-color, 0 0 0) / var(--retroify-shadow-opacity, 0.3)); } [data-retroify="true"] h3 { font-size: 1.5em; text-shadow: 2px 2px 0 rgb(var(--retroify-shadow-color, 0 0 0) / var(--retroify-shadow-opacity, 0.3)); } [data-retroify="true"] h4 { font-size: 1.25em; text-shadow: 1px 1px 0 rgb(var(--retroify-shadow-color, 0 0 0) / var(--retroify-shadow-opacity, 0.3)); } [data-retroify="true"] h5 { font-size: 1em; text-shadow: 1px 1px 0 rgb(var(--retroify-shadow-color, 0 0 0) / var(--retroify-shadow-opacity, 0.3)); } [data-retroify="true"] h6 { font-size: 0.8em; text-shadow: 1px 1px 0 rgb(var(--retroify-shadow-color, 0 0 0) / var(--retroify-shadow-opacity, 0.3)); } /* Rest of the styles remain the same */ /* Preserve FontAwesome icons */ [data-retroify="true"] i.fa, [data-retroify="true"] i.fab, [data-retroify="true"] i.fas { font-size: 2.08em; } /* Image styles */ [data-retroify="true"] img { box-shadow: 6px 6px 0 rgb(var(--retroify-shadow-color, 0 0 0) / var(--retroify-shadow-opacity, 0.3)); image-rendering: pixelated; filter: contrast(150%) brightness(110%); border-radius: 0; ${features_imageRendering}: pixelated; ${features_imageRendering}: -moz-crisp-edges; ${features_imageRendering}: crisp-edges; } /* Anchor styles */ [data-retroify="true"] a { cursor: pointer; text-shadow: 1px 1px 0 rgb(var(--retroify-shadow-color, 0 0 0) / var(--retroify-shadow-opacity, 0.3)); } [data-retroify="true"] a:hover { cursor: pointer; text-shadow: 2px 2px 0 rgb(var(--retroify-shadow-color, 0 0 0) / var(--retroify-shadow-opacity, 0.3)); } [data-retroify="true"] a:active { top: 5px; left: 5px; cursor: pointer; text-shadow: none; } /* Button styles */ [data-retroify="true"] button, [data-retroify="true"] input[type="button"], [data-retroify="true"] input[type="submit"], [data-retroify="true"] .btn { position: relative; top: 0; left: 0; margin-right: 3px; margin-bottom: 3px; transform: scale(1); transition: all 0.1s ease; border-radius: 0 !important; padding: 12px; border-width: 2px; border-style: solid; box-shadow: 4px 4px 0 rgba(0, 0, 0, 0.3); font-size: 0.8em; } /* Hover states for buttons */ [data-retroify="true"] button:hover, [data-retroify="true"] input[type="button"]:hover, [data-retroify="true"] input[type="submit"]:hover, [data-retroify="true"] .btn:hover { top: -2px; left: -2px; cursor: pointer; box-shadow: 6px 6px 0 rgba(0, 0, 0, 0.3); border-radius: 0 !important; } /* Active states for buttons */ [data-retroify="true"] button:active, [data-retroify="true"] input[type="button"]:active, [data-retroify="true"] input[type="submit"]:active, [data-retroify="true"] .btn:active { top: 4px !important; left: 4px !important; box-shadow: 0px 0px 0 !important; border-radius: 0 !important; } [data-retroify="true"] .input-group-text, [data-retroify="true"] input:not([type="button"]):not([type="submit"]), [data-retroify="true"] textarea { border: 2px solid currentColor !important; border-radius: 0 !important; box-shadow: 4px 4px 0 rgb(var(--retroify-shadow-color, 0 0 0) / var(--retroify-shadow-opacity, 0.3)); padding: 6px !important; font-size: 0.9em; } [data-retroify="true"] select { border: 2px solid currentColor !important; border-radius: 0 !important; box-shadow: 4px 4px 0 rgb(var(--retroify-shadow-color, 0 0 0) / var(--retroify-shadow-opacity, 0.3)); padding: 6px !important; font-size: 0.9em; } [data-retroify="true"] .card, [data-retroify="true"] .alert, [data-retroify="true"] .modal-content { border-radius: 0 !important; box-shadow: 4px 4px 0 rgb(var(--retroify-shadow-color, 0 0 0) / var(--retroify-shadow-opacity, 0.3)) !important; border: 2px solid currentColor !important; } [data-retroify="true"] hr { border: none; border-bottom: 4px dashed currentColor; margin: 2em 0; } [data-retroify="true"] table { border-collapse: separate; border-spacing: 0; border: 2px solid currentColor; box-shadow: 4px 4px 0 rgb(var(--retroify-shadow-color, 0 0 0) / var(--retroify-shadow-opacity, 0.3)); } [data-retroify="true"] th { border: 1px solid currentColor; padding: 8px; text-shadow: 1px 1px 0 rgb(var(--retroify-shadow-color, 0 0 0) / var(--retroify-shadow-opacity, 0.3)); } [data-retroify="true"] td { border: 1px solid currentColor; padding: 8px; } .retroify-scanlines::before { content: ""; position: fixed; top: -50px; left: 0; width: 100%; height: calc(100% + 50px); background: repeating-linear-gradient( 0deg, rgb(var(--retroify-shadow-color, 0 0 0) / 0.03), rgb(var(--retroify-shadow-color, 0 0 0) / 0.03) 2px, transparent 3px, transparent 5px ); pointer-events: none; z-index: 9999; animation: retroify-scanline-scroll 8s linear infinite; } @keyframes retroify-scanline-scroll { 0% { transform: translateY(0); } 100% { transform: translateY(50px); } } @keyframes retroify-float { 0% { transform: translateY(0); } 50% { transform: translateY(-4px); } 100% { transform: translateY(0); } } [data-retroify="true"] .floating { animation: retroify-float 2s ease-in-out infinite; } [data-retroify="true"] .blink { animation: retroify-blink 1s step-end infinite; } @keyframes retroify-blink { 50% { opacity: 0; } } /* Reduced Motion */ @media (prefers-reduced-motion: reduce) { [data-retroify="true"] .floating { animation: none !important; } [data-retroify="true"] button, [data-retroify="true"] .btn { transition: none !important; } } /* Print styles */ @media print { .retroify-scanlines::before { display: none !important; } } `,styleSheet=(document.querySelector("style[data-retroify]")||((styleSheet=document.createElement("style")).setAttribute("data-retroify",""),styleSheet.textContent=test,document.head.appendChild(styleSheet)),document.querySelector('link[href*="Press+Start+2P"]')||((test=document.createElement("link")).href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap",test.rel="stylesheet",document.head.appendChild(test)),{version:"0.2.1",isSupported:function(){return void 0!==window&&"undefined"!=typeof document&&"querySelector"in document&&"classList"in document.documentElement},_init:function(){return!!this.isSupported()||(console.warn("Retroify: Browser environment not supported"),!1)},apply:function(target){if(!this._init())return this;let applyToTargetElement=element=>{element._originalStyle||(element._originalStyle=element.style),element.setAttribute("data-retroify","true"),"button"===element.tagName.toLowerCase()&&element.setAttribute("aria-pressed","false");var child,selector=`*:not(${BLACKLISTED_TAGS.join(", ")})`;for(child of element.querySelectorAll(selector))child.setAttribute("data-retroify","true"),"button"===child.tagName.toLowerCase()&&child.setAttribute("aria-pressed","false")};try{let rootElement;if(target?(rootElement="string"==typeof target?document.querySelector(target):target)==document.body&&this.toggleScanlines(!0):(rootElement=document.body,this.toggleScanlines(!0)),!rootElement)throw new Error("Target element not found");if(!(rootElement instanceof HTMLElement))throw new Error("Invalid target type. Expected HTMLElement or string selector");rootElement.setAttribute("data-retroify-root","true"),applyToTargetElement(rootElement);var observer=new MutationObserver(mutations=>{for(var mutation of mutations)if("childList"===mutation.type)for(var node of mutation.addedNodes)node.nodeType===Node.ELEMENT_NODE&&node.parentElement&&"true"===node.parentElement.getAttribute("data-retroify")&&applyToTargetElement(node)});return observer.observe(rootElement,{childList:!0,subtree:!0}),rootElement._retroifyObserver=observer,window.dispatchEvent(new CustomEvent("retroify:applied",{detail:{target:rootElement}})),this}catch(e){return console.error("Retroify:",e.message),this}},remove:function(target){if(!this._init())return this;try{var rootElement=target?"string"==typeof target?document.querySelector(target):target:document.body;if(rootElement)return rootElement._retroifyObserver&&(rootElement._retroifyObserver.disconnect(),delete rootElement._retroifyObserver),rootElement._originalStyle&&(Object.assign(rootElement.style,rootElement._originalStyle),delete rootElement._originalStyle),rootElement.removeAttribute("data-retroify"),rootElement.removeAttribute("data-retroify-root"),rootElement.querySelectorAll("[data-retroify]").forEach(element=>{element.removeAttribute("data-retroify"),element._originalStyle&&(Object.assign(element.style,element._originalStyle),delete element._originalStyle)}),window.dispatchEvent(new CustomEvent("retroify:removed",{detail:{target:rootElement}})),this;throw new Error("Target element not found")}catch(e){return console.error("Retroify:",e.message),this}},toggle:function(){return document.body.hasAttribute("data-retroify")?(this.toggleScanlines(!1),this.remove(document.body)):(this.toggleScanlines(!0),this.apply(document.body)),this},toggleScanlines:function(enable){return"boolean"==typeof enable?enable?document.body.classList.add("retroify-scanlines"):document.body.classList.remove("retroify-scanlines"):document.body.classList.toggle("retroify-scanlines"),this},destroy:function(){this.remove(),this.toggleScanlines(!1);var style=document.querySelector("style[data-retroify]");return style&&style.remove(),this},_validateSelector:function(selector){try{return document.querySelector(selector),!0}catch(e){return console.warn("Retroify: Invalid selector syntax"),!1}}});document.addEventListener("error",function(e){"LINK"===e.target.tagName&&e.target.href.includes("Press+Start+2P")&&console.warn("Retroify: Failed to load Press Start 2P font. Falling back to system fonts.")},!0),window.addEventListener("unload",()=>{window.retroify&&window.retroify.destroy()}),void 0===window.retroify&&Object.defineProperty(window,"retroify",{value:Object.freeze(styleSheet),writable:!1,configurable:!1})})("undefined"!=typeof window?window:this);