diff --git a/build-log.txt b/build-log.txt
new file mode 100644
index 00000000..3e0cfb75
Binary files /dev/null and b/build-log.txt differ
diff --git a/src/plugins/monofile.ts b/src/plugins/monofile.ts
index 0a6af2de..c4fa93eb 100644
--- a/src/plugins/monofile.ts
+++ b/src/plugins/monofile.ts
@@ -19,7 +19,7 @@ import { AddBetterSEQTAElements } from "@/seqta/ui/AddBetterSEQTAElements";
import { updateAllColors } from "@/seqta/ui/colors/Manager";
import { applySelectedFont } from "@/seqta/ui/fonts/Manager";
import { verboseInfo, verboseLog } from "@/utils/verboseLog";
-import loading from "@/seqta/ui/Loading";
+import loading, { stopLoadingAnimation } from "@/seqta/ui/Loading";
import { SendNewsPage } from "@/seqta/utils/SendNewsPage";
import { getEngageRoutePage } from "@/seqta/utils/engageRoute";
import {
@@ -78,6 +78,7 @@ let engageHashListenerAttached = false;
export async function finishLoad() {
if (betterSeqtaFinishLoadDone) return;
+
betterSeqtaFinishLoadDone = true;
try {
@@ -93,6 +94,7 @@ export async function finishLoad() {
const loadingbk = document.getElementById("loading");
loadingbk?.classList.add("closeLoading");
+ stopLoadingAnimation();
await delay(501);
loadingbk?.remove();
} catch (err) {
diff --git a/src/seqta/bootstrap/engageHashListener.ts b/src/seqta/bootstrap/engageHashListener.ts
new file mode 100644
index 00000000..59893d2d
--- /dev/null
+++ b/src/seqta/bootstrap/engageHashListener.ts
@@ -0,0 +1,20 @@
+import { getEngageRoutePage } from "@/seqta/utils/engageRoute";
+import {
+ loadEngageHomePage,
+ updateEngageHomeMenuActive,
+} from "@/seqta/utils/Loaders/LoadEngageHomePage";
+import { isSeqtaEngageExperience } from "@/seqta/utils/isSeqtaEngage";
+
+let engageHashListenerAttached = false;
+
+export function attachEngageHashListenerIfNeeded(): void {
+ if (!isSeqtaEngageExperience() || engageHashListenerAttached) return;
+ engageHashListenerAttached = true;
+ window.addEventListener("hashchange", () => {
+ if (getEngageRoutePage() === "home") {
+ void loadEngageHomePage();
+ } else {
+ updateEngageHomeMenuActive(false);
+ }
+ });
+}
diff --git a/src/seqta/ui/Loading.ts b/src/seqta/ui/Loading.ts
index a6fd3d2e..41030244 100644
--- a/src/seqta/ui/Loading.ts
+++ b/src/seqta/ui/Loading.ts
@@ -1,102 +1,238 @@
import browser from "webextension-polyfill";
import stringToHTML from "@/seqta/utils/stringToHTML";
import loadingSpinner from "./loading-spinner.html?raw";
+import loadingOverlay from "./loading-overlay.html?raw";
+import { startLoadingCanvas } from "./loadingCanvas";
+import {
+ pickLoadingVariant,
+ type LoadingVariant,
+} from "./loadingVariants";
-export function AppendLoadingSymbol(givenID: any, position: any) {
- let loadingsymbol = stringToHTML(/* html */ `
-
+let stopCanvas: (() => void) | null = null;
+
+const loadingStyles = /* css */ `
+ .bkloading {
+ --bk-line-color: rgba(255, 255, 255, 0.08);
+ --bk-line-accent: rgba(255, 255, 255, 0.15);
+ --bk-line-blue: rgba(96, 165, 250, 0.42);
+ --bk-grid-color: rgba(255, 255, 255, 0.028);
+ --bk-spin-outer: 1s;
+ --bk-spin-inner: 3s;
+ --bk-spin-small: 3s;
+ --bk-stage-name: bkloading-stage-in;
+ --bk-stage-duration: 0.85s;
+ position: absolute;
+ inset: 0;
+ z-index: 1000000;
+ overflow: hidden;
+ contain: strict;
+ color: #f4f4f5;
+ background:
+ radial-gradient(ellipse 70% 45% at 50% 95%, rgba(37, 99, 235, 0.12), transparent 62%),
+ linear-gradient(180deg, #010101 0%, #040404 50%, #080808 100%);
+ opacity: 1;
+ transition: opacity 0.85s cubic-bezier(0.4, 0, 0.2, 1);
+ }
+
+ .bkloading.closeLoading {
+ opacity: 0;
+ pointer-events: none;
+ }
+
+ .bkloading__canvas {
+ position: absolute;
+ inset: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+ }
+
+ .bkloading__vignette {
+ position: absolute;
+ inset: 0;
+ pointer-events: none;
+ background: radial-gradient(ellipse at center, transparent 32%, rgba(0, 0, 0, 0.75) 100%);
+ opacity: 0.88;
+ }
+
+ .bkloading__content {
+ position: absolute;
+ inset: 0;
+ display: grid;
+ place-items: center;
+ pointer-events: none;
+ }
+
+ .bkloading__logo-stage,
+ .bkloading-inline {
+ position: relative;
+ }
+
+ .bkloading__logo-stage {
+ width: 220px;
+ height: 220px;
+ animation-name: var(--bk-stage-name);
+ animation-duration: var(--bk-stage-duration);
+ animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
+ animation-fill-mode: both;
+ }
+
+ .bkloading-inline {
+ width: 120px;
+ height: 120px;
+ margin: 0 auto;
+ }
+
+ .bkloading__logo-stage .svg,
+ .bkloading-inline .svg {
+ transform-origin: center center;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ backface-visibility: hidden;
+ will-change: transform;
+ }
+
+ .bkloading__logo-stage .logo,
+ .bkloading-inline .logo {
+ transform: translate3d(-50%, -50%, 0);
+ }
+
+ .bkloading__logo-stage .logo {
+ z-index: 2;
+ }
+
+ .bkloading__logo-stage .big-circle {
+ margin: -90px;
+ animation: bkloading-spin var(--bk-spin-inner) linear infinite;
+ }
+
+ .bkloading__logo-stage .small-circle {
+ margin: -67px;
+ animation: bkloading-spin var(--bk-spin-small) linear infinite;
+ }
+
+ .bkloading__logo-stage .outer-circle {
+ margin: -110px;
+ animation: bkloading-spin-reverse var(--bk-spin-outer) linear infinite;
+ }
+
+ .bkloading-inline .big-circle {
+ margin: -88px;
+ animation: bkloading-spin 3s linear infinite;
+ }
+
+ .bkloading-inline .small-circle {
+ margin: -66px;
+ animation: bkloading-spin-reverse 3s linear infinite;
+ }
+
+ .bkloading-inline .outer-circle {
+ margin: -108px;
+ animation: bkloading-spin 4.5s linear infinite;
+ }
+
+ .bkloading__version {
+ position: absolute;
+ right: 12px;
+ bottom: 10px;
+ font-size: 0.95rem;
+ letter-spacing: 0.02em;
+ color: rgba(255, 255, 255, 0.35);
+ pointer-events: none;
+ }
+
+ @keyframes bkloading-stage-in {
+ from {
+ opacity: 0;
+ transform: translate3d(0, 12px, 0) scale(0.94);
+ }
+ to {
+ opacity: 1;
+ transform: translate3d(0, 0, 0) scale(1);
+ }
+ }
+
+ @keyframes bkloading-spin {
+ to {
+ transform: rotate(360deg);
+ }
+ }
+
+ @keyframes bkloading-spin-reverse {
+ to {
+ transform: rotate(-360deg);
+ }
+ }
+
+ @media (prefers-reduced-motion: reduce) {
+ .bkloading__logo-stage .big-circle,
+ .bkloading__logo-stage .small-circle,
+ .bkloading__logo-stage .outer-circle,
+ .bkloading-inline .big-circle,
+ .bkloading-inline .small-circle,
+ .bkloading-inline .outer-circle {
+ animation: none !important;
+ }
+ }
+`;
+
+function overlayWithSpinner(): string {
+ return loadingOverlay.replace(
+ "
",
+ `
${loadingSpinner}
`,
+ );
+}
+
+function applyVariantTheme(root: HTMLElement, variant: LoadingVariant) {
+ const { theme } = variant;
+ root.dataset.variant = variant.id;
+ root.style.background = theme.background;
+ root.style.setProperty("--bk-spin-outer", theme.spinOuter);
+ root.style.setProperty("--bk-spin-inner", theme.spinInner);
+ root.style.setProperty("--bk-spin-small", theme.spinSmall);
+ root.style.setProperty("--bk-stage-name", theme.stageName);
+ root.style.setProperty("--bk-stage-duration", theme.stageDuration);
+
+ const vignette = root.querySelector(".bkloading__vignette") as HTMLElement | null;
+ if (vignette) vignette.style.opacity = String(theme.vignetteOpacity);
+}
+
+function startCanvasForRoot(root: HTMLElement, variant: LoadingVariant) {
+ stopCanvas?.();
+ const canvas = root.querySelector(".bkloading__canvas") as HTMLCanvasElement | null;
+ stopCanvas = canvas ? startLoadingCanvas(canvas, root, variant) : null;
+}
+
+export function AppendLoadingSymbol(givenID: string, position: string) {
+ const loadingsymbol = stringToHTML(/* html */ `
+
${loadingSpinner}
`).firstChild;
- document.querySelector(position).appendChild(loadingsymbol);
+ document.querySelector(position)?.appendChild(loadingsymbol!);
+}
+
+export function stopLoadingAnimation() {
+ stopCanvas?.();
+ stopCanvas = null;
}
export default function loading() {
- let loadinghtml = stringToHTML(/* html */ `
-
-
+ ${overlayWithSpinner()}
+
v${browser.runtime.getManifest().version}
+
`);
- .svg {
- transform-origin: center;
- position: absolute;
- top: 50%;
- left: 50%;
- will-change: transform;
- }
- .logo {
- transform: translate(-50%, -50%);
- }
- .big-circle {
- margin: -88px;
- will-change: transform;
- animation-timing-function: ease;
- animation: spin 3s linear infinite;
- -moz-animation: spin 3s linear infinite;
- }
- .small-circle {
- margin: -66px;
- will-change: transform;
- animation-timing-function: ease;
- animation: spin 3s linear infinite;
- -moz-animation: spin 3s linear infinite;
- }
- .outer-circle {
- margin: -108px;
- will-change: transform;
- animation-direction: alternate-reverse;
- animation: spinback 1s linear infinite;
- -moz-animation: spinback 1s linear infinite;
- }
- @-moz-keyframes spin {
- 100% {
- -moz-transform: rotate(360deg);
- }
- }
- @-webkit-keyframes spin {
- 100% {
- -webkit-transform: rotate(360deg);
- }
- }
- @keyframes spin {
- 100% {
- -webkit-transform: rotate(360deg);
- transform: rotate(360deg);
- }
- }
- @keyframes spinback {
- 100% {
- -webkit-transform: rotate(-360deg);
- transform: rotate(-360deg);
- }
- }
-
- ${loadingSpinner}
-
v${
- browser.runtime.getManifest().version
- }
`);
- var html = document.getElementsByTagName("html")[0];
- html.append(loadinghtml.firstChild!);
+ const root = loadinghtml.firstChild as HTMLElement;
+ document.documentElement.append(root);
+
+ applyVariantTheme(root, variant);
+ startCanvasForRoot(root, variant);
}
diff --git a/src/seqta/ui/loading-overlay.html b/src/seqta/ui/loading-overlay.html
new file mode 100644
index 00000000..3ec381ce
--- /dev/null
+++ b/src/seqta/ui/loading-overlay.html
@@ -0,0 +1,5 @@
+