I lost my theme's spine so brought it back. All hail CSS rotation.
The Spine: On Rotating a Header Until It Became Architecture
At some point the header stopped being navigation. It stopped being a polite horizontal bar asking to be clicked. It became a structural element. A margin with authority. A binding.
I wanted the site to feel less like a webpage and more like a book cracked open along its left edge. Something that contains the writing instead of hovering above it.
So I rotated the header. And then everything got weird.
Rotation Is a Lie (Until You Understand It)
The header is rotated:
transform: rotate(-90deg);
That seems harmless. It is not harmless. When you rotate an element, CSS does not "swap" width and height in a friendly way. It simply rotates the box in space. Your mental model must rotate with it.
After a -90deg rotation:
CSS property What it becomes visually
-------------- --------------------------
`width` Vertical length of spine
`height` Thickness of spine
That means this line:
height: var(--spine-gutter);
...does not control height in the way your brain wants it to. It controls how thick the left margin appears. You are no longer sizing a header. You are carving a column into the viewport.
The Math That Prevents the Spine From Eating Itself
The spine contains two lanes:
- Title
- Navigation (including the dark mode switch)
That means the thickness must account for:
- Lane height (Ă 2)
- The gap between them
- Padding on top and bottom
- A small safety margin so Safari doesn't take a bite out of your nav links
Here's the calculation:
--spine-lane: clamp(2.6rem, 5.8vw, 3.9rem);
--spine-row-gap: .40rem;
--spine-pad-y: .45rem;
--spine-gutter: calc(
(2 * var(--spine-lane))
+ var(--spine-row-gap)
+ (2 * var(--spine-pad-y))
+ .25rem
);
Centering a Rotated Thing Without Summoning Chaos
The stable version is almost boring:
header{
position: fixed;
left: 0;
top: 50%;
transform-origin: left center;
transform:
translateY(-50%)
translateX(var(--spine-shove))
rotate(-90deg);
}
Three rules:
top: 50%+translateY(-50%)centers vertically.translateX(var(--spine-shove))pushes the rotated element into view by a fixed amount.- No percentage offsets tied to viewport geometry.
The result:
- Portrait works.
- Landscape works.
- Safari chrome appears and disappears and nothing explodes.
The spine becomes anchored. Not drifting. Not haunted.
The Background Problem: Floating vs Structural
Originally the header had its own background. That made it look like a floating strip on top of the grid background, like a sticker.
That was wrong.
The fix was architectural:
body::before{
content:"";
position: fixed;
top: 0;
left: 0;
height: 100vh;
width: var(--spine-gutter);
background: var(--block-background);
border-right: var(--rule-heavy);
}
Now the spine is:
- Full height
- Dimensionally tied to
--spine-gutter - Independent from the rotated content
- A margin that exists even if the header vanished
The content does not overlap it. It grows beside it.
Orientation, Safari, and the Betrayal of vh
On iPad, 100vh does not always mean "the visible screen." Safari's dynamic chrome shifts it.
So the spine constrains itself like this:
@supports (height: 100dvh){
header{
max-width: calc(100dvh - (2 * var(--spine-inset)));
}
}
dvh = dynamic viewport height.
Which means when the browser UI moves, the math updates.
The Formula Generalizes
If I ever want three rows:
--spine-gutter =
(3 * lane-height)
+ (2 * row-gap)
+ (2 * padding)
+ safety
It scales.
Why I Like This
The spine is not decorative. It is a constraint with backbone...