Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 132 additions & 27 deletions assets/scss/common/_custom.scss
Original file line number Diff line number Diff line change
Expand Up @@ -723,14 +723,19 @@ $map-pulse-offsets:
top: 50%;
left: 100%;
transform: translateY(-50%);
// 13rem where the map has room for it, and no wider than the room the mark actually
// has where it does not. `--map-room` is set per mark by the generated stylesheet:
// Between 10rem and 13rem, sized to its longest line — the place name or a handle —
// and never wider than the room the mark actually has. Every card used to be 13rem
// flat, which left "cr7258" with 100px of empty card beside it: mostly background, and
// that is what made these read as oversized. `max-content` costs nothing here — the
// card is absolutely positioned, so it has no siblings to push around.
//
// `--map-room` is set per mark by the generated stylesheet:
// the percentage of the map's width between this dot and the edge the card opens
// towards, and `1cqw` is one percent of that width (see `.worldmap-hits`). The
// 0.25rem is slack, so the widest cards stop just short of the map's edge rather
// than exactly on it.
//
// The fixed width is declared first and deliberately kept: it is what a browser
// The flat 13rem cap is declared first and deliberately kept: it is what a browser
// without container query units falls back to, which is where every card was before
// this line. Only narrow screens are affected either way — at 1440 a card in the
// middle of the map has some 460px of room, so the `min()` picks 13rem and nothing
Expand All @@ -740,18 +745,46 @@ $map-pulse-offsets:
// Interpolated because Sass reads a bare `min()` as its own function and rejects a
// `calc()` of units it cannot resolve; wrapped like this it is passed through as the
// CSS it is.
width: 13rem;
width: #{"min(13rem, calc(var(--map-room, 100) * 1cqw - 0.25rem))"};
padding: 0.625rem 0.75rem;
width: max-content;
// A floor as well as a cap, or shrink-to-fit goes too far the other way: "cr7258" made
// a 95px card, and a panel that narrow reads as a tooltip clipped rather than as a list
// of people. 10rem is the width of the longest thing any card holds today — "Amsterdam,
// Netherlands" — so in practice the cards are one width, and the content sizing is what
// keeps a longer handle than that from being truncated rather than what varies them.
//
// Capped by the room the mark has, exactly as the maximum is: `min-width` beats
// `max-width` in the cascade, so a bare 10rem floor would have overridden the cap below
// and put the narrow-screen overflow straight back.
min-width: 10rem;
min-width: #{"min(10rem, calc(var(--map-room, 100) * 1cqw - 0.25rem))"};
max-width: 13rem;
max-width: #{"min(13rem, calc(var(--map-room, 100) * 1cqw - 0.25rem))"};
padding: 0.625rem 0.6875rem;
text-align: left;
// Opaque: it sits on the map, and a tint would leave coastlines running through
// the faces.
background: var(--bs-body-bg, #fff);
border: 1px solid $map-land;
border-radius: 0.5rem;
box-shadow: 0 0.5rem 1.5rem rgba(0, 0, 0, 0.08);
// Rounded to match the project cards below the map rather than to a value of its
// own: 0.5rem here against their 0.75rem read as a different family of box on the
// same page.
border-radius: 0.75rem;
// Two shadows rather than one. The wide soft one lifts the card off the land; the
// tight dark one under it is what gives it an edge to sit on, which a single 24px
// blur cannot do at any alpha — turned up it greys the map, turned down the card
// floats without landing.
box-shadow:
0 0.75rem 2rem rgba(0, 0, 0, 0.09),
0 0.125rem 0.375rem rgba(0, 0, 0, 0.04);
// Text sized against the page, not the map: this is html, so unlike the city
// labels in the svg it does not shrink with the viewBox.
//
// 13px rather than the 14px this was. A card is 187px wide at its widest and holds ten
// rows at its fullest, and 14px in it was body copy in a panel the size of a tooltip:
// the handles ran to the edge and the card read as a page fragment dropped on the map.
// A step down is what makes it a label — and it buys the rows the breathing space
// below, at no cost in legibility for a handle a reader is scanning rather than
// reading.
font-size: 0.8125rem;
line-height: 1.4;
opacity: 0;
Expand All @@ -770,12 +803,48 @@ $map-pulse-offsets:
// it, the neighbour needs one more move of the mouse to answer. Only ever a
// neighbour of the city you are already reading, and only while you are reading it.
pointer-events: auto;

// A caret pointing back at the dot, on the card's own vertical centre — which is the
// dot's centre, because that is what the card is positioned from. It is the one thing
// that says which mark a card belongs to, and it earns its place on the cluster
// around the Pearl River Delta, where a card can float over three other dots and
// otherwise names its city without pointing at it.
//
// A rotated square with two borders, the classic recipe: the corner made by
// `border-left` and `border-bottom` faces left once the square is turned 45°. Half of
// it hangs outside the card, and its own background covers the length of the card's
// border it crosses.
&::before {
content: "";
position: absolute;
top: 50%;
left: -0.3125rem;
width: 0.5625rem;
height: 0.5625rem;
background: var(--bs-body-bg, #fff);
border-left: 1px solid $map-land;
border-bottom: 1px solid $map-land;
border-bottom-left-radius: 0.0625rem;
transform: translateY(-50%) rotate(45deg);
}
}

// East of the middle, the card opens the other way — see the template.
// East of the middle, the card opens the other way — see the template. The caret turns
// with it: the other two borders make the corner that faces right.
.worldmap-hit-left .worldmap-city {
left: auto;
right: 100%;

&::before {
left: auto;
right: -0.3125rem;
border-left: 0;
border-bottom: 0;
border-top: 1px solid $map-land;
border-right: 1px solid $map-land;
border-bottom-left-radius: 0;
border-top-right-radius: 0.0625rem;
}
}

.worldmap-hit:hover .worldmap-city,
Expand All @@ -787,60 +856,94 @@ $map-pulse-offsets:
visibility 0s;
}

