overlay-positioning.html

  1<!DOCTYPE html>
  2<html lang="en">
  3<head>
  4  <meta charset="UTF-8">
  5  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6  <title>Overlay Positioning Edge Cases</title>
  7  <style>
  8    body {
  9      font-family: system-ui, sans-serif;
 10      background: #f9fafb;
 11      padding: 2rem;
 12      max-width: 900px;
 13      margin: 0 auto;
 14    }
 15    h1 { font-size: 2rem; margin-bottom: 0.5rem; }
 16    h2 { font-size: 1.125rem; margin: 2.5rem 0 0.75rem; color: #6b7280; border-bottom: 1px solid #e5e7eb; padding-bottom: 0.5rem; }
 17    .scenario { margin-bottom: 2rem; padding: 1rem; border: 1px dashed #d1d5db; border-radius: 8px; }
 18    .scenario-label { font-size: 0.75rem; color: #6b7280; margin-bottom: 0.5rem; text-transform: uppercase; letter-spacing: 0.05em; }
 19
 20    /* ============================================
 21       1. TRANSFORM ANCESTOR
 22       position:absolute overlays on body use document coords,
 23       but getBoundingClientRect is viewport-relative.
 24       Transforms on ancestors create new stacking contexts.
 25       ============================================ */
 26    .transform-container {
 27      transform: translateY(0);
 28      padding: 1rem;
 29      background: #f3f4f6;
 30      border-radius: 8px;
 31    }
 32    .transform-rotate {
 33      transform: rotate(0deg);
 34      padding: 1rem;
 35      background: #f3f4f6;
 36      border-radius: 8px;
 37    }
 38    .transform-scale {
 39      transform: scale(1);
 40      padding: 1rem;
 41      background: #f3f4f6;
 42      border-radius: 8px;
 43    }
 44    .transform-nested-outer {
 45      transform: translateX(0);
 46      padding: 1rem;
 47      background: #e5e7eb;
 48      border-radius: 8px;
 49    }
 50    .transform-nested-inner {
 51      transform: translateY(0);
 52      padding: 1rem;
 53      background: #d1d5db;
 54      border-radius: 8px;
 55    }
 56
 57    /* ============================================
 58       2. CLOSED <details>
 59       Elements inside collapsed details may report
 60       a bounding rect but aren't visually present.
 61       ============================================ */
 62    details { margin-bottom: 1rem; }
 63    summary { cursor: pointer; font-weight: 600; }
 64
 65    /* ============================================
 66       3. STICKY / FIXED POSITIONING
 67       Sticky elements change their containing block
 68       behavior during scroll.
 69       ============================================ */
 70    .sticky-container {
 71      height: 200px;
 72      overflow-y: auto;
 73      border: 1px solid #d1d5db;
 74      border-radius: 8px;
 75    }
 76    .sticky-header {
 77      position: sticky;
 78      top: 0;
 79      background: #1f2937;
 80      color: white;
 81      padding: 0.5rem 1rem;
 82      z-index: 10;
 83    }
 84    .sticky-content {
 85      padding: 1rem;
 86    }
 87
 88    /* ============================================
 89       4. OVERFLOW HIDDEN
 90       Elements that extend beyond an overflow:hidden
 91       parent are visually clipped but still in the DOM.
 92       ============================================ */
 93    .overflow-hidden-container {
 94      overflow: hidden;
 95      height: 60px;
 96      border: 1px solid #d1d5db;
 97      border-radius: 8px;
 98      padding: 0.5rem 1rem;
 99    }
100
101    /* ============================================
102       5. ABSOLUTE / RELATIVE POSITIONING
103       Elements that are offset from their normal flow
104       position via top/left.
105       ============================================ */
106    .relative-offset {
107      position: relative;
108      top: 20px;
109      left: 40px;
110      padding: 1rem;
111      background: #f3f4f6;
112      border-radius: 8px;
113    }
114    .absolute-container {
115      position: relative;
116      height: 120px;
117      background: #f3f4f6;
118      border-radius: 8px;
119    }
120    .absolute-child {
121      position: absolute;
122      bottom: 10px;
123      right: 10px;
124    }
125
126    /* ============================================
127       6. FLEXBOX / GRID CENTERING
128       Elements centered via flex/grid may have
129       unexpected positions vs. document flow.
130       ============================================ */
131    .flex-center {
132      display: flex;
133      justify-content: center;
134      align-items: center;
135      height: 120px;
136      background: #f3f4f6;
137      border-radius: 8px;
138    }
139    .grid-center {
140      display: grid;
141      place-items: center;
142      height: 120px;
143      background: #f3f4f6;
144      border-radius: 8px;
145    }
146
147    /* ============================================
148       7. WILL-CHANGE / CONTAIN / FILTER
149       These CSS properties create new containing
150       blocks, similar to transforms.
151       ============================================ */
152    .will-change-container {
153      will-change: transform;
154      padding: 1rem;
155      background: #f3f4f6;
156      border-radius: 8px;
157    }
158    .contain-container {
159      contain: layout;
160      padding: 1rem;
161      background: #f3f4f6;
162      border-radius: 8px;
163    }
164    .filter-container {
165      filter: brightness(1);
166      padding: 1rem;
167      background: #f3f4f6;
168      border-radius: 8px;
169    }
170    .backdrop-filter-container {
171      backdrop-filter: blur(0px);
172      padding: 1rem;
173      background: #f3f4f6;
174      border-radius: 8px;
175    }
176
177    /* ============================================
178       8. MARGIN COLLAPSE / NEGATIVE MARGINS
179       Elements with collapsed or negative margins
180       can shift from expected position.
181       ============================================ */
182    .negative-margin-container {
183      padding: 2rem;
184      background: #f3f4f6;
185      border-radius: 8px;
186    }
187    .negative-margin-child {
188      margin-top: -1rem;
189      margin-left: -1rem;
190    }
191
192    /* ============================================
193       Anti-pattern triggers (tiny-text, cramped, ai-color)
194       used across scenarios.
195       ============================================ */
196    .tiny { font-size: 10px; color: #374151; line-height: 1.4; }
197    .cramped-box { border: 1px solid #d1d5db; padding: 3px; font-size: 14px; border-radius: 4px; }
198    .ai-btn { background: linear-gradient(135deg, #8b5cf6, #6366f1); color: white; padding: 8px 16px; border: none; border-radius: 6px; font-size: 14px; cursor: pointer; }
199    .side-tab-card { border-left: 4px solid #8b5cf6; padding: 1rem; background: white; border-radius: 0 8px 8px 0; }
200  </style>
201</head>
202<body>
203
204<h1>Overlay Positioning Edge Cases</h1>
205<p>Each scenario places a detectable anti-pattern inside a layout context that can cause overlay misalignment.</p>
206
207<!-- ═══════════════════════════════════════════
208     1. TRANSFORM ANCESTORS
209     ═══════════════════════════════════════════ -->
210<h2>1. Transform Ancestors</h2>
211
212<div class="scenario">
213  <div class="scenario-label">1a. translateY(0) container</div>
214  <div class="transform-container">
215    <span class="tiny">This tiny text is inside a transform: translateY(0) container. The overlay should frame this text precisely.</span>
216  </div>
217</div>
218
219<div class="scenario">
220  <div class="scenario-label">1b. rotate(0deg) container</div>
221  <div class="transform-rotate">
222    <span class="tiny">Tiny text inside a transform: rotate(0deg) container.</span>
223  </div>
224</div>
225
226<div class="scenario">
227  <div class="scenario-label">1c. scale(1) container</div>
228  <div class="transform-scale">
229    <div class="cramped-box">Cramped text inside a transform: scale(1) container.</div>
230  </div>
231</div>
232
233<div class="scenario">
234  <div class="scenario-label">1d. Nested transforms</div>
235  <div class="transform-nested-outer">
236    <div class="transform-nested-inner">
237      <span class="tiny">Tiny text nested inside two levels of transformed parents.</span>
238    </div>
239  </div>
240</div>
241
242<div class="scenario">
243  <div class="scenario-label">1e. Transform + absolute child</div>
244  <div class="transform-container" style="position: relative; height: 100px;">
245    <div style="position: absolute; bottom: 8px; right: 8px;">
246      <span class="tiny">Absolutely positioned tiny text inside transformed parent.</span>
247    </div>
248  </div>
249</div>
250
251<!-- ═══════════════════════════════════════════
252     2. CLOSED <details>
253     ═══════════════════════════════════════════ -->
254<h2>2. Closed Details</h2>
255
256<div class="scenario">
257  <div class="scenario-label">2a. Closed details with anti-pattern inside</div>
258  <details>
259    <summary>Click to expand (closed by default)</summary>
260    <span class="tiny">This tiny text is hidden inside a closed details element. No overlay should appear for it.</span>
261    <div class="cramped-box">Cramped text also hidden inside closed details.</div>
262  </details>
263</div>
264
265<div class="scenario">
266  <div class="scenario-label">2b. Open details with anti-pattern inside</div>
267  <details open>
268    <summary>This details is open</summary>
269    <span class="tiny">This tiny text IS visible because details is open. Overlay should frame it correctly.</span>
270  </details>
271</div>
272
273<div class="scenario">
274  <div class="scenario-label">2c. Nested details (outer open, inner closed)</div>
275  <details open>
276    <summary>Outer details (open)</summary>
277    <p>Some visible content.</p>
278    <details>
279      <summary>Inner details (closed)</summary>
280      <span class="tiny">Hidden tiny text inside nested closed details.</span>
281    </details>
282  </details>
283</div>
284
285<div class="scenario">
286  <div class="scenario-label">2d. Transform inside closed details</div>
287  <details>
288    <summary>Closed with transform inside</summary>
289    <div class="transform-container">
290      <span class="tiny">Tiny text inside transform inside closed details. Double trouble.</span>
291    </div>
292  </details>
293</div>
294
295<!-- ═══════════════════════════════════════════
296     3. STICKY / FIXED
297     ═══════════════════════════════════════════ -->
298<h2>3. Sticky and Fixed Positioning</h2>
299
300<div class="scenario">
301  <div class="scenario-label">3a. Sticky header inside scrollable container</div>
302  <div class="sticky-container">
303    <div class="sticky-header">
304      <span class="tiny">Tiny text in a sticky header.</span>
305    </div>
306    <div class="sticky-content">
307      <p>Scroll this container to test sticky behavior.</p>
308      <p>More content to enable scrolling.</p>
309      <p>Even more content here.</p>
310      <div class="cramped-box">Cramped text below the sticky header.</div>
311      <p>Additional content.</p>
312      <p>More filler text.</p>
313    </div>
314  </div>
315</div>
316
317<div class="scenario">
318  <div class="scenario-label">3b. Fixed footer (stays at bottom of viewport)</div>
319  <p>The fixed footer at the bottom of the page has tiny text. Its overlay should stay fixed too.</p>
320</div>
321
322<!-- Actual fixed footer -- outside scenario container so it's truly fixed -->
323<div style="position: fixed; bottom: 0; left: 0; right: 0; background: #1f2937; color: white; padding: 0.5rem 1rem; z-index: 50;">
324  <span class="tiny">Tiny text in a fixed footer -- overlay should track this on scroll.</span>
325</div>
326
327<!-- ═══════════════════════════════════════════
328     4. OVERFLOW HIDDEN
329     ═══════════════════════════════════════════ -->
330<h2>4. Overflow Hidden</h2>
331
332<div class="scenario">
333  <div class="scenario-label">4a. Content clipped by overflow:hidden</div>
334  <div class="overflow-hidden-container">
335    <p>This paragraph is visible.</p>
336    <span class="tiny">This tiny text is below the overflow cutoff, clipped but still in DOM.</span>
337    <p>This content is also clipped away.</p>
338  </div>
339</div>
340
341<div class="scenario">
342  <div class="scenario-label">4b. overflow:hidden + transform ancestor</div>
343  <div class="transform-container">
344    <div class="overflow-hidden-container">
345      <p>Visible inside transform + overflow.</p>
346      <span class="tiny">Clipped tiny text inside a transformed overflow:hidden container.</span>
347    </div>
348  </div>
349</div>
350
351<!-- ═══════════════════════════════════════════
352     5. ABSOLUTE / RELATIVE OFFSETS
353     ═══════════════════════════════════════════ -->
354<h2>5. Position Offsets</h2>
355
356<div class="scenario">
357  <div class="scenario-label">5a. Relative position with top/left offset</div>
358  <div class="relative-offset">
359    <span class="tiny">This tiny text is shifted via position:relative + top/left offset.</span>
360  </div>
361  <div style="height: 30px;"></div><!-- spacer for the offset -->
362</div>
363
364<div class="scenario">
365  <div class="scenario-label">5b. Absolute child in relative parent</div>
366  <div class="absolute-container">
367    <p>Normal flow content at top.</p>
368    <div class="absolute-child">
369      <span class="tiny">Absolutely positioned tiny text at bottom-right.</span>
370    </div>
371  </div>
372</div>
373
374<div class="scenario">
375  <div class="scenario-label">5c. Negative top offset</div>
376  <div style="padding-top: 2rem;">
377    <div style="position: relative; top: -1rem;">
378      <span class="tiny">Tiny text pulled upward via negative top offset.</span>
379    </div>
380  </div>
381</div>
382
383<!-- ═══════════════════════════════════════════
384     6. FLEX / GRID CENTERING
385     ═══════════════════════════════════════════ -->
386<h2>6. Flex and Grid Centering</h2>
387
388<div class="scenario">
389  <div class="scenario-label">6a. Flex-centered anti-pattern</div>
390  <div class="flex-center">
391    <span class="tiny">Centered tiny text inside a flex container.</span>
392  </div>
393</div>
394
395<div class="scenario">
396  <div class="scenario-label">6b. Grid-centered anti-pattern</div>
397  <div class="grid-center">
398    <div class="cramped-box">Cramped text inside a grid-centered container.</div>
399  </div>
400</div>
401
402<div class="scenario">
403  <div class="scenario-label">6c. Flex with transform</div>
404  <div class="flex-center transform-container">
405    <button class="ai-btn">AI Gradient Button in Flex + Transform</button>
406  </div>
407</div>
408
409<!-- ═══════════════════════════════════════════
410     7. WILL-CHANGE / CONTAIN / FILTER
411     ═══════════════════════════════════════════ -->
412<h2>7. Containing Block Creators</h2>
413
414<div class="scenario">
415  <div class="scenario-label">7a. will-change: transform</div>
416  <div class="will-change-container">
417    <span class="tiny">Tiny text inside a will-change: transform container.</span>
418  </div>
419</div>
420
421<div class="scenario">
422  <div class="scenario-label">7b. contain: layout</div>
423  <div class="contain-container">
424    <span class="tiny">Tiny text inside a contain: layout container.</span>
425  </div>
426</div>
427
428<div class="scenario">
429  <div class="scenario-label">7c. filter: brightness(1)</div>
430  <div class="filter-container">
431    <span class="tiny">Tiny text inside a filter container (creates containing block).</span>
432  </div>
433</div>
434
435<div class="scenario">
436  <div class="scenario-label">7d. backdrop-filter</div>
437  <div class="backdrop-filter-container">
438    <span class="tiny">Tiny text inside a backdrop-filter container.</span>
439  </div>
440</div>
441
442<!-- ═══════════════════════════════════════════
443     8. NEGATIVE MARGINS
444     ═══════════════════════════════════════════ -->
445<h2>8. Margin Collapse and Negative Margins</h2>
446
447<div class="scenario">
448  <div class="scenario-label">8a. Negative margin shifting element position</div>
449  <div class="negative-margin-container">
450    <div class="negative-margin-child">
451      <span class="tiny">Tiny text pulled out of its parent via negative margins.</span>
452    </div>
453  </div>
454</div>
455
456<!-- ═══════════════════════════════════════════
457     9. COMBINATION SCENARIOS
458     ═══════════════════════════════════════════ -->
459<h2>9. Combined Edge Cases</h2>
460
461<div class="scenario">
462  <div class="scenario-label">9a. Transform + sticky + overflow</div>
463  <div class="transform-container">
464    <div class="sticky-container">
465      <div class="sticky-header">
466        <span class="tiny">Tiny text in sticky header inside transform inside scrollable overflow.</span>
467      </div>
468      <div class="sticky-content">
469        <p>Scroll content line 1.</p>
470        <p>Scroll content line 2.</p>
471        <p>Scroll content line 3.</p>
472        <div class="cramped-box">Cramped box scrolled below sticky.</div>
473        <p>Scroll content line 4.</p>
474        <p>Scroll content line 5.</p>
475      </div>
476    </div>
477  </div>
478</div>
479
480<div class="scenario">
481  <div class="scenario-label">9b. Closed details + transform + flex</div>
482  <details>
483    <summary>Closed combo scenario</summary>
484    <div class="flex-center transform-container">
485      <span class="tiny">Triple-nested: closed details > flex > transform > tiny text.</span>
486    </div>
487  </details>
488</div>
489
490<div class="scenario">
491  <div class="scenario-label">9c. Grid + absolute + transform</div>
492  <div class="grid-center" style="position: relative; height: 150px;">
493    <div class="transform-container" style="position: absolute; top: 10px; left: 10px; right: 10px;">
494      <span class="tiny">Absolute inside grid inside transform: overlays must track all three contexts.</span>
495    </div>
496    <div style="position: absolute; bottom: 10px; right: 10px;">
497      <div class="cramped-box">Absolute cramped box at bottom-right of grid.</div>
498    </div>
499  </div>
500</div>
501
502<div class="scenario">
503  <div class="scenario-label">9d. Overflow hidden + absolute breakout</div>
504  <div style="position: relative; overflow: hidden; height: 80px; background: #f3f4f6; border-radius: 8px; padding: 1rem;">
505    <p>Visible content.</p>
506    <div style="position: absolute; bottom: -20px; left: 0; right: 0;">
507      <span class="tiny">Absolute-positioned tiny text that breaks out below overflow:hidden.</span>
508    </div>
509  </div>
510</div>
511
512<div class="scenario">
513  <div class="scenario-label">9e. Side-tab card inside transform + relative offset</div>
514  <div class="transform-container">
515    <div style="position: relative; left: 50px;">
516      <div class="side-tab-card">
517        This card has a side-tab border accent and is offset inside a transform container.
518      </div>
519    </div>
520  </div>
521</div>
522
523<script src="/js/detect-antipatterns-browser.js"></script>
524</body>
525</html>