diff --git a/src/css/injected.scss b/src/css/injected.scss
index dccad4a4..de253f59 100644
--- a/src/css/injected.scss
+++ b/src/css/injected.scss
@@ -635,16 +635,14 @@ html.bsplus-custom-sidebar-pending #menu > .icon-cover,
clip: rect(0, 0, 0, 0) !important;
}
-/* Custom Svelte sidebar: SEQTA-like `#menu > ul > li.item` / `.sub` for theme CSS. */
+/* Custom Svelte sidebar: keep `#menu > ul > li.item` / `.sub` shape for theme CSS. */
#menu.bsplus-custom-sidebar {
- /* SEQTA uses absolute top/bottom:0 — do not switch to relative or height collapses. */
position: absolute !important;
top: 0 !important;
bottom: 0 !important;
left: 0 !important;
display: flex !important;
flex-direction: column !important;
- /* No padding-top: logo `ul::before` + theme `.sub { top: 71.5px }` match native. */
box-sizing: border-box !important;
height: auto !important;
max-height: none !important;
@@ -659,23 +657,20 @@ html.bsplus-custom-sidebar-pending #menu > .icon-cover,
position: relative;
}
- /* Theme drill panels assume static hasChildren + absolute .sub filling #menu. */
li.hasChildren {
position: static !important;
}
- /*
- * Native SEQTA leaves `li.hasChildren.active` on the hidden list after click-through.
- * Global `#menu:has(> ul > li.hasChildren.active) > ul > li:not(.hasChildren.active)`
- * then sets pointer-events:none on our custom root rows. Override that here; drill
- * locking only follows the custom list's own active folder.
- */
+ /* Native list may keep `.active` after click-through; force our rows clickable,
+ then lock siblings only while a custom drill `.sub` is open. */
> #bsplus-sidebar-root > li.item {
pointer-events: auto !important;
+ width: auto !important;
+ max-width: none !important;
+ margin: 2px 6px !important;
+ box-sizing: border-box !important;
}
- /* Only lock siblings while a drill `.sub` is open — not when a folder is
- merely route-highlighted with `.active` at the root list. */
&:has(> #bsplus-sidebar-root > li.hasChildren.active > .sub)
> #bsplus-sidebar-root
> li:not(.hasChildren.active),
@@ -692,40 +687,42 @@ html.bsplus-custom-sidebar-pending #menu > .icon-cover,
> #bsplus-sidebar-root li.hasChildren.active > .sub {
pointer-events: auto;
- /* Let the #menu chrome (gradient / theme) show through — themes often paint
- `.sub` with flat `--background-primary` which reads as a white slab. */
background: transparent !important;
background-image: none !important;
- animation: bsplus-submenu-enter 0.32s cubic-bezier(0.22, 1, 0.36, 1);
}
> #bsplus-sidebar-root li.hasChildren.active > .sub:has(.hasChildren.active) {
pointer-events: none !important;
- animation: none;
}
> #bsplus-sidebar-root li.hasChildren.active .hasChildren.active > .sub {
pointer-events: auto !important;
background: transparent !important;
background-image: none !important;
+ }
+
+ /* One-shot enter — not tied to `.active` (SEQTA strips/restores that on every
+ nested click, which would restart the animation). */
+ > #bsplus-sidebar-root .sub.bsplus-sub-enter {
animation: bsplus-submenu-enter 0.32s cubic-bezier(0.22, 1, 0.36, 1);
}
- /* Keep folder + leaf rows on the same rhythm (themes often only style
- `:not(.hasChildren)`, which left folders centred / differently spaced). */
- > #bsplus-sidebar-root li.item {
- width: auto !important;
- max-width: none !important;
- margin: 2px 6px !important;
- box-sizing: border-box !important;
- }
-
- /* Folder label stays in flow so sidebar-animation translateX can run.
- Visually covered by the absolute `.sub` panel. */
> #bsplus-sidebar-root > li.hasChildren.active > label {
pointer-events: none !important;
}
+ /* Theme decorations on root leaves (Beach palm/sand) must not show through
+ the transparent drill `.sub` panel. */
+ > #bsplus-sidebar-root:has(> li.hasChildren.active > .sub)
+ > li:not(.hasChildren.active)::before,
+ > #bsplus-sidebar-root:has(> li.hasChildren.active > .sub)
+ > li:not(.hasChildren.active)::after {
+ content: none !important;
+ display: none !important;
+ animation: none !important;
+ opacity: 0 !important;
+ }
+
#bsplus-sidebar-root .sub .back {
display: flex;
align-items: center;
@@ -849,6 +846,12 @@ body.icon-only-sidebar:not(:has(#menu li.hasChildren.active)) {
display: none !important;
}
+ /* Compact BetterSEQTA mark instead of the full wordmark. */
+ &.student #menu > ul::before {
+ background-size: 36px 36px !important;
+ background-position: center center !important;
+ }
+
}
[class*="notifications__items___"] {
-ms-overflow-style: none !important;
diff --git a/src/css/injected/sidebar-animation.scss b/src/css/injected/sidebar-animation.scss
index 88cb2e2e..f789b142 100644
--- a/src/css/injected/sidebar-animation.scss
+++ b/src/css/injected/sidebar-animation.scss
@@ -8,12 +8,16 @@
}
#menu > ul:has(li.hasChildren.active) > li::before,
+#menu > ul:has(li.hasChildren.active) > li::after,
#menu > ul ul:has(li.hasChildren.active) > li::before,
+#menu > ul ul:has(li.hasChildren.active) > li::after,
#menu > ul:has(li.hasChildren.active) > li > label,
#menu > ul:has(li.hasChildren.active) > li > svg,
#menu > ul ul:has(li.hasChildren.active) > li > label,
#menu > ul ul:has(li.hasChildren.active) > li > svg {
- transform: translateX(-320px);
+ /* !important beats theme animations (e.g. Beach palm-slide forwards). */
+ transform: translateX(-320px) !important;
+ animation: none !important;
}
#menu li.hasChildren:not(.active) li .sub {
diff --git a/src/plugins/built-in/gradeAnalytics/injectAnalyticsMenuItem.ts b/src/plugins/built-in/gradeAnalytics/injectAnalyticsMenuItem.ts
index 152b80d3..80c62547 100644
--- a/src/plugins/built-in/gradeAnalytics/injectAnalyticsMenuItem.ts
+++ b/src/plugins/built-in/gradeAnalytics/injectAnalyticsMenuItem.ts
@@ -10,19 +10,12 @@ import { ChangeMenuItemPositions } from "@/seqta/utils/Openers/menuOrder";
import { isMenuOptionsOpen } from "@/seqta/utils/Openers/menuOptionsState";
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
import { applyMenuItemVisibility } from "@/seqta/utils/menuItemVisibility";
+import { getNativeMenuList } from "@/seqta/ui/sidebar/parseNativeMenu";
const ANALYTICS_MENU_ICON = MenuitemSVGKey.analytics;
export const ANALYTICS_MENU_CLASS = "betterseqta-grade-analytics-item";
export const ANALYTICS_MENU_KEY = "analytics";
-function getNativeMenuList(): HTMLElement | null {
- const menu = document.getElementById("menu");
- if (!menu) return null;
- return menu.querySelector(
- ":scope > ul:not(#bsplus-sidebar-root)",
- ) as HTMLElement | null;
-}
-
function loadAnalyticsPageLazy() {
void import("./loadAnalyticsPage").then((m) => m.loadAnalyticsPage());
}
diff --git a/src/seqta/ui/colors/Manager.ts b/src/seqta/ui/colors/Manager.ts
index e0805280..bb55220c 100644
--- a/src/seqta/ui/colors/Manager.ts
+++ b/src/seqta/ui/colors/Manager.ts
@@ -10,6 +10,8 @@ import { getCustomThemeAdaptiveCssVariableBindings } from "@/seqta/ui/colors/cus
import { resolveExtensionAssetUrl } from "@/lib/extensionAssetUrl";
import darkLogo from "@/resources/icons/betterseqta-light-full.png";
import lightLogo from "@/resources/icons/betterseqta-dark-full.png";
+import darkIcon from "@/resources/icons/betterseqta-light-icon.png";
+import lightIcon from "@/resources/icons/betterseqta-dark-icon.png";
const ADAPTIVE_THEME_TRANSITION_MS = 400;
const LOGO_STYLE_ID = "bsplus-logo-style";
@@ -88,7 +90,8 @@ function cancelColorTransition() {
/** Chromium does not always resolve extension URLs inside CSS variables on ::before. */
function applyBetterseqtaLogoBackground(isDark: boolean) {
- const url = resolveExtensionAssetUrl(isDark ? darkLogo : lightLogo);
+ const fullUrl = resolveExtensionAssetUrl(isDark ? darkLogo : lightLogo);
+ const iconUrl = resolveExtensionAssetUrl(isDark ? darkIcon : lightIcon);
let styleEl = document.getElementById(LOGO_STYLE_ID);
if (!styleEl) {
styleEl = document.createElement("style");
@@ -98,7 +101,11 @@ function applyBetterseqtaLogoBackground(isDark: boolean) {
styleEl.textContent = `
body.student #menu > ul::before,
#title::before {
- background-image: url("${url}") !important;
+ background-image: url("${fullUrl}") !important;
+ }
+ /* Icon-only sidebar: wordmark → compact mark (theme-aware). */
+ body.student.icon-only-sidebar:not(:has(#menu li.hasChildren.active)) #menu > ul::before {
+ background-image: url("${iconUrl}") !important;
}
`;
}
diff --git a/src/seqta/ui/sidebar/Sidebar.svelte b/src/seqta/ui/sidebar/Sidebar.svelte
index bd91bf9a..ca6cdb79 100644
--- a/src/seqta/ui/sidebar/Sidebar.svelte
+++ b/src/seqta/ui/sidebar/Sidebar.svelte
@@ -34,33 +34,17 @@
function closeEdit() {
sidebarState.setEditMode(false);
- menuEl.classList.remove("bsplus-sidebar-edit-mode");
void import("@/seqta/utils/Openers/menuOptionsState").then((mod) => {
mod.setMenuOptionsOpen(false);
});
}
- function findFolder(
- items: SidebarItemModel[],
- key: string,
- ): SidebarItemModel | null {
- for (const item of items) {
- if (item.key === key) return item;
- if (item.children.length) {
- const nested = findFolder(item.children, key);
- if (nested) return nested;
- }
- }
- return null;
- }
-
- /** Folder meta for each drill frame (icon/label from the live tree). */
- const drillFolders = $derived.by(() => {
- return sidebarState.drillStack.map((frame) => {
- const folder = findFolder(sidebarState.items, frame.key);
- return { frame, folder };
- });
- });
+ const drillFolders = $derived(
+ sidebarState.drillStack.map((frame) => ({
+ frame,
+ folder: sidebarState.findByKey(frame.key),
+ })),
+ );
function onToggleVisible(key: string, visible: boolean) {
sidebarState.setItemVisibility(key, visible);
@@ -105,26 +89,13 @@
};
});
- // SEQTA strips `.active` from `#menu li` after clicks — keep it on our custom
- // rows so theme animations (palm/sand) and drill `.sub` chrome stay applied.
+ // SEQTA strips `.active` from `#menu li` after clicks — re-apply from state.
+ // Do NOT MutationObserver class changes here: restore writes `.active`, SEQTA
+ // strips it again, and the feedback loop freezes the tab.
$effect(() => {
- // Re-run when the route-active key changes so we re-bind the observer target.
void sidebarState.activeKey;
void sidebarState.isDrilling;
-
- const root = document.getElementById("bsplus-sidebar-root");
- if (!root) return;
-
restoreCustomMenuActive();
-
- const observer = new MutationObserver(() => restoreCustomMenuActive());
- observer.observe(root, {
- subtree: true,
- attributes: true,
- attributeFilter: ["class"],
- });
-
- return () => observer.disconnect();
});
@@ -151,9 +122,8 @@
{item}
active={sidebarState.activeKey === item.key}
compact={sidebarState.compact}
- editMode={sidebarState.editMode}
+ editMode={true}
visible={itemVisible(item.key)}
- drillEnter={false}
{onActivate}
{onToggleVisible}
{onDragStart}
@@ -173,21 +143,19 @@
{:else if drillFolders.length}
-
+
{#each sidebarState.visibleRootItems as item (item.key)}
{#if item.key === drillFolders[0].frame.key}
{@render drillLevel(drillFolders, 0)}
{:else}