// The place, on one line and in one ink, with a hairline under it — the same hairline the
// legend rules its rows with, so the card and the list beside the map are ruled by the
// same line.
//
// The city and the country were two tones here for a while, the country in the lighter
// ink. It read wrong: the comma belongs to neither half, so it took the city's weight and
// the line broke as "Shanghai," + "China" rather than as one place name. A place name is
// one thing, so it gets one format.
//
// The same weight as the handles under it, not the semibold this was: the rule is what
// separates the head from the list, and with a line already doing that the bold was a
// second, louder way of saying it — on a 144px card that made the place name the loudest
// thing in a panel that exists to show the people. Its position and the rule are enough.
.worldmap-city-head {
margin: 0 0 0.5rem;
font-weight: 600;
line-height: 1.35;
margin: 0 0 0.4375rem;
padding-bottom: 0.4375rem;
border-bottom: 1px solid $map-land;
line-height: 1.3;
// A hair of tracking on the one line that is a name rather than a handle, and the only
// thing here that is read as words.
letter-spacing: 0.01em;
}

.worldmap-city-people {
margin: 0;
padding-left: 0;
list-style: none;

li {
padding: 0.125rem 0;
}

// The link is the row, not a word in it: the face and the handle are one target, and
// the flexbox that used to be the `li` lives here so the target is the whole line
// rather than 13px of text.
// rather than 14px of text.
a {
display: flex;
align-items: center;
gap: 0.5rem;
// Out into the card's own padding, so the highlight below is a band across the card
// rather than a pill floating inside it — and back again as padding, so the text
// still starts where the heading's does.
margin: 0 -0.375rem;
// 4px above and below each face rather than 3. A 20px circle with 3px of air read as
// a column of faces touching each other; the pair of changes — smaller face, wider
// gap — is what turns the stack into a list of people.
padding: 0.25rem 0.375rem;
border-radius: 0.5rem;
// Ink, not link colour. Ten orange handles inside a small white card over a map of
// orange dots would have been the loudest thing in the section, and every row here
// is a link — so nothing is distinguished by colouring them all. What marks them is
// the underline on hover and the cursor, which is what a row this size can carry.
// the row lighting up under the pointer, which is what a row this size can carry.
color: inherit;
text-decoration: none;
transition: background-color 0.12s ease;

// The mark's own colour at a tenth: the row a pointer is on belongs to the map's
// palette, and a grey band inside an orange-dotted card read as a disabled row. Kept
// to a tint rather than the 22% the halo uses, because the handle has to stay
// readable on top of it.
&:hover,
&:focus-visible {
color: inherit;
text-decoration: underline;
background: rgba($map-mark, 0.1);
}

// Keyboard reaches these: the card opens on `:focus-within`, so tabbing into it is
// how it is read without a pointer, and each row has to say which one it is on.
// how it is read without a pointer, and each row has to say which one it is on. The
// tint alone would not — it is the same one the pointer leaves behind.
&:focus-visible {
outline: 2px solid $map-mark;
outline-offset: 2px;
border-radius: 0.25rem;
outline-offset: -1px;
}
}
}

