diff --git a/components.js b/components.js new file mode 100644 index 0000000..5aee078 --- /dev/null +++ b/components.js @@ -0,0 +1,1570 @@ +const smButton = document.createElement('template') +smButton.innerHTML = ` + +
+ +
`; +customElements.define('sm-button', + class extends HTMLElement { + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(smButton.content.cloneNode(true)) + } + static get observedAttributes() { + return ['disabled']; + } + + get disabled() { + return this.hasAttribute('disabled') + } + + set disabled(value) { + if (value) { + this.setAttribute('disabled', '') + }else { + this.removeAttribute('disabled') + } + } + + handleKeyDown(e) { + if (!this.hasAttribute('disabled') && (e.key === 'Enter' || e.code === 'Space')) { + e.preventDefault() + this.click() + } + } + + connectedCallback() { + if (!this.hasAttribute('disabled')) { + this.setAttribute('tabindex', '0') + } + this.setAttribute('role', 'button') + this.addEventListener('keydown', this.handleKeyDown) + } + attributeChangedCallback(name, oldVal, newVal) { + if (name === 'disabled') { + this.removeAttribute('tabindex') + this.setAttribute('aria-disabled', 'true') + } + else { + this.setAttribute('tabindex', '0') + this.setAttribute('aria-disabled', 'false') + } + } + }) +const smForm = document.createElement('template') +smForm.innerHTML = ` + +
+ +
+` + +customElements.define('sm-form', class extends HTMLElement { + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(smForm.content.cloneNode(true)) + + this.form = this.shadowRoot.querySelector('form') + this.formElements + this.requiredElements + this.submitButton + this.resetButton + this.allRequiredValid = false + + this.debounce = this.debounce.bind(this) + this.handleInput = this.handleInput.bind(this) + this.handleKeydown = this.handleKeydown.bind(this) + this.reset = this.reset.bind(this) + } + debounce(callback, wait) { + let timeoutId = null; + return (...args) => { + window.clearTimeout(timeoutId); + timeoutId = window.setTimeout(() => { + callback.apply(null, args); + }, wait); + }; + } + handleInput(e) { + this.allRequiredValid = this.requiredElements.every(elem => elem.isValid) + if (!this.submitButton) return; + if (this.allRequiredValid) { + this.submitButton.disabled = false; + } + else { + this.submitButton.disabled = true; + } + } + handleKeydown(e) { + if (e.key === 'Enter' && e.target.tagName !== 'SM-TEXTAREA' ) { + if (this.allRequiredValid) { + this.submitButton.click() + } + else { + this.requiredElements.find(elem => !elem.isValid).vibrate() + } + } + } + reset() { + this.formElements.forEach(elem => elem.reset()) + } + connectedCallback() { + const slot = this.shadowRoot.querySelector('slot') + slot.addEventListener('slotchange', e => { + this.formElements = [...this.querySelectorAll('sm-input, sm-textarea, sm-checkbox, tags-input, file-input, sm-switch, sm-radio')] + this.requiredElements = this.formElements.filter(elem => elem.hasAttribute('required')) + this.submitButton = e.target.assignedElements().find(elem => elem.getAttribute('variant') === 'primary' || elem.getAttribute('type') === 'submit'); + this.resetButton = e.target.assignedElements().find(elem => elem.getAttribute('type') === 'reset'); + if (this.resetButton) { + this.resetButton.addEventListener('click', this.reset) + } + }) + this.addEventListener('input', this.debounce(this.handleInput, 100)) + this.addEventListener('keydown', this.debounce(this.handleKeydown, 100)) + } + disconnectedCallback() { + this.removeEventListener('input', this.debounce(this.handleInput, 100)) + this.removeEventListener('keydown', this.debounce(this.handleKeydown, 100)) + } +}) + +const smInput = document.createElement('template') +smInput.innerHTML = ` + +
+ +

+
+`; +customElements.define('sm-input', + class extends HTMLElement { + + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(smInput.content.cloneNode(true)) + + this.inputParent = this.shadowRoot.querySelector('.input') + this.input = this.shadowRoot.querySelector('input') + this.clearBtn = this.shadowRoot.querySelector('.clear') + this.label = this.shadowRoot.querySelector('.label') + this.feedbackText = this.shadowRoot.querySelector('.feedback-text') + this.outerContainer = this.shadowRoot.querySelector('.outer-container') + this._helperText + this._errorText + this.isRequired = false + this.validationFunction + this.reflectedAttributes = ['value', 'required', 'disabled', 'type', 'inputmode', 'readonly', 'min', 'max', 'pattern', 'minlength', 'maxlength', 'step'] + + this.reset = this.reset.bind(this) + this.focusIn = this.focusIn.bind(this) + this.focusOut = this.focusOut.bind(this) + this.fireEvent = this.fireEvent.bind(this) + this.checkInput = this.checkInput.bind(this) + this.vibrate = this.vibrate.bind(this) + } + + static get observedAttributes() { + return ['value', 'placeholder', 'required', 'disabled', 'type', 'inputmode', 'readonly', 'min', 'max', 'pattern', 'minlength', 'maxlength', 'step', 'helper-text', 'error-text'] + } + + get value() { + return this.input.value + } + + set value(val) { + this.input.value = val; + this.checkInput() + this.fireEvent() + } + + get placeholder() { + return this.getAttribute('placeholder') + } + + set placeholder(val) { + this.setAttribute('placeholder', val) + } + + get type() { + return this.getAttribute('type') + } + + set type(val) { + this.setAttribute('type', val) + } + + get validity() { + return this.input.validity + } + + set disabled(value) { + if (value) + this.inputParent.classList.add('disabled') + else + this.inputParent.classList.remove('disabled') + } + set readOnly(value) { + if (value) { + this.setAttribute('readonly', '') + } else { + this.removeAttribute('readonly') + } + } + set customValidation(val) { + + this.validationFunction = val + } + set errorText(val) { + this._errorText = val + } + set helperText(val) { + this._helperText = val + } + get isValid() { + if (this.input.value !== '') { + const _isValid = this.input.checkValidity() + let _customValid = true + if (this.validationFunction) { + _customValid = Boolean(this.validationFunction(this.input.value)) + } + if (_isValid && _customValid) { + this.feedbackText.classList.remove('error') + this.feedbackText.classList.add('success') + this.feedbackText.textContent = '' + } else { + if (this._errorText) { + this.feedbackText.classList.add('error') + this.feedbackText.classList.remove('success') + this.feedbackText.innerHTML = ` + + ${this._errorText} + ` + } + } + return (_isValid && _customValid) + } + } + reset(){ + this.value = '' + } + + focusIn(){ + this.input.focus() + } + + focusOut(){ + this.input.blur() + } + + fireEvent(){ + let event = new Event('input', { + bubbles: true, + cancelable: true, + composed: true + }); + this.dispatchEvent(event); + } + + checkInput(e){ + if (!this.hasAttribute('readonly')) { + if (this.input.value.trim() !== '') { + this.clearBtn.classList.remove('hide') + } else { + this.clearBtn.classList.add('hide') + if (this.isRequired) { + this.feedbackText.textContent = '* required' + } + } + } + if (!this.hasAttribute('placeholder') || this.getAttribute('placeholder').trim() === '') return; + if (this.input.value !== '') { + if (this.animate) + this.inputParent.classList.add('animate-label') + else + this.label.classList.add('hide') + } else { + if (this.animate) + this.inputParent.classList.remove('animate-label') + else + this.label.classList.remove('hide') + } + } + vibrate() { + this.outerContainer.animate([ + { transform: 'translateX(-1rem)' }, + { transform: 'translateX(1rem)' }, + { transform: 'translateX(-0.5rem)' }, + { transform: 'translateX(0.5rem)' }, + { transform: 'translateX(0)' }, + ], { + duration: 300, + easing: 'ease' + }) + } + + + connectedCallback() { + this.animate = this.hasAttribute('animate') + this.setAttribute('role', 'textbox') + this.input.addEventListener('input', this.checkInput) + this.clearBtn.addEventListener('click', this.reset) + } + + attributeChangedCallback(name, oldValue, newValue) { + if (oldValue !== newValue) { + if (this.reflectedAttributes.includes(name)) { + if (this.hasAttribute(name)) { + this.input.setAttribute(name, this.getAttribute(name) ? this.getAttribute(name) : '') + } + else { + this.input.removeAttribute(name) + } + } + if (name === 'placeholder') { + this.label.textContent = newValue; + this.setAttribute('aria-label', newValue); + } + else if (this.hasAttribute('value')) { + this.checkInput() + } + else if (name === 'type') { + if (this.hasAttribute('type') && this.getAttribute('type') === 'number') { + this.input.setAttribute('inputmode', 'numeric') + } + } + else if (name === 'helper-text') { + this._helperText = this.getAttribute('helper-text') + } + else if (name === 'error-text') { + this._errorText = this.getAttribute('error-text') + } + else if (name === 'required') { + this.isRequired = this.hasAttribute('required') + if (this.isRequired) { + this.feedbackText.textContent = '* required' + this.setAttribute('aria-required', 'true') + } + else { + this.feedbackText.textContent = '' + this.setAttribute('aria-required', 'false') + } + } + else if (name === 'readonly') { + if (this.hasAttribute('readonly')) { + this.inputParent.classList.add('readonly') + } else { + this.inputParent.classList.remove('readonly') + } + } + else if (name === 'disabled') { + if (this.hasAttribute('disabled')) { + this.inputParent.classList.add('disabled') + } + else { + this.inputParent.classList.remove('disabled') + } + } + } + } + disconnectedCallback() { + this.input.removeEventListener('input', this.checkInput) + this.clearBtn.removeEventListener('click', this.reset) + } + }) +const smNotifications = document.createElement('template') +smNotifications.innerHTML = ` + +
+` + +customElements.define('sm-notifications', class extends HTMLElement { + constructor() { + super() + this.shadow = this.attachShadow({ + mode: 'open' + }).append(smNotifications.content.cloneNode(true)) + + this.notificationPanel = this.shadowRoot.querySelector('.notification-panel') + this.animationOptions = { + duration: 300, + fill: "forwards", + easing: "cubic-bezier(0.175, 0.885, 0.32, 1.275)" + } + + this.push = this.push.bind(this) + this.createNotification = this.createNotification.bind(this) + this.removeNotification = this.removeNotification.bind(this) + this.clearAll = this.clearAll.bind(this) + + } + + randString(length) { + let result = ''; + const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + for (let i = 0; i < length; i++) + result += characters.charAt(Math.floor(Math.random() * characters.length)); + return result; + } + + createNotification(message, options) { + const { pinned = false, icon = '' } = options + const notification = document.createElement('div') + notification.id = this.randString(8) + notification.classList.add('notification') + let composition = `` + composition += ` +
${icon}
+

${message}

+ ` + if (pinned) { + notification.classList.add('pinned') + composition += ` + + ` + } + notification.innerHTML = composition + return notification + } + + push(message, options = {}) { + const notification = this.createNotification(message, options) + this.notificationPanel.append(notification) + notification.animate([ + { + transform: `translateY(1rem)`, + opacity: '0' + }, + { + transform: `none`, + opacity: '1' + }, + ], this.animationOptions) + return notification.id + } + + removeNotification(notification) { + notification.animate([ + { + transform: `none`, + opacity: '1' + }, + { + transform: `translateY(0.5rem)`, + opacity: '0' + } + ], this.animationOptions).onfinish = () => { + notification.remove() + } + } + + clearAll() { + Array.from(this.notificationPanel.children).forEach(child => { + this.removeNotification(child) + }) + } + + connectedCallback() { + this.notificationPanel.addEventListener('click', e => { + if (e.target.closest('.close')) ( + this.removeNotification(e.target.closest('.notification')) + ) + }) + + const observer = new MutationObserver(mutationList => { + mutationList.forEach(mutation => { + if (mutation.type === 'childList') { + if (mutation.addedNodes.length && !mutation.addedNodes[0].classList.contains('pinned')) { + setTimeout(() => { + this.removeNotification(mutation.addedNodes[0]) + }, 5000); + } + } + }) + }) + observer.observe(this.notificationPanel, { + childList: true, + }) + } +}) +const smPopup = document.createElement('template') +smPopup.innerHTML = ` + + +`; +customElements.define('sm-popup', class extends HTMLElement { + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(smPopup.content.cloneNode(true)) + + this.allowClosing = false + this.isOpen = false + this.pinned = false + this.popupStack + this.offset + this.touchStartY = 0 + this.touchEndY = 0 + this.touchStartTime = 0 + this.touchEndTime = 0 + this.touchEndAnimataion + + this.popupContainer = this.shadowRoot.querySelector('.popup-container') + this.popup = this.shadowRoot.querySelector('.popup') + this.popupBodySlot = this.shadowRoot.querySelector('.popup-body slot') + this.popupHeader = this.shadowRoot.querySelector('.popup-top') + + this.resumeScrolling = this.resumeScrolling.bind(this) + this.show = this.show.bind(this) + this.hide = this.hide.bind(this) + this.handleTouchStart = this.handleTouchStart.bind(this) + this.handleTouchMove = this.handleTouchMove.bind(this) + this.handleTouchEnd = this.handleTouchEnd.bind(this) + this.movePopup = this.movePopup.bind(this) + } + + static get observedAttributes() { + return ['open']; + } + + get open() { + return this.isOpen + } + + resumeScrolling() { + const scrollY = document.body.style.top; + window.scrollTo(0, parseInt(scrollY || '0') * -1); + setTimeout(() => { + document.body.style.overflow = 'auto'; + document.body.style.top = 'initial' + }, 300); + } + + show(options = {}) { + const {pinned = false, popupStack = undefined} = options + if (popupStack) + this.popupStack = popupStack + if (this.popupStack && !this.hasAttribute('open')) { + this.popupStack.push({ + popup: this, + permission: pinned + }) + if (this.popupStack.items.length > 1) { + this.popupStack.items[this.popupStack.items.length - 2].popup.classList.add('stacked') + } + this.dispatchEvent( + new CustomEvent("popupopened", { + bubbles: true, + detail: { + popup: this, + popupStack: this.popupStack + } + }) + ) + this.setAttribute('open', '') + this.pinned = pinned + this.isOpen = true + } + this.popupContainer.classList.remove('hide') + this.popup.style.transform = 'none'; + document.body.style.overflow = 'hidden'; + document.body.style.top = `-${window.scrollY}px` + return this.popupStack + } + hide() { + if (window.innerWidth < 640) + this.popup.style.transform = 'translateY(100%)'; + else + this.popup.style.transform = 'translateY(3rem)'; + this.popupContainer.classList.add('hide') + this.removeAttribute('open') + if (typeof this.popupStack !== 'undefined') { + this.popupStack.pop() + if (this.popupStack.items.length) { + this.popupStack.items[this.popupStack.items.length - 1].popup.classList.remove('stacked') + } else { + this.resumeScrolling() + } + } else { + this.resumeScrolling() + } + + if (this.forms.length) { + setTimeout(() => { + this.forms.forEach(form => form.reset()) + }, 300); + } + setTimeout(() => { + this.dispatchEvent( + new CustomEvent("popupclosed", { + bubbles: true, + detail: { + popup: this, + popupStack: this.popupStack + } + }) + ) + this.isOpen = false + }, 300); + } + + handleTouchStart(e) { + this.touchStartY = e.changedTouches[0].clientY + this.popup.style.transition = 'transform 0.1s' + this.touchStartTime = e.timeStamp + } + + handleTouchMove(e) { + if (this.touchStartY < e.changedTouches[0].clientY) { + this.offset = e.changedTouches[0].clientY - this.touchStartY; + this.touchEndAnimataion = window.requestAnimationFrame(() => this.movePopup()) + } + } + + handleTouchEnd(e) { + this.touchEndTime = e.timeStamp + cancelAnimationFrame(this.touchEndAnimataion) + this.touchEndY = e.changedTouches[0].clientY + this.popup.style.transition = 'transform 0.3s' + this.threshold = this.popup.getBoundingClientRect().height * 0.3 + if (this.touchEndTime - this.touchStartTime > 200) { + if (this.touchEndY - this.touchStartY > this.threshold) { + if (this.pinned) { + this.show() + return + } else + this.hide() + } else { + this.show() + } + } else { + if (this.touchEndY > this.touchStartY) + if (this.pinned) { + this.show() + return + } + else + this.hide() + } + } + + movePopup() { + this.popup.style.transform = `translateY(${this.offset}px)` + } + + connectedCallback() { + this.popupBodySlot.addEventListener('slotchange', () => { + this.forms = this.querySelectorAll('sm-form') + }) + this.popupContainer.addEventListener('mousedown', e => { + if (e.target === this.popupContainer && !this.pinned) { + if (this.pinned) { + this.show() + } else + this.hide() + } + }) + + const resizeObserver = new ResizeObserver(entries => { + for (let entry of entries) { + if (entry.contentBoxSize) { + // Firefox implements `contentBoxSize` as a single content rect, rather than an array + const contentBoxSize = Array.isArray(entry.contentBoxSize) ? entry.contentBoxSize[0] : entry.contentBoxSize; + this.threshold = contentBoxSize.blockSize.height * 0.3 + } else { + this.threshold = entry.contentRect.height * 0.3 + } + } + }); + resizeObserver.observe(this) + + + this.popupHeader.addEventListener('touchstart', (e) => { this.handleTouchStart(e) }, { passive: true }) + this.popupHeader.addEventListener('touchmove', (e) => { this.handleTouchMove(e) }, { passive: true }) + this.popupHeader.addEventListener('touchend', (e) => { this.handleTouchEnd(e) }, { passive: true }) + } + disconnectedCallback() { + this.popupHeader.removeEventListener('touchstart', this.handleTouchStart, { passive: true }) + this.popupHeader.removeEventListener('touchmove', this.handleTouchMove, { passive: true }) + this.popupHeader.removeEventListener('touchend', this.handleTouchEnd, { passive: true }) + resizeObserver.unobserve() + } + attributeChangedCallback(name, oldVal, newVal) { + if (name === 'open') { + if (this.hasAttribute('open')) { + this.show() + } + } + } +}) +const spinner = document.createElement('template') +spinner.innerHTML = ` + + + +` +class SquareLoader extends HTMLElement { + constructor() { + super(); + this.attachShadow({ + mode: 'open' + }).append(spinner.content.cloneNode(true)) + } +} + +window.customElements.define('sm-spinner', SquareLoader); +const themeToggle = document.createElement('template') +themeToggle.innerHTML = ` + + +` + +class ThemeToggle extends HTMLElement { + constructor() { + super(); + + this.attachShadow({ + mode: 'open' + }).append(themeToggle.content.cloneNode(true)) + + this.isChecked = false + this.hasTheme = 'light' + + this.toggleState = this.toggleState.bind(this) + this.fireEvent = this.fireEvent.bind(this) + this.handleThemeChange = this.handleThemeChange.bind(this) + } + static get observedAttributes() { + return ['checked']; + } + + daylight() { + this.hasTheme = 'light' + document.body.dataset.theme = 'light' + this.setAttribute('aria-checked', 'false') + } + + nightlight() { + this.hasTheme = 'dark' + document.body.dataset.theme = 'dark' + this.setAttribute('aria-checked', 'true') + } + + toggleState() { + this.toggleAttribute('checked') + this.fireEvent() + } + handleKeyDown(e) { + if (e.code === 'Space') { + this.toggleState() + } + } + handleThemeChange(e) { + if (e.detail.theme !== this.hasTheme) { + if (e.detail.theme === 'dark') { + this.setAttribute('checked', '') + } + else { + this.removeAttribute('checked') + } + } + } + + fireEvent() { + this.dispatchEvent( + new CustomEvent('themechange', { + bubbles: true, + composed: true, + detail: { + theme: this.hasTheme + } + }) + ) + } + + connectedCallback() { + this.setAttribute('role', 'switch') + this.setAttribute('aria-label', 'theme toggle') + if (localStorage.getItem(`${window.location.hostname}-theme`) === "dark") { + this.nightlight(); + this.setAttribute('checked', '') + } else if (localStorage.getItem(`${window.location.hostname}-theme`) === "light") { + this.daylight(); + this.removeAttribute('checked') + } + else { + if (window.matchMedia(`(prefers-color-scheme: dark)`).matches) { + this.nightlight(); + this.setAttribute('checked', '') + } else { + this.daylight(); + this.removeAttribute('checked') + } + } + this.addEventListener("click", this.toggleState); + this.addEventListener("keydown", this.handleKeyDown); + document.addEventListener('themechange', this.handleThemeChange) + } + + disconnectedCallback() { + this.removeEventListener("click", this.toggleState); + this.removeEventListener("keydown", this.handleKeyDown); + document.removeEventListener('themechange', this.handleThemeChange) + } + + attributeChangedCallback(name, oldVal, newVal) { + if (name === 'checked') { + if (this.hasAttribute('checked')) { + this.nightlight(); + localStorage.setItem(`${window.location.hostname}-theme`, "dark"); + } else { + this.daylight(); + localStorage.setItem(`${window.location.hostname}-theme`, "light"); + } + } + } +} + +window.customElements.define('theme-toggle', ThemeToggle); \ No newline at end of file diff --git a/css/main.css b/css/main.css new file mode 100644 index 0000000..7f31619 --- /dev/null +++ b/css/main.css @@ -0,0 +1,639 @@ +* { + padding: 0; + margin: 0; + -webkit-box-sizing: border-box; + box-sizing: border-box; + font-family: "Roboto", sans-serif; +} + +:root { + font-size: clamp(1rem, 1.2vmax, 3rem); +} + +html, +body { + height: 100%; + scroll-behavior: smooth; +} + +body { + color: rgba(var(--text-color), 1); + background: rgba(var(--background-color), 1); +} +body, +body * { + --accent-color: #FF8181; + --accent-color--light: rgb(231, 67, 67, 0.06); + --text-color: 36, 36, 36; + --background-color: 255, 246, 246; + --foreground-color: rgb(255, 250, 250); + --danger-color: red; + --green: #00913c; + scrollbar-width: thin; +} + +body[data-theme=dark], +body[data-theme=dark] * { + --accent-color: #ff7d7d; + --accent-color--light: rgba(236, 184, 184, 0.1); + --text-color: 240, 240, 240; + --text-color-light: 170, 170, 170; + --background-color: 10, 10, 10; + --foreground-color: rgb(20, 20, 20); + --danger-color: rgb(255, 106, 106); + --green: #00E676; +} +body[data-theme=dark] ::-webkit-calendar-picker-indicator { + -webkit-filter: invert(1); + filter: invert(1); +} + +p { + font-size: 0.9rem; + max-width: 70ch; + line-height: 1.7; + color: rgba(var(--text-color), 0.8); +} +p:not(:last-of-type) { + margin-bottom: 1.5rem; +} + +a:where([class]) { + color: inherit; + text-decoration: none; +} +a:where([class]):focus-visible { + -webkit-box-shadow: 0 0 0 0.1rem rgba(var(--text-color), 1) inset; + box-shadow: 0 0 0 0.1rem rgba(var(--text-color), 1) inset; +} + +a { + color: var(--accent-color); +} + +button, +.button { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + position: relative; + display: -webkit-inline-box; + display: -ms-inline-flexbox; + display: inline-flex; + border: none; + background-color: transparent; + overflow: hidden; + color: inherit; + cursor: pointer; + -webkit-transition: -webkit-transform 0.3s; + transition: -webkit-transform 0.3s; + transition: transform 0.3s; + transition: transform 0.3s, -webkit-transform 0.3s; +} + +.button { + padding: 0.4rem 0.6rem; + border-radius: 0.3rem; + font-weight: 500; + font-size: 0.8rem; + border: solid thin rgba(var(--text-color), 0.5); +} + +button:disabled { + opacity: 0.5; +} + +a.button { + padding: 0.6rem 1.2rem; + border-radius: 0.3rem; + background-color: rgba(var(--text-color), 0.06); +} + +a:-webkit-any-link:focus-visible { + outline: rgba(var(--text-color), 1) 0.1rem solid; +} + +a:-moz-any-link:focus-visible { + outline: rgba(var(--text-color), 1) 0.1rem solid; +} + +a:any-link:focus-visible { + outline: rgba(var(--text-color), 1) 0.1rem solid; +} + +sm-button { + --border-radius: 0.3rem; +} +sm-button[variant=primary] .icon { + fill: rgba(var(--background-color), 1); +} +sm-button[disabled] .icon { + fill: rgba(var(--text-color), 0.6); +} + +ul { + list-style: none; +} + +.flex { + display: -webkit-box; + display: -ms-flexbox; + display: flex; +} + +.grid { + display: grid; +} + +.hide { + opacity: 0; + pointer-events: none; +} + +.hide-completely { + display: none !important; +} + +.no-transformations { + -webkit-transform: none !important; + transform: none !important; +} + +.overflow-ellipsis { + width: 100%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.breakable { + overflow-wrap: break-word; + word-wrap: break-word; + -ms-word-break: break-all; + word-break: break-word; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +.full-bleed { + grid-column: 1/4; +} + +.h1 { + font-size: 2.5rem; +} + +.h2 { + font-size: 2rem; +} + +.h3 { + font-size: 1.4rem; +} + +.h4 { + font-size: 1rem; +} + +.h5 { + font-size: 0.8rem; +} + +.uppercase { + text-transform: uppercase; +} + +.capitalize { + text-transform: capitalize; +} + +.flex { + display: -webkit-box; + display: -ms-flexbox; + display: flex; +} + +.grid { + display: grid; +} + +.grid-3 { + grid-template-columns: 1fr auto auto; +} + +.flow-column { + grid-auto-flow: column; +} + +.gap-0-5 { + gap: 0.5rem; +} + +.gap-1 { + gap: 1rem; +} + +.gap-1-5 { + gap: 1.5rem; +} + +.gap-2 { + gap: 2rem; +} + +.gap-3 { + gap: 3rem; +} + +.text-align-right { + text-align: right; +} + +.align-start { + -webkit-box-align: start; + -ms-flex-align: start; + align-items: flex-start; +} + +.align-center { + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} + +.text-center { + text-align: center; +} + +.justify-start { + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: start; +} + +.justify-center { + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; +} + +.justify-right { + margin-left: auto; +} + +.align-self-center { + -ms-flex-item-align: center; + align-self: center; +} + +.justify-self-center { + justify-self: center; +} + +.justify-self-start { + justify-self: start; +} + +.justify-self-end { + justify-self: end; +} + +.direction-column { + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; +} + +.space-between { + -webkit-box-pack: justify; + -ms-flex-pack: justify; + justify-content: space-between; +} + +.stretch { + -webkit-box-pack: stretch; + -ms-flex-pack: stretch; + justify-content: stretch; + justify-items: stretch; +} +.stretch > * { + width: 100%; +} + +.ripple { + position: absolute; + border-radius: 50%; + -webkit-transform: scale(0); + transform: scale(0); + background: rgba(var(--text-color), 0.16); + pointer-events: none; +} + +.interact { + position: relative; + overflow: hidden; + cursor: pointer; + -webkit-tap-highlight-color: transparent; +} + +.observe-empty-state:empty { + display: none; +} + +.observe-empty-state:not(:empty) ~ .empty-state { + display: none; +} + +.icon { + width: 1.2rem; + height: 1.2rem; + fill: rgba(var(--text-color), 0.8); +} + +.button__icon { + height: 1.2rem; + width: 1.2rem; +} +.button__icon--left { + margin-right: 0.5rem; +} +.button__icon--right { + margin-left: 0.5rem; +} + +.icon-button { + padding: 0.6rem; + border-radius: 0.5rem; + background-color: var(--accent-color--light); +} +.icon-button .icon { + fill: var(--accent-color); +} + +button:active, +.nav-item:active { + -webkit-transform: scale(0.9); + transform: scale(0.9); +} + +#main_page { + padding: 1.5rem; +} +#main_page > section:nth-of-type(1) { + -ms-flex-line-pack: start; + align-content: flex-start; +} + +#logo { + display: grid; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + width: 100%; + grid-template-columns: auto 1fr; + gap: 0 0.3rem; + margin-right: 1rem; +} +#logo h4 { + text-transform: capitalize; + font-size: 1rem; + font-weight: 600; +} +#logo #main_logo { + height: 1.4rem; + width: 1.4rem; + fill: rgba(var(--text-color), 1); + stroke: none; +} + +details summary { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + cursor: pointer; +} +details[open] > summary { + margin-bottom: 1rem; +} +details[open] > summary .icon { + -webkit-transform: rotate(180deg); + transform: rotate(180deg); +} + +.page-layout { + display: grid; + grid-template-columns: 1.5rem minmax(0, 1fr) 1.5rem; +} +.page-layout > * { + grid-column: 2/3; +} + +#homepage { + height: 100%; + display: grid; + gap: 0 1.5rem; + grid-template-rows: auto 1fr auto; + grid-template-columns: minmax(0, 1fr); + grid-template-areas: "main-header" "subpages" "main-nav"; +} + +#main_nav { + grid-area: main-nav; + position: relative; + display: -webkit-box; + display: -ms-flexbox; + display: flex; +} + +.nav-item { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + padding: 0.8rem; + border-radius: 0.5rem; + color: inherit; + -webkit-transition: -webkit-transform 0.3s; + transition: -webkit-transform 0.3s; + transition: transform 0.3s; + transition: transform 0.3s, -webkit-transform 0.3s; +} +.nav-item--active .icon { + fill: var(--accent-color); +} +.nav-item--active .nav-item__title { + color: var(--accent-color); +} + +#main_header { + grid-area: main-header; + padding: 1.5rem; + display: grid; + gap: 1rem; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + grid-template-columns: 1fr auto auto; +} + +#subpage_container { + grid-area: subpages; +} + +#dashboard { + display: grid; + gap: 1.5rem; +} + +#quick_actions_container { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + gap: 1.5rem; + margin: 2rem 0; +} + +.quick-action { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} +.quick-action__icon { + padding: 1rem; + border-radius: 1rem; + background-color: var(--accent-color--light); + margin-bottom: 0.8rem; +} +.quick-action__icon .icon { + height: 1.5rem; + width: 1.5rem; + fill: var(--accent-color); +} +.quick-action__title { + font-size: 0.7rem; + font-weight: 500; +} + +@media screen and (max-width: 640px) { + #dashboard { + padding: 0 1.5rem; + } + + .nav-item { + -webkit-box-flex: 1; + -ms-flex: 1; + flex: 1; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + } + .nav-item__title { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + font-size: 0.7rem; + font-weight: 500; + color: rgba(var(--text-color), 0.8); + margin-top: 0.3rem; + } +} +@media screen and (min-width: 640px) { + .page-layout { + grid-template-columns: 1fr 80vw 1fr; + } + + #homepage { + background-color: var(--foreground-color); + grid-template-rows: auto 1fr; + grid-template-columns: auto minmax(0, 1fr) 20rem; + grid-template-areas: "main-header main-header user-section" "main-nav subpages user-section"; + } + + #main_nav { + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + padding: 0 1rem; + } + #main_nav::after { + position: absolute; + right: 0; + content: ""; + width: 1px; + background-color: var(--accent-color); + height: 80%; + } + + .nav-item--active { + background-color: var(--accent-color--light); + } + .nav-item:last-of-type { + margin-top: auto; + margin-bottom: 1.5rem; + } + .nav-item__title { + display: none; + } + + #user_section { + grid-area: user-section; + background-color: rgba(var(--background-color), 1); + } +} +@media screen and (min-width: 1024px) { + #homepage { + gap: 0 3rem; + grid-template-columns: 14rem minmax(0, 1fr) 24rem; + } + + .nav-item__title { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + font-size: 0.9rem; + font-weight: 500; + color: rgba(var(--text-color), 0.8); + margin-left: 0.5rem; + } +} +@media (any-hover: hover) { + ::-webkit-scrollbar { + width: 0.5rem; + height: 0.5rem; + } + + ::-webkit-scrollbar-thumb { + background: rgba(var(--text-color), 0.3); + border-radius: 1rem; + } + ::-webkit-scrollbar-thumb:hover { + background: rgba(var(--text-color), 0.5); + } + + .nav-item { + -webkit-transition: background-color 0.3s, -webkit-transform 0.3s; + transition: background-color 0.3s, -webkit-transform 0.3s; + transition: background-color 0.3s, transform 0.3s; + transition: background-color 0.3s, transform 0.3s, -webkit-transform 0.3s; + } + .nav-item:hover { + background-color: var(--accent-color--light); + } +} \ No newline at end of file diff --git a/css/main.min.css b/css/main.min.css new file mode 100644 index 0000000..c5ec493 --- /dev/null +++ b/css/main.min.css @@ -0,0 +1 @@ +*{padding:0;margin:0;-webkit-box-sizing:border-box;box-sizing:border-box;font-family:"Roboto",sans-serif}:root{font-size:clamp(1rem, 1.2vmax, 3rem)}html,body{height:100%;scroll-behavior:smooth}body{color:rgba(var(--text-color), 1);background:rgba(var(--background-color), 1)}body,body *{--accent-color: #FF8181;--accent-color--light: rgb(231, 67, 67, 0.06);--text-color: 36, 36, 36;--background-color: 255, 246, 246;--foreground-color: rgb(255, 250, 250);--danger-color: red;--green: #00913c;scrollbar-width:thin}body[data-theme=dark],body[data-theme=dark] *{--accent-color: #ff7d7d;--accent-color--light: rgba(236, 184, 184, 0.1);--text-color: 240, 240, 240;--text-color-light: 170, 170, 170;--background-color: 10, 10, 10;--foreground-color: rgb(20, 20, 20);--danger-color: rgb(255, 106, 106);--green: #00E676}body[data-theme=dark] ::-webkit-calendar-picker-indicator{-webkit-filter:invert(1);filter:invert(1)}p{font-size:.9rem;max-width:70ch;line-height:1.7;color:rgba(var(--text-color), 0.8)}p:not(:last-of-type){margin-bottom:1.5rem}a:where([class]){color:inherit;text-decoration:none}a:where([class]):focus-visible{-webkit-box-shadow:0 0 0 .1rem rgba(var(--text-color), 1) inset;box-shadow:0 0 0 .1rem rgba(var(--text-color), 1) inset}a{color:var(--accent-color)}button,.button{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;position:relative;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;border:none;background-color:transparent;overflow:hidden;color:inherit;cursor:pointer;-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s, -webkit-transform .3s}.button{padding:.4rem .6rem;border-radius:.3rem;font-weight:500;font-size:.8rem;border:solid thin rgba(var(--text-color), 0.5)}button:disabled{opacity:.5}a.button{padding:.6rem 1.2rem;border-radius:.3rem;background-color:rgba(var(--text-color), 0.06)}a:-webkit-any-link:focus-visible{outline:rgba(var(--text-color), 1) .1rem solid}a:-moz-any-link:focus-visible{outline:rgba(var(--text-color), 1) .1rem solid}a:any-link:focus-visible{outline:rgba(var(--text-color), 1) .1rem solid}sm-button{--border-radius: 0.3rem}sm-button[variant=primary] .icon{fill:rgba(var(--background-color), 1)}sm-button[disabled] .icon{fill:rgba(var(--text-color), 0.6)}ul{list-style:none}.flex{display:-webkit-box;display:-ms-flexbox;display:flex}.grid{display:grid}.hide{opacity:0;pointer-events:none}.hide-completely{display:none !important}.no-transformations{-webkit-transform:none !important;transform:none !important}.overflow-ellipsis{width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.breakable{overflow-wrap:break-word;word-wrap:break-word;-ms-word-break:break-all;word-break:break-word;-ms-hyphens:auto;-webkit-hyphens:auto;hyphens:auto}.full-bleed{grid-column:1/4}.h1{font-size:2.5rem}.h2{font-size:2rem}.h3{font-size:1.4rem}.h4{font-size:1rem}.h5{font-size:.8rem}.uppercase{text-transform:uppercase}.capitalize{text-transform:capitalize}.flex{display:-webkit-box;display:-ms-flexbox;display:flex}.grid{display:grid}.grid-3{grid-template-columns:1fr auto auto}.flow-column{grid-auto-flow:column}.gap-0-5{gap:.5rem}.gap-1{gap:1rem}.gap-1-5{gap:1.5rem}.gap-2{gap:2rem}.gap-3{gap:3rem}.text-align-right{text-align:right}.align-start{-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.align-center{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.text-center{text-align:center}.justify-start{-webkit-box-pack:start;-ms-flex-pack:start;justify-content:start}.justify-center{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.justify-right{margin-left:auto}.align-self-center{-ms-flex-item-align:center;align-self:center}.justify-self-center{justify-self:center}.justify-self-start{justify-self:start}.justify-self-end{justify-self:end}.direction-column{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.space-between{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.stretch{-webkit-box-pack:stretch;-ms-flex-pack:stretch;justify-content:stretch;justify-items:stretch}.stretch>*{width:100%}.ripple{position:absolute;border-radius:50%;-webkit-transform:scale(0);transform:scale(0);background:rgba(var(--text-color), 0.16);pointer-events:none}.interact{position:relative;overflow:hidden;cursor:pointer;-webkit-tap-highlight-color:transparent}.observe-empty-state:empty{display:none}.observe-empty-state:not(:empty)~.empty-state{display:none}.icon{width:1.2rem;height:1.2rem;fill:rgba(var(--text-color), 0.8)}.button__icon{height:1.2rem;width:1.2rem}.button__icon--left{margin-right:.5rem}.button__icon--right{margin-left:.5rem}.icon-button{padding:.6rem;border-radius:.5rem;background-color:var(--accent-color--light)}.icon-button .icon{fill:var(--accent-color)}button:active,.nav-item:active{-webkit-transform:scale(0.9);transform:scale(0.9)}#main_page{padding:1.5rem}#main_page>section:nth-of-type(1){-ms-flex-line-pack:start;align-content:flex-start}#logo{display:grid;-webkit-box-align:center;-ms-flex-align:center;align-items:center;width:100%;grid-template-columns:auto 1fr;gap:0 .3rem;margin-right:1rem}#logo h4{text-transform:capitalize;font-size:1rem;font-weight:600}#logo #main_logo{height:1.4rem;width:1.4rem;fill:rgba(var(--text-color), 1);stroke:none}details summary{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:pointer}details[open]>summary{margin-bottom:1rem}details[open]>summary .icon{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.page-layout{display:grid;grid-template-columns:1.5rem minmax(0, 1fr) 1.5rem}.page-layout>*{grid-column:2/3}#homepage{height:100%;display:grid;gap:0 1.5rem;grid-template-rows:auto 1fr auto;grid-template-columns:minmax(0, 1fr);grid-template-areas:"main-header" "subpages" "main-nav"}#main_nav{grid-area:main-nav;position:relative;display:-webkit-box;display:-ms-flexbox;display:flex}.nav-item{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:.8rem;border-radius:.5rem;color:inherit;-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s, -webkit-transform .3s}.nav-item--active .icon{fill:var(--accent-color)}.nav-item--active .nav-item__title{color:var(--accent-color)}#main_header{grid-area:main-header;padding:1.5rem;display:grid;gap:1rem;-webkit-box-align:center;-ms-flex-align:center;align-items:center;grid-template-columns:1fr auto auto}#subpage_container{grid-area:subpages}#dashboard{display:grid;gap:1.5rem}#quick_actions_container{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;gap:1.5rem;margin:2rem 0}.quick-action{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.quick-action__icon{padding:1rem;border-radius:1rem;background-color:var(--accent-color--light);margin-bottom:.8rem}.quick-action__icon .icon{height:1.5rem;width:1.5rem;fill:var(--accent-color)}.quick-action__title{font-size:.7rem;font-weight:500}@media screen and (max-width: 640px){#dashboard{padding:0 1.5rem}.nav-item{-webkit-box-flex:1;-ms-flex:1;flex:1;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.nav-item__title{display:-webkit-box;display:-ms-flexbox;display:flex;font-size:.7rem;font-weight:500;color:rgba(var(--text-color), 0.8);margin-top:.3rem}}@media screen and (min-width: 640px){.page-layout{grid-template-columns:1fr 80vw 1fr}#homepage{background-color:var(--foreground-color);grid-template-rows:auto 1fr;grid-template-columns:auto minmax(0, 1fr) 20rem;grid-template-areas:"main-header main-header user-section" "main-nav subpages user-section"}#main_nav{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;padding:0 1rem}#main_nav::after{position:absolute;right:0;content:"";width:1px;background-color:var(--accent-color);height:80%}.nav-item--active{background-color:var(--accent-color--light)}.nav-item:last-of-type{margin-top:auto;margin-bottom:1.5rem}.nav-item__title{display:none}#user_section{grid-area:user-section;background-color:rgba(var(--background-color), 1)}}@media screen and (min-width: 1024px){#homepage{gap:0 3rem;grid-template-columns:14rem minmax(0, 1fr) 24rem}.nav-item__title{display:-webkit-box;display:-ms-flexbox;display:flex;font-size:.9rem;font-weight:500;color:rgba(var(--text-color), 0.8);margin-left:.5rem}}@media(any-hover: hover){::-webkit-scrollbar{width:.5rem;height:.5rem}::-webkit-scrollbar-thumb{background:rgba(var(--text-color), 0.3);border-radius:1rem}::-webkit-scrollbar-thumb:hover{background:rgba(var(--text-color), 0.5)}.nav-item{-webkit-transition:background-color .3s,-webkit-transform .3s;transition:background-color .3s,-webkit-transform .3s;transition:background-color .3s,transform .3s;transition:background-color .3s,transform .3s,-webkit-transform .3s}.nav-item:hover{background-color:var(--accent-color--light)}} \ No newline at end of file diff --git a/css/main.scss b/css/main.scss new file mode 100644 index 0000000..ff7e021 --- /dev/null +++ b/css/main.scss @@ -0,0 +1,570 @@ +* { + padding: 0; + margin: 0; + box-sizing: border-box; + font-family: 'Roboto', sans-serif; +} + +:root { + font-size: clamp(1rem, 1.2vmax, 3rem); +} + +html, +body { + height: 100%; + scroll-behavior: smooth; +} + +body { + &, + * { + --accent-color: #FF8181; + --accent-color--light: rgb(231, 67, 67, 0.06); + --text-color: 36, 36, 36; + --background-color: 255, 246, 246; + --foreground-color: rgb(255, 250, 250); + --danger-color: red; + --green: #00913c; + scrollbar-width: thin; + } + + color: rgba(var(--text-color), 1); + background: rgba(var(--background-color), 1); +} + +body[data-theme='dark'] { + + &, + * { + --accent-color: #ff7d7d; + --accent-color--light: rgba(236, 184, 184, 0.1); + --text-color: 240, 240, 240; + --text-color-light: 170, 170, 170; + --background-color: 10, 10, 10; + --foreground-color: rgb(20, 20, 20); + --danger-color: rgb(255, 106, 106); + --green: #00E676; + } + + ::-webkit-calendar-picker-indicator { + filter: invert(1); + } +} + +p { + font-size: 0.9rem; + max-width: 70ch; + line-height: 1.7; + color: rgba(var(--text-color), 0.8); + + &:not(:last-of-type) { + margin-bottom: 1.5rem; + } +} + +a:where([class]) { + color: inherit; + text-decoration: none; + + &:focus-visible { + box-shadow: 0 0 0 0.1rem rgba(var(--text-color), 1) inset; + } +} + +a { + color: var(--accent-color); +} + +button, +.button { + user-select: none; + position: relative; + display: inline-flex; + border: none; + background-color: transparent; + overflow: hidden; + color: inherit; + cursor: pointer; + transition: transform 0.3s; +} +.button{ + padding: 0.4rem 0.6rem; + border-radius: 0.3rem; + font-weight: 500; + font-size: 0.8rem; + border: solid thin rgba(var(--text-color), 0.5); +} + +button:disabled { + opacity: 0.5; +} + +a.button { + padding: 0.6rem 1.2rem; + border-radius: 0.3rem; + background-color: rgba(var(--text-color), 0.06); +} + +a:any-link:focus-visible { + outline: rgba(var(--text-color), 1) 0.1rem solid; +} + +sm-button { + --border-radius: 0.3rem; + + &[variant="primary"] { + .icon { + fill: rgba(var(--background-color), 1); + } + } + + &[disabled] { + .icon { + fill: rgba(var(--text-color), 0.6); + } + } +} + +ul { + list-style: none; +} + +.flex { + display: flex; +} + +.grid { + display: grid; +} + +.hide { + opacity: 0; + pointer-events: none; +} + +.hide-completely { + display: none !important; +} + +.no-transformations { + transform: none !important; +} + +.overflow-ellipsis { + width: 100%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.breakable { + overflow-wrap: break-word; + word-wrap: break-word; + -ms-word-break: break-all; + word-break: break-word; + -ms-hyphens: auto; + -moz-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +.full-bleed { + grid-column: 1/4; +} + +.h1 { + font-size: 2.5rem; +} + +.h2 { + font-size: 2rem; +} + +.h3 { + font-size: 1.4rem; +} + +.h4 { + font-size: 1rem; +} + +.h5 { + font-size: 0.8rem; +} + +.uppercase { + text-transform: uppercase; +} + +.capitalize { + text-transform: capitalize; +} + +.flex { + display: flex; +} + +.grid { + display: grid; +} + +.grid-3 { + grid-template-columns: 1fr auto auto; +} + +.flow-column { + grid-auto-flow: column; +} + +.gap-0-5 { + gap: 0.5rem; +} + +.gap-1 { + gap: 1rem; +} + +.gap-1-5 { + gap: 1.5rem; +} + +.gap-2 { + gap: 2rem; +} + +.gap-3 { + gap: 3rem; +} + +.text-align-right { + text-align: right; +} + +.align-start { + align-items: flex-start; +} + +.align-center { + align-items: center; +} + +.text-center { + text-align: center; +} + +.justify-start { + justify-content: start; +} + +.justify-center { + justify-content: center; +} + +.justify-right { + margin-left: auto; +} + +.align-self-center { + align-self: center; +} + +.justify-self-center { + justify-self: center; +} + +.justify-self-start { + justify-self: start; +} + +.justify-self-end { + justify-self: end; +} + +.direction-column { + flex-direction: column; +} + +.space-between { + justify-content: space-between; +} + +.stretch { + justify-content: stretch; + justify-items: stretch; + + &>* { + width: 100%; + } +} + +.ripple { + position: absolute; + border-radius: 50%; + transform: scale(0); + background: rgba(var(--text-color), 0.16); + pointer-events: none; +} + +.interact { + position: relative; + overflow: hidden; + cursor: pointer; + -webkit-tap-highlight-color: transparent; +} + +.observe-empty-state:empty { + display: none; +} + +.observe-empty-state:not(:empty)~.empty-state { + display: none; +} + +.icon { + width: 1.2rem; + height: 1.2rem; + fill: rgba(var(--text-color), 0.8); +} + +.button__icon { + height: 1.2rem; + width: 1.2rem; + + &--left { + margin-right: 0.5rem; + } + + &--right { + margin-left: 0.5rem; + } +} + +.icon-button{ + padding: 0.6rem; + border-radius: 0.5rem; + background-color: var(--accent-color--light); + .icon{ + fill: var(--accent-color); + } +} + +button:active, +.nav-item:active{ + transform: scale(0.9); +} + + +#main_page { + padding: 1.5rem; + + &>section:nth-of-type(1) { + align-content: flex-start; + } +} +#logo { + display: grid; + align-items: center; + width: 100%; + grid-template-columns: auto 1fr; + gap: 0 0.3rem; + margin-right: 1rem; + + h4 { + text-transform: capitalize; + font-size: 1rem; + font-weight: 600; + } + + #main_logo { + height: 1.4rem; + width: 1.4rem; + fill: rgba(var(--text-color), 1); + stroke: none; + } +} +details { + summary { + user-select: none; + cursor: pointer; + } + + &[open] > summary { + margin-bottom: 1rem; + .icon { + transform: rotate(180deg); + } + } + +} +.page-layout{ + display: grid; + grid-template-columns: 1.5rem minmax(0, 1fr) 1.5rem; + & > * { + grid-column: 2/3; + } +} +#homepage{ + height: 100%; + display: grid; + gap: 0 1.5rem; + grid-template-rows: auto 1fr auto; + grid-template-columns: minmax(0, 1fr); + grid-template-areas: 'main-header' 'subpages' 'main-nav'; +} +#main_nav{ + grid-area: main-nav; + position: relative; + display: flex; +} +.nav-item{ + display: flex; + align-items: center; + padding: 0.8rem; + border-radius: 0.5rem; + color: inherit; + transition: transform 0.3s; + &--active{ + .icon{ + fill: var(--accent-color); + } + .nav-item__title{ + color: var(--accent-color); + } + } +} +#main_header { + grid-area: main-header; + padding: 1.5rem; + display: grid; + gap: 1rem; + align-items: center; + grid-template-columns: 1fr auto auto; +} +#subpage_container{ + grid-area: subpages; +} +#dashboard{ + display: grid; + gap: 1.5rem; +} +#quick_actions_container{ + display: flex; + flex-wrap: wrap; + gap: 1.5rem; + margin: 2rem 0; +} +.quick-action{ + display: flex; + flex-direction: column; + align-items: center; + &__icon{ + padding: 1rem; + border-radius: 1rem; + background-color: var(--accent-color--light); + margin-bottom: 0.8rem; + .icon{ + height: 1.5rem; + width: 1.5rem; + fill: var(--accent-color); + } + } + &__title{ + font-size: 0.7rem; + font-weight: 500; + } +} +@media screen and (max-width: 640px) { + #dashboard{ + padding: 0 1.5rem; + } + .nav-item{ + flex: 1; + flex-direction: column; + &__title{ + display: flex; + font-size: 0.7rem; + font-weight: 500; + color: rgba(var(--text-color), 0.8); + margin-top: 0.3rem; + } + } +} +@media screen and (min-width: 640px) { + .page-layout{ + grid-template-columns: 1fr 80vw 1fr; + } + #homepage{ + background-color: var(--foreground-color); + grid-template-rows: auto 1fr; + grid-template-columns: auto minmax(0, 1fr) 20rem; + grid-template-areas: 'main-header main-header user-section' 'main-nav subpages user-section'; + } + #main_nav{ + flex-direction: column; + padding: 0 1rem; + &::after{ + position: absolute; + right: 0; + content: ''; + width: 1px; + background-color: var(--accent-color); + height: 80%; + } + } + .nav-item{ + &--active{ + background-color: var(--accent-color--light); + } + &:last-of-type{ + margin-top: auto; + margin-bottom: 1.5rem; + } + .icon{ + + } + &__title{ + display: none; + } + } + + #user_section{ + grid-area: user-section; + background-color: rgba(var(--background-color), 1); + } +} +@media screen and (min-width: 1024px) { + #homepage{ + gap: 0 3rem; + grid-template-columns: 14rem minmax(0, 1fr) 24rem; + } + .nav-item{ + &__title{ + display: flex; + font-size: 0.9rem; + font-weight: 500; + color: rgba(var(--text-color), 0.8); + margin-left: 0.5rem; + } + } +} +@media (any-hover: hover) { + ::-webkit-scrollbar { + width: 0.5rem; + height: 0.5rem; + } + + ::-webkit-scrollbar-thumb { + background: rgba(var(--text-color), 0.3); + border-radius: 1rem; + + &:hover { + background: rgba(var(--text-color), 0.5); + } + } + .nav-item{ + transition: background-color 0.3s, transform 0.3s; + &:hover{ + background-color: var(--accent-color--light); + } + } +} \ No newline at end of file diff --git a/index.html b/index.html index d7951c8..2a7b5ed 100644 --- a/index.html +++ b/index.html @@ -1,2280 +1,3004 @@ - - + - FLO Bank + + + + RanchiMall Bank + + + + + + + - + const DummyCallBack = (d, e) => { + if (e) console.error("DummyCallback", e); + console.log("DummyCallback", d); + } + - TEST_MODE - (use console) + + + +

+

+
+ Cancel + OK +
+
+ +

+

+ +
+ Cancel + OK +
+
+
+

Sign In

+ + + SIgn In + +
+
+
+ + + +
+ +
+
+
+

+ + + + Quick actions +

+
+ + +
+
+
+
+

+ + + + Recent transactions +

+ +
+
+

No transactions so far. Confirmed transactions will be shown here.

+
+
+
+
+
+
+
+ + + + const mergeRecursive = (obj1, obj2) => { + for (var p in obj2) { + try { + if(obj2[p].constructor == Object) + obj1[p] = mergeRecursive(obj1[p], obj2[p]); + // Property in destination object set; update its value. + else if (Ext.isArray(obj2[p])) { + // obj1[p] = []; + if (obj2[p].length < 1) + obj1[p] = obj2[p]; + else + obj1[p] = mergeRecursive(obj1[p], obj2[p]); + }else + obj1[p] = obj2[p]; + } catch (e) { + // Property in destination object not set; create it and set its value. + obj1[p] = obj2[p]; + } + } + return obj1; + } + + const cleanse = (obj) => { + Object.keys(obj).forEach(key => { + var value = obj[key]; + if (typeof value === "object" && value !== null) { + // Recurse... + cleanse(value); + // ...and remove if now "empty" (NOTE: insert your definition of "empty" here) + //if (!Object.keys(value).length) + // delete obj[key]; + } + else if (value === null) + delete obj[key];// null, remove it + }); + if(obj.constructor.toString().indexOf("Array") != -1) {obj = obj.filter(function (el) { + return el != null; + });} + return obj; + } + + /*obj is original object or array, diff is the output of findDiff */ + window.mergeDiff = (obj, diff) => { + if(Object.keys(diff.updated).length !== 0) + obj = mergeRecursive(obj, diff.updated) + if(Object.keys(diff.deleted).length !== 0){ + obj = mergeRecursive(obj, diff.deleted) + obj = cleanse(obj) + } + if(Object.keys(diff.added).length !== 0) + obj = mergeRecursive(obj, diff.added) + return obj + } + })(); + - - + - + - + - - - + + + +