Give the content pages the landing's look

Moving from the landing to a page read as changing sites: a narrow
centred column on a flat background, light headings, no cards. The
page now opens like the landing's hero — the same two lights, the same
left edge as the logo, a bold title with the excerpt as its lead — and
the body's headings are bold like every section title on the landing.

The lights move into a shared mixin (ui/_lights.scss) so the hero and
the pages cannot drift apart. On a page they sit on the whole <main>
with `overflow: clip`, so they fade out on their own instead of ending
in a hard edge right where the text starts, and `clip` rather than
`hidden` keeps the sticky side column working.

A side column keeps a page from being a dead end: "keep reading" cards
for the other pages, its own category first, and a card to get in
touch, both on the landing's card surface.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
pigui
2026-09-28 10:38:04 +02:00
parent 80a1924519
commit 9af6b5aff9
5 changed files with 288 additions and 108 deletions

View File

@@ -1,3 +1,5 @@
@use './lights';
:host {
display: block;
}
@@ -16,55 +18,86 @@
}
}
// The lights sit on the whole page rather than on its top: on the hero alone
// they end in a hard edge where it does, and here the text starts right there.
// A page is tall enough for them to fade out on their own.
.content-page {
padding-block: 4rem 5rem;
@include lights.glow($overflow: clip);
}
// The top of the page is the landing's hero, minus the stats and buttons: the
// same left edge as the logo, the same heading weight.
.content-hero {
padding-block: 5rem 2.5rem;
@media (min-width: 768px) {
padding-block: 6rem 7rem;
padding-block: 7rem 3.5rem;
}
}
// A reading column, not the landing's full width: prose past ~70 characters a
// line is hard to follow.
.content-page__inner {
max-width: 48rem;
}
.content-page__title {
.content-hero__title {
margin: 0;
font-size: clamp(2rem, 5vw, 3rem);
line-height: 1.1;
max-width: 22ch;
font-size: clamp(2.25rem, 5.5vw, 3.5rem);
line-height: 1.08;
letter-spacing: -0.02em;
}
.content-page__lead {
margin: 1.25rem 0 0;
line-height: 1.6;
.content-hero__lead {
max-width: 46ch;
margin: 1.5rem 0 0;
}
// Reading column and a side column, on the landing's own grid width. The
// column keeps prose to a readable measure; the side keeps the page from
// ending in a dead end.
.content-page__layout {
display: grid;
gap: 3rem;
padding-bottom: 5rem;
@media (min-width: 1024px) {
grid-template-columns: minmax(0, 46rem) minmax(16rem, 20rem);
justify-content: space-between;
gap: 4rem;
padding-bottom: 7rem;
}
}
.content-page__media {
margin: 2.5rem 0 0;
margin: 0 0 2.5rem;
overflow: hidden;
border-radius: var(--pi-theme-card-radius, 1.5rem);
}
// The `<img>` belongs to `app-api-image`, so a scoped rule cannot reach it.
.content-page__media ::ng-deep img {
display: block;
width: 100%;
height: auto;
}
.content-page__body {
margin-top: 2.5rem;
font-size: 1.0625rem;
line-height: 1.75;
// The body is HTML from the panel's editor, not this component's template,
// so it carries none of the scoping attributes: only `::ng-deep` reaches it.
// Confined to this container, it styles nothing else on the page.
::ng-deep {
// Bold, like every heading on the landing — a light heading here was the
// one thing that made the page read as another site.
h2 {
margin: 2.5rem 0 0.75rem;
font-size: 1.5rem;
line-height: 1.25;
margin: 3rem 0 1rem;
font-size: clamp(1.5rem, 3vw, 1.875rem);
font-weight: 700;
line-height: 1.2;
letter-spacing: -0.015em;
}
h3 {
margin: 2rem 0 0.5rem;
font-size: 1.2rem;
margin: 2rem 0 0.75rem;
font-size: 1.25rem;
font-weight: 700;
}
// One rhythm for both: left alone, the library's sheet sets paragraphs
@@ -80,12 +113,20 @@
ul,
ol {
margin: 0 0 1.25rem;
margin: 0 0 1.5rem;
padding-left: 1.25rem;
}
li {
margin-bottom: 0.5rem;
&::marker {
color: var(--pi-color-primary);
}
}
strong {
font-weight: 700;
}
a {
@@ -98,20 +139,77 @@
}
}
.content-page__back {
display: inline-block;
margin-top: 3rem;
color: var(--pi-color-grey-600, #5b5b6b);
text-decoration: none;
.content-page__aside {
display: flex;
flex-direction: column;
gap: 2rem;
&:hover {
color: inherit;
// Stays in view beside a long page, under the sticky header.
@media (min-width: 1024px) {
position: sticky;
top: 6rem;
align-self: start;
}
}
// The `<img>` belongs to `app-api-image`, so a scoped rule cannot reach it.
.content-page__media ::ng-deep img {
display: block;
width: 100%;
height: auto;
.content-page__related {
display: grid;
gap: 1rem;
margin: 0;
padding: 0;
list-style: none;
}
// `landing-card` is the landing's card surface and hover, drawn on a link
// rather than on `pi-card`, which brings no padding or radius of its own here.
.content-page__card,
.content-page__cta {
display: block;
padding: 1.25rem 1.5rem;
border-radius: var(--pi-theme-card-radius, 1.5rem);
box-shadow: var(--pi-theme-card-shadow, none);
}
.content-page__card {
color: inherit;
text-decoration: none;
}
.content-page__card-title {
display: block;
font-size: 1.0625rem;
font-weight: 700;
}
.content-page__card-text {
display: -webkit-box;
margin-top: 0.375rem;
overflow: hidden;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
font-size: 0.875rem;
line-height: 1.5;
// Relative to the card's own text colour, so it holds up in dark mode too,
// where the grey token is nearly the background.
color: color-mix(in srgb, currentColor 68%, transparent);
}
.content-page__cta {
padding: 1.5rem;
// The call to action is not a link as a whole, so it does not lift.
&:hover {
transform: none;
}
p {
margin: 0.5rem 0 1.25rem;
line-height: 1.6;
}
}
.content-page__cta-title {
margin: 0;
font-size: 1.375rem;
font-weight: 700;
}