// The layout every page reached by its address shares — a content page, a
// project, a member — so moving between them reads as one site.
//
// A partial each body `@use`s rather than a global sheet: component styles are
// scoped, and each body gets its own scoped copy of these rules.
// 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: 7rem 3.5rem;
}
}
.content-hero__title {
margin: 0;
max-width: 22ch;
font-size: clamp(2.25rem, 5.5vw, 3.5rem);
line-height: 1.08;
letter-spacing: -0.02em;
}
.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: 0 0 2.5rem;
overflow: hidden;
border-radius: var(--pi-theme-card-radius, 1.5rem);
}
// The `` 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 {
font-size: 1.0625rem;
// 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: 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.75rem;
font-size: 1.25rem;
font-weight: 700;
}
// One rhythm for both: left alone, the library's sheet sets paragraphs
// tighter than list items and the body reads in two different spacings.
p,
li {
line-height: 1.7;
}
p {
margin: 0 0 1rem;
}
// The bullet outside the text, where a list's bullet goes. `@pi/ui`'s
// reset puts it inside, and the editor wraps each item's text in a `
`: // together the bullet sat alone on one line and the text, a block, fell to // the next — with a paragraph's margins on top. ul, ol { margin: 0 0 1.5rem; padding-left: 1.5rem; } li { margin: 0 0 0.5rem; padding-left: 0.25rem; list-style-position: outside; &::marker { color: var(--pi-color-primary); } > p { margin: 0; } } strong { font-weight: 700; } a { color: var(--pi-color-primary); } code { padding: 0.1em 0.35em; border-radius: 0.375rem; background: color-mix(in srgb, var(--pi-color-primary) 10%, transparent); font-size: 0.9em; } pre { margin: 0 0 1.5rem; padding: 1rem 1.25rem; overflow-x: auto; border-radius: var(--pi-border-radius-medium, 0.75rem); background: color-mix(in srgb, var(--pi-color-primary) 7%, transparent); line-height: 1.55; code { padding: 0; background: none; } } // `@pi/ui` already frames a quote with its own quotation marks; only the // spacing is this page's business. blockquote { margin: 0 0 1.5rem; p:last-child { margin-bottom: 0; } } hr { margin: 2.5rem 0; border: 0; border-top: 1px solid color-mix(in srgb, currentColor 15%, transparent); } // Images in the text — attached in the panel's editor, or by URL through // the API. Never wider than the column, and set apart from the prose. img { display: block; max-width: 100%; height: auto; margin: 2rem 0; border-radius: var(--pi-border-radius-medium, 0.75rem); } figure { margin: 2rem 0; img { margin: 0; } } figcaption { margin-top: 0.5rem; font-size: 0.875rem; color: color-mix(in srgb, currentColor 68%, transparent); } > :first-child { margin-top: 0; } } } .content-page__aside { display: flex; flex-direction: column; gap: 2rem; // Stays in view beside a long page, under the sticky header. @media (min-width: 1024px) { position: sticky; top: 6rem; align-self: start; } } .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; } // Hero actions — a demo, a repository — and the chips under them. .content-hero__actions { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-top: 2rem; a { text-decoration: none; } } .content-hero__chips { display: flex; flex-wrap: wrap; gap: 0.375rem; margin: 1.5rem 0 0; padding: 0; list-style: none; } // A section inside the reading column, under its own bold heading. .content-page__section { margin-top: 3.5rem; > h2 { margin: 0 0 1.25rem; font-size: clamp(1.5rem, 3vw, 1.875rem); font-weight: 700; line-height: 1.2; letter-spacing: -0.015em; } } .content-page__main > :first-child { margin-top: 0; } // A card in the side column that holds facts rather than a link. .content-page__panel { display: block; padding: 1.25rem 1.5rem; border-radius: var(--pi-theme-card-radius, 1.5rem); box-shadow: var(--pi-theme-card-shadow, none); &:hover { transform: none; } h2 { margin: 0 0 1rem; font-size: 1.0625rem; font-weight: 700; } } // Label and value, one pair a row. .content-page__facts { display: grid; gap: 0.875rem; margin: 0; dt { font-size: 0.75rem; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: color-mix(in srgb, currentColor 60%, transparent); } dd { margin: 0.125rem 0 0; } a { color: var(--pi-color-primary); } } .content-page__muted { color: color-mix(in srgb, currentColor 68%, transparent); } // Lists this layout spaces with `gap`. `@pi/ui`'s reset adds a top margin to // every `li + li`, which suits a list of prose and staggers everything else. .content-hero__chips, .content-page__related { > li { margin: 0; } } .content-hero__chips { align-items: center; }