refactor(z-index): name the stacking layers as a token scale

Replace ~48 magic z-index values (z-10..z-[9999] + 2 inline zIndex) with named
tailwind zIndex tokens (base/menu/chrome/paneltab/panel/rail/overlay/popover/
tooltip/tuner). Values are UNCHANGED — a rename, not a renumber — so layering is
identical; the names give one source of truth for the stack.
This commit is contained in:
2026-07-18 19:11:25 +02:00
parent 7039abc4d3
commit c91caffa5e
26 changed files with 71 additions and 46 deletions
+1 -1
View File
@@ -130,7 +130,7 @@ export default function AddToPlaylist({
<div
ref={panelRef}
style={{ position: "fixed", top: coords.top, left: coords.left, width: 240 }}
className="z-50 glass-menu rounded-xl p-2 shadow-2xl"
className="z-overlay glass-menu rounded-xl p-2 shadow-2xl"
onClick={(e) => e.stopPropagation()}
>
<div className="text-xs text-muted px-1.5 pb-1.5">
+1 -1
View File
@@ -48,7 +48,7 @@ export default function BackToTop({ dep, threshold = 600 }: { dep?: unknown; thr
return createPortal(
<button
onClick={toTop}
className={`fixed bottom-5 right-5 z-30 inline-flex items-center justify-center w-16 h-16 rounded-full glass-card glass-hover text-fg shadow-lg hover:text-accent transition-all duration-200 ${
className={`fixed bottom-5 right-5 z-chrome inline-flex items-center justify-center w-16 h-16 rounded-full glass-card glass-hover text-fg shadow-lg hover:text-accent transition-all duration-200 ${
show ? "opacity-100 translate-y-0" : "opacity-0 translate-y-3 pointer-events-none"
}`}
>
+3 -3
View File
@@ -260,7 +260,7 @@ export default function ChannelPage({
<button
data-testid="channel-back"
onClick={closeChannel}
className="absolute top-5 left-7 sm:left-9 z-10 inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm bg-black/45 text-white hover:bg-black/65 backdrop-blur-sm"
className="absolute top-5 left-7 sm:left-9 z-base inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm bg-black/45 text-white hover:bg-black/65 backdrop-blur-sm"
>
<ArrowLeft className="w-4 h-4" />
{t("channel.back")}
@@ -268,9 +268,9 @@ export default function ChannelPage({
</div>
<div className="px-4 sm:px-6">
{/* Identity + actions. relative+z-10 so the avatar that overlaps up into the banner
{/* Identity + actions. relative+z-base so the avatar that overlaps up into the banner
paints ABOVE it (the banner's positioned container would otherwise cover it). */}
<div className="relative z-10 flex items-end gap-4 -mt-8">
<div className="relative z-base flex items-end gap-4 -mt-8">
<Avatar
src={ch?.thumbnail_url ?? null}
fallback={name}
+1 -1
View File
@@ -742,7 +742,7 @@ function TagsCell({
<div
ref={fade.ref}
style={fade.style}
className={`glass-menu absolute left-0 z-30 w-44 max-h-[264px] overflow-y-auto no-scrollbar rounded-xl p-1.5 animate-[popIn_0.16s_ease] ${
className={`glass-menu absolute left-0 z-chrome w-44 max-h-[264px] overflow-y-auto no-scrollbar rounded-xl p-1.5 animate-[popIn_0.16s_ease] ${
openUp ? "bottom-full mb-1" : "top-full mt-1"
}`}
>
+2 -2
View File
@@ -208,7 +208,7 @@ export default function DataTable<T>({
return (
<div
ref={popRef}
className="glass-menu absolute left-0 top-full mt-1 z-30 w-52 rounded-xl p-2 animate-[popIn_0.16s_ease]"
className="glass-menu absolute left-0 top-full mt-1 z-chrome w-52 rounded-xl p-2 animate-[popIn_0.16s_ease]"
>
{f.kind === "text" && (
<input
@@ -373,7 +373,7 @@ export default function DataTable<T>({
away with the collapsed table box in some browsers. The `::after` paints a short
fade just BELOW the header so rows dissolve as they slide under it (the header itself
stays crisp — the page scroller's own top fade is off on these pages). */}
<thead className="sticky top-0 z-10 after:pointer-events-none after:absolute after:inset-x-0 after:top-full after:h-4 after:bg-[linear-gradient(to_bottom,var(--bg),transparent)] after:content-['']">
<thead className="sticky top-0 z-base after:pointer-events-none after:absolute after:inset-x-0 after:top-full after:h-4 after:bg-[linear-gradient(to_bottom,var(--bg),transparent)] after:content-['']">
<tr className="text-muted [&>th]:shadow-[inset_0_-1px_0_var(--border)]">
{columns.map((col) => {
const sorted_ = sort?.key === col.key;
+1 -1
View File
@@ -447,7 +447,7 @@ function QuotaUserPicker({ onPick }: { onPick: (u: { id: number; email: string }
className={inputCls}
/>
{open && filtered.length > 0 && (
<div className="absolute z-10 mt-1 w-full rounded-lg glass-menu shadow-xl overflow-hidden">
<div className="absolute z-base mt-1 w-full rounded-lg glass-menu shadow-xl overflow-hidden">
{filtered.map((u) => (
<button
key={u.id}
+1 -1
View File
@@ -106,7 +106,7 @@ export default function Header({
// Absolutely positioned within the (relative) content column, so its left edge tracks the
// actual left zone: it sits just inside the content, reclaiming the space when the nav rail is
// slim and/or no filter sidebar is present — no hard-coded offset.
<div className="pointer-events-none absolute top-3 left-3 right-[10%] z-30 flex items-center gap-3">
<div className="pointer-events-none absolute top-3 left-3 right-[10%] z-chrome flex items-center gap-3">
<ModuleName
label={t(moduleLabelKey[page])}
labels={moduleLabels}
+2 -2
View File
@@ -103,7 +103,7 @@ export default function LanguageSwitcher({
</button>
{open &&
createPortal(
<div style={{ position: "fixed", left: pos.left, bottom: pos.bottom, zIndex: 40 }}>
<div className="z-rail" style={{ position: "fixed", left: pos.left, bottom: pos.bottom }}>
{menu}
</div>,
document.body
@@ -127,7 +127,7 @@ export default function LanguageSwitcher({
<div
className={`glass-menu absolute ${
align === "right" ? "right-0" : "left-0"
} mt-1 w-40 rounded-xl p-1.5 z-40 animate-[popIn_0.16s_ease]`}
} mt-1 w-40 rounded-xl p-1.5 z-rail animate-[popIn_0.16s_ease]`}
>
{LANGUAGES.map((l) => (
<button
+1 -1
View File
@@ -52,7 +52,7 @@ export default function Modal({
return createPortal(
<div
className="fixed inset-0 z-50 flex items-center justify-center p-4 sm:p-6 bg-black/70 backdrop-blur-sm"
className="fixed inset-0 z-overlay flex items-center justify-center p-4 sm:p-6 bg-black/70 backdrop-blur-sm"
onMouseDown={(e) => {
pressedOnBackdrop.current = e.target === e.currentTarget;
}}
+2 -2
View File
@@ -278,7 +278,7 @@ export default function NavSidebar({
// margin, and a hover zone out there would fire from the dead space next to the rail —
// leaving the side panel to the left would pop the rail open with nothing under the cursor.
{...peek.handlers}
className={`glass absolute top-0 bottom-0 left-0 z-40 rounded-2xl flex flex-col py-3 m-3 transition-[width] ${
className={`glass absolute top-0 bottom-0 left-0 z-rail rounded-2xl flex flex-col py-3 m-3 transition-[width] ${
slim ? "w-[56px] px-2" : "w-52 px-3"
}`}
aria-label={t("nav.primary")}
@@ -390,7 +390,7 @@ export default function NavSidebar({
<div
ref={acctPanelRef}
style={{ position: "fixed", left: acctPos.left, bottom: acctPos.bottom }}
className="glass w-60 rounded-xl p-3 z-50 animate-[popIn_0.16s_ease]"
className="glass w-60 rounded-xl p-3 z-overlay animate-[popIn_0.16s_ease]"
>
<div className="flex items-center gap-3 pb-3 border-b border-border">
<AvatarImg
+1 -1
View File
@@ -39,7 +39,7 @@ export default function PanelGroup({
ref={setNodeRef}
style={style}
className={`glass-card rounded-xl ${
isDragging ? "relative z-10 shadow-2xl ring-1 ring-accent" : ""
isDragging ? "relative z-base shadow-2xl ring-1 ring-accent" : ""
} ${hidden && editing ? "opacity-50" : ""}`}
>
<div className="flex items-center gap-1.5 px-3 py-2">
+7 -7
View File
@@ -585,14 +585,14 @@ export default function PlayerModal({
// Portaled to <body>, like every other full-screen overlay here. A fixed, full-viewport modal
// must not render inside the page scroller: once that scroller is a stacking context (its
// edge-fade mask makes it one) this z-50 is scoped inside it, and the nav rail (z-40) and the
// header (z-30) paint over the modal — worst at browser zoom, where the centered card is smaller
// edge-fade mask makes it one) this z-overlay is scoped inside it, and the nav rail (z-rail) and the
// header (z-chrome) paint over the modal — worst at browser zoom, where the centered card is smaller
// relative to the rail/panel. (Same class as PlexPlayer; this one was the miss in that sweep.)
return createPortal(
<div
ref={dialogRef}
data-testid="player-modal"
className="fixed inset-0 z-50 flex items-center justify-center p-4 sm:p-6 bg-black/80 backdrop-blur-sm"
className="fixed inset-0 z-overlay flex items-center justify-center p-4 sm:p-6 bg-black/80 backdrop-blur-sm"
onClick={onClose}
role="dialog"
aria-modal="true"
@@ -642,21 +642,21 @@ export default function PlayerModal({
}}
title={t("player.shortcutsHint")}
aria-label={t("player.shortcutsHint")}
className={`absolute inset-x-0 top-[12%] bottom-[22%] z-10 cursor-pointer ${
className={`absolute inset-x-0 top-[12%] bottom-[22%] z-base cursor-pointer ${
nativeControls ? "pointer-events-none" : ""
}`}
/>
)}
{/* Discreet badge while YouTube's own controls have taken over (overlay yielded). */}
{playerError == null && nativeControls && (
<div className="pointer-events-none absolute top-3 left-3 z-20 flex items-center gap-1.5 rounded-full bg-black/60 px-2.5 py-1 text-[11px] text-white/90 shadow-lg backdrop-blur-sm">
<div className="pointer-events-none absolute top-3 left-3 z-menu flex items-center gap-1.5 rounded-full bg-black/60 px-2.5 py-1 text-[11px] text-white/90 shadow-lg backdrop-blur-sm">
<Settings className="h-3.5 w-3.5" />
{t("player.nativeControls")}
</div>
)}
{/* Volume level flash (auto-fades). pointer-events-none so it never blocks the video. */}
{volumeUi != null && (
<div className="pointer-events-none absolute top-3 left-1/2 -translate-x-1/2 z-20 flex items-center gap-2 rounded-full bg-black/70 px-3 py-1.5 text-xs text-white shadow-lg">
<div className="pointer-events-none absolute top-3 left-1/2 -translate-x-1/2 z-menu flex items-center gap-2 rounded-full bg-black/70 px-3 py-1.5 text-xs text-white shadow-lg">
{volumeUi === 0 ? <VolumeX className="h-4 w-4" /> : <Volume2 className="h-4 w-4" />}
<div className="h-1.5 w-28 overflow-hidden rounded-full bg-white/25">
<div className="h-full rounded-full bg-white transition-all" style={{ width: `${volumeUi}%` }} />
@@ -768,7 +768,7 @@ export default function PlayerModal({
descRect &&
createPortal(
<div
className="fixed z-[60] bg-surface border border-border rounded-xl shadow-2xl p-4"
className="fixed z-popover bg-surface border border-border rounded-xl shadow-2xl p-4"
style={{
left: descRect.left,
bottom: descRect.bottom,
+3 -3
View File
@@ -231,7 +231,7 @@ export default function PlexBrowse({
// The fallback is portaled for the same reason the player itself is: it is a fixed
// full-viewport overlay, and this tree renders inside the page scroller.
return (
<Suspense fallback={createPortal(<div className="fixed inset-0 z-50 bg-black" />, document.body)}>
<Suspense fallback={createPortal(<div className="fixed inset-0 z-overlay bg-black" />, document.body)}>
<PlexPlayer itemId={sub.view.id} queue={sub.view.queue} onClose={sub.back} />
</Suspense>
);
@@ -924,7 +924,7 @@ function PlexShowView({
<>
{/* Hero */}
<div className="glass relative rounded-2xl p-4 sm:p-5 mb-8 flex gap-4 sm:gap-5">
<div className="absolute right-3 top-3 z-10">
<div className="absolute right-3 top-3 z-base">
<DetailCustomizeMenu
artBg={artBg}
onToggleArtBg={toggleArtBg}
@@ -1121,7 +1121,7 @@ function PlexSeasonView({
) : (
<>
<div className="glass relative rounded-2xl p-4 sm:p-5 mb-8 flex gap-4 sm:gap-5">
<div className="absolute right-3 top-3 z-10">
<div className="absolute right-3 top-3 z-base">
<DetailCustomizeMenu
artBg={artBg}
onToggleArtBg={toggleArtBg}
+4 -4
View File
@@ -948,12 +948,12 @@ export default function PlexPlayer({ itemId, onClose, queue }: Props) {
// Portaled to <body>, like every other full-screen overlay here (Modal, PlayerModal, ChatDock,
// BackToTop). A fixed, full-viewport player must not be painted inside the page scroller: the
// moment that scroller gets a mask/filter/transform/contain it becomes a stacking context, and
// this z-50 is then scoped inside it — the rail, the side panel and the header paint straight
// this z-overlay is then scoped inside it — the rail, the side panel and the header paint straight
// over the player. That is exactly what the scroller's edge-fade did before this moved out.
return createPortal(
<div
ref={wrapRef}
className="fixed left-0 top-0 z-50 bg-black flex items-center justify-center select-none"
className="fixed left-0 top-0 z-overlay bg-black flex items-center justify-center select-none"
onMouseMove={wake}
onClick={wake}
onWheel={onWheel}
@@ -1546,7 +1546,7 @@ export default function PlexPlayer({ itemId, onClose, queue }: Props) {
{/* Keyboard-shortcut cheat sheet (toggle with "h" or the keyboard button). */}
{helpOpen && (
<div
className="absolute inset-0 z-20 grid place-items-center bg-black/60 p-4"
className="absolute inset-0 z-menu grid place-items-center bg-black/60 p-4"
onClick={(e) => {
e.stopPropagation();
setHelpOpen(false);
@@ -1609,7 +1609,7 @@ export default function PlexPlayer({ itemId, onClose, queue }: Props) {
{/* Rich info overlay (poster/cast/ratings) — "i" or the info button; video keeps playing. */}
{infoOpen && detail && (
<div
className="absolute inset-0 z-20 grid place-items-center overflow-y-auto bg-black/70 p-4"
className="absolute inset-0 z-menu grid place-items-center overflow-y-auto bg-black/70 p-4"
onClick={(e) => {
e.stopPropagation();
setInfoOpen(false);
+1 -1
View File
@@ -56,7 +56,7 @@ function UserShare({ job }: { job: DownloadJob }) {
className={inputCls}
/>
{open && filtered.length > 0 && (
<div className="absolute z-10 mt-1 w-full rounded-lg glass-menu shadow-xl overflow-hidden">
<div className="absolute z-base mt-1 w-full rounded-lg glass-menu shadow-xl overflow-hidden">
{filtered.map((u) => (
<button
key={u.id}
+2 -2
View File
@@ -60,7 +60,7 @@ export default function SidePanel({
<button
onClick={onToggleCollapse}
aria-label={pinned ? t("sidebar.unpin") : t("sidebar.pin")}
className={`glass glass-hover absolute top-3 left-0 z-[34] flex flex-col items-center gap-2 px-1.5 py-3 rounded-l-none rounded-r-xl transition-opacity ${
className={`glass glass-hover absolute top-3 left-0 z-paneltab flex flex-col items-center gap-2 px-1.5 py-3 rounded-l-none rounded-r-xl transition-opacity ${
expanded ? "opacity-0" : ""
} ${count > 0 ? "ring-1 ring-accent/40" : ""}`}
>
@@ -79,7 +79,7 @@ export default function SidePanel({
</button>
{expanded && (
<aside className="glass absolute top-3 bottom-3 left-0 z-[35] w-64 rounded-2xl flex flex-col overflow-hidden">
<aside className="glass absolute top-3 bottom-3 left-0 z-panel w-64 rounded-2xl flex flex-col overflow-hidden">
{/* Row 1 — identity only: pin, icon, title (flexes + truncates last), count, customize.
Module actions live on their own row below so the title always has room. */}
<div className="flex items-center gap-1.5 px-3 py-2.5 border-b border-border/60 flex-none">
+1 -1
View File
@@ -134,7 +134,7 @@ export default function SyncStatus({
<div
ref={popRef}
style={{ position: "fixed", top: pos.top, right: pos.right }}
className="glass-menu w-64 rounded-xl p-3 z-50 text-xs text-muted animate-[popIn_0.16s_ease]"
className="glass-menu w-64 rounded-xl p-3 z-overlay text-xs text-muted animate-[popIn_0.16s_ease]"
>
<div
title={t("header.sync.countTooltip")}
+2 -2
View File
@@ -82,8 +82,8 @@ function TagRow({
ref={popFade.ref}
onMouseEnter={openPop}
onMouseLeave={closeSoon}
style={{ position: "fixed", left: pos.left, top: pos.top, zIndex: 60, ...popFade.style }}
className="glass-menu w-56 max-h-72 overflow-y-auto no-scrollbar rounded-xl p-1.5 animate-[popIn_0.16s_ease]"
style={{ position: "fixed", left: pos.left, top: pos.top, ...popFade.style }}
className="z-popover glass-menu w-56 max-h-72 overflow-y-auto no-scrollbar rounded-xl p-1.5 animate-[popIn_0.16s_ease]"
>
<div className="text-[11px] uppercase tracking-wide text-muted px-2 py-1">
{t("tagManager.onChannels")}
+1 -1
View File
@@ -31,7 +31,7 @@ export default function Toaster() {
const toasts = useSyncExternalStore(subscribe, getActiveToasts, getActiveToasts);
return (
<div className="absolute bottom-4 left-4 z-50 flex flex-col gap-2 w-80 max-w-[calc(100vw-2rem)]">
<div className="absolute bottom-4 left-4 z-overlay flex flex-col gap-2 w-80 max-w-[calc(100vw-2rem)]">
{toasts.map((toast) => {
const { icon: Icon, color, bar } = LEVEL_STYLE[toast.level];
return (
+1 -1
View File
@@ -83,7 +83,7 @@ export default function Tooltip({
top: coords.top,
transform: `translateX(-50%) translateY(${coords.placement === "top" ? "-100%" : "0"})`,
}}
className="glass pointer-events-none z-[100] w-max max-w-[240px] px-2.5 py-1.5 rounded-lg text-xs leading-snug text-fg font-normal normal-case tracking-normal animate-[fadeIn_0.12s_ease]"
className="glass pointer-events-none z-tooltip w-max max-w-[240px] px-2.5 py-1.5 rounded-lg text-xs leading-snug text-fg font-normal normal-case tracking-normal animate-[fadeIn_0.12s_ease]"
>
{hint}
</div>,
+3 -3
View File
@@ -405,7 +405,7 @@ export default function VideoEditor({ job, onClose }: { job: DownloadJob; onClos
))}
{/* draggable boundary markers */}
{boundaries.map((b, bi) => (
<div key={bi} className="absolute inset-y-0 -ml-1.5 w-3 flex items-center justify-center z-10" style={{ left: pct(b) }}>
<div key={bi} className="absolute inset-y-0 -ml-1.5 w-3 flex items-center justify-center z-base" style={{ left: pct(b) }}>
<div
className="w-1 h-full bg-white/90 cursor-ew-resize rounded"
onPointerDown={(e) => {
@@ -426,13 +426,13 @@ export default function VideoEditor({ job, onClose }: { job: DownloadJob; onClos
</div>
))}
{/* playhead */}
<div className="absolute inset-y-0 w-0.5 bg-white pointer-events-none z-20" style={{ left: pct(current) }} />
<div className="absolute inset-y-0 w-0.5 bg-white pointer-events-none z-menu" style={{ left: pct(current) }} />
</div>
{/* hover-scrub thumbnail */}
{strip && hoverTime != null && (
<div
className="absolute bottom-full mb-2 pointer-events-none z-30 rounded-md overflow-hidden border border-border shadow-xl bg-black"
className="absolute bottom-full mb-2 pointer-events-none z-chrome rounded-md overflow-hidden border border-border shadow-xl bg-black"
style={{ left: hoverLeft, width: HOVER_W }}
>
<div style={{ width: HOVER_W, height: HOVER_W / aspect, ...tileStyle(Math.floor(hoverTime / (strip.interval_s || 1))) }} />
+1 -1
View File
@@ -119,7 +119,7 @@ export default function ViewSwitcher<T extends string>({
role="menu"
aria-label={label}
onKeyDown={onMenuKeyDown}
className="glass-menu absolute right-0 top-full mt-1 z-30 min-w-44 p-1.5 rounded-xl animate-[popIn_0.16s_ease]"
className="glass-menu absolute right-0 top-full mt-1 z-chrome min-w-44 p-1.5 rounded-xl animate-[popIn_0.16s_ease]"
>
{/* The modes are one exclusive choice, so the radio items need a group to scope their
checked-ness to — otherwise they read as independent checkable items. */}
+1 -1
View File
@@ -228,7 +228,7 @@ function Lightbox({ src, label, onClose }: { src: string; label: string; onClose
}, [onClose]);
return (
<div
className="fixed inset-0 z-[60] grid place-items-center bg-black/80 backdrop-blur-sm p-4 sm:p-8"
className="fixed inset-0 z-popover grid place-items-center bg-black/80 backdrop-blur-sm p-4 sm:p-8"
onClick={onClose}
role="dialog"
aria-modal="true"
+2 -2
View File
@@ -37,11 +37,11 @@ export function DraftSaveBar({
const saving = state === "saving";
const floating = variant === "floating";
const wrap = floating
? "fixed bottom-4 left-1/2 -translate-x-1/2 z-40 glass rounded-2xl shadow-lg flex items-center justify-between gap-4 px-4 py-3 w-[min(48rem,calc(100%-2rem))]"
? "fixed bottom-4 left-1/2 -translate-x-1/2 z-rail glass rounded-2xl shadow-lg flex items-center justify-between gap-4 px-4 py-3 w-[min(48rem,calc(100%-2rem))]"
: "flex items-center justify-between gap-3 border-t border-border/60 px-4 py-3 bg-card/40";
// The floating variant is fixed to the viewport but renders from a page inside the page
// scroller, so it goes to <body> — otherwise the scroller's stacking context (its edge-fade
// mask) scopes this z-40 and fades the bar's bottom edge. The inline variant is in normal flow
// mask) scopes this z-rail and fades the bar's bottom edge. The inline variant is in normal flow
// and stays put.
const bar = (
<div data-testid="draft-save-bar" className={wrap}>
+1 -1
View File
@@ -141,7 +141,7 @@ export function DetailCustomizeMenu({
{open && (
<div
ref={menuRef}
className="glass-menu absolute right-0 top-full z-20 mt-1 w-52 rounded-xl p-1.5 text-sm"
className="glass-menu absolute right-0 top-full z-menu mt-1 w-52 rounded-xl p-1.5 text-sm"
style={{ background: "color-mix(in srgb, var(--surface) 58%, transparent)" }}
>
<PrefToggle label={t("plex.info.prefArtBg")} on={artBg} onClick={onToggleArtBg} />
+25
View File
@@ -16,6 +16,31 @@ export default {
fontFamily: {
sans: ["Inter", "system-ui", "-apple-system", "Segoe UI", "Roboto", "sans-serif"],
},
// Named z-index layers — the single source of truth for stacking order. Values are
// deliberately UNCHANGED from the magic numbers they replaced (this was a rename, not a
// renumber), so the visual layering is identical; the names just make the stack legible and
// give future changes one place to edit. Low → high:
// base(10) in-content decorations: sticky table head, card badges, in-flow dropdowns
// menu(20) menus/overlays sitting just over content
// chrome(30) floating chrome: the header band, back-to-top, content filter/sort menus
// paneltab / panel (34/35) the collapsed tab vs the expanded side panel (aside over tab)
// rail(40) full-height rails + docked bars: nav rail, chat dock, floating save bar
// overlay(50) full-screen modals/players, toaster, onboarding, popovers over content
// popover(60) a popover that must sit over an overlay (player scrub, tag menu) + the login gate
// tooltip(100) tooltips, above everything interactive
// tuner(9999) the dev/lab glass tuner, deliberately on top of all
zIndex: {
base: "10",
menu: "20",
chrome: "30",
paneltab: "34",
panel: "35",
rail: "40",
overlay: "50",
popover: "60",
tooltip: "100",
tuner: "9999",
},
},
},
plugins: [],