# Layout Issue Catalogue β refer_landing_page
**Date:** 2026-06-16
**Scope:** all `app/**/page.tsx`, `components/*.tsx`, `app/globals.css`, `tailwind.config.ts`, `app/intro/_components/HeroComposition.tsx`.
**Method:** source review + live dev render (`npx next dev -p 4510`, all 5 routes, 200 OK). No browser available so visual verification is by DOM + class composition analysis; defects that can be derived from class+CSS math are flagged as **Verified**, defects that would need a viewport to be 100% sure are flagged as **Likely**.
Severity legend: π΄ bug (visible defect) Β· π likely (probable defect worth fixing) Β· π‘ polish (improvement, not a bug).
---
## 1. π΄ `border-current/30` and `border-current/20` do NOT actually follow the current text color
**Files:**
- `app/lectures/page.tsx:86` β `
... level ...`
- `app/lectures/page.tsx:90` β `
`
- `app/standardization/page.tsx:145` β `
`
- `app/standardization/page.tsx:150` β `
`
- `app/intro/page.tsx:246` β `
` (thrust card points)
**What's wrong:** `currentColor` is a CSS keyword, not a hex. Tailwind's opacity-modifier syntax (`border-current/30`) compiles the color to a *fixed* fallback gray (`rgb(229 231 235 / 0.3)`), not the actual inherited text color. The intent is clearly "in the rest state, a 30%-opacity border in the card's own text color; on hover (when text becomes ivory), the border becomes 30%-opacity ivory." In practice the border stays a fixed gray that does not shift on hover.
**Fix direction:**
- Replace `border-current/30` with `border-vermillion/30` (or `border-cobalt/30`) for the static state, plus a `group-hover:border-ivory/30` to honor the hover swap. Or:
- Use a plain `border-current` and let the hover state's `text-ivory` carry it (no opacity).
- Same for the `border-current/20` rules.
**Confidence:** Verified. The CSS that Tailwind emits is a static color, not a `currentColor` cascade.
---
## 2. π΄ `HeroComposition` node pulse animation drifts the circles off-position (SVG transform-origin bug)
**File:** `app/intro/_components/HeroComposition.tsx:82-93`
```tsx
{nodes.map((n, i) => (
))}
```
Combined with the keyframe in `tailwind.config.ts:96-99`:
```
node-pulse: { 0%, 100%: { opacity: 0.35, transform: "scale(1)" }, 50%: { opacity: 1, transform: "scale(1.12)" } }
```
**What's wrong:** SVG `` elements have a CSS `transform-box` default of `view-box` in modern Chrome/Safari but `fill-box` (or `border-box` historically) in some Firefox versions. The inline `transformOrigin: '70px 90px'` is in the element's own coordinate space, NOT relative to the circle's bounding box. When `transform: scale(1.12)` is applied at the 50% keyframe, the circle scales from the SVG user-units origin `(0,0)` instead of from its own `(cx, cy)`. In Chrome with `transform-box: view-box` (current default), the circle stays roughly in place (origin = SVG user origin = `70,90` if you set the inline `transformOrigin` to that), but Firefox's older default causes the circle to translate by `0.12 * 70 = 8.4px` to the right and `0.12 * 90 = 10.8px` down on every pulse.
**Fix direction:** add `transform-box: fill-box; transform-origin: center;` to the circle (either via a new utility in `globals.css` or inline `style={{ transformBox: 'fill-box', transformOrigin: 'center' }}`). Then `scale(1.12)` will scale the circle around its own center regardless of `cx`/`cy`.
**Confidence:** Likely (Chrome 79+ default `view-box` mostly masks this; Firefox pre-122 / pre-2024 still drifts). Worth fixing because the docstring in `DESIGN.md` calls out node-pulse as a key visual.
---
## 3. π΄ `HeroComposition` bottom-right annotation text overlaps the QUIC stream curves
**File:** `app/intro/_components/HeroComposition.tsx:99-109`
```tsx
QUIC Β· STREAMS
```
The four QUIC stream paths are rendered with `y = 250 + i * 30` (i = 0..3 β 250, 280, 310, 340) and end points at `360, y-20` (so 230, 260, 290, 320). The curves use control points up to `y+40` (290, 320, 350, **380**). The bottom-right text is at `y=356`. The 4th stream's curve crosses through the text region from roughly `(240, 380)` to `(360, 320)`, which is the same y-band the text sits in.
**Fix direction:** move the text up to `y=374` (clear of stream 4's body) or move the text outside the frame entirely (e.g. add a small white-rect "chip" behind it) β simplest: shift the text `y` to ~`24` from the bottom and ensure no stream path control point enters that band. Or reduce the number of streams to 3.
**Confidence:** Verified by SVG geometry.
---
## 4. π΄ `HeroComposition` outer frame stroke is clipped at the SVG edges
**File:** `app/intro/_components/HeroComposition.tsx:43-51`
```tsx
```
A `strokeWidth` of `1.5` means the stroke straddles the path: 0.75px outside, 0.75px inside. With the rect at `x=6, y=6` inside a `viewBox="0 0 380 380"`, the 0.75px outer half is still inside the viewBox (`x=6 - 0.75 = 5.25` is still > 0). So this is actually fine β I had it wrong on the first pass. **No fix needed.** Keeping it here so it doesn't get re-flagged.
**Confidence:** Verified. Frame is fine.
---
## 5. π Footer has excessive top whitespace (`mt-24` Γ `flex-1` main)
**File:** `components/Footer.tsx:15`
```tsx