Updated privacy policy, Fixed many issues, started working on other parts of the website

Update .dockerignore, .gitignore, and 27 more files...
This commit is contained in:
2022-03-18 21:03:00 +01:00
parent 2288eb64d0
commit a04c63e8bb
29 changed files with 30832 additions and 35 deletions

View File

@@ -43,6 +43,12 @@ div#body-overlay {
.ucase {
text-transform: uppercase;
}
.font-size-8 {
font-size: 0.8rem!important;
}
.font-size-10 {
font-size: 1.0rem!important;
}
.font-size-26 {
font-size: 2.6rem!important;
}
@@ -142,6 +148,15 @@ div.content-tag-container a:not(:first-child) {
}
}
/* Fixes */
/* TODO: Fix it when it is closed */
div.last-inner-collapse-border-fix {
border: var(--collapse-content-border-width) solid var(--dm-collapse-content-border-color) !important;
border-top: 0 !important;
border-bottom-left-radius: var(--collapse-content-border-radius) !important;
border-bottom-right-radius: var(--collapse-content-border-radius) !important;
}
/* Trash */
/*.lang-icon {

View File

@@ -0,0 +1,32 @@
# Contributing to Halfmoon
Please read this short document if you want to contribute to Halfmoon.
## Our goal
Before getting to the issues and pull requests, it is important to understand what the framework is trying to do.
Halfmoon's stated goal is really simple - create a customizable frontend framework, with a built-in dark mode, that is great for building dashboards and tools. However, our other goals are more implicit:
- Everything has to be **super functional and snappy**. Ideally, that means no performance issues and just consistently good execution.
- Everything has to **look good** as well, and have a **single, consistent style**.
The most important thing here is building a **really good (looking) framework that also works very well**, and that should always be the top priority.
## Issues
Please keep the following things in mind when raising issues:
- Feature requests are welcome. However, please make sure that the feature you are requesting falls under the scope of the project.
- **Do not** post personal support questions as issues. For those, use this [tag on Stack Overflow](https://stackoverflow.com/questions/tagged/halfmoon).
- When raising an issue, please be as thorough as possible. For possible bugs, reproducible code is always a great sign.
## Pull requests
**Please ask first** before you put in work for a pull request. Things are constantly being worked on, and it is very difficult to share everything publicly in a meaningful way. So asking lets us avoid working on the same thing, or worse, someone putting in a lot of work for a PR that does not fit into the scope of the project. Please do not take this the wrong way; at this stage, the project needs strong leadership to become something special.
The best way to ask is through email: [hey.halfmoon@gmail.com](mailto:hey.halfmoon@gmail.com). I am always open to proper discussions about the framework, especially when it comes to contributions and pull requests.
## License
Any contribution you make will fall under the license being used by the framework: [MIT license](https://www.gethalfmoon.com/license/).

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Halfmoon UI
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.

View File

@@ -0,0 +1,95 @@
# [Halfmoon](https://www.gethalfmoon.com)
> Front-end framework with a built-in dark mode and full customizability using CSS variables; great for building dashboards and tools.
- **Built-in dark mode**—Halfmoon comes with a built-in, toggleable dark mode, which is one of its most important and defining features.
- **Fully customizable using CSS variables**—The framework is built entirely using CSS variables (also known as CSS custom properties). There are close to *1,500 CSS variables*, which means that almost everything can be customized by overriding a property, making it very easy to theme Halfmoon to fit your brand. [Learn more about customization](https://www.gethalfmoon.com/docs/customize/).
- **Great for building dashboards and tools**—The components have a very standard look and feel to them, making them suitable for dashboards and tools. Moreover, a lot of importance is placed on components such as forms, navbars, sidebars, dropdowns, toasts, shortcuts, etc. and there are also *tons of utilities* available.
- **Optional JS library**—Many of the components found in Halfmoon are built to work without JavaScript. However, the framework still comes with a powerful JavaScript library with no extra dependencies, such as jQuery.
- **Bootstrap like classes**—The class names should be instantly familiar to anyone who has used Bootstrap.
- **Cross-browser compatibility**—Fully supports almost all the browsers under the sun, including really old ones like Internet Explorer 11.
To learn more, go to [the documentation](https://www.gethalfmoon.com/docs/introduction/).
## Quickstart
The quickest way to get started with Halfmoon is by using the CDN to include the following files:
```html
<!-- Halfmoon CSS -->
<link href="https://cdn.jsdelivr.net/npm/halfmoon@1.1.1/css/halfmoon-variables.min.css" rel="stylesheet" />
<!--
Or,
Use the following (no variables, supports IE11):
<link href="https://cdn.jsdelivr.net/npm/halfmoon@1.1.1/css/halfmoon.min.css" rel="stylesheet" />
-->
<!-- Halfmoon JS -->
<script src="https://cdn.jsdelivr.net/npm/halfmoon@1.1.1/js/halfmoon.min.js"></script>
```
**Pleast note**, the JS file should be placed at the end of the `<body>` tag. Otherwise, some things may not work as expected. For example, using the `onclick="..."` event to call one of Halfmoon's built-in methods will not work **unless** the JS file is placed at the end of the `<body>` tag.
## Using npm
```
npm install halfmoon
```
After installation, the required CSS and JS file can be imported in the following way:
```javascript
// Include CSS file
require("halfmoon/css/halfmoon-variables.min.css");
/*
Or,
Include the following (no variables, supports IE11):
require("halfmoon/css/halfmoon.min.css");
*/
// Import JS library
const halfmoon = require("halfmoon");
```
Please note that manual initialization is required for some components, that is, after the DOM is loaded, the following method needs to be called:
```javascript
// Call this method after the DOM has been loaded
halfmoon.onDOMContentLoaded();
```
This initializes all of the components that require JavaScript, such as dropdowns, custom file inputs, shortcuts, etc.
In this way, Halfmoon can be used with frameworks that use the virtual DOM, such as React and Vue. For instance, in the case of Vue, the `halfmoon.onDOMContentLoaded()` method would be called inside the `mounted()` hook of your component.
## Using React
If you are using React to call the built-in methods, such as `halfmoon.toggleSidebar()`, please make sure the call is made in a way that binds the correct context. There are two ways to do this:
1. Using an anonymous method:
```html
<button className="btn" type="button" onClick={() => halfmoon.toggleSidebar()}>
```
2. Using `bind`:
```html
<button className="btn" type="button" onClick={halfmoon.toggleSidebar.bind(halfmoon)}>
```
You can find more details in the [React documentation](https://reactjs.org/docs/faq-functions.html#why-is-binding-necessary-at-all).
## Starter template generator
You can use the [starter template generator](https://www.gethalfmoon.com/docs/page-building/#starter-template-generator) to generate boilerplates for your project. The generator takes your settings and adds the appropriate classes and defines the required containers and elements.
Once again, we recommend reading [the documentation](https://www.gethalfmoon.com/docs/introduction/), as it contains a lot of examples to help you quickly build websites.
## License
Halfmoon is licensed under the [MIT](https://www.gethalfmoon.com/license/) license.
## Copyright
Copyright 2020, Halfmoon UI

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,3 @@
var halfmoon = require("./js/halfmoon-module");
module.exports = halfmoon;

View File

@@ -0,0 +1,513 @@
/*
* -----------------------------------------------------------------------------
* Halfmoon JS (module)
* Version: 1.1.1
* https://www.gethalfmoon.com
* Copyright, Halfmoon UI
* Licensed under MIT (https://www.gethalfmoon.com/license)
* -----------------------------------------------------------------------------
* The above notice must be included in its entirety when this file is used.
*/
/* Start polyfills */
// Polyfill for Element.matches()
if (!Element.prototype.matches) {
Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector;
}
// Polyfill for Element.closest()
if (!Element.prototype.closest) {
Element.prototype.closest = function(s) {
var el = this;
do {
if (el.matches(s)) return el;
el = el.parentElement || el.parentNode;
} while (el !== null && el.nodeType === 1);
return null;
};
}
// Polyfill for Element.classList (http://purl.eligrey.com/github/classList.js/blob/master/classList.js)
"document"in self&&("classList"in document.createElement("_")&&(!document.createElementNS||"classList"in document.createElementNS("http://www.w3.org/2000/svg","g"))||!function(t){"use strict";if("Element"in t){var e="classList",n="prototype",i=t.Element[n],s=Object,r=String[n].trim||function(){return this.replace(/^\s+|\s+$/g,"")},o=Array[n].indexOf||function(t){for(var e=0,n=this.length;n>e;e++)if(e in this&&this[e]===t)return e;return-1},c=function(t,e){this.name=t,this.code=DOMException[t],this.message=e},a=function(t,e){if(""===e)throw new c("SYNTAX_ERR","The token must not be empty.");if(/\s/.test(e))throw new c("INVALID_CHARACTER_ERR","The token must not contain space characters.");return o.call(t,e)},l=function(t){for(var e=r.call(t.getAttribute("class")||""),n=e?e.split(/\s+/):[],i=0,s=n.length;s>i;i++)this.push(n[i]);this._updateClassName=function(){t.setAttribute("class",this.toString())}},u=l[n]=[],h=function(){return new l(this)};if(c[n]=Error[n],u.item=function(t){return this[t]||null},u.contains=function(t){return~a(this,t+"")},u.add=function(){var t,e=arguments,n=0,i=e.length,s=!1;do t=e[n]+"",~a(this,t)||(this.push(t),s=!0);while(++n<i);s&&this._updateClassName()},u.remove=function(){var t,e,n=arguments,i=0,s=n.length,r=!1;do for(t=n[i]+"",e=a(this,t);~e;)this.splice(e,1),r=!0,e=a(this,t);while(++i<s);r&&this._updateClassName()},u.toggle=function(t,e){var n=this.contains(t),i=n?e!==!0&&"remove":e!==!1&&"add";return i&&this[i](t),e===!0||e===!1?e:!n},u.replace=function(t,e){var n=a(t+"");~n&&(this.splice(n,1,e),this._updateClassName())},u.toString=function(){return this.join(" ")},s.defineProperty){var f={get:h,enumerable:!0,configurable:!0};try{s.defineProperty(i,e,f)}catch(p){void 0!==p.number&&-2146823252!==p.number||(f.enumerable=!1,s.defineProperty(i,e,f))}}else s[n].__defineGetter__&&i.__defineGetter__(e,h)}}(self),function(){"use strict";var t=document.createElement("_");if(t.classList.add("c1","c2"),!t.classList.contains("c2")){var e=function(t){var e=DOMTokenList.prototype[t];DOMTokenList.prototype[t]=function(t){var n,i=arguments.length;for(n=0;i>n;n++)t=arguments[n],e.call(this,t)}};e("add"),e("remove")}if(t.classList.toggle("c3",!1),t.classList.contains("c3")){var n=DOMTokenList.prototype.toggle;DOMTokenList.prototype.toggle=function(t,e){return 1 in arguments&&!this.contains(t)==!e?e:n.call(this,t)}}"replace"in document.createElement("_").classList||(DOMTokenList.prototype.replace=function(t,e){var n=this.toString().split(" "),i=n.indexOf(t+"");~i&&(n=n.slice(i),this.remove.apply(this,n),this.add(e),this.add.apply(this,n.slice(1)))}),t=null}());
/* End polyfills */
/* Halfmoon JS core */
var halfmoon = {
// Getting the required elements
// Re-initialized once the DOM is loaded (to avoid issues with virtual DOM)
pageWrapper: document.getElementsByClassName("page-wrapper")[0],
stickyAlerts: document.getElementsByClassName("sticky-alerts")[0],
darkModeOn: false, // Also re-initialized once the DOM is loaded (see below)
// Create cookie
createCookie: function(name, value, days) {
var expires;
if (days) {
var date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
expires = "; expires=" + date.toGMTString();
}
else {
expires = "";
}
document.cookie = name + "=" + value + expires + "; path=/";
},
// Read cookie
readCookie: function(name) {
var nameEQ = name + "=";
var ca = document.cookie.split(";");
for(var i=0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) === " ") {
c = c.substring(1, c.length);
}
if (c.indexOf(nameEQ) === 0) {
return c.substring(nameEQ.length,c.length);
}
}
return null;
},
// Erase cookie
eraseCookie: function(name) {
this.createCookie(name, "", -1);
},
// Toggle light/dark mode
toggleDarkMode: function() {
if (document.body.classList.contains("dark-mode")) {
document.body.classList.remove("dark-mode");
this.darkModeOn = false;
this.createCookie("halfmoon_preferredMode", "light-mode", 365);
} else {
document.body.classList.add("dark-mode");
this.darkModeOn = true;
this.createCookie("halfmoon_preferredMode", "dark-mode", 365);
}
},
// Get preferred mode
getPreferredMode: function() {
if (this.readCookie("halfmoon_preferredMode")) {
return this.readCookie("halfmoon_preferredMode");
} else {
return "not-set";
}
},
// Toggles sidebar
toggleSidebar: function() {
if (this.pageWrapper) {
if (this.pageWrapper.getAttribute("data-sidebar-hidden")) {
this.pageWrapper.removeAttribute("data-sidebar-hidden");
} else {
this.pageWrapper.setAttribute("data-sidebar-hidden", "hidden");
}
}
},
// Deactivate all the dropdown toggles when another one is active
deactivateAllDropdownToggles: function() {
var activeDropdownToggles = document.querySelectorAll("[data-toggle='dropdown'].active");
for (var i = 0; i < activeDropdownToggles.length; i++) {
activeDropdownToggles[i].classList.remove("active");
activeDropdownToggles[i].closest(".dropdown").classList.remove("show");
}
},
// Toggle modal (using Javascript)
toggleModal: function(modalId) {
var modal = document.getElementById(modalId);
if (modal) {
modal.classList.toggle("show");
}
},
/* Code block for handling sticky alerts */
// Make an ID for an element
makeId: function(length) {
var result = "";
var characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var charactersLength = characters.length;
for ( var i = 0; i < length; i++ ) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
},
// Toast an alert (show, fade, dispose)
toastAlert: function(alertId, timeShown) {
var alertElement = document.getElementById(alertId);
// Setting the default timeShown
if (timeShown === undefined) {
timeShown = 5000;
}
// Alert is only toasted if it does not have the .show class
if (!alertElement.classList.contains("show")) {
// Add .alert-block class if it does not exist
if (!alertElement.classList.contains("alert-block")) {
alertElement.classList.add("alert-block");
}
// Show the alert
// The 0.25 seconds delay is for the animation
setTimeout(function() {
alertElement.classList.add("show");
}, 250);
// Wait some time (timeShown + 250) and fade out the alert
var timeToFade = timeShown + 250;
setTimeout(function() {
alertElement.classList.add("fade");
}, timeToFade);
// Wait some more time (timeToFade + 500) and dispose the alert (by removing the .alert-block class)
// Again, the extra delay is for the animation
// Remove the .show and .fade classes (so the alert can be toasted again)
var timeToDestroy = timeToFade + 500;
setTimeout(function() {
alertElement.classList.remove("alert-block");
alertElement.classList.remove("show");
alertElement.classList.remove("fade");
}, timeToDestroy);
}
},
// Create a sticky alert, display it, and then remove it
initStickyAlert: function(param) {
// Setting the variables from the param
var content = ("content" in param) ? param.content: "";
var title = ("title" in param) ? param.title: "";
var alertType = ("alertType" in param) ? param.alertType: "";
var fillType = ("fillType" in param) ? param.fillType: "";
var hasDismissButton = ("hasDismissButton" in param) ? param.hasDismissButton: true;
var timeShown = ("timeShown" in param) ? param.timeShown: 5000;
// Create the alert element
var alertElement = document.createElement("div");
// Set ID to the alert element
alertElement.setAttribute("id", this.makeId(6));
// Add the title
if (title) {
content = "<h4 class='alert-heading'>" + title + "</h4>" + content;
}
// Add the classes to the alert element
alertElement.classList.add("alert");
if (alertType) {
alertElement.classList.add(alertType);
}
if (fillType) {
alertElement.classList.add(fillType);
}
// Add the close button to the content (if required)
if (hasDismissButton) {
content = "<button class='close' data-dismiss='alert' type='button' aria-label='Close'><span aria-hidden='true'>&times;</span></button>" + content;
}
// Add the content to the alert element
alertElement.innerHTML = content;
// Append the alert element to the sticky alerts
this.stickyAlerts.insertBefore(alertElement, this.stickyAlerts.childNodes[0]);
// Toast the alert
this.toastAlert(alertElement.getAttribute("id"), timeShown);
},
/* End code block for handling sticky alerts */
// Click handler that can be overridden by users if needed
clickHandler: function(event) {},
// Keydown handler that can be overridden by users if needed
keydownHandler: function(event) {},
}
/* Things done once the DOM is loaded */
function halfmoonOnDOMContentLoaded() {
// Re-initializing the required elements (to avoid issues with virtual DOM)
if (!halfmoon.pageWrapper) {
halfmoon.pageWrapper = document.getElementsByClassName("page-wrapper")[0];
}
if (!halfmoon.stickyAlerts) {
halfmoon.stickyAlerts = document.getElementsByClassName("sticky-alerts")[0];
}
// Handle the cookie and variable for dark mode
// 1. First preference is given to the cookie if it exists
if (halfmoon.readCookie("halfmoon_preferredMode")) {
if (halfmoon.readCookie("halfmoon_preferredMode") == "dark-mode") {
halfmoon.darkModeOn = true;
} else {
halfmoon.darkModeOn = false;
}
} else {
// 2. If cookie does not exist, next preference is for the dark mode setting
if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) {
halfmoon.darkModeOn = true;
} else {
// 3. If all else fails, re-initialize the dark mode preference depending on the .dark-mode class
if (document.body.classList.contains("dark-mode")) {
halfmoon.darkModeOn = true;
} else {
halfmoon.darkModeOn = false;
}
}
}
// Automatically set preferred theme
// But only if one of the data-attribute is provided
if (document.body.getAttribute("data-set-preferred-mode-onload") || document.body.getAttribute("data-set-preferred-theme-onload")) {
if (halfmoon.darkModeOn) {
if (!document.body.classList.contains("dark-mode")) {
document.body.classList.add("dark-mode");
}
} else {
if (document.body.classList.contains("dark-mode")) {
document.body.classList.remove("dark-mode");
}
}
}
// Hiding sidebar on first load on small screens (unless data-attribute provided)
// Or on larger screens when sidebar type is overlayed-all
if (document.documentElement.clientWidth <= 768) {
if (halfmoon.pageWrapper) {
if (!halfmoon.pageWrapper.getAttribute("data-show-sidebar-onload-sm-and-down")) {
halfmoon.pageWrapper.setAttribute("data-sidebar-hidden", "hidden");
}
}
} else {
if (halfmoon.pageWrapper) {
if (halfmoon.pageWrapper.getAttribute("data-sidebar-type") === "overlayed-all") {
halfmoon.pageWrapper.setAttribute("data-sidebar-hidden", "hidden");
}
}
}
// Adding the click event listener
document.addEventListener(
"click",
function(event) {
var eventCopy = event;
var target = event.target;
// Handle clicks on dropdown toggles
if (target.matches("[data-toggle='dropdown']") || target.matches("[data-toggle='dropdown'] *")) {
if (target.matches("[data-toggle='dropdown'] *")) {
target = target.closest("[data-toggle='dropdown']");
}
if (target.classList.contains("active")) {
target.classList.remove("active");
target.closest(".dropdown").classList.remove("show");
} else {
halfmoon.deactivateAllDropdownToggles();
target.classList.add("active");
target.closest(".dropdown").classList.add("show");
}
} else {
if (!target.matches(".dropdown-menu *")) {
halfmoon.deactivateAllDropdownToggles();
}
}
// Handle clicks on alert dismiss buttons
if (target.matches(".alert [data-dismiss='alert']") || target.matches(".alert [data-dismiss='alert'] *")) {
if (target.matches(".alert [data-dismiss='alert'] *")) {
target = target.closest(".alert [data-dismiss='alert']");
}
target.parentNode.classList.add("dispose");
}
// Handle clicks on modal toggles
if (target.matches("[data-toggle='modal']") || target.matches("[data-toggle='modal'] *")) {
if (target.matches("[data-toggle='modal'] *")) {
target = target.closest("[data-toggle='modal']");
}
var targetModal = document.getElementById(target.getAttribute("data-target"));
if (targetModal) {
if (targetModal.classList.contains("modal")) {
halfmoon.toggleModal(target.getAttribute("data-target"));
}
}
}
// Handle clicks on modal dismiss buttons
if (target.matches(".modal [data-dismiss='modal']") || target.matches(".modal [data-dismiss='modal'] *")) {
if (target.matches(".modal [data-dismiss='modal'] *")) {
target = target.closest(".modal [data-dismiss='modal']");
}
target.closest(".modal").classList.remove("show");
}
// Handle clicks on modal overlays
if (target.matches(".modal-dialog")) {
var parentModal = target.closest(".modal");
if (!parentModal.getAttribute("data-overlay-dismissal-disabled")) {
if (parentModal.classList.contains("show")) {
parentModal.classList.remove("show");
} else {
window.location.hash = "#";
}
}
}
// Call the click handler method to handle any logic set by the user in their projects to handle clicks
halfmoon.clickHandler(eventCopy);
},
false
);
// Adding the key down event listener (for shortcuts and accessibility)
document.addEventListener(
"keydown",
function(event) {
var eventCopy = event;
// Shortcuts are triggered only if no input, textarea, or select has focus,
// If the control key or command key is not pressed down,
// And if the enabling data attribute is present on the DOM's body
if (!(document.querySelector("input:focus") || document.querySelector("textarea:focus") || document.querySelector("select:focus"))) {
event = event || window.event;
if (!(event.ctrlKey || event.metaKey)) {
// Toggle sidebar when [shift] + [S] keys are pressed
if (document.body.getAttribute("data-sidebar-shortcut-enabled")) {
if (event.shiftKey && event.which == 83) {
// Variable to store whether a modal is open or not
var modalOpen = false;
// Hash exists, so we check if it belongs to a modal
if (window.location.hash) {
var hash = window.location.hash.substring(1);
var elem = document.getElementById(hash);
if (elem) {
if (elem.classList.contains("modal")) {
modalOpen = true;
}
}
}
// Check for a modal with the .show class
if (document.querySelector(".modal.show")) {
modalOpen = true;
}
// This shortcut works only if no modal is open
if (!modalOpen) {
halfmoon.toggleSidebar();
event.preventDefault();
}
}
}
// Toggle dark mode when [shift] + [D] keys are pressed
if (document.body.getAttribute("data-dm-shortcut-enabled")) {
if (event.shiftKey && event.which == 68) {
halfmoon.toggleDarkMode();
event.preventDefault();
}
}
}
}
// Handling other keydown events
if (event.which === 27) {
// Close dropdown menu (if one is open) when [esc] key is pressed
if (document.querySelector("[data-toggle='dropdown'].active")) {
var elem = document.querySelector("[data-toggle='dropdown'].active");
elem.classList.remove("active");
elem.closest(".dropdown").classList.remove("show");
event.preventDefault();
}
// Close modal (if one is open, and if no dropdown menu is open) when [esc] key is pressed
// Conditional on dropdowns so that dropdowns on modals can be closed with the keyboard without closing the modal
else {
// Hash exists, so we check if it belongs to a modal
if (window.location.hash) {
var hash = window.location.hash.substring(1);
var elem = document.getElementById(hash);
if (elem) {
if (elem.classList.contains("modal")) {
if (!elem.getAttribute("data-esc-dismissal-disabled")) {
window.location.hash = "#";
event.preventDefault();
}
}
}
}
// Check for a modal with the .show class
if (document.querySelector(".modal.show")) {
var elem = document.querySelector(".modal.show");
if (!elem.getAttribute("data-esc-dismissal-disabled")) {
elem.classList.remove("show");
event.preventDefault();
}
}
}
}
// Call the keydown handler method to handle any logic set by the user in their projects to handle keydown events
halfmoon.keydownHandler(eventCopy);
}
);
// Handling custom file inputs
var halfmoonCustomFileInputs = document.querySelectorAll(".custom-file input");
for (var i = 0; i < halfmoonCustomFileInputs.length; i++) {
var customFile = halfmoonCustomFileInputs[i];
// Create file name container element, add the class name, and set default value
// Append it to the custom file element
var fileNamesContainer = document.createElement("div");
fileNamesContainer.classList.add("file-names");
var dataDefaultValue = customFile.getAttribute("data-default-value");
if (dataDefaultValue) {
fileNamesContainer.innerHTML = dataDefaultValue;
} else {
fileNamesContainer.innerHTML = "No file chosen";
}
customFile.parentNode.appendChild(fileNamesContainer);
// Add the event listener that will update the contents of the file name container element on change
customFile.addEventListener(
"change",
function(event) {
fileNamesContainer = event.target.parentNode.querySelector(".file-names");
if (event.target.files.length === 1) {
fileNamesContainer.innerHTML = event.target.files[0].name;
} else if (event.target.files.length > 1) {
fileNamesContainer.innerHTML = event.target.files.length + " files";
} else {
fileNamesContainer.innerHTML = "No file chosen";
}
}
);
}
// Adding the .with-transitions class to the page-wrapper so that transitions are enabled
// This way, the weird bug on Chrome is avoided, where the transitions run on load
if (halfmoon.pageWrapper) {
halfmoon.pageWrapper.classList.add("with-transitions");
}
}
// Add the halfmoonOnDOMContentLoaded to the main halfmoon object
// And export the halfmoon object as a module
halfmoon.onDOMContentLoaded = halfmoonOnDOMContentLoaded;
module.exports = halfmoon;

View File

@@ -0,0 +1,511 @@
/*
* -----------------------------------------------------------------------------
* Halfmoon JS
* Version: 1.1.1
* https://www.gethalfmoon.com
* Copyright, Halfmoon UI
* Licensed under MIT (https://www.gethalfmoon.com/license)
* -----------------------------------------------------------------------------
* The above notice must be included in its entirety when this file is used.
*/
/* Start polyfills */
// Polyfill for Element.matches()
if (!Element.prototype.matches) {
Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector;
}
// Polyfill for Element.closest()
if (!Element.prototype.closest) {
Element.prototype.closest = function(s) {
var el = this;
do {
if (el.matches(s)) return el;
el = el.parentElement || el.parentNode;
} while (el !== null && el.nodeType === 1);
return null;
};
}
// Polyfill for Element.classList (http://purl.eligrey.com/github/classList.js/blob/master/classList.js)
"document"in self&&("classList"in document.createElement("_")&&(!document.createElementNS||"classList"in document.createElementNS("http://www.w3.org/2000/svg","g"))||!function(t){"use strict";if("Element"in t){var e="classList",n="prototype",i=t.Element[n],s=Object,r=String[n].trim||function(){return this.replace(/^\s+|\s+$/g,"")},o=Array[n].indexOf||function(t){for(var e=0,n=this.length;n>e;e++)if(e in this&&this[e]===t)return e;return-1},c=function(t,e){this.name=t,this.code=DOMException[t],this.message=e},a=function(t,e){if(""===e)throw new c("SYNTAX_ERR","The token must not be empty.");if(/\s/.test(e))throw new c("INVALID_CHARACTER_ERR","The token must not contain space characters.");return o.call(t,e)},l=function(t){for(var e=r.call(t.getAttribute("class")||""),n=e?e.split(/\s+/):[],i=0,s=n.length;s>i;i++)this.push(n[i]);this._updateClassName=function(){t.setAttribute("class",this.toString())}},u=l[n]=[],h=function(){return new l(this)};if(c[n]=Error[n],u.item=function(t){return this[t]||null},u.contains=function(t){return~a(this,t+"")},u.add=function(){var t,e=arguments,n=0,i=e.length,s=!1;do t=e[n]+"",~a(this,t)||(this.push(t),s=!0);while(++n<i);s&&this._updateClassName()},u.remove=function(){var t,e,n=arguments,i=0,s=n.length,r=!1;do for(t=n[i]+"",e=a(this,t);~e;)this.splice(e,1),r=!0,e=a(this,t);while(++i<s);r&&this._updateClassName()},u.toggle=function(t,e){var n=this.contains(t),i=n?e!==!0&&"remove":e!==!1&&"add";return i&&this[i](t),e===!0||e===!1?e:!n},u.replace=function(t,e){var n=a(t+"");~n&&(this.splice(n,1,e),this._updateClassName())},u.toString=function(){return this.join(" ")},s.defineProperty){var f={get:h,enumerable:!0,configurable:!0};try{s.defineProperty(i,e,f)}catch(p){void 0!==p.number&&-2146823252!==p.number||(f.enumerable=!1,s.defineProperty(i,e,f))}}else s[n].__defineGetter__&&i.__defineGetter__(e,h)}}(self),function(){"use strict";var t=document.createElement("_");if(t.classList.add("c1","c2"),!t.classList.contains("c2")){var e=function(t){var e=DOMTokenList.prototype[t];DOMTokenList.prototype[t]=function(t){var n,i=arguments.length;for(n=0;i>n;n++)t=arguments[n],e.call(this,t)}};e("add"),e("remove")}if(t.classList.toggle("c3",!1),t.classList.contains("c3")){var n=DOMTokenList.prototype.toggle;DOMTokenList.prototype.toggle=function(t,e){return 1 in arguments&&!this.contains(t)==!e?e:n.call(this,t)}}"replace"in document.createElement("_").classList||(DOMTokenList.prototype.replace=function(t,e){var n=this.toString().split(" "),i=n.indexOf(t+"");~i&&(n=n.slice(i),this.remove.apply(this,n),this.add(e),this.add.apply(this,n.slice(1)))}),t=null}());
/* End polyfills */
/* Halfmoon JS core */
var halfmoon = {
// Getting the required elements
// Re-initialized once the DOM is loaded (to avoid issues with virtual DOM)
pageWrapper: document.getElementsByClassName("page-wrapper")[0],
stickyAlerts: document.getElementsByClassName("sticky-alerts")[0],
darkModeOn: false, // Also re-initialized once the DOM is loaded (see below)
// Create cookie
createCookie: function(name, value, days) {
var expires;
if (days) {
var date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
expires = "; expires=" + date.toGMTString();
}
else {
expires = "";
}
document.cookie = name + "=" + value + expires + "; path=/";
},
// Read cookie
readCookie: function(name) {
var nameEQ = name + "=";
var ca = document.cookie.split(";");
for(var i=0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) === " ") {
c = c.substring(1, c.length);
}
if (c.indexOf(nameEQ) === 0) {
return c.substring(nameEQ.length,c.length);
}
}
return null;
},
// Erase cookie
eraseCookie: function(name) {
this.createCookie(name, "", -1);
},
// Toggle light/dark mode
toggleDarkMode: function() {
if (document.body.classList.contains("dark-mode")) {
document.body.classList.remove("dark-mode");
this.darkModeOn = false;
this.createCookie("halfmoon_preferredMode", "light-mode", 365);
} else {
document.body.classList.add("dark-mode");
this.darkModeOn = true;
this.createCookie("halfmoon_preferredMode", "dark-mode", 365);
}
},
// Get preferred mode
getPreferredMode: function() {
if (this.readCookie("halfmoon_preferredMode")) {
return this.readCookie("halfmoon_preferredMode");
} else {
return "not-set";
}
},
// Toggles sidebar
toggleSidebar: function() {
if (this.pageWrapper) {
if (this.pageWrapper.getAttribute("data-sidebar-hidden")) {
this.pageWrapper.removeAttribute("data-sidebar-hidden");
} else {
this.pageWrapper.setAttribute("data-sidebar-hidden", "hidden");
}
}
},
// Deactivate all the dropdown toggles when another one is active
deactivateAllDropdownToggles: function() {
var activeDropdownToggles = document.querySelectorAll("[data-toggle='dropdown'].active");
for (var i = 0; i < activeDropdownToggles.length; i++) {
activeDropdownToggles[i].classList.remove("active");
activeDropdownToggles[i].closest(".dropdown").classList.remove("show");
}
},
// Toggle modal (using Javascript)
toggleModal: function(modalId) {
var modal = document.getElementById(modalId);
if (modal) {
modal.classList.toggle("show");
}
},
/* Code block for handling sticky alerts */
// Make an ID for an element
makeId: function(length) {
var result = "";
var characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var charactersLength = characters.length;
for ( var i = 0; i < length; i++ ) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
},
// Toast an alert (show, fade, dispose)
toastAlert: function(alertId, timeShown) {
var alertElement = document.getElementById(alertId);
// Setting the default timeShown
if (timeShown === undefined) {
timeShown = 5000;
}
// Alert is only toasted if it does not have the .show class
if (!alertElement.classList.contains("show")) {
// Add .alert-block class if it does not exist
if (!alertElement.classList.contains("alert-block")) {
alertElement.classList.add("alert-block");
}
// Show the alert
// The 0.25 seconds delay is for the animation
setTimeout(function() {
alertElement.classList.add("show");
}, 250);
// Wait some time (timeShown + 250) and fade out the alert
var timeToFade = timeShown + 250;
setTimeout(function() {
alertElement.classList.add("fade");
}, timeToFade);
// Wait some more time (timeToFade + 500) and dispose the alert (by removing the .alert-block class)
// Again, the extra delay is for the animation
// Remove the .show and .fade classes (so the alert can be toasted again)
var timeToDestroy = timeToFade + 500;
setTimeout(function() {
alertElement.classList.remove("alert-block");
alertElement.classList.remove("show");
alertElement.classList.remove("fade");
}, timeToDestroy);
}
},
// Create a sticky alert, display it, and then remove it
initStickyAlert: function(param) {
// Setting the variables from the param
var content = ("content" in param) ? param.content: "";
var title = ("title" in param) ? param.title: "";
var alertType = ("alertType" in param) ? param.alertType: "";
var fillType = ("fillType" in param) ? param.fillType: "";
var hasDismissButton = ("hasDismissButton" in param) ? param.hasDismissButton: true;
var timeShown = ("timeShown" in param) ? param.timeShown: 5000;
// Create the alert element
var alertElement = document.createElement("div");
// Set ID to the alert element
alertElement.setAttribute("id", this.makeId(6));
// Add the title
if (title) {
content = "<h4 class='alert-heading'>" + title + "</h4>" + content;
}
// Add the classes to the alert element
alertElement.classList.add("alert");
if (alertType) {
alertElement.classList.add(alertType);
}
if (fillType) {
alertElement.classList.add(fillType);
}
// Add the close button to the content (if required)
if (hasDismissButton) {
content = "<button class='close' data-dismiss='alert' type='button' aria-label='Close'><span aria-hidden='true'>&times;</span></button>" + content;
}
// Add the content to the alert element
alertElement.innerHTML = content;
// Append the alert element to the sticky alerts
this.stickyAlerts.insertBefore(alertElement, this.stickyAlerts.childNodes[0]);
// Toast the alert
this.toastAlert(alertElement.getAttribute("id"), timeShown);
},
/* End code block for handling sticky alerts */
// Click handler that can be overridden by users if needed
clickHandler: function(event) {},
// Keydown handler that can be overridden by users if needed
keydownHandler: function(event) {},
}
/* Things done once the DOM is loaded */
function halfmoonOnDOMContentLoaded() {
// Re-initializing the required elements (to avoid issues with virtual DOM)
if (!halfmoon.pageWrapper) {
halfmoon.pageWrapper = document.getElementsByClassName("page-wrapper")[0];
}
if (!halfmoon.stickyAlerts) {
halfmoon.stickyAlerts = document.getElementsByClassName("sticky-alerts")[0];
}
// Handle the cookie and variable for dark mode
// 1. First preference is given to the cookie if it exists
if (halfmoon.readCookie("halfmoon_preferredMode")) {
if (halfmoon.readCookie("halfmoon_preferredMode") == "dark-mode") {
halfmoon.darkModeOn = true;
} else {
halfmoon.darkModeOn = false;
}
} else {
// 2. If cookie does not exist, next preference is for the dark mode setting
if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) {
halfmoon.darkModeOn = true;
} else {
// 3. If all else fails, re-initialize the dark mode preference depending on the .dark-mode class
if (document.body.classList.contains("dark-mode")) {
halfmoon.darkModeOn = true;
} else {
halfmoon.darkModeOn = false;
}
}
}
// Automatically set preferred theme
// But only if one of the data-attribute is provided
if (document.body.getAttribute("data-set-preferred-mode-onload") || document.body.getAttribute("data-set-preferred-theme-onload")) {
if (halfmoon.darkModeOn) {
if (!document.body.classList.contains("dark-mode")) {
document.body.classList.add("dark-mode");
}
} else {
if (document.body.classList.contains("dark-mode")) {
document.body.classList.remove("dark-mode");
}
}
}
// Hiding sidebar on first load on small screens (unless data-attribute provided)
// Or on larger screens when sidebar type is overlayed-all
if (document.documentElement.clientWidth <= 768) {
if (halfmoon.pageWrapper) {
if (!halfmoon.pageWrapper.getAttribute("data-show-sidebar-onload-sm-and-down")) {
halfmoon.pageWrapper.setAttribute("data-sidebar-hidden", "hidden");
}
}
} else {
if (halfmoon.pageWrapper) {
if (halfmoon.pageWrapper.getAttribute("data-sidebar-type") === "overlayed-all") {
halfmoon.pageWrapper.setAttribute("data-sidebar-hidden", "hidden");
}
}
}
// Adding the click event listener
document.addEventListener(
"click",
function(event) {
var eventCopy = event;
var target = event.target;
// Handle clicks on dropdown toggles
if (target.matches("[data-toggle='dropdown']") || target.matches("[data-toggle='dropdown'] *")) {
if (target.matches("[data-toggle='dropdown'] *")) {
target = target.closest("[data-toggle='dropdown']");
}
if (target.classList.contains("active")) {
target.classList.remove("active");
target.closest(".dropdown").classList.remove("show");
} else {
halfmoon.deactivateAllDropdownToggles();
target.classList.add("active");
target.closest(".dropdown").classList.add("show");
}
} else {
if (!target.matches(".dropdown-menu *")) {
halfmoon.deactivateAllDropdownToggles();
}
}
// Handle clicks on alert dismiss buttons
if (target.matches(".alert [data-dismiss='alert']") || target.matches(".alert [data-dismiss='alert'] *")) {
if (target.matches(".alert [data-dismiss='alert'] *")) {
target = target.closest(".alert [data-dismiss='alert']");
}
target.parentNode.classList.add("dispose");
}
// Handle clicks on modal toggles
if (target.matches("[data-toggle='modal']") || target.matches("[data-toggle='modal'] *")) {
if (target.matches("[data-toggle='modal'] *")) {
target = target.closest("[data-toggle='modal']");
}
var targetModal = document.getElementById(target.getAttribute("data-target"));
if (targetModal) {
if (targetModal.classList.contains("modal")) {
halfmoon.toggleModal(target.getAttribute("data-target"));
}
}
}
// Handle clicks on modal dismiss buttons
if (target.matches(".modal [data-dismiss='modal']") || target.matches(".modal [data-dismiss='modal'] *")) {
if (target.matches(".modal [data-dismiss='modal'] *")) {
target = target.closest(".modal [data-dismiss='modal']");
}
target.closest(".modal").classList.remove("show");
}
// Handle clicks on modal overlays
if (target.matches(".modal-dialog")) {
var parentModal = target.closest(".modal");
if (!parentModal.getAttribute("data-overlay-dismissal-disabled")) {
if (parentModal.classList.contains("show")) {
parentModal.classList.remove("show");
} else {
window.location.hash = "#";
}
}
}
// Call the click handler method to handle any logic set by the user in their projects to handle clicks
halfmoon.clickHandler(eventCopy);
},
false
);
// Adding the key down event listener (for shortcuts and accessibility)
document.addEventListener(
"keydown",
function(event) {
var eventCopy = event;
// Shortcuts are triggered only if no input, textarea, or select has focus,
// If the control key or command key is not pressed down,
// And if the enabling data attribute is present on the DOM's body
if (!(document.querySelector("input:focus") || document.querySelector("textarea:focus") || document.querySelector("select:focus"))) {
event = event || window.event;
if (!(event.ctrlKey || event.metaKey)) {
// Toggle sidebar when [shift] + [S] keys are pressed
if (document.body.getAttribute("data-sidebar-shortcut-enabled")) {
if (event.shiftKey && event.which == 83) {
// Variable to store whether a modal is open or not
var modalOpen = false;
// Hash exists, so we check if it belongs to a modal
if (window.location.hash) {
var hash = window.location.hash.substring(1);
var elem = document.getElementById(hash);
if (elem) {
if (elem.classList.contains("modal")) {
modalOpen = true;
}
}
}
// Check for a modal with the .show class
if (document.querySelector(".modal.show")) {
modalOpen = true;
}
// This shortcut works only if no modal is open
if (!modalOpen) {
halfmoon.toggleSidebar();
event.preventDefault();
}
}
}
// Toggle dark mode when [shift] + [D] keys are pressed
if (document.body.getAttribute("data-dm-shortcut-enabled")) {
if (event.shiftKey && event.which == 68) {
halfmoon.toggleDarkMode();
event.preventDefault();
}
}
}
}
// Handling other keydown events
if (event.which === 27) {
// Close dropdown menu (if one is open) when [esc] key is pressed
if (document.querySelector("[data-toggle='dropdown'].active")) {
var elem = document.querySelector("[data-toggle='dropdown'].active");
elem.classList.remove("active");
elem.closest(".dropdown").classList.remove("show");
event.preventDefault();
}
// Close modal (if one is open, and if no dropdown menu is open) when [esc] key is pressed
// Conditional on dropdowns so that dropdowns on modals can be closed with the keyboard without closing the modal
else {
// Hash exists, so we check if it belongs to a modal
if (window.location.hash) {
var hash = window.location.hash.substring(1);
var elem = document.getElementById(hash);
if (elem) {
if (elem.classList.contains("modal")) {
if (!elem.getAttribute("data-esc-dismissal-disabled")) {
window.location.hash = "#";
event.preventDefault();
}
}
}
}
// Check for a modal with the .show class
if (document.querySelector(".modal.show")) {
var elem = document.querySelector(".modal.show");
if (!elem.getAttribute("data-esc-dismissal-disabled")) {
elem.classList.remove("show");
event.preventDefault();
}
}
}
}
// Call the keydown handler method to handle any logic set by the user in their projects to handle keydown events
halfmoon.keydownHandler(eventCopy);
}
);
// Handling custom file inputs
var halfmoonCustomFileInputs = document.querySelectorAll(".custom-file input");
for (var i = 0; i < halfmoonCustomFileInputs.length; i++) {
var customFile = halfmoonCustomFileInputs[i];
// Create file name container element, add the class name, and set default value
// Append it to the custom file element
var fileNamesContainer = document.createElement("div");
fileNamesContainer.classList.add("file-names");
var dataDefaultValue = customFile.getAttribute("data-default-value");
if (dataDefaultValue) {
fileNamesContainer.innerHTML = dataDefaultValue;
} else {
fileNamesContainer.innerHTML = "No file chosen";
}
customFile.parentNode.appendChild(fileNamesContainer);
// Add the event listener that will update the contents of the file name container element on change
customFile.addEventListener(
"change",
function(event) {
fileNamesContainer = event.target.parentNode.querySelector(".file-names");
if (event.target.files.length === 1) {
fileNamesContainer.innerHTML = event.target.files[0].name;
} else if (event.target.files.length > 1) {
fileNamesContainer.innerHTML = event.target.files.length + " files";
} else {
fileNamesContainer.innerHTML = "No file chosen";
}
}
);
}
// Adding the .with-transitions class to the page-wrapper so that transitions are enabled
// This way, the weird bug on Chrome is avoided, where the transitions run on load
if (halfmoon.pageWrapper) {
halfmoon.pageWrapper.classList.add("with-transitions");
}
}
// Call the function when the DOM is loaded
document.addEventListener("DOMContentLoaded", halfmoonOnDOMContentLoaded);

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,31 @@
{
"name": "halfmoon",
"version": "1.1.1",
"description": "Front-end framework with a built-in dark mode and full customizability using CSS variables; great for building dashboards and tools",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/halfmoonui/halfmoon.git"
},
"keywords": [
"halfmoon",
"css",
"javascript",
"dark-theme",
"dark-mode",
"dashboard",
"web-framework",
"css-framework",
"css-variables",
"css-custom-properties"
],
"author": "Halfmoon UI",
"license": "MIT",
"bugs": {
"url": "https://github.com/halfmoonui/halfmoon/issues"
},
"homepage": "https://github.com/halfmoonui/halfmoon#readme"
}

View File

@@ -0,0 +1,92 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Meta tags -->
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport" />
<meta name="viewport" content="width=device-width" />
<!-- Favicon and title -->
<link rel="icon" href="path/to/fav.png">
<title>Starter template - Halfmoon</title>
<!-- Halfmoon CSS -->
<link href="css/halfmoon-variables.min.css" rel="stylesheet" />
<!--
Or,
Use the following (no variables, supports IE11):
<link href="css/halfmoon.min.css" rel="stylesheet" />
-->
</head>
<body class="with-custom-webkit-scrollbars with-custom-css-scrollbars" data-dm-shortcut-enabled="true" data-sidebar-shortcut-enabled="true" data-set-preferred-theme-onload="true">
<!-- Modals go here -->
<!-- Reference: https://www.gethalfmoon.com/docs/modal -->
<!-- Page wrapper start -->
<div class="page-wrapper with-navbar with-sidebar with-navbar-fixed-bottom" data-sidebar-type="overlayed-sm-and-down">
<!-- Sticky alerts (toasts), empty container -->
<!-- Reference: https://www.gethalfmoon.com/docs/sticky-alerts-toasts -->
<div class="sticky-alerts"></div>
<!-- Navbar start -->
<nav class="navbar">
<!-- Reference: https://www.gethalfmoon.com/docs/navbar -->
</nav>
<!-- Navbar end -->
<!-- Sidebar overlay -->
<div class="sidebar-overlay" onclick="halfmoon.toggleSidebar()"></div>
<!-- Sidebar start -->
<div class="sidebar">
<!-- Reference: https://www.gethalfmoon.com/docs/sidebar -->
</div>
<!-- Sidebar end -->
<!-- Content wrapper start -->
<div class="content-wrapper">
<!--
Add your page's main content here
Examples:
1. https://www.gethalfmoon.com/docs/content-and-cards/#building-a-page
2. https://www.gethalfmoon.com/docs/grid-system/#building-a-dashboard
-->
<div class="w-full h-full d-flex align-items-center justify-content-center">
<div class="content">
<h1 class="content-title">Halfmoon starter template</h1>
<div>
<a href="https://www.gethalfmoon.com" class="btn btn-link px-0">Halfmoon website</a>
</div>
<div>
<a href="https://www.gethalfmoon.com/docs" class="btn btn-link px-0">Halfmoon docs</a>
</div>
<div>
<a href="https://www.gethalfmoon.com/docs/page-building/#starter-template-generator" class="btn btn-link px-0">Starter template generator</a>
</div>
<div>
<a href="https://www.twitter.com/halfmoonui" class="btn btn-link px-0">Follow on Twitter for updates</a>
</div>
<div class="mt-20">
Toggles:
<button class="btn btn-sm" type="button" onclick="halfmoon.toggleDarkMode()">Dark mode</button>
<button class="btn btn-sm" type="button" onclick="halfmoon.toggleSidebar()">Sidebar</button>
</div>
</div>
</div>
</div>
<!-- Content wrapper end -->
<!-- Navbar fixed bottom start -->
<nav class="navbar navbar-fixed-bottom">
<!-- Reference: https://www.gethalfmoon.com/docs/navbar#navbar-fixed-bottom -->
</nav>
<!-- Navbar fixed bottom end -->
</div>
<!-- Page wrapper end -->
<!-- Halfmoon JS -->
<script src="js/halfmoon.min.js"></script>
</body>
</html>

View File

@@ -1,2 +1,2 @@
# Quantum Font
100% font made by [sesohq](https://www.sesohq.com/) over at [sesohq.sellfy.store](https://sesohq.sellfy.store/p/3enu/).
100% free font made by [sesohq](https://www.sesohq.com/) over at [sesohq.sellfy.store](https://sesohq.sellfy.store/p/3enu/).