„Modul:Mainpage/cards.css” változatai közötti eltérés
Innen: SCWIKI
More actions
Imported from starcitizen.tools (auto-sync, new page only) |
Port from starcitizen.tools upstream (test-page staging) |
||
| 73. sor: | 73. sor: | ||
.home-kicker--inset { | .home-kicker--inset { | ||
margin-top: var( --space-md ); | margin-top: var( --space-md ); | ||
} | |||
/* ---- the scroll fade ------------------------------------------------------ | |||
Two cards hold an unbounded list, and a list cut off with no cue reads as a | |||
bug rather than as more. Both get the same pair of gradients; the mainpage | |||
gadget decides when each is true. | |||
The default is INVISIBLE, not visible. CSS cannot see how far a box is | |||
scrolled, so a fade it paints unconditionally covers the last line when the | |||
content fits exactly and still promises more at the end of a long list. | |||
Without JS there is no fade and the scrollbar is the cue, which is honest. | |||
--home-fade-top / --home-fade-bottom are published by the gadget: where the | |||
scroller sits inside the frame, so one rule serves cards whose lists start | |||
at different heights. The inline inset MATCHES .home-pad's padding, because | |||
an absolutely positioned child resolves against the padding box. */ | |||
.home-scrollfade { | |||
position: relative; | |||
} | |||
.home-scrollfade::before, | |||
.home-scrollfade::after { | |||
content: ""; | |||
position: absolute; | |||
inset-inline: var( --space-md ); | |||
block-size: var( --space-xl ); | |||
/* The rows underneath stay clickable; the fade is a picture, not a lid. */ | |||
pointer-events: none; | |||
opacity: 0; | |||
transition: opacity 0.15s ease; | |||
} | |||
.home-scrollfade::before { | |||
inset-block-start: var( --home-fade-top, 0px ); | |||
background-image: linear-gradient( to bottom, var( --color-surface-0 ), transparent ); | |||
} | |||
.home-scrollfade::after { | |||
inset-block-end: var( --home-fade-bottom, 0px ); | |||
background-image: linear-gradient( to top, var( --color-surface-0 ), transparent ); | |||
} | |||
.home-scrollfade--up::before { | |||
opacity: 1; | |||
} | |||
.home-scrollfade--down::after { | |||
opacity: 1; | |||
} | |||
@media ( prefers-reduced-motion: reduce ) { | |||
.home-scrollfade::before, | |||
.home-scrollfade::after { | |||
transition: none; | |||
} | |||
} | } | ||
| 98. sor: | 154. sor: | ||
block-size: 83px; | block-size: 83px; | ||
aspect-ratio: auto; | aspect-ratio: auto; | ||
} | |||
/* ---- the event card, split design ------------------------------------------ | |||
Module:Mainpage/Event/Legacy. A wider picture column than Module:CardLua's | |||
own 13rem default, which is the right general value but leaves this card's | |||
photograph in a slot narrower than it is tall: 208x248 at the full measure, | |||
208x270 at 900. A landscape screenshot cropped to 0.77 keeps well under half | |||
its width. At 16rem the slot is 256x248 and 256x270, near enough square, and | |||
the text column still sets three comfortable lines at its narrowest (186px | |||
of prose at a 900px viewport, measured). | |||
Scoped rather than changed in CardLua: 13rem is that module's considered | |||
default for a card of unknown content, and the event card is only the first | |||
consumer to have an opinion. | |||
Keyed to 900 because that is where the side-by-side layout exists at all — | |||
CardLua stacks the split card below 639 and Module:Mainpage/styles.css | |||
stacks it again for the 6/6 stage in between, so this range is disjoint from | |||
both and neither has to know about it. */ | |||
@media ( min-width: 900px ) { | |||
.home-event-split .t-card__media-layout--split > .t-card__media { | |||
inline-size: 16rem; | |||
} | |||
} | |||
/* ---- this patch ----------------------------------------------------------- | |||
Kicker, a scrolling list, a foot link — the same three parts as the day card. | |||
`block-size: 0` is what bounds this card, and overflow-y alone would not: a | |||
grid row sizes to its tallest item's CONTENT, so an uncapped list took the | |||
card to 634px and the event card with it. A definite zero base keeps the | |||
list out of that contribution, so the row is the event card's and flex-grow | |||
hands the list whatever is left — at every width, with no number to keep in | |||
step with the card next door. | |||
The floor is for the single column below 640, where there is no sibling to | |||
set the row at all. */ | |||
.home-patch__list { | |||
block-size: 0; | |||
flex-grow: 1; | |||
min-block-size: calc( var( --line-height-small ) * 6 ); | |||
overflow-y: auto; | |||
font-size: var( --font-size-small ); | |||
line-height: var( --line-height-small ); | |||
color: var( --color-subtle ); | |||
} | |||
.home-patch__list ul { | |||
margin: 0; | |||
padding-inline-start: var( --space-md ); | |||
} | } | ||
| 338. sor: | 446. sor: | ||
content being silently truncated to whatever happened to fit. | content being silently truncated to whatever happened to fit. | ||
KNOWN LIMIT, pre-dating the scroll fade. TabberNeue pins | |||
`section.style.height` to the active panel's max-content height on every | |||
activation, init included, so a day whose panel is taller than the card | |||
the plate | grows the card instead of scrolling: measured 404px and a 1.77 plate on a | ||
short panel against 1206px and 0.59 on a 1082px one. The patch card's | |||
`block-size: 0` cannot fix it, because an inline height beats a stylesheet; | |||
only max-block-size clamps it, and a single max tall enough for the full | |||
measure is too tall at 900 and takes the plate off 16:9 there instead. It | |||
needs a per-stage max or the inline height cleared, and has neither. | |||
Below 900 the section FLOOR sets the card at 284px, which happens on a day | |||
with one event exactly as on a day with twenty. | |||
The floor is what carries the card in a context with no plate beside it to | The floor is what carries the card in a context with no plate beside it to | ||
| 357. sor: | 464. sor: | ||
rejects scrollbar-width and scrollbar-color — so it is the platform's. */ | rejects scrollbar-width and scrollbar-color — so it is the platform's. */ | ||
.home-otd .tabber { | .home-otd .tabber { | ||
display: flex; | display: flex; | ||
flex-direction: column; | flex-direction: column; | ||
| 374. sor: | 479. sor: | ||
min-block-size: calc( var( --line-height-x-small ) * 8 ); | min-block-size: calc( var( --line-height-x-small ) * 8 ); | ||
overflow-y: auto; | overflow-y: auto; | ||
} | } | ||
A lap jelenlegi, 2026. szeptember 1., 20:40-kori változata
/* Main page — the cards.
---------------------------------------------------------------------------
The cards for the two bands below the game row. The twelve-column grid they
sit on, the span classes and the band gap all live in
Module:Mainpage/styles.css, so every band on the page shares one grid; this
file is only the components.
Band 1, what is worth reading: [ featured 8 ][ on this day 4 ]
Band 2, how to take part: [ your wiki 8, spanning 2 rows ]
[ support 4 ]
[ discussion 4 ]
Band 3, the directory: [ six groups of text links ]
The measure stays at 1080. The featured plate states its own 16:9 rather
than taking whatever shape the column leaves over, so a wider band does not
improve its crop and only grows its height. Nothing clips at eight columns
either, so there is no case for widening past --width-layout.
Sizes and spacing come from Citizen's tokens throughout. The literals that
remain are display figures above the top of the type scale, one caption below
its bottom, and optical nudges — each commented where it sits.
NEVER put `t-card` on the same element as a class that sets `display`.
Module:CardLua declares `.t-card { display: flex; flex-direction: column }`
at single-class specificity, so which wins is decided by stylesheet source
order, which this page does not control. Placement properties like
grid-column are safe on the card; arrangement goes on a nested element. */
.home-pad {
display: flex;
flex-direction: column;
flex-grow: 1;
min-width: 0;
padding: var( --space-md );
}
.home-kicker {
font-family: var( --font-family-monospace );
font-size: var( --font-size-x-small );
line-height: var( --line-height-x-small );
font-weight: var( --font-weight-medium );
letter-spacing: 0.12em;
text-transform: uppercase;
color: var( --color-subtle );
margin-bottom: var( --space-xs );
}
.home-title {
font-size: var( --font-size-medium );
line-height: var( --line-height-medium );
font-weight: var( --font-weight-medium );
color: var( --color-emphasized );
}
.home-body {
margin-top: var( --space-xxs );
font-size: var( --font-size-small );
line-height: var( --line-height-small );
color: var( --color-subtle );
}
.home-more {
margin-top: auto;
padding-top: var( --space-xs );
font-size: var( --font-size-small );
line-height: var( --line-height-small );
font-weight: var( --font-weight-medium );
}
/* A kicker that is not the first thing in its card, so it has no padding above
it to sit under. Used for the Follow row on the discussion card. */
.home-kicker--inset {
margin-top: var( --space-md );
}
/* ---- the scroll fade ------------------------------------------------------
Two cards hold an unbounded list, and a list cut off with no cue reads as a
bug rather than as more. Both get the same pair of gradients; the mainpage
gadget decides when each is true.
The default is INVISIBLE, not visible. CSS cannot see how far a box is
scrolled, so a fade it paints unconditionally covers the last line when the
content fits exactly and still promises more at the end of a long list.
Without JS there is no fade and the scrollbar is the cue, which is honest.
--home-fade-top / --home-fade-bottom are published by the gadget: where the
scroller sits inside the frame, so one rule serves cards whose lists start
at different heights. The inline inset MATCHES .home-pad's padding, because
an absolutely positioned child resolves against the padding box. */
.home-scrollfade {
position: relative;
}
.home-scrollfade::before,
.home-scrollfade::after {
content: "";
position: absolute;
inset-inline: var( --space-md );
block-size: var( --space-xl );
/* The rows underneath stay clickable; the fade is a picture, not a lid. */
pointer-events: none;
opacity: 0;
transition: opacity 0.15s ease;
}
.home-scrollfade::before {
inset-block-start: var( --home-fade-top, 0px );
background-image: linear-gradient( to bottom, var( --color-surface-0 ), transparent );
}
.home-scrollfade::after {
inset-block-end: var( --home-fade-bottom, 0px );
background-image: linear-gradient( to top, var( --color-surface-0 ), transparent );
}
.home-scrollfade--up::before {
opacity: 1;
}
.home-scrollfade--down::after {
opacity: 1;
}
@media ( prefers-reduced-motion: reduce ) {
.home-scrollfade::before,
.home-scrollfade::after {
transition: none;
}
}
/* ---- the event card, banner design ----------------------------------------
Module:Mainpage/Event. Every file in Category:Main page banner images is
1080x83, so the band holds that height at every card width and crops
sideways rather than scaling down with the column: at 715px of card it shows
about two thirds of the strip, at 288px about a quarter, and the ships stay
the size they were drawn either way. Scaling instead would put 22px of
picture in the narrowest column.
The height is stated in px, not a token, because it is not a spacing
decision — it is the pixel height of the source files, and it has to change
only if those change.
CardLua's banner layout states 21 / 7 for the ordinary case of a landscape
photograph across the top. Cancelled rather than overridden: both dimensions
are known here, so a ratio has nothing left to supply.
The clock under it is NOT styled from here. The card asks Module:Countdown
for `t-countdown--flat` and the clock arranges itself; a copy of those
declarations lived here once and went stale the first time the clock changed. */
.home-event-banner .t-card__media {
block-size: 83px;
aspect-ratio: auto;
}
/* ---- the event card, split design ------------------------------------------
Module:Mainpage/Event/Legacy. A wider picture column than Module:CardLua's
own 13rem default, which is the right general value but leaves this card's
photograph in a slot narrower than it is tall: 208x248 at the full measure,
208x270 at 900. A landscape screenshot cropped to 0.77 keeps well under half
its width. At 16rem the slot is 256x248 and 256x270, near enough square, and
the text column still sets three comfortable lines at its narrowest (186px
of prose at a 900px viewport, measured).
Scoped rather than changed in CardLua: 13rem is that module's considered
default for a card of unknown content, and the event card is only the first
consumer to have an opinion.
Keyed to 900 because that is where the side-by-side layout exists at all —
CardLua stacks the split card below 639 and Module:Mainpage/styles.css
stacks it again for the 6/6 stage in between, so this range is disjoint from
both and neither has to know about it. */
@media ( min-width: 900px ) {
.home-event-split .t-card__media-layout--split > .t-card__media {
inline-size: 16rem;
}
}
/* ---- this patch -----------------------------------------------------------
Kicker, a scrolling list, a foot link — the same three parts as the day card.
`block-size: 0` is what bounds this card, and overflow-y alone would not: a
grid row sizes to its tallest item's CONTENT, so an uncapped list took the
card to 634px and the event card with it. A definite zero base keeps the
list out of that contribution, so the row is the event card's and flex-grow
hands the list whatever is left — at every width, with no number to keep in
step with the card next door.
The floor is for the single column below 640, where there is no sibling to
set the row at all. */
.home-patch__list {
block-size: 0;
flex-grow: 1;
min-block-size: calc( var( --line-height-small ) * 6 );
overflow-y: auto;
font-size: var( --font-size-small );
line-height: var( --line-height-small );
color: var( --color-subtle );
}
.home-patch__list ul {
margin: 0;
padding-inline-start: var( --space-md );
}
/* ---- featured -------------------------------------------------------------
The plate states 16:9 rather than inheriting whatever shape the row leaves,
because object-fit: cover cuts whichever axis overflows: a box flatter than
the source loses its top and bottom, a taller box loses its sides. Featured
Page Images run about 1.78:1, so the box matches them and crops neither.
COUPLING: this only holds while nothing else in the row is taller. The plate
grows with the card, and a grid row stretched by a tall neighbour overrides
aspect-ratio — which is why the day card beside it fixes its own height and
scrolls, rather than growing with however many events the day has. */
.home-feat {
position: relative;
/* Creates a stacking context, which is what lets the art and scrim below
sit at z-index -1 and still paint above the card's own ground. */
z-index: 0;
display: flex;
flex-direction: column;
flex-grow: 1;
aspect-ratio: 16 / 9;
}
/* Behind the in-flow body rather than above it. */
.home-feat__media,
.home-feat__scrim {
z-index: -1;
}
.home-feat__media {
position: absolute;
inset: 0;
}
/* MediaWiki wraps a file in spans and a <picture>, all inline by default, so
the chain has to be opened up before object-fit can reach the image. Listed
one by one because the sanitizer rejects :is() and :where(). */
.home-feat__media span,
.home-feat__media a,
.home-feat__media picture {
display: block;
inline-size: 100%;
block-size: 100%;
}
.home-feat__media img {
display: block;
inline-size: 100%;
block-size: 100%;
object-fit: cover;
/* Longhand on purpose: the sanitizer takes var() as a whole `transition`
value but not as one component beside a literal property name, which is
the house idiom (see Module:CardLua). */
transition: var( --transition-hover );
transition-property: transform;
}
/* A slow push on the art, so an image card answers the pointer with something
an image card can actually do. The card clips, so the overflow never shows. */
.home-feat:hover .home-feat__media img {
transform: scale( 1.03 );
}
/* Two stops, not one: a hard floor under the text and a light wash over the
rest, so the picture keeps its top half. */
.home-feat__scrim {
position: absolute;
inset: 0;
background: linear-gradient( to top, rgba( 8, 11, 14, 0.92 ) 0%, rgba( 8, 11, 14, 0.55 ) 42%, rgba( 8, 11, 14, 0.12 ) 100% );
}
.home-feat__body {
margin-top: auto;
padding: var( --space-sm );
display: flex;
flex-direction: column;
gap: var( --space-xxs );
min-width: 0;
}
/* The label is Module:BadgeLua, so the card takes the same badge component as
the rest of the wiki rather than a one-off kicker. Only its colours are
overridden: this one sits on artwork in both themes, so it cannot follow the
surface tokens the badge normally takes.
The row wrapper is load-bearing. A flex item is blockified, so the badge's
own display: inline-flex resolves to flex and it would stretch the full
width of the card. Wrapped, the row is the flex item and the badge inside it
stays inline and shrinks to its text. */
.home-feat__badge-row {
display: flex;
}
.home-feat__badge.t-badge {
background-color: rgba( 255, 255, 255, 0.12 );
border-color: rgba( 255, 255, 255, 0.18 );
color: #d7e3ee;
}
/* Title and caption sit on one baseline across the card's width rather than
stacking down its left edge, so the pair costs two rows of the card's height
instead of a column of its width and leaves more of the artwork clear. */
.home-feat__foot {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: var( --space-md );
min-width: 0;
}
/* A display size, above the top of the type scale, so it is a literal.
min-width: 0 because it is a flex item, and a flex item refuses to go below
its content's intrinsic width without it — the row would overflow instead of
the title wrapping. */
.home-feat__title {
min-width: 0;
font-size: 2.5rem;
line-height: 1.15;
font-weight: var( --font-weight-medium );
letter-spacing: -0.02em;
color: #f4f8fb;
}
/* THE WHOLE CARD IS THE LINK, and it is its own anchor rather than a stretch
pseudo-element on the title. That is not a preference, it is what lets the
caption move.
A transform makes an element the containing block for its absolutely
positioned descendants. The body below carries one on hover, so a stretch
pseudo-element hung off the title would resolve against the body rather than
the card, and the click target would shrink to whatever the body covers.
As a sibling of the body the anchor is unaffected: the body can transform
freely and the click target stays the whole card.
The two objections to an overlay anchor are both answered — it carries a
real accessible name in the span below, and the visible title is not itself a
link, so it adds no second stop to the same destination. */
.home-feat__link a {
position: absolute;
inset: 0;
z-index: 1;
}
/* Same clip-path pattern as .home-hero__sr in Module:Mainpage/styles.css,
repeated rather than shared because that one belongs to the hero and this one
to the card. A single .home-sr for both is worth doing if a third ever wants
it. */
.home-feat__sr {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset( 50% );
white-space: nowrap;
}
/* The card carries the hover the way CardLua's linked cards do. No
:focus-within here — the sanitizer rejects it — but the overlay anchor is a
real element and keeps the skin's focus ring. */
.home-feat-card {
transition: var( --transition-hover );
transition-property: border-color;
}
.home-feat-card:hover {
border-color: var( --border-color-interactive--hover );
}
/* The tagline, not a summary: the settings page tells the next editor to put
"something short and fun" there, and that voice is worth keeping.
It sits to the right of the title on the same baseline, and it is the half
allowed to shrink — the title is the thing that has to stay whole. */
.home-feat__tag {
flex: 0 1 auto;
min-width: 0;
text-align: right;
font-size: var( --font-size-small );
line-height: var( --line-height-small );
font-style: italic;
color: #cbd7e2;
}
/* Hovering clears the overlay off the artwork: scrim and caption both slide
down out of the card and fade as they go. The card clips, so neither is ever
visible below it, and the anchor above stays put so the card is still one
click target the whole way through. */
.home-feat__scrim,
.home-feat__body {
transition: var( --transition-hover );
transition-property: transform, opacity;
}
.home-feat:hover .home-feat__scrim,
.home-feat:hover .home-feat__body {
transform: translateY( 100% );
opacity: 0;
}
/* The reveal survives; the movement does not. A fade is not the part that
causes trouble, so reduced motion keeps it and drops the slide rather than
losing the behaviour altogether.
Hover only, deliberately. Keyboard focus does not trigger this: the
sanitizer rejects :focus-within, and it is the better outcome anyway — a
keyboard user tabbing onto the card keeps the title in view rather than
watching the label they are aiming at disappear. */
@media ( prefers-reduced-motion: reduce ) {
.home-feat:hover .home-feat__scrim,
.home-feat:hover .home-feat__body {
transform: none;
}
}
/* ---- on this day ----------------------------------------------------------
Module:Transcluder hands over the day page's own `class="timeline"` tables,
so the rules below restyle a wikitable rather than build a list.
The card shows BOTH of the day page's tables: real-life events (table 1) and
in-lore events (table 2), as the two panels of a TabberNeue tabber rather
than stacked under a heading each, which does not fit the column this card
gets. The tabs also do the labelling the two sources need:
their years are not distinguishable on sight, since a lore row reads 2773
where a real-life row reads 1991, and only someone who knows the in-universe
calendar could tell which is which.
The two differ in markup as well: real-life cells arrive wrapped in
<ul><li>, lore cells are bare text, which is why the list reset below exists
at all. When a day has no lore yet the page supplies its own row reading
"No info yet" plus an edit invitation, so the empty state is the day page's
to own rather than something to detect here. */
/* The tabber fills the card and the panel scrolls. How many events a day has
is not something this card gets to decide — some days carry two, some a
dozen — so the height is fixed and the overflow is handled, rather than the
content being silently truncated to whatever happened to fit.
KNOWN LIMIT, pre-dating the scroll fade. TabberNeue pins
`section.style.height` to the active panel's max-content height on every
activation, init included, so a day whose panel is taller than the card
grows the card instead of scrolling: measured 404px and a 1.77 plate on a
short panel against 1206px and 0.59 on a 1082px one. The patch card's
`block-size: 0` cannot fix it, because an inline height beats a stylesheet;
only max-block-size clamps it, and a single max tall enough for the full
measure is too tall at 900 and takes the plate off 16:9 there instead. It
needs a per-stage max or the inline height cleared, and has neither.
Below 900 the section FLOOR sets the card at 284px, which happens on a day
with one event exactly as on a day with twenty.
The floor is what carries the card in a context with no plate beside it to
set the height. It is derived from the row leading rather than picked, so it
tracks the type scale. The scrollbar cannot be styled — TemplateStyles
rejects scrollbar-width and scrollbar-color — so it is the platform's. */
.home-otd .tabber {
display: flex;
flex-direction: column;
flex-grow: 1;
min-block-size: 0;
/* Pulls the strip back under the kicker. The tab buttons carry 7px of top
padding of their own, so the kicker's 8px bottom margin would otherwise
read as 15px of separation; this trims it to a gap in line with the
kicker-to-content spacing in the cards beside it. */
margin-block-start: calc( var( --space-xxs ) * -1 );
}
.home-otd .tabber__section {
flex-grow: 1;
min-block-size: calc( var( --line-height-x-small ) * 8 );
overflow-y: auto;
}
/* The minimal tabber treatment from Module:InfoboxLua, which solves the same
problem: TabberNeue ships page-level tab chrome, and dropped into a card it
outweighs what it is navigating.
The move worth copying is the padding/margin swap. A tab's inline PADDING
makes its box wider than its label, and TabberNeue's indicator tracks the
box — so the underline runs wider than the word and the first label sits
indented from everything else in the card. Trading the padding for an equal
margin makes box and label the same thing, and the negative margin on the
header pulls the strip back to the card's own left edge, so the first tab
lines up with the kicker above it and the year column below.
Exact tokens rather than InfoboxLua's ems: the type here is a step smaller
than TabberNeue's default, and an em would then resolve differently on the
header than on the tabs and leave the two out of register. */
.home-otd .tabber__header {
margin-inline: calc( var( --space-sm ) * -1 );
/* Tabber's bottom rule, dropped to reduce clutter — the rows below already
carry their own dividers. */
box-shadow: none;
}
.home-otd .tabber__tab {
margin-inline: var( --space-sm );
padding-inline: 0;
font-size: var( --font-size-small );
line-height: var( --line-height-small );
font-weight: var( --font-weight-normal );
color: var( --color-subtle );
}
/* State by colour ALONE, plus the indicator. Never by weight: a heavier glyph
is a wider glyph, so a bolder active tab grows on click and shoves the tab
after it — and the indicator with it — every time someone switches. Every tab
keeps one weight so the strip cannot move.
Deliberately not TabberNeue's progressive blue either: the entries below are
full of blue links, so a blue tab label joins that noise instead of standing
out from it. The indicator underline still carries the blue, which is where
it reads as state rather than as a link. */
.home-otd .tabber__tab[aria-selected="true"] {
color: var( --color-emphasized );
}
.home-otd table {
inline-size: 100%;
border-collapse: collapse;
margin: 0;
background: none;
}
.home-otd th,
.home-otd td {
border: 0;
padding: 0;
background: none;
vertical-align: top;
}
.home-otd tr {
display: flex;
gap: var( --space-sm );
padding-block: var( --space-xs );
font-size: var( --font-size-small );
line-height: var( --line-height-x-small );
}
.home-otd tr + tr {
border-block-start: var( --border-subtle );
}
/* The year. A floor rather than a fixed measure: four digits is what both
calendars use, real-life and in-lore alike, so 4ch keeps the second column's
left edge straight down the card — but the empty-lore row puts the words
"No info yet" in this cell, which a fixed 4ch would break across three lines.
nowrap plus a minimum lets that one row take the width it needs while every
real year still lands on the same line. */
.home-otd td:first-child {
flex: none;
min-inline-size: 4ch;
white-space: nowrap;
/* Size and leading are INHERITED from the row on purpose, not restated. A
year set a step smaller than the entry beside it sits in the same line
box with more half-leading, so the two baselines never quite meet;
sharing the row's metrics is what puts them on one line. Inheriting
rather than repeating the token also means the two columns cannot drift
apart if the row's size is ever changed. */
font-family: var( --font-family-monospace );
font-weight: var( --font-weight-medium );
font-variant-numeric: tabular-nums;
color: var( --color-subtle );
}
/* No clamp on the entry. A two-line window cut mid-word — "releases a digital
virus which" — which reads as broken rather than as truncated, and there is
no honest ellipsis available: TemplateStyles rejects `display: -webkit-box`,
so line-clamp's usual form is out. The panel scrolls, so an entry simply runs
to its natural length. */
.home-otd td:last-child {
min-width: 0;
}
/* The foot carries two things on this card: where to read the rest, and where
to add to it. space-between rather than an auto margin on the second, and
flex-wrap so the pair stacks instead of overflowing if either label grows —
insurance, not a live case: the two come to 212px against the 254px the
narrowest card gives them, so today they always share the line. */
.home-otd .home-more {
display: flex;
flex-wrap: wrap;
align-items: baseline;
justify-content: space-between;
gap: var( --space-sm );
}
/* Transcluder brings the day page's bullets with it; inside a two-line window
a marker and its indent are pure noise. */
.home-otd ul,
.home-otd li {
list-style: none;
margin: 0;
padding: 0;
}
/* ---- your wiki ------------------------------------------------------------
The recent-changes list reads as evidence rather than as navigation: the
claim, the proof, and the invitation in one card. It carries usernames, for
editor recognition, which is what earns the card the wide column rather than
a narrow aside. */
/* The header is the pitch and the button, and nothing else: the count belongs
beside the list it counts, not above the sentence. Two zones rather than
three also leaves the sentence enough of the card to stay on one line at the
full measure.
NO RULE UNDERNEATH. `border-block-end: var( --border-subtle )` here would be
the identical 1px drawn between every row of the list below, at the same
width and with no gap to the first row, so the header would read as row zero
rather than as a header. Spacing carries it instead, as it does under the
directory's group labels. The list keeps its own dividers, because there the
rules ARE the structure: they separate like from like, and a header is not
like a row. */
.home-part__head {
display: flex;
align-items: center;
gap: var( --space-md );
padding-bottom: var( --space-md );
}
.home-part__text {
flex: 1 1 auto;
min-width: 0;
}
/* display: flex so the wrapper takes the button's real height. ButtonLua's
root is an inline span, so an unopened wrapper is only as tall as its line
box, and align-self would then centre that box rather than the button inside
it. Same shape as the .home-btns wrappers below, same fix.
align-self, not the parent's align-items: a child's own align-self always
wins, and Citizen's button styles set their own. */
.home-part__btn {
flex: none;
display: flex;
align-self: center;
}
/* The count lives beside the list it is a count of, rather than in the header.
The label pays for itself twice: it says what the ten page names below are,
which nothing else on the card does.
flex-wrap rather than a breakpoint. The two halves come to 121 + 144px and
the card bottoms out around 303 on a 360px phone, so they still clear each
other by about 38px — five characters of the mono face, which reads as a gap
and not as one string. A stacking rule keyed to a width would have cost 30px
of card height at every narrow size for a collision that does not happen;
wrap costs nothing until it is genuinely needed, and then handles it. */
.home-act__cap {
display: flex;
flex-wrap: wrap;
align-items: baseline;
justify-content: space-between;
gap: var( --space-sm );
padding-bottom: var( --space-xs );
}
.home-act__caplabel {
font-family: var( --font-family-monospace );
font-size: var( --font-size-x-small );
line-height: var( --line-height-x-small );
font-weight: var( --font-weight-medium );
letter-spacing: 0.12em;
text-transform: uppercase;
color: var( --color-subtle );
}
.home-act__stat {
flex: none;
font-family: var( --font-family-monospace );
font-size: var( --font-size-x-small );
line-height: var( --line-height-x-small );
color: var( --color-subtle );
font-variant-numeric: tabular-nums;
}
.home-act__stat b {
font-weight: var( --font-weight-medium );
color: var( --color-emphasized );
}
/* DPL emits one of these per change: page, who, when. Three things a reader
scans left to right without stopping. */
.home-act__row {
display: flex;
align-items: baseline;
gap: var( --space-xs );
padding-block: var( --space-xxs );
font-size: var( --font-size-small );
line-height: var( --line-height-x-small );
min-width: 0;
}
.home-act__row + .home-act__row {
border-block-start: var( --border-subtle );
}
.home-act__row a {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.home-act__by {
flex: none;
font-size: var( --font-size-x-small );
color: var( --color-subtle );
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-inline-size: 14ch;
}
.home-act__when {
margin-inline-start: auto;
flex: none;
font-family: var( --font-family-monospace );
font-size: var( --font-size-x-small );
color: var( --color-subtle );
}
/* Two ranges, because the card's width is not monotonic in the viewport's. The
header stacks when the CARD drops under about 360px, which is where the
sentence beside a 120px button goes past four lines: a 358px card gives four,
a 318px card six, a 298px card seven. That happens in two places: on a phone,
where the column is the viewport (400px and under), and again in the 6/6
stage, where a 640–700px viewport gives a 288–318px card, NARROWER than the
phone ever gets.
The gap between them is deliberate. From 401 to 639 the card is 369–607px,
and a full-width button there cannot fill it: .cdx-button carries Codex's own
448px max-width (the Discord and Patreon buttons below hit the same ceiling),
so stacking would leave dead space to the button's right and cost height for
nothing. */
@media ( max-width: 400px ), ( min-width: 640px ) and ( max-width: 700px ) {
.home-part__head {
display: block;
}
/* The CHILD combinator again: ButtonLua puts t-button on the wrapper AND
on the inner .cdx-button, so a descendant selector would blockify the
Codex button too and drop the flex that spaces its icon from its label. */
.home-part__btn {
display: block;
margin-top: var( --space-sm );
}
.home-part__btn > .t-button {
display: block;
min-width: 0;
}
.home-part__btn .cdx-button {
width: 100%;
}
}
/* ---- support and discussion -----------------------------------------------
Two cards rather than one with a rule through it: a hairline between
sections is a card boundary you are refusing to draw, and split, both halves
size to their own content instead of one stretching to fill.
Three tiers of link, and the tiers are the argument. Patreon and Ko-fi share
a row because they are one favour offered two ways, so neither gets to be
primary. Discord takes a full-width button of its own because it is the
channel people join rather than follow. The rest are follows, so they are
icon-only quiet buttons, which also avoids X and Threads arriving as two
indistinguishable brand-black squares in a dark theme.
The pair sharing a row is what sets this card's floor at about 260px: below
that the Patreon label overflows its own button. That measurement is what
the 6/6 stage in Module:Mainpage/styles.css exists to respect. */
.home-give,
.home-talk {
display: flex;
flex-direction: column;
flex-grow: 1;
justify-content: center;
align-items: flex-start;
padding: var( --space-md );
min-width: 0;
}
/* Layout only. The buttons themselves are Module:ButtonLua, which brings the
Codex chrome, focus rings, and hover and active grounds derived from one
--t-button-ground by a fixed perceptual step. Patreon, Ko-fi and Discord are
declared there as t-button--branded brands rather than restyled here, so
anything else on the wiki linking those services gets the same button. */
.home-btns {
align-self: stretch;
display: grid;
grid-template-columns: 1fr 1fr;
gap: var( --space-xs );
margin-top: var( --space-sm );
}
/* ButtonLua's root is an inline <span> and the Codex button inside it
shrink-wraps to its label, so a button left alone fills neither its grid cell
nor its card. Opening both up is what makes Patreon and Ko-fi equal halves
and Discord full width. The wrapper div around the Discord button is there
for the same reason: an inline span alone on a line gets swallowed into a
<p>, which then owns the block box instead of the button.
The CHILD combinator is load-bearing. ButtonLua puts `t-button` on both the
outer wrapper AND the inner .cdx-button, so a descendant selector here sets
`display: block` on the Codex button too — which drops its flex, and with it
the gap that separates the icon from the label. */
.home-btns > .t-button,
.home-btn--wide > .t-button {
display: block;
min-width: 0;
}
.home-btns .cdx-button,
.home-btn--wide .cdx-button {
width: 100%;
}
.home-btn--wide {
align-self: stretch;
margin-top: var( --space-sm );
}
.home-icons {
display: flex;
flex-wrap: wrap;
gap: var( --space-xs );
margin-top: var( --space-xs );
}
/* ---- the directory --------------------------------------------------------
One grouped text directory at the foot, carrying everything that would
otherwise be a pile of links at the bottom of the page. Text and not tiles:
image pills at this size cost about two screens on a phone before any
content appears, so anything here that reintroduces artwork undoes the
decision that retired them.
Six groups fill the measure in one row.
One rule across the top of the whole band, where the directory genuinely
does start, rather than one under each of the six labels. */
.home-dir {
display: grid;
/* Intrinsic, not a column count, and so the only band on the page with no
breakpoint of its own. A fixed six columns truncates its own links long
before the page reaches phone width. auto-fit drops a column whenever the
next one would fall under the floor, so the directory reflows on its own —
six groups at the measure, four around 640, two on a phone.
125px is where the longest label, "Pledge vehicles", stops fitting. It is
a literal because the sanitizer rejects var() inside minmax(). */
grid-template-columns: repeat( auto-fit, minmax( 125px, 1fr ) );
gap: var( --space-md );
margin-top: var( --space-xs );
padding-top: var( --space-md );
border-top: var( --border-subtle );
}
.home-dir__group {
min-width: 0;
}
.home-dir__label {
font-family: var( --font-family-monospace );
font-size: var( --font-size-x-small );
line-height: var( --line-height-x-small );
font-weight: var( --font-weight-medium );
letter-spacing: 0.12em;
text-transform: uppercase;
color: var( --color-subtle );
/* Spacing carries the hierarchy, not a rule. Six short lists are not six
sections, so a line under every label would divide them harder than the
content warrants — and the band's own rule above already says where the
directory begins. */
padding-bottom: var( --space-sm );
}
/* The directory is emitted as a real list, which is the right shape for it and
what lets a screen reader skip it in one move; only the marker and its indent
go. */
.home-dir__group ul,
.home-dir__group li {
list-style: none;
margin: 0;
padding: 0;
}
.home-dir__group a {
display: block;
padding-block: 3px;
font-size: var( --font-size-small );
line-height: var( --line-height-small );
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* ---- the narrow plate ------------------------------------------------------
Everything below 900 — the 6/6 stage in Module:Mainpage/styles.css as well as
the single column under it — puts the featured card in the 288–607px range,
which is narrower than it ever gets at 8/4. So the narrow treatment is keyed
to 900, not to the single-column breakpoint: a 2.5rem title on a 288px card
wraps, and 16:9 alone would leave the plate 162px tall.
The floor and the ratio do not fight: `min-width: 0` on the grid item
(Module:Mainpage/styles.css) stops the two of them implying a minimum WIDTH,
so the ratio yields to the column once the card is under 391px wide and the
plate goes as tall as the floor says.
Below 639 the grid order also becomes the reading order, and the bands
collapse in DOM order: featured, on this day, your wiki, support,
discussion. Editorial content ahead of the asks, which is where asks
belong. */
@media ( max-width: 899.98px ) {
.home-feat {
min-block-size: 220px;
}
.home-feat__title {
font-size: 1.875rem;
}
}
/* ---- the foot -------------------------------------------------------------
Where to go to change what the page says. Every other link on the page is
for a reader; these two are for whoever maintains it, so they are set apart
from the directory above by spacing rather than by a rule — the same way the
directory's own group labels are — and in the mono face, which on this page
marks instrumentation rather than content.
The band carries the page's normal bottom padding, so nothing extra is
needed under it. */
.home-foot {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: var( --space-md );
padding-top: var( --space-lg );
font-family: var( --font-family-monospace );
font-size: var( --font-size-x-small );
line-height: var( --line-height-x-small );
color: var( --color-subtle );
}