diff --git a/sass/base/_bootstrap-overload.scss b/sass/base/_bootstrap-overload.scss new file mode 100644 index 0000000..1d02e16 --- /dev/null +++ b/sass/base/_bootstrap-overload.scss @@ -0,0 +1,4 @@ +.nav-link.active { + color: $primary; + font-weight: bold; +} diff --git a/sass/base/_common.scss b/sass/base/_common.scss new file mode 100644 index 0000000..2bd6dce --- /dev/null +++ b/sass/base/_common.scss @@ -0,0 +1 @@ +// Empty. diff --git a/sass/base/_fonts.scss b/sass/base/_fonts.scss new file mode 100644 index 0000000..2bd6dce --- /dev/null +++ b/sass/base/_fonts.scss @@ -0,0 +1 @@ +// Empty. diff --git a/sass/fo.scss b/sass/fo.scss new file mode 100644 index 0000000..ca97517 --- /dev/null +++ b/sass/fo.scss @@ -0,0 +1,12 @@ +@charset "UTF-8"; + +// * UTILITIES +@import "./utilities/variables"; + +// * VENDOR +@import "./vendor/bootstrap"; + +// * BASE +@import "./base/bootstrap-overload"; +@import "./base/fonts"; +@import "./base/common"; diff --git a/sass/mail-theme.scss b/sass/mail-theme.scss new file mode 100644 index 0000000..f818313 --- /dev/null +++ b/sass/mail-theme.scss @@ -0,0 +1,313 @@ +@use "sass:color"; +@import "utilities/variables"; + +// 1. Include new custom variables +@import "utilities/bootstrap-variables"; + +// 2. Include functions first (so you can manipulate colors, SVGs, calc, etc) +@import "bootstrap/scss/functions"; + +// 3. Include remainder of required Bootstrap stylesheets +@import "bootstrap/scss/variables"; + +// * Laravel mail vendor + +/* Base */ + +body, +body *:not(html, style, br, tr, code) { + position: relative; + box-sizing: border-box; + font-family: + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + Roboto, + Helvetica, + Arial, + sans-serif, + "Apple Color Emoji", + "Segoe UI Emoji", + "Segoe UI Symbol"; +} + +body { + width: 100% !important; + height: 100%; + padding: 0; + margin: 0; + background-color: $body-bg; + color: $body-color; + line-height: 1.4; + text-size-adjust: none; +} + +p, +ul, +ol, +blockquote { + line-height: 1.4; + text-align: left; +} + +img { + max-width: 100%; +} + +a { + color: $link-color; +} + +a img { + border: 0; +} + +/* Typography */ + +h1 { + margin-top: 0; + color: $color-primary; + font-size: 18px; + font-weight: bold; + text-align: left; +} + +h2 { + margin-top: 0; + font-size: 16px; + font-weight: bold; + text-align: left; +} + +h3 { + margin-top: 0; + font-size: 14px; + font-weight: bold; + text-align: left; +} + +p { + margin-top: 0; + font-size: 16px; + line-height: 1.5em; + text-align: left; +} + +.sub { + font-size: 12px; +} + +/* Layout */ + +.wrapper { + -premailer-width: 100%; + width: 100%; + padding: 0; + margin: 0; + background-color: $bg-lightened; + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; +} + +.content { + -premailer-width: 100%; + width: 100%; + padding: 0; + margin: 0; + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; +} + +/* Header */ + +.header { + padding: 25px 0; + text-align: center; +} + +.header a { + color: $color-primary; + font-size: 19px; + font-weight: bold; + text-decoration: none; +} + +/* Logo */ + +.logo { + width: 75px; + height: 75px; + max-height: 75px; +} + +/* Body */ + +.body { + -premailer-width: 100%; + width: 100%; + padding: 0; + border-top: 1px solid #{$bg-lightened}; + border-bottom: 1px solid #{$bg-lightened}; + margin: 0; + background-color: $bg-lightened; + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; +} + +.inner-body { + -premailer-width: 570px; + width: 570px; + padding: 0; + border-width: 1px; + border-color: color.scale($body-bg, $lightness: -30%); + border-radius: 2px; + margin: 0 auto; + background-color: $body-bg; + box-shadow: 0 2px 0 rgb(0 0 150 / 2.5%), 2px 4px 0 rgb(0 0 150 / 1.5%); + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; +} + +/* Subcopy */ + +.subcopy { + padding-top: 25px; + border-top: 1px solid color.scale($body-bg, $lightness: -30%); + margin-top: 25px; +} + +.subcopy p { + font-size: 14px; +} + +/* Footer */ + +.footer { + -premailer-width: 570px; + width: 570px; + padding: 0; + margin: 0 auto; + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + text-align: center; +} + +.footer p { + color: #b0adc5; + font-size: 12px; + text-align: center; +} + +.footer a { + color: #b0adc5; + text-decoration: underline; +} + +/* Tables */ + +.table table { + -premailer-width: 100%; + width: 100%; + margin: 30px auto; + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; +} + +.table th { + padding-bottom: 8px; + border-bottom: 1px solid #edeff2; + margin: 0; +} + +.table td { + padding: 10px 0; + margin: 0; + color: #74787e; + font-size: 15px; + line-height: 18px; +} + +.content-cell { + max-width: 100vw; + padding: 32px; +} + +/* Buttons */ + +.action { + -premailer-width: 100%; + width: 100%; + padding: 0; + margin: 30px auto; + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + text-align: center; +} + +.button { + display: inline-block; + overflow: hidden; + border-radius: 4px; + color: $body-bg; + text-decoration: none; + text-size-adjust: none; +} + +.button-blue, +.button-primary { + border-top: 8px solid #{$color-primary}; + border-right: 18px solid #{$color-primary}; + border-bottom: 8px solid #{$color-primary}; + border-left: 18px solid #{$color-primary}; + background-color: #{$color-primary}; +} + +.button-green, +.button-success { + border-top: 8px solid #{$color-success}; + border-right: 18px solid #{$color-success}; + border-bottom: 8px solid #{$color-success}; + border-left: 18px solid #{$color-success}; + background-color: #{$color-success}; +} + +.button-red, +.button-error { + border-top: 8px solid #{$color-danger}; + border-right: 18px solid #{$color-danger}; + border-bottom: 8px solid #{$color-danger}; + border-left: 18px solid #{$color-danger}; + background-color: #{$color-danger}; +} + +/* Panels */ + +.panel { + border-left: $color-primary solid 4px; + margin: 21px 0; +} + +.panel-content { + padding: 16px; + background-color: $bg-lightened; + color: $body-color; +} + +.panel-content p { + color: $body-color; +} + +.panel-item { + padding: 0; +} + +.panel-item p:last-of-type { + padding-bottom: 0; + margin-bottom: 0; +} + +/* Utilities */ + +.break-all { + word-break: break-all; +} diff --git a/sass/pages/.gitkeep b/sass/pages/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/sass/partials/.gitkeep b/sass/partials/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/sass/utilities/_bootstrap-custom.scss b/sass/utilities/_bootstrap-custom.scss new file mode 100644 index 0000000..50438f2 --- /dev/null +++ b/sass/utilities/_bootstrap-custom.scss @@ -0,0 +1,44 @@ +@use "sass:list"; +@import "mixins"; + +// SET SOME CLASSES WITH BOOTSTRAP BREAKPOINTS +// * Reponsive borders +@each $breakpoint in map-keys($grid-breakpoints) { + @include media-breakpoint-up($breakpoint) { + $infix: breakpoint-infix($breakpoint, $grid-breakpoints); + + @include generate-bs-borders($infix, $prefix); + + @each $color, $value in $theme-colors { + @include generate-bs-borders($infix, $prefix, $color); + } + } +} + +// * Responsive sizings (used in laravel errors layouts) +@each $breakpoint in map-keys($grid-breakpoints) { + @include media-breakpoint-up($breakpoint) { + $infix: breakpoint-infix($breakpoint, $grid-breakpoints); + + .w#{$infix}-25 { + width: 25% !important; + } + .w#{$infix}-50 { + width: 50% !important; + } + .w#{$infix}-75 { + width: 75% !important; + } + .w#{$infix}-100 { + width: 100% !important; + } + + @each $prop, $unit in (width: vw, height: vh) { + @each $size in [25, 50, 75, 100] { + .#{$unit}#{$infix}-#{$size} { + #{$prop}: #{$size}#{$unit} !important; + } + } + } + } +} diff --git a/sass/utilities/_bootstrap-variables.scss b/sass/utilities/_bootstrap-variables.scss new file mode 100644 index 0000000..7af4d0e --- /dev/null +++ b/sass/utilities/_bootstrap-variables.scss @@ -0,0 +1,24 @@ +// Override bootstrap colors +$primary: $color-primary; +$secondary: $color-secondary; +$success: $color-success; +$info: $color-info; +$warning: $color-warning; +$danger: $color-danger; +$light: $color-light; +$dark: $color-dark; +$theme-colors: ( + primary: $primary, + secondary: $secondary, + success: $success, + info: $info, + warning: $warning, + danger: $danger, + light: $light, + dark: $dark, +); + +// Override bootstrap vars +$font-family-base: $font-default-family-base; +$font-size-base: $font-default-size-base; +$line-height-base: $line-default-height-base; diff --git a/sass/utilities/_mixins.scss b/sass/utilities/_mixins.scss new file mode 100644 index 0000000..b0fc0d1 --- /dev/null +++ b/sass/utilities/_mixins.scss @@ -0,0 +1,54 @@ +@mixin generate-bs-borders($infix, $prefix, $color:null) { + $color-var: var(--#{$prefix}border-color); + + @if $color { + $color-var: rgba(var(--#{$prefix}#{$color}-rgb)); + $color: "-#{$color}"; + } + + .border#{$infix}#{$color}-top { + border-top: $border-width var(--#{$prefix}border-style) #{$color-var} !important; + } + .border#{$infix}#{$color}-end { + border-right: $border-width var(--#{$prefix}border-style) #{$color-var} !important; + } + .border#{$infix}#{$color}-bottom { + border-bottom: $border-width var(--#{$prefix}border-style) #{$color-var} !important; + } + .border#{$infix}#{$color}-start { + border-left: $border-width var(--#{$prefix}border-style) #{$color-var} !important; + } + + .border#{$infix}#{$color}-0 { border: 0 !important; } + .border#{$infix}#{$color}-top-0 { border-top: 0 !important; } + .border#{$infix}#{$color}-end-0 { border-right: 0 !important; } + .border#{$infix}#{$color}-bottom-0 { border-bottom: 0 !important; } + .border#{$infix}#{$color}-start-0 { border-left: 0 !important; } + + @each $idx, $b-width in $border-widths { + $pidx: list.index(($border-widths), ($idx $b-width)); + + .border#{$infix}#{$color}-top-#{$idx} { + border-top: $b-width var(--#{$prefix}border-style) #{$color-var} !important; + } + .border#{$infix}#{$color}-end-#{$idx} { + border-right: $b-width var(--#{$prefix}border-style) #{$color-var} !important; + } + .border#{$infix}#{$color}-bottom-#{$idx} { + border-bottom: $b-width var(--#{$prefix}border-style) #{$color-var} !important; + } + .border#{$infix}#{$color}-start-#{$idx} { + border-left: $b-width var(--#{$prefix}border-style) #{$color-var} !important; + } + } + + .border#{$infix}#{$color}-x { + border-right: $border-width var(--#{$prefix}border-style) #{$color-var} !important; + border-left: $border-width var(--#{$prefix}border-style) #{$color-var} !important; + } + + .border#{$infix}#{$color}-y { + border-top: $border-width var(--#{$prefix}border-style) #{$color-var} !important; + border-bottom: $border-width var(--#{$prefix}border-style) #{$color-var} !important; + } +} diff --git a/sass/utilities/_variables.scss b/sass/utilities/_variables.scss new file mode 100644 index 0000000..f6b3f76 --- /dev/null +++ b/sass/utilities/_variables.scss @@ -0,0 +1,24 @@ +// * COLORS +$color-white: #fff; +$color-light: #f8f9fa; +$color-dark: #212529; +$color-success: #198754; +$color-danger: #dc3545; +$color-info: #0dcaf0; +$color-warning: #ffc107; +$color-primary: #0d6efd; +$color-secondary: #6c757d; + +// * BACKGROUNDS COLORS +$bg-lightened: $color-light; +$bg-darkened: $color-dark; +$bg-action: $color-primary; + +// * FONTS COLORS +$color-default-titles: $color-primary; +$color-default-texts: $color-dark; + +// * FONTS +$font-default-family-base: "Imprima Regular", sans-serif; // - The default site font family. +$font-default-size-base: 1rem; // Assumes the browser default, typically `16px` +$line-default-height-base: 1.65; diff --git a/sass/vendor/_bootstrap.scss b/sass/vendor/_bootstrap.scss new file mode 100644 index 0000000..513133f --- /dev/null +++ b/sass/vendor/_bootstrap.scss @@ -0,0 +1,44 @@ +// 1. Include new custom variables +@import "./../utilities/bootstrap-variables"; + +// 2. Include functions first (so you can manipulate colors, SVGs, calc, etc) +@import "bootstrap/scss/functions"; + +// 3. Include remainder of required Bootstrap stylesheets +@import "bootstrap/scss/variables"; +@import "bootstrap/scss/variables-dark"; + +// 4. Include remainder of required parts +@import "bootstrap/scss/maps"; +@import "bootstrap/scss/mixins"; +@import "bootstrap/scss/root"; + +// 5. Optionally include any other parts as needed +@import "bootstrap/scss/alert"; +@import "bootstrap/scss/buttons"; +@import "bootstrap/scss/button-group"; +@import "bootstrap/scss/containers"; +@import "bootstrap/scss/dropdown"; +@import "bootstrap/scss/forms"; +@import "bootstrap/scss/grid"; +@import "bootstrap/scss/helpers"; +@import "bootstrap/scss/images"; +@import "bootstrap/scss/list-group"; +@import "bootstrap/scss/nav"; +@import "bootstrap/scss/navbar"; +@import "bootstrap/scss/pagination"; +@import "bootstrap/scss/spinners"; +@import "bootstrap/scss/reboot"; +@import "bootstrap/scss/tables"; +@import "bootstrap/scss/tooltip"; +@import "bootstrap/scss/transitions"; +@import "bootstrap/scss/type"; +@import "bootstrap/scss/utilities"; +@import "bootstrap/scss/close"; +@import "bootstrap/scss/breadcrumb"; + +// 6. Optionally include utilities API last to generate classes based on the Sass map in `_utilities.scss` +@import "bootstrap/scss/utilities/api"; + +// 7 Overload bootstrap classes +@import "./../utilities/bootstrap-custom"; diff --git a/ts/base/common.ts b/ts/base/common.ts new file mode 100644 index 0000000..696ea68 --- /dev/null +++ b/ts/base/common.ts @@ -0,0 +1,44 @@ +// * Video frames. +document.addEventListener("DOMContentLoaded", function () { + const videoFrameList = document.querySelectorAll( + ".video-frame" + ) as NodeListOf; + for (const videoLink of videoFrameList) { + if (!videoLink.dataset.type) { + throw new Error("Missing dataset type"); + } + if (!videoLink.dataset.frame) { + throw new Error("Missing dataset frame"); + } + // @ts-ignore + window.__CN_VIDEO = window.__CN_VIDEO ?? {}; + + const newIframe = document.createElement("iframe"); + newIframe.frameBorder = "0"; + newIframe.allow = "fullscreen"; + newIframe.width = "1920px"; + newIframe.height = "1080px"; + newIframe.setAttribute("allowfullscreen", "allowfullscreen"); + + // @ts-ignore + const instance = new FsLightbox(); + instance.props.sources = [newIframe]; + + setTimeout(() => { + videoLink.addEventListener("click", (e) => { + e.preventDefault(); + + // * This comes fslightbox lib + // @ts-ignore + instance.open(); + setTimeout(function () { + // @ts-ignore + newIframe.src = window.__CN_VIDEO[videoLink.dataset.type] + ? String(videoLink.href) + : String(videoLink.dataset.frame); + }, 50); + return false; + }); + }, 300); + } +}); diff --git a/ts/components/shims-vue.d.ts b/ts/components/shims-vue.d.ts new file mode 100644 index 0000000..166cfb8 --- /dev/null +++ b/ts/components/shims-vue.d.ts @@ -0,0 +1,5 @@ +declare module "*.vue" { + import type { DefineComponent } from "vue"; + const component: DefineComponent<{}, {}, any>; + export default component; +} diff --git a/ts/env.d.ts b/ts/env.d.ts new file mode 100644 index 0000000..a2ab964 --- /dev/null +++ b/ts/env.d.ts @@ -0,0 +1,8 @@ +/// + +declare module "*.vue" { + import type { DefineComponent } from "vue"; + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types + const component: DefineComponent<{}, {}, any>; + export default component; +} diff --git a/ts/fo.ts b/ts/fo.ts new file mode 100644 index 0000000..054fa1d --- /dev/null +++ b/ts/fo.ts @@ -0,0 +1,15 @@ +// ASSETS +/// +import.meta.glob(["./../../assets/images/**"]); + +// UTILITIES +import "./utilities/bootstrap"; +import "./utilities/fontawesome"; +import "./utilities/vue-fo"; + +// CUSTOM +import "./base/common"; + +// Cookies +import CookieNotice from "@ldc-web/cookie-notice"; +window.CookieNotice = CookieNotice; diff --git a/ts/index.d.ts b/ts/index.d.ts new file mode 100644 index 0000000..e63fb02 --- /dev/null +++ b/ts/index.d.ts @@ -0,0 +1,15 @@ +export { }; + +import type CookieNotice from "@ldc-web/cookie-notice"; +import { AxiosStatic } from "axios"; +// import type { +// Axios +// } from "./../../../ts/front-definition.d.ts"; + +declare global { + + interface Window { + CookieNotice: typeof CookieNotice; + axios: AxiosStatic; + } +} diff --git a/ts/modules/bsTooltips.ts b/ts/modules/bsTooltips.ts new file mode 100644 index 0000000..6467bcd --- /dev/null +++ b/ts/modules/bsTooltips.ts @@ -0,0 +1,26 @@ +import type * as bootstrap from "bootstrap"; + +let tooltips: Array = []; + +export default { + methods: { + initTooltips() { + // Disable tooltips on reboot. + tooltips.forEach((tooltip) => { + tooltip.disable(); + tooltip.hide(); + }); + tooltips = [].slice + .call(document.querySelectorAll("[data-bs-toggle=\"tooltip\"]")) + .map(function (tooltipTriggerEl) { + return new window.bootstrap.Tooltip(tooltipTriggerEl, { + customClass: "large-tooltip", + delay: { + show: 1250, + hide: 0, + }, + }); + }); + }, + }, +}; diff --git a/ts/pages/.gitkeep b/ts/pages/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/ts/partials/.gitkeep b/ts/partials/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/ts/types/.gitkeep b/ts/types/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/ts/utilities/bootstrap.ts b/ts/utilities/bootstrap.ts new file mode 100644 index 0000000..3457350 --- /dev/null +++ b/ts/utilities/bootstrap.ts @@ -0,0 +1,21 @@ +import axios from "axios"; +import * as bootstrap from "bootstrap"; +import bsTooltips from "./../modules/bsTooltips"; + +/** + * We'll load the axios HTTP library which allows us to easily issue requests + * to our Laravel back-end. This library automatically handles sending the + * CSRF token as a header based on the value of the "XSRF" token cookie. + */ +axios.defaults.headers.common = { + Accept: "application/json, text/plain, */*", + "X-Requested-With": "XMLHttpRequest", + "X-CSRF-TOKEN": document + .querySelector("meta[name=\"csrf-token\"]")! + .getAttribute("content"), +}; + +window.axios = axios; +window.bootstrap = bootstrap; + +bsTooltips.methods.initTooltips(); diff --git a/ts/utilities/fontawesome.ts b/ts/utilities/fontawesome.ts new file mode 100644 index 0000000..5d12d53 --- /dev/null +++ b/ts/utilities/fontawesome.ts @@ -0,0 +1,90 @@ +import { config, dom, library } from "@fortawesome/fontawesome-svg-core"; +import { + faArrowAltCircleDown, + faArrowAltCircleUp, + faArrowDown, + faArrowLeft, + faArrowLeftLong, + faArrowRight, + faArrowRightFromBracket, + faArrowUp, + faArrowsAltH, + faArrowsAltV, + faBackspace, + faBan, + faCheck, + faChevronLeft, + faChevronRight, + faCircleCheck, + faCircleExclamation, + faCircleInfo, + faCircleNotch, + faCircleXmark, + faCopy, + faCrop, + faEraser, + faEye, + faEyeSlash, + faFolderOpen, + faListUl, + faPencil, + faPlus, + faQuestionCircle, + faRedoAlt, + faSearch, + faSearchMinus, + faSearchPlus, + faSliders, + faSyncAlt, + faTimes, + faTrash, + faUndoAlt, + faWandMagic +} from "@fortawesome/free-solid-svg-icons"; + +config.autoReplaceSvg = false; +library.add(faEraser); +library.add(faArrowLeftLong); +library.add(faPlus); +library.add(faTrash); +library.add(faEye); +library.add(faEyeSlash); +library.add(faFolderOpen); +library.add(faCrop); +library.add(faQuestionCircle); +library.add(faCheck); +library.add(faUndoAlt); +library.add(faRedoAlt); +library.add(faSyncAlt); +library.add(faSearchPlus); +library.add(faSearchMinus); +library.add(faArrowLeft); +library.add(faArrowRight); +library.add(faArrowUp); +library.add(faArrowDown); +library.add(faArrowsAltH); +library.add(faArrowsAltV); +library.add(faWandMagic); +library.add(faPencil); +library.add(faTimes); +library.add(faBackspace); +library.add(faSearch); +library.add(faArrowRightFromBracket); +library.add(faArrowAltCircleDown); +library.add(faArrowAltCircleUp); +library.add(faCircleCheck); +library.add(faCircleXmark); +library.add(faCopy); +library.add(faListUl); +library.add(faBan); +library.add(faCircleExclamation); +library.add(faCircleInfo); + +// Front +library.add(faChevronLeft); +library.add(faChevronRight); +library.add(faCircleXmark); +library.add(faCircleNotch); +library.add(faSliders); + +dom.watch(); diff --git a/ts/utilities/vue-fo.ts b/ts/utilities/vue-fo.ts new file mode 100644 index 0000000..d46a0f4 --- /dev/null +++ b/ts/utilities/vue-fo.ts @@ -0,0 +1,56 @@ +import type { App, Component } from "vue"; +import { createApp } from "vue"; + +document.addEventListener("DOMContentLoaded", () => { + const setupApp = (app: App) => { + app.config.globalProperties.window = window; + app.config.globalProperties.document = document; + }; + + // Register components + /// + const vues = import.meta.glob("./../components/*.vue") as Record< + string, + () => Promise<{ default: Component }> + >; + for (const vueCptName in vues) { + const vueCptPromise = vues[vueCptName]; + let htmlKey = vueCptName.split("/").pop(); + if (!htmlKey) { + throw new Error(`Cannot load ${vueCptName}`); + } + htmlKey = htmlKey + .split(".")[0] + .replace(/\.?([A-Z])/g, function (x, y) { + return "-" + y.toLowerCase(); + }) + .replace(/^-/, "") + .replace("-component", ""); + + const el = document.getElementById(htmlKey), + elGroup = document.getElementsByClassName(htmlKey); + // * Init using ID + if (el) { + vueCptPromise().then((vueParsedFile) => { + const app = createApp(vueParsedFile.default, { + json: el.dataset.json ?? {}, + }); + setupApp(app); + app.mount(el); + }); + } + // * Init Using class + for (const grpEl of elGroup) { + // * Hack to prevent loop weirdness + vueCptPromise().then((vueParsedFile) => { + setTimeout(() => { + const app = createApp(vueParsedFile.default, { + json: (grpEl as HTMLElement).dataset.json ?? {}, + }); + setupApp(app); + app.mount(grpEl); + }, 500); + }); + } + } +});