|
|
|
|
@@ -0,0 +1,392 @@
|
|
|
|
|
# Z-Index & Stacking Context Management
|
|
|
|
|
|
|
|
|
|
## Overview
|
|
|
|
|
|
|
|
|
|
This document tracks z-index values and stacking contexts across the application to prevent layering conflicts and make reasoning about visual hierarchy easy.
|
|
|
|
|
|
|
|
|
|
## The Z-Index Constants System
|
|
|
|
|
|
|
|
|
|
**Location:** `src/constants/zIndex.ts`
|
|
|
|
|
|
|
|
|
|
All z-index values should be defined in this file and imported where needed:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
import { Z_INDEX } from '../constants/zIndex'
|
|
|
|
|
|
|
|
|
|
// Use it like this:
|
|
|
|
|
zIndex: Z_INDEX.NAV_BAR
|
|
|
|
|
zIndex: Z_INDEX.MODAL
|
|
|
|
|
zIndex: Z_INDEX.GAME_NAV.HAMBURGER_MENU
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Z-Index Layering Hierarchy
|
|
|
|
|
|
|
|
|
|
From lowest to highest:
|
|
|
|
|
|
|
|
|
|
| Layer | Range | Purpose | Examples |
|
|
|
|
|
|-------|-------|---------|----------|
|
|
|
|
|
| **Base Content** | 0-99 | Default page content, game elements | Background elements, game tracks, cards |
|
|
|
|
|
| **Navigation & UI Chrome** | 100-999 | Fixed navigation, sticky headers | AppNavBar, page headers |
|
|
|
|
|
| **Overlays & Dropdowns** | 1000-9999 | Tooltips, popovers, dropdowns, tutorial tooltips | Tutorial tooltips (50-100), ConfigForm (50), dropdowns (999-1000) |
|
|
|
|
|
| **Modals & Dialogs** | 10000-19999 | Modal dialogs, confirmation dialogs | Modal backdrop (10000), Modal content (10001) |
|
|
|
|
|
| **Top-Level Overlays** | 20000+ | Toasts, critical notifications | Toast notifications (20000) |
|
|
|
|
|
|
|
|
|
|
## Stacking Context Rules
|
|
|
|
|
|
|
|
|
|
### What Creates a Stacking Context?
|
|
|
|
|
|
|
|
|
|
These CSS properties create new stacking contexts (z-index values are relative within them):
|
|
|
|
|
|
|
|
|
|
1. `position: fixed` or `position: sticky` with z-index
|
|
|
|
|
2. `position: absolute` or `position: relative` with z-index
|
|
|
|
|
3. `opacity` < 1
|
|
|
|
|
4. `transform` (any value)
|
|
|
|
|
5. `filter` (any value except none)
|
|
|
|
|
6. `isolation: isolate`
|
|
|
|
|
|
|
|
|
|
### Key Insight
|
|
|
|
|
|
|
|
|
|
**Z-index values are only compared within the same stacking context!**
|
|
|
|
|
|
|
|
|
|
If Element A creates a stacking context with `z-index: 1` and Element B is outside that context with `z-index: 999`, Element B will be on top regardless of child z-indexes inside Element A.
|
|
|
|
|
|
|
|
|
|
### Example
|
|
|
|
|
|
|
|
|
|
```tsx
|
|
|
|
|
// Parent creates stacking context
|
|
|
|
|
<div style={{ position: 'relative', zIndex: 1 }}>
|
|
|
|
|
{/* This child's z-index is relative to parent, not global! */}
|
|
|
|
|
<div style={{ position: 'absolute', zIndex: 999999 }}>
|
|
|
|
|
I'm still under elements with zIndex: 2 outside my parent!
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div style={{ position: 'relative', zIndex: 2 }}>
|
|
|
|
|
I'm on top of the z-index: 999999 element above!
|
|
|
|
|
</div>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Current Z-Index Audit (2025-10-20)
|
|
|
|
|
|
|
|
|
|
### ✅ Using Z_INDEX Constants (Good!)
|
|
|
|
|
|
|
|
|
|
| Component | Value | Source |
|
|
|
|
|
|-----------|-------|--------|
|
|
|
|
|
| AppNavBar (Panda section) | `Z_INDEX.NAV_BAR` (100) | `src/components/AppNavBar.tsx:464` |
|
|
|
|
|
| AppNavBar hamburger | `Z_INDEX.GAME_NAV.HAMBURGER_MENU` (9999) | `src/components/AppNavBar.tsx:165` |
|
|
|
|
|
| AbacusDisplayDropdown | `Z_INDEX.GAME_NAV.HAMBURGER_NESTED_DROPDOWN` (10000) | `src/components/AbacusDisplayDropdown.tsx:99` |
|
|
|
|
|
|
|
|
|
|
### ⚠️ Hardcoded Z-Index Values (Need Migration)
|
|
|
|
|
|
|
|
|
|
#### Critical Navigation Issues
|
|
|
|
|
|
|
|
|
|
| Component | Line | Value | Issue | Fix |
|
|
|
|
|
|-----------|------|-------|-------|-----|
|
|
|
|
|
| **AppNavBar (fixed section)** | 587 | `1000` | ❌ Should use `Z_INDEX.NAV_BAR` (100), but increased to 1000 to fix tutorial tooltip overlap | Define `TUTORIAL_TOOLTIP` in constants, set nav to proper layer |
|
|
|
|
|
| AppNavBar (badge) | 645 | `50` | Should use constant | Add `Z_INDEX.BADGE` |
|
|
|
|
|
|
|
|
|
|
#### Tutorial System
|
|
|
|
|
|
|
|
|
|
| Component | Line | Value | Purpose |
|
|
|
|
|
|-----------|------|-------|---------|
|
|
|
|
|
| TutorialPlayer | 643 | `50` | Tooltip container |
|
|
|
|
|
| Tutorial shared/EditorComponents | 569, 590 | `50` | Tooltip button |
|
|
|
|
|
| Tutorial shared/EditorComponents | 612 | `100` | Dropdown content (must be above tooltip) |
|
|
|
|
|
| Tutorial decomposition CSS | 73 | `50` | Legacy CSS |
|
|
|
|
|
| TutorialEditor | 65, 812, 2339 | `1000`, `10` | Various overlays |
|
|
|
|
|
|
|
|
|
|
#### Modals & Overlays
|
|
|
|
|
|
|
|
|
|
| Component | Line | Value | Purpose |
|
|
|
|
|
|-----------|------|-------|---------|
|
|
|
|
|
| Modal (common) | 59 | `10000` | Modal backdrop |
|
|
|
|
|
| ModerationPanel | 1994, 2009 | `10001`, `10002` | Moderation overlays |
|
|
|
|
|
| ToastContext | 171 | `10001` | Toast notifications (should be 20000!) |
|
|
|
|
|
| Join page | 35 | `10000` | Join page overlay |
|
|
|
|
|
| EmojiPicker | 636 | `10000` | Emoji picker modal |
|
|
|
|
|
|
|
|
|
|
#### Dropdowns & Popovers
|
|
|
|
|
|
|
|
|
|
| Component | Line | Value | Purpose |
|
|
|
|
|
|-----------|------|-------|---------|
|
|
|
|
|
| FormatSelectField | 115 | `999` | Dropdown |
|
|
|
|
|
| DeploymentInfoModal | 37, 55 | `9998`, `9999` | Info modal layers |
|
|
|
|
|
| RoomInfo | 338, 562 | `9999`, `10000` | Room tooltips |
|
|
|
|
|
| GameTitleMenu | 119 | `9999` | Game menu |
|
|
|
|
|
| PlayerTooltip | 69 | `9999` | Player tooltip |
|
|
|
|
|
|
|
|
|
|
#### Game Elements
|
|
|
|
|
|
|
|
|
|
| Component | Line | Value | Purpose |
|
|
|
|
|
|-----------|------|-------|---------|
|
|
|
|
|
| Complement Race Game | Multiple | `0`, `1` | Base game layers |
|
|
|
|
|
| Complement Race Track | 118, 140, 151 | `10`, `5`, `20` | Track, AI racers, player |
|
|
|
|
|
| Complement Race HUD | 51, 106, 119, 137, 168 | `10`, `1000` | HUD elements |
|
|
|
|
|
| GameCountdown | 58 | `1000` | Countdown overlay |
|
|
|
|
|
| RouteCelebration | 31 | `9999` | Celebration overlay |
|
|
|
|
|
| Matching GameCard | 203, 229, 243, 272, 386 | `9`, `10`, `-1`, `8`, `1` | Card layers |
|
|
|
|
|
| Matching PlayerStatusBar | 154, 181, 202 | `10`, `10`, `5` | Status bars |
|
|
|
|
|
|
|
|
|
|
#### Misc UI
|
|
|
|
|
|
|
|
|
|
| Component | Line | Value | Purpose |
|
|
|
|
|
|-----------|------|-------|---------|
|
|
|
|
|
| HeroAbacus | 89, 127, 163 | `10` | Hero section layers |
|
|
|
|
|
| ChampionArena | 425, 514, 554, 614 | `10`, `1`, `1`, `10` | Arena layers |
|
|
|
|
|
| NetworkPlayerIndicator | 118, 145, 169, 192, 275 | `-1`, `2`, `1`, `2`, `10` | Player avatars |
|
|
|
|
|
| ConfigurationForm | 521, 502 | `50` | Config overlays |
|
|
|
|
|
|
|
|
|
|
## The Recent Bug: Tutorial Tooltips Over Nav Bar
|
|
|
|
|
|
|
|
|
|
**Problem:** Tutorial tooltips (z-index: 50, 100) were appearing over the navigation bar.
|
|
|
|
|
|
|
|
|
|
**Root Cause:**
|
|
|
|
|
- Nav bar was using `Z_INDEX.NAV_BAR` = 100 in one place
|
|
|
|
|
- But also hardcoded `zIndex: 30` in the fixed positioning section (line 587)
|
|
|
|
|
- Tutorial tooltips use hardcoded `zIndex: 50` and `zIndex: 100`
|
|
|
|
|
- Since 50 and 100 > 30, tooltips appeared on top
|
|
|
|
|
|
|
|
|
|
**Temporary Fix:** Increased nav bar's hardcoded value from 30 to 1000
|
|
|
|
|
|
|
|
|
|
**Proper Fix Needed:**
|
|
|
|
|
1. Define tutorial tooltip z-indexes in constants file
|
|
|
|
|
2. Update nav bar to consistently use `Z_INDEX.NAV_BAR`
|
|
|
|
|
3. Ensure NAV_BAR > TUTORIAL_TOOLTIP in the hierarchy
|
|
|
|
|
4. Consider: Should tutorial tooltips be in the 1000-9999 range (overlays) rather than 50-100?
|
|
|
|
|
|
|
|
|
|
## Guidelines for Choosing Z-Index Values
|
|
|
|
|
|
|
|
|
|
### 1. **Always Import and Use Z_INDEX Constants**
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// ✅ Good
|
|
|
|
|
import { Z_INDEX } from '../constants/zIndex'
|
|
|
|
|
zIndex: Z_INDEX.NAV_BAR
|
|
|
|
|
|
|
|
|
|
// ❌ Bad
|
|
|
|
|
zIndex: 100 // Magic number!
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 2. **Add New Values to Constants File First**
|
|
|
|
|
|
|
|
|
|
Before using a new z-index value, add it to `src/constants/zIndex.ts`:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
export const Z_INDEX = {
|
|
|
|
|
// ... existing values ...
|
|
|
|
|
|
|
|
|
|
TUTORIAL: {
|
|
|
|
|
TOOLTIP: 500, // Tutorial tooltips (overlays layer)
|
|
|
|
|
DROPDOWN: 600, // Tutorial dropdown (above tooltip)
|
|
|
|
|
},
|
|
|
|
|
} as const
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 3. **Choose the Right Layer**
|
|
|
|
|
|
|
|
|
|
Ask yourself:
|
|
|
|
|
- Is this base content? → Use 0-99
|
|
|
|
|
- Is this navigation/UI chrome? → Use 100-999
|
|
|
|
|
- Is this a dropdown/tooltip/overlay? → Use 1000-9999
|
|
|
|
|
- Is this a modal dialog? → Use 10000-19999
|
|
|
|
|
- Is this a toast notification? → Use 20000+
|
|
|
|
|
|
|
|
|
|
### 4. **Understand Your Stacking Context**
|
|
|
|
|
|
|
|
|
|
Before setting z-index, ask:
|
|
|
|
|
- What is my parent's stacking context?
|
|
|
|
|
- Am I comparing against siblings or global elements?
|
|
|
|
|
- Does my element create a new stacking context?
|
|
|
|
|
|
|
|
|
|
### 5. **Document Special Cases**
|
|
|
|
|
|
|
|
|
|
If you must deviate from the constants, document why:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// HACK: Needs to be above tutorial tooltips (50) but below modals (10000)
|
|
|
|
|
// TODO: Migrate to Z_INDEX.TUTORIAL.TOOLTIP system
|
|
|
|
|
zIndex: 100
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Migration Plan
|
|
|
|
|
|
|
|
|
|
### Phase 1: Update Constants File ✅ TODO
|
|
|
|
|
|
|
|
|
|
Add missing constants to `src/constants/zIndex.ts`:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
export const Z_INDEX = {
|
|
|
|
|
// Base content layer (0-99)
|
|
|
|
|
BASE: 0,
|
|
|
|
|
CONTENT: 1,
|
|
|
|
|
HERO_SECTION: 10, // Hero abacus components
|
|
|
|
|
|
|
|
|
|
// Game content layers (0-99)
|
|
|
|
|
GAME_CONTENT: {
|
|
|
|
|
TRACK: 0,
|
|
|
|
|
CONTROLS: 1,
|
|
|
|
|
RACER_AI: 5,
|
|
|
|
|
RACER_PLAYER: 10,
|
|
|
|
|
RACER_FLAG: 20,
|
|
|
|
|
HUD: 50,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Navigation and UI chrome (100-999)
|
|
|
|
|
NAV_BAR: 1000, // ⚠️ Currently needs to be 1000 due to tutorial tooltips
|
|
|
|
|
STICKY_HEADER: 100,
|
|
|
|
|
BADGE: 50,
|
|
|
|
|
|
|
|
|
|
// Overlays and dropdowns (1000-9999)
|
|
|
|
|
TUTORIAL: {
|
|
|
|
|
TOOLTIP: 500, // Tutorial tooltips
|
|
|
|
|
DROPDOWN: 600, // Tutorial dropdowns (must be > tooltip)
|
|
|
|
|
EDITOR: 700, // Tutorial editor
|
|
|
|
|
},
|
|
|
|
|
DROPDOWN: 1000,
|
|
|
|
|
TOOLTIP: 1000,
|
|
|
|
|
POPOVER: 1000,
|
|
|
|
|
CONFIG_FORM: 1000,
|
|
|
|
|
PLAYER_TOOLTIP: 1000,
|
|
|
|
|
GAME_COUNTDOWN: 1000,
|
|
|
|
|
|
|
|
|
|
// High overlays (9000-9999)
|
|
|
|
|
CELEBRATION: 9000,
|
|
|
|
|
INFO_MODAL: 9998,
|
|
|
|
|
|
|
|
|
|
// Modal and dialog layers (10000-19999)
|
|
|
|
|
MODAL_BACKDROP: 10000,
|
|
|
|
|
MODAL: 10001,
|
|
|
|
|
MODERATION_PANEL: 10001,
|
|
|
|
|
EMOJI_PICKER: 10000,
|
|
|
|
|
|
|
|
|
|
// Top-level overlays (20000+)
|
|
|
|
|
TOAST: 20000,
|
|
|
|
|
|
|
|
|
|
// Special navigation layers for game pages
|
|
|
|
|
GAME_NAV: {
|
|
|
|
|
HAMBURGER_MENU: 9999,
|
|
|
|
|
HAMBURGER_NESTED_DROPDOWN: 10000,
|
|
|
|
|
},
|
|
|
|
|
} as const
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Phase 2: Migrate High-Priority Components
|
|
|
|
|
|
|
|
|
|
Priority order:
|
|
|
|
|
1. **Navigation components** (AppNavBar, etc.) - most critical for user experience
|
|
|
|
|
2. **Tutorial system** (TutorialPlayer, tooltips) - currently conflicting
|
|
|
|
|
3. **Modals and overlays** - ensure they're always on top
|
|
|
|
|
4. **Game HUDs** - ensure proper layering
|
|
|
|
|
5. **Everything else**
|
|
|
|
|
|
|
|
|
|
### Phase 3: Add Linting Rule
|
|
|
|
|
|
|
|
|
|
Consider adding an ESLint rule to prevent raw z-index numbers:
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
// Warn when zIndex is used with a number literal
|
|
|
|
|
'no-magic-numbers': ['warn', {
|
|
|
|
|
ignore: [0, 1, -1],
|
|
|
|
|
ignoreArrayIndexes: true,
|
|
|
|
|
enforceConst: true,
|
|
|
|
|
}]
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Debugging Z-Index Issues
|
|
|
|
|
|
|
|
|
|
### Checklist
|
|
|
|
|
|
|
|
|
|
When elements aren't layering correctly:
|
|
|
|
|
|
|
|
|
|
1. **Check the value**
|
|
|
|
|
- [ ] What z-index does each element have?
|
|
|
|
|
- [ ] Are they using constants or magic numbers?
|
|
|
|
|
|
|
|
|
|
2. **Check the stacking context**
|
|
|
|
|
- [ ] What are the parent elements?
|
|
|
|
|
- [ ] Do any parents create stacking contexts? (position + z-index, opacity, transform, etc.)
|
|
|
|
|
- [ ] Are we comparing siblings or elements in different contexts?
|
|
|
|
|
|
|
|
|
|
3. **Verify the DOM hierarchy**
|
|
|
|
|
- [ ] Use browser DevTools to inspect the DOM tree
|
|
|
|
|
- [ ] Check the "Layers" panel in Chrome DevTools
|
|
|
|
|
- [ ] Look for transforms, opacity, filters on parent elements
|
|
|
|
|
|
|
|
|
|
4. **Test the fix**
|
|
|
|
|
- [ ] Does the fix work in all scenarios?
|
|
|
|
|
- [ ] Did we introduce new conflicts?
|
|
|
|
|
- [ ] Should we update the constants file?
|
|
|
|
|
|
|
|
|
|
### DevTools Tips
|
|
|
|
|
|
|
|
|
|
**Chrome DevTools:**
|
|
|
|
|
1. Open DevTools → More Tools → Layers
|
|
|
|
|
2. Select an element and see its stacking context
|
|
|
|
|
3. View the 3D layer composition
|
|
|
|
|
|
|
|
|
|
**Firefox DevTools:**
|
|
|
|
|
1. Inspector → Layout → scroll to "Z-index"
|
|
|
|
|
2. Shows the stacking context parent
|
|
|
|
|
|
|
|
|
|
## Examples
|
|
|
|
|
|
|
|
|
|
### Good: Using Constants
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
import { Z_INDEX } from '@/constants/zIndex'
|
|
|
|
|
|
|
|
|
|
export function MyTooltip() {
|
|
|
|
|
return (
|
|
|
|
|
<div className={css({
|
|
|
|
|
position: 'absolute',
|
|
|
|
|
zIndex: Z_INDEX.TOOLTIP, // ✅ Clear and maintainable
|
|
|
|
|
})}>
|
|
|
|
|
Tooltip content
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Bad: Magic Numbers
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
export function MyTooltip() {
|
|
|
|
|
return (
|
|
|
|
|
<div className={css({
|
|
|
|
|
position: 'absolute',
|
|
|
|
|
zIndex: 500, // ❌ Where did 500 come from? How does it relate to other elements?
|
|
|
|
|
})}>
|
|
|
|
|
Tooltip content
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Good: Documenting Stacking Context
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// Creates a new stacking context for card contents
|
|
|
|
|
<div className={css({
|
|
|
|
|
position: 'relative',
|
|
|
|
|
zIndex: Z_INDEX.BASE,
|
|
|
|
|
transform: 'translateZ(0)', // ⚠️ Creates stacking context!
|
|
|
|
|
})}>
|
|
|
|
|
{/* Child z-indexes are relative to this context */}
|
|
|
|
|
<div className={css({
|
|
|
|
|
position: 'absolute',
|
|
|
|
|
zIndex: Z_INDEX.CONTENT, // Relative to parent, not global
|
|
|
|
|
})}>
|
|
|
|
|
Card face
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Resources
|
|
|
|
|
|
|
|
|
|
- [MDN: CSS Stacking Context](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_positioned_layout/Understanding_z-index/Stacking_context)
|
|
|
|
|
- [What The Heck, z-index??](https://www.joshwcomeau.com/css/stacking-contexts/) by Josh Comeau
|
|
|
|
|
- [Z-Index Playground](https://thirumanikandan.com/posts/learn-z-index-using-a-visualization-tool)
|
|
|
|
|
|
|
|
|
|
## Last Updated
|
|
|
|
|
|
|
|
|
|
2025-10-20 - Initial audit and documentation created
|