// 20px, not the 24px this started at. Ten of them stacked at 24px was a column of faces
// with the handles as captions; at 20 the face and its handle are the same size of thing,
// which is what a row of a list is. The image is still requested at `s=64`, so it is a
// 20px circle drawn from three times the pixels on any screen.
.worldmap-avatar {
flex: 0 0 auto;
width: 24px;
height: 24px;
width: 20px;
height: 20px;
border-radius: 50%;
// Hotlinked from avatars.githubusercontent.com, which the site's own CSP allows
// (`img-src`). A missing or slow avatar leaves a grey circle rather than a gap
// that moves the handle beside it.
background: $map-land;
// A hairline ring, so a pale or white-cornered avatar still reads as a circle against
// the card. `box-shadow` rather than `border`, which would eat a pixel of the face.
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.06);
}

.worldmap-city-login {
Expand All @@ -859,13 +962,15 @@ $map-pulse-offsets:
display: flex;
align-items: center;
gap: 0.5rem;
margin: 0.125rem 0 0;
padding: 0.125rem 0;
// The same vertical rhythm as the rows above, so the ellipsis lands on the beat the
// faces are on.
margin: 0;
padding: 0.25rem 0;
font-size: 0.75rem;
color: $map-text;
}

// Standing in for the avatar that would have been here: same 24px circle, the map's own
// Standing in for the avatar that would have been here: same 20px circle, the map's own
// land as its fill, so the column of faces continues to the end of the card.
.worldmap-city-more-icon {
flex: 0 0 auto;
Expand Down
21 changes: 17 additions & 4 deletions layouts/_default/home.html
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,20 @@ <h2 class="feature-grid-title">Contributors</h2>
{{/* The place, and only the place. The count was here — "Shanghai,
China — 6 contributors" — and it said twice what the card
already shows: the faces below are the count, and the dot's
size is what carries it on the map. */}}
size is what carries it on the map.

`.label` whole, in one format on one line. It was split on the
comma for a while so the country could carry the lighter ink,
and that put the comma on the city's side of the split, in the
city's weight: the line read "Shanghai," and then "China"
instead of reading "Shanghai, China".

`.label` rather than `.short` plus `.country`, which are also
here: `.label` is the one that carries the abbreviations the
map is drawn with — "USA" and "UK" rather than "United States"
and "United Kingdom" — and those are what fit on a line 13rem
wide. A country-level mark like India is a label with no comma
in it, and needs no case of its own. */}}
<p class="worldmap-city-head">{{ .label }}</p>
<ul class="worldmap-city-people">
{{ range .people -}}
Expand All @@ -181,7 +194,7 @@ <h2 class="feature-grid-title">Contributors</h2>
same fact to keep in step.

The whole row is the link — avatar and handle both —
so the target is the 24px face and its name rather
so the target is the 20px face and its name rather
than a line of 13px text. Same tab, like every other
link on this page. */}}
<a href="https://github.com/{{ .login }}">
Expand All @@ -192,7 +205,7 @@ <h2 class="feature-grid-title">Contributors</h2>
wraps. `lazy` and the `s=64` in the url are what
keep 34 faces off the critical path — see
tools/contributors.mjs. */}}
<img class="worldmap-avatar" src="{{ .avatar }}" alt="" width="24" height="24"
<img class="worldmap-avatar" src="{{ .avatar }}" alt="" width="20" height="20"
loading="lazy" decoding="async">
<span class="worldmap-city-login">{{ .login }}</span>
</a>
Expand All @@ -212,7 +225,7 @@ <h2 class="feature-grid-title">Contributors</h2>
thing this row knows. */}}
{{ with .more }}
<p class="worldmap-city-more">
<svg class="worldmap-city-more-icon" width="24" height="24" viewBox="0 0 24 24"
<svg class="worldmap-city-more-icon" width="20" height="20" viewBox="0 0 24 24"
fill="currentColor" stroke="none" aria-hidden="true">
<circle cx="6" cy="12" r="1.75" />
<circle cx="12" cy="12" r="1.75" />
Expand Down
Loading