mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 14:16:28 +03:00
The default lives in each surface's fallback, not in the markup: show() checks whichever row the fallback names every time a tab opens, so the checked attribute on the option never survived. Persuade, Operate, and Read now open on Full palette. Experience keeps Restrained because four working colors are not on its allow list, and a default pointing at a blocked row would leave that tab with nothing selected. The font screen's boards also take the chosen palette under Restrained, which is the one answer no strategy block rewrites. They were drawing a page the palette had never reached while screen 03 kept its color under the same answer. Prepared with AI assistance (Cursor). Co-authored-by: Cursor <cursoragent@cursor.com>
7551 lines
224 KiB
CSS
7551 lines
224 KiB
CSS
/* Vendored from the site's design system (now in the private impeccable-site
|
|
repo) so the picker stays self-contained. Update by re-copying from there. */
|
|
@import "./vendor/kinpaku-tokens.css";
|
|
@import "./vendor/kinpaku-kit.css";
|
|
|
|
/* Ink for surfaces painted in a color the user chose, not in one of ours:
|
|
swatch labels and the wireframe artboards. Every --ks-* text token swaps
|
|
between the light and dark themes, which would invert these against
|
|
palettes that never changed. Values are the system's own ink and raised
|
|
paper, so neither is pure black or pure white. contrastInk() in
|
|
scripts/color.js mirrors their luminance; change both together. */
|
|
:root {
|
|
--pk-ink-dark: oklch(14% 0.018 95);
|
|
--pk-ink-light: oklch(99% 0.008 95);
|
|
}
|
|
|
|
/* Scoped subset of the reset imported by site/styles/main.css through
|
|
site/styles/tokens.css (lines 12-27). */
|
|
.picker-page,
|
|
.picker-page *,
|
|
.picker-page *::before,
|
|
.picker-page *::after {
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.picker-page * {
|
|
margin: 0;
|
|
}
|
|
|
|
.picker-page :is(button, input, textarea, select) {
|
|
font-family: inherit;
|
|
}
|
|
|
|
body.picker-page {
|
|
margin: 0;
|
|
min-height: 100vh;
|
|
min-height: 100dvh;
|
|
background: var(--ks-lacquer);
|
|
color: var(--ks-text);
|
|
font-family: var(--ks-font);
|
|
font-size: var(--ks-type-body-size);
|
|
line-height: var(--ks-type-body-line);
|
|
-webkit-font-smoothing: antialiased;
|
|
-moz-osx-font-smoothing: grayscale;
|
|
text-rendering: optimizeLegibility;
|
|
}
|
|
|
|
/* The gold-leaf face, as the site sets it: a flat kinpaku fill with the leaf
|
|
asset over it at 38%, not the leaf itself as the fill. The texture is a
|
|
pseudo-element because a background-image cannot carry its own opacity, and
|
|
the fixed 340px render keeps the crack structure readable at button size
|
|
where `cover` would average the asset into flat gold.
|
|
|
|
Kept here rather than in the vendored kit for one reason: the picker serves
|
|
its own compressed copy of the leaf from its own assets directory, so the
|
|
URL has to come from --pk-foil (set on <body> in layouts/Picker.astro). The
|
|
rest matches `.ks-button.ks-button-primary` in the site's kinpaku-kit.css,
|
|
including the hover that lifts to vivid gold rather than the pale beige the
|
|
vendored copy still carries. */
|
|
.picker-page .ks-button.ks-button-primary {
|
|
position: relative;
|
|
isolation: isolate;
|
|
}
|
|
|
|
/* The paint is gated on :not([disabled]) so it never competes with the disabled
|
|
state below. Without the gate this selector is (0,3,0) and the kit's
|
|
`.ks-button[disabled]` is (0,2,0), so a disabled button kept the full gold
|
|
fill and read as clickable. */
|
|
.picker-page .ks-button.ks-button-primary:not([disabled]) {
|
|
color: var(--ks-dark-ink);
|
|
background: var(--ks-kinpaku);
|
|
border-color: var(--ks-kinpaku);
|
|
}
|
|
|
|
.picker-page .ks-button.ks-button-primary::before {
|
|
content: "";
|
|
position: absolute;
|
|
inset: 0;
|
|
z-index: -1;
|
|
border-radius: inherit;
|
|
background: var(--pk-foil) 68% 44% / 340px auto no-repeat;
|
|
opacity: 0.38;
|
|
pointer-events: none;
|
|
transition: opacity 180ms var(--ks-ease);
|
|
}
|
|
|
|
.picker-page .ks-button.ks-button-primary:not([disabled]):hover {
|
|
color: var(--ks-dark-ink);
|
|
background: var(--ks-kinpaku-vivid);
|
|
border-color: var(--ks-kinpaku-vivid);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.picker-page .ks-button.ks-button-primary:not([disabled]):hover::before {
|
|
opacity: 0.26;
|
|
}
|
|
|
|
.picker-page .ks-button.ks-button-primary:not([disabled]):active {
|
|
background: var(--ks-kinpaku-rich);
|
|
border-color: var(--ks-kinpaku-rich);
|
|
transform: translateY(0);
|
|
}
|
|
|
|
/* Select this palette waits for a committed color, and the kit's disabled state
|
|
drops the fill to transparent. The leaf has to go with it, or the texture
|
|
floats on the page with no gold under it. */
|
|
.picker-page .ks-button.ks-button-primary[disabled]::before {
|
|
opacity: 0;
|
|
}
|
|
|
|
/* A disabled button answering the pointer is the other half of the same bug:
|
|
the kit's `.ks-button.ks-button-secondary:hover` is (0,3,0) and outranks its
|
|
own `.ks-button[disabled]` at (0,2,0), so the tint and the lift still fired
|
|
on a button that cannot be pressed. Restating the state here puts it at
|
|
(0,3,0) at rest and (0,4,0) under the pointer, which clears every variant
|
|
rule in the kit without an !important. */
|
|
.picker-page .ks-button[disabled],
|
|
.picker-page .ks-button[disabled]:hover,
|
|
.picker-page .ks-button[disabled]:active {
|
|
color: var(--ks-text-mute-deep);
|
|
background: transparent;
|
|
border-color: var(--ks-rule);
|
|
cursor: not-allowed;
|
|
transform: none;
|
|
}
|
|
|
|
.picker-shell {
|
|
position: relative;
|
|
min-height: 100vh;
|
|
min-height: 100svh;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.picker-hero-art {
|
|
position: absolute;
|
|
inset: 0;
|
|
z-index: 0;
|
|
background-position: left center;
|
|
background-size: cover;
|
|
background-repeat: no-repeat;
|
|
/* Same grade the site gives .hero-rebuild-art over this asset. */
|
|
filter: saturate(1.2) contrast(1.08);
|
|
pointer-events: none;
|
|
transition: opacity 180ms var(--ks-ease);
|
|
}
|
|
|
|
#picker-form {
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
.picker-screen {
|
|
min-height: 100vh;
|
|
min-height: 100svh;
|
|
display: grid;
|
|
align-items: center;
|
|
}
|
|
|
|
.picker-screen:not([data-active]) {
|
|
display: none;
|
|
}
|
|
|
|
/* The palette screen and the strategy screen show the same two things: the four
|
|
chosen colors, and the page they are being judged on. Between those two the
|
|
objects move and resize rather than being redrawn, so each carries a name on
|
|
both of its copies. An inactive screen is display:none and therefore not
|
|
rendered, which is what leaves each name on exactly one element at capture.
|
|
|
|
Named per role rather than once for the whole strip: each color then travels
|
|
to its own slot, so a strip that was reordered reads as four colors moving to
|
|
new places instead of one block sliding. */
|
|
:is(.picker-screen[data-screen="02"], .picker-screen[data-screen="03"]) .picker-band[data-band="primary"] {
|
|
view-transition-name: pk-band-primary;
|
|
}
|
|
|
|
:is(.picker-screen[data-screen="02"], .picker-screen[data-screen="03"]) .picker-band[data-band="secondary"] {
|
|
view-transition-name: pk-band-secondary;
|
|
}
|
|
|
|
:is(.picker-screen[data-screen="02"], .picker-screen[data-screen="03"]) .picker-band[data-band="tertiary"] {
|
|
view-transition-name: pk-band-tertiary;
|
|
}
|
|
|
|
:is(.picker-screen[data-screen="02"], .picker-screen[data-screen="03"]) .picker-band[data-band="neutral"] {
|
|
view-transition-name: pk-band-neutral;
|
|
}
|
|
|
|
/* Three screens judge their answer on the same drawing, so what the transition
|
|
carries is one component in three sizes: it grows out of the surface tile
|
|
into the palette screen's panel, and again onto the strategy stage, rather
|
|
than dissolving into a different page at each step. The hidden surfaces are
|
|
display: none and so are not rendered, which is what keeps the name unique
|
|
while every chosen surface stays mounted.
|
|
|
|
On 01b the frame is named rather than the component inside it. The tile draws
|
|
that component at its canonical width under a scale transform, and a group
|
|
animating its own size as well as that transform counts the reduction twice.
|
|
The frame is the drawing's real box at tile scale and carries the same 16 / 7
|
|
the other two do, so the pair interpolates without distortion. Only the
|
|
leading tile is marked, which is both the drawing screen 02 goes on to show
|
|
and what keeps the name on one element. */
|
|
.picker-screen[data-screen="01b"] .picker-mode-tile[data-lead] .picker-mode-preview,
|
|
.picker-screen[data-screen="02"] .picker-preview,
|
|
.picker-screen[data-screen="03"] .picker-strategy-stage > .picker-preview {
|
|
view-transition-name: pk-test-page;
|
|
}
|
|
|
|
::view-transition-group(*) {
|
|
animation-duration: 340ms;
|
|
animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
}
|
|
|
|
/* Mirrors site/styles/home-rebuild.css's .hero-rebuild-container width math. */
|
|
.picker-container {
|
|
position: relative;
|
|
width: 100%;
|
|
max-width: 1500px;
|
|
margin: 0 auto;
|
|
padding-inline: 56px;
|
|
}
|
|
|
|
.picker-screen[data-screen="01"] .picker-container {
|
|
padding-inline: 75px;
|
|
}
|
|
|
|
.picker-start {
|
|
max-width: 620px;
|
|
display: grid;
|
|
justify-items: start;
|
|
gap: 28px;
|
|
}
|
|
|
|
/* Brand lockup, matching the site header: carved-tile mark plus tracked
|
|
wordmark, both in kinpaku gold. */
|
|
.picker-brand {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
color: var(--ks-kinpaku);
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.picker-brand-logo {
|
|
display: grid;
|
|
place-items: center;
|
|
width: 34px;
|
|
height: 34px;
|
|
}
|
|
|
|
.picker-brand-logo svg {
|
|
width: 32px;
|
|
height: 32px;
|
|
}
|
|
|
|
.picker-brand-name {
|
|
font-family: var(--ks-font-wordmark);
|
|
font-size: 1.3rem;
|
|
font-weight: 400;
|
|
line-height: 1;
|
|
letter-spacing: 0.08em;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.picker-title {
|
|
color: var(--ks-champagne);
|
|
font-family: var(--ks-font-display);
|
|
font-size: var(--ks-type-display-size);
|
|
font-weight: var(--ks-type-display-weight);
|
|
line-height: var(--ks-type-display-line);
|
|
letter-spacing: var(--ks-type-display-track);
|
|
text-wrap: balance;
|
|
}
|
|
|
|
.picker-actions {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 14px;
|
|
margin-top: 4px;
|
|
}
|
|
|
|
.picker-key-hint {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
gap: 12px;
|
|
margin-top: 12px;
|
|
color: var(--ks-text-muted);
|
|
}
|
|
|
|
.picker-key-cluster {
|
|
display: grid;
|
|
gap: 4px;
|
|
}
|
|
|
|
.picker-key-row {
|
|
display: flex;
|
|
gap: 4px;
|
|
}
|
|
|
|
.picker-key-row-top {
|
|
justify-content: center;
|
|
}
|
|
|
|
.picker-keycap {
|
|
min-width: 26px;
|
|
height: 26px;
|
|
display: inline-grid;
|
|
place-items: center;
|
|
padding-inline: 6px;
|
|
background: var(--ks-lacquer-raised);
|
|
border: 1px solid var(--ks-rule);
|
|
border-radius: 2px;
|
|
color: var(--ks-champagne);
|
|
font-family: var(--ks-mono);
|
|
font-size: var(--ks-type-mono-size);
|
|
line-height: 1;
|
|
}
|
|
|
|
.picker-key-caption {
|
|
color: var(--ks-text-muted);
|
|
font-size: var(--ks-type-mono-size);
|
|
line-height: 1.5;
|
|
}
|
|
|
|
/* The art belongs to the start screen and the finish. Every question in
|
|
between is judged against a plain ground, because a photograph behind a
|
|
palette or a wireframe is a second opinion nobody asked for. */
|
|
.picker-shell:has(#picker-form[data-current="01b"]) .picker-hero-art,
|
|
.picker-shell:has(#picker-form[data-current="02"]) .picker-hero-art,
|
|
.picker-shell:has(#picker-form[data-current="03"]) .picker-hero-art,
|
|
.picker-shell:has(#picker-form[data-current="04"]) .picker-hero-art,
|
|
.picker-shell:has(#picker-form[data-current="05"]) .picker-hero-art,
|
|
.picker-shell:has(#picker-form[data-current="06"]) .picker-hero-art,
|
|
.picker-shell:has(#picker-form[data-current="07"]) .picker-hero-art,
|
|
.picker-shell:has(#picker-form[data-current="08"]) .picker-hero-art,
|
|
.picker-shell:has(#picker-form[data-current="09"]) .picker-hero-art,
|
|
.picker-shell:has(#picker-form[data-current="10"]) .picker-hero-art,
|
|
.picker-shell:has(#picker-form[data-current="11"]) .picker-hero-art {
|
|
opacity: 0;
|
|
}
|
|
|
|
/* One hairline per step, gold once passed, plus the count and the name of the
|
|
step in hand so the position is legible without counting ticks. */
|
|
.picker-progress {
|
|
position: fixed;
|
|
top: 32px;
|
|
left: max(56px, calc((100vw - 1500px) / 2 + 56px));
|
|
z-index: 5;
|
|
display: grid;
|
|
gap: 10px;
|
|
transition: opacity 180ms var(--ks-ease);
|
|
}
|
|
|
|
/* Eleven steps hold the same total width five did: the row is a measure of
|
|
how far along the interview is, and a wider one would start competing with
|
|
the question. The ticks divide whatever that width is.
|
|
|
|
Eleven is the ceiling rather than the count. A question no chosen surface
|
|
takes is not a step, so the navigation writes the run's own count here and
|
|
the ticks past it leave the grid: the row still measures the same distance,
|
|
divided between the questions this run is actually asked. */
|
|
.picker-progress-track {
|
|
width: 232px;
|
|
display: grid;
|
|
grid-template-columns: repeat(var(--pk-steps, 11), minmax(0, 1fr));
|
|
gap: 5px;
|
|
}
|
|
|
|
.picker-progress-track i {
|
|
height: 2px;
|
|
background: var(--ks-rule);
|
|
transition: background-color 260ms var(--ks-ease);
|
|
}
|
|
|
|
.picker-progress-track i[data-off] {
|
|
display: none;
|
|
}
|
|
|
|
.picker-progress[data-step="1"] .picker-progress-track i:nth-child(-n + 1),
|
|
.picker-progress[data-step="2"] .picker-progress-track i:nth-child(-n + 2),
|
|
.picker-progress[data-step="3"] .picker-progress-track i:nth-child(-n + 3),
|
|
.picker-progress[data-step="4"] .picker-progress-track i:nth-child(-n + 4),
|
|
.picker-progress[data-step="5"] .picker-progress-track i:nth-child(-n + 5),
|
|
.picker-progress[data-step="6"] .picker-progress-track i:nth-child(-n + 6),
|
|
.picker-progress[data-step="7"] .picker-progress-track i:nth-child(-n + 7),
|
|
.picker-progress[data-step="8"] .picker-progress-track i:nth-child(-n + 8),
|
|
.picker-progress[data-step="9"] .picker-progress-track i:nth-child(-n + 9),
|
|
.picker-progress[data-step="10"] .picker-progress-track i:nth-child(-n + 10),
|
|
.picker-progress[data-step="11"] .picker-progress-track i {
|
|
background: var(--ks-kinpaku);
|
|
}
|
|
|
|
.picker-progress-label {
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: 10px;
|
|
font-family: var(--ks-font);
|
|
font-size: 0.68rem;
|
|
letter-spacing: 0.16em;
|
|
text-transform: uppercase;
|
|
color: var(--ks-text-muted);
|
|
}
|
|
|
|
.picker-progress-index b {
|
|
color: var(--ks-kinpaku);
|
|
font-weight: 500;
|
|
}
|
|
|
|
.picker-progress-name {
|
|
color: var(--ks-champagne);
|
|
letter-spacing: 0.1em;
|
|
}
|
|
|
|
#picker-form[data-current="01"] .picker-progress {
|
|
opacity: 0;
|
|
visibility: hidden;
|
|
}
|
|
|
|
.picker-palette {
|
|
display: grid;
|
|
gap: 48px;
|
|
padding-block: clamp(32px, 5svh, 48px) clamp(24px, 3.5svh, 48px);
|
|
}
|
|
|
|
/* Every question wears the site's section-heading treatment: the same thin
|
|
display face `.slop-teaser-title` gets inside `.slop-pattern-head` on the
|
|
homepage. Centered rather than set in a 15ch column, because a question owns
|
|
the top of the screen instead of leading a left-hand text block. */
|
|
.picker-question-title {
|
|
margin: 0;
|
|
color: var(--ks-champagne);
|
|
font-family: var(--ks-font-display);
|
|
font-size: clamp(3rem, 5.6vw, 5.5rem);
|
|
font-weight: 100;
|
|
line-height: 1.04;
|
|
letter-spacing: -0.005em;
|
|
text-align: center;
|
|
text-wrap: balance;
|
|
}
|
|
|
|
.picker-palette-grid {
|
|
display: grid;
|
|
grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
|
|
gap: 50px;
|
|
align-items: center;
|
|
}
|
|
|
|
.picker-palette-subgrid {
|
|
display: grid;
|
|
grid-template-rows: auto auto;
|
|
gap: 30px;
|
|
min-width: 0;
|
|
align-self: start;
|
|
}
|
|
|
|
.picker-palette-subgrid > .picker-actions-stack {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 14px;
|
|
}
|
|
|
|
.picker-deck-column {
|
|
--picker-card-size: min(39vw, 660px, calc(100svh - 292px));
|
|
display: grid;
|
|
justify-items: center;
|
|
gap: 16px;
|
|
min-width: 0;
|
|
}
|
|
|
|
.picker-deck-stage {
|
|
position: relative;
|
|
width: calc(var(--picker-card-size) + 64px);
|
|
height: var(--picker-card-size);
|
|
perspective: 1000px;
|
|
perspective-origin: center 42%;
|
|
}
|
|
|
|
.picker-deck-scroller {
|
|
position: absolute;
|
|
inset: 0;
|
|
z-index: 1;
|
|
overflow-y: auto;
|
|
overflow-x: hidden;
|
|
scroll-snap-type: y mandatory;
|
|
scrollbar-width: none;
|
|
overscroll-behavior: contain;
|
|
}
|
|
|
|
.picker-deck-scroller::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
|
|
.picker-snap-point {
|
|
height: var(--picker-card-size);
|
|
scroll-snap-align: start;
|
|
scroll-snap-stop: always;
|
|
}
|
|
|
|
.picker-card-layer {
|
|
position: absolute;
|
|
inset: 0;
|
|
z-index: 2;
|
|
pointer-events: none;
|
|
transform-style: preserve-3d;
|
|
}
|
|
|
|
.picker-card {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
z-index: 0;
|
|
width: var(--picker-card-size);
|
|
opacity: 0;
|
|
backface-visibility: hidden;
|
|
transform-origin: center center;
|
|
transform-style: preserve-3d;
|
|
transition:
|
|
opacity 240ms var(--ks-ease),
|
|
transform 240ms var(--ks-ease);
|
|
}
|
|
|
|
.picker-card[data-pos="0"] {
|
|
z-index: 5;
|
|
opacity: 1;
|
|
transform: translateX(0) rotateY(0) rotateZ(0) rotateX(0) translateZ(0);
|
|
}
|
|
|
|
.picker-card[data-pos="1"] {
|
|
z-index: 4;
|
|
opacity: 0.52;
|
|
transform: translateX(11%) rotateY(0) rotateZ(6deg) rotateX(0) translateZ(-160px);
|
|
}
|
|
|
|
.picker-card[data-pos="2"] {
|
|
z-index: 3;
|
|
opacity: 0.2;
|
|
transform: translateX(19%) rotateY(0) rotateZ(8deg) rotateX(0) translateZ(-180px);
|
|
}
|
|
|
|
.picker-card[data-pos="-1"] {
|
|
z-index: 4;
|
|
opacity: 0.52;
|
|
transform: translateX(-11%) rotateY(0) rotateZ(-6deg) rotateX(0) translateZ(-160px);
|
|
}
|
|
|
|
.picker-card[data-pos="-2"] {
|
|
z-index: 3;
|
|
opacity: 0.2;
|
|
transform: translateX(-19%) rotateY(0) rotateZ(-8deg) rotateX(0) translateZ(-180px);
|
|
}
|
|
|
|
.picker-card.is-far {
|
|
opacity: 0;
|
|
transform: translateX(0) rotateY(0) rotateZ(0) rotateX(0) translateZ(-220px);
|
|
}
|
|
|
|
.picker-card[data-exit="left"] {
|
|
z-index: 6;
|
|
animation: picker-card-exit-left 280ms var(--ks-ease) both;
|
|
}
|
|
|
|
.picker-card[data-exit="right"] {
|
|
z-index: 6;
|
|
animation: picker-card-exit-right 280ms var(--ks-ease) both;
|
|
}
|
|
|
|
@keyframes picker-card-exit-left {
|
|
0% {
|
|
opacity: 1;
|
|
transform: translateX(0) rotateY(0) rotateZ(0) rotateX(0) translateZ(0);
|
|
}
|
|
|
|
52% {
|
|
opacity: 0.78;
|
|
transform: translateX(-116%) rotateY(-24deg) rotateZ(0) rotateX(2deg) translateZ(-156px);
|
|
}
|
|
|
|
100% {
|
|
opacity: 0.52;
|
|
transform: translateX(-11%) rotateY(0) rotateZ(-6deg) rotateX(0) translateZ(-160px);
|
|
}
|
|
}
|
|
|
|
@keyframes picker-card-exit-right {
|
|
0% {
|
|
opacity: 1;
|
|
transform: translateX(0) rotateY(0) rotateZ(0) rotateX(0) translateZ(0);
|
|
}
|
|
|
|
52% {
|
|
opacity: 0.78;
|
|
transform: translateX(116%) rotateY(24deg) rotateZ(0) rotateX(2deg) translateZ(-156px);
|
|
}
|
|
|
|
100% {
|
|
opacity: 0.52;
|
|
transform: translateX(11%) rotateY(0) rotateZ(6deg) rotateX(0) translateZ(-160px);
|
|
}
|
|
}
|
|
|
|
.picker-card-face {
|
|
position: relative;
|
|
width: var(--picker-card-size);
|
|
aspect-ratio: 1;
|
|
overflow: hidden;
|
|
background: var(--ks-lacquer-raised);
|
|
border: 1px solid var(--ks-rule);
|
|
border-radius: 2px;
|
|
}
|
|
|
|
.picker-cue-face img {
|
|
width: 100%;
|
|
height: 100%;
|
|
display: block;
|
|
object-fit: cover;
|
|
}
|
|
|
|
.picker-seed-face {
|
|
display: grid;
|
|
grid-template-columns: repeat(4, 1fr);
|
|
}
|
|
|
|
.picker-seed-face span {
|
|
background: var(--seed-color);
|
|
}
|
|
|
|
.picker-ring {
|
|
position: absolute;
|
|
left: var(--x);
|
|
top: var(--y);
|
|
z-index: 2;
|
|
width: 45px;
|
|
height: 45px;
|
|
padding: 0;
|
|
border: 0;
|
|
border-radius: 50%;
|
|
background: transparent;
|
|
cursor: grab;
|
|
opacity: 1;
|
|
pointer-events: none;
|
|
transform: translate(-50%, -50%);
|
|
touch-action: none;
|
|
transition: opacity 180ms var(--ks-ease);
|
|
}
|
|
|
|
.picker-ring::before {
|
|
content: "";
|
|
position: absolute;
|
|
inset: 6px;
|
|
background: var(--marker-color);
|
|
border: 2px solid var(--ks-lacquer-raised);
|
|
border-radius: 50%;
|
|
box-shadow:
|
|
0 0 12px color-mix(in srgb, var(--ks-champagne) 16%, transparent),
|
|
inset 0 0 0 1px color-mix(in srgb, var(--ks-champagne) 40%, transparent);
|
|
}
|
|
|
|
.picker-ring::after {
|
|
content: "";
|
|
position: absolute;
|
|
inset: 1px;
|
|
border: 2px solid white;
|
|
border-radius: 50%;
|
|
/* Dark edge on both sides of the white hairline so the hover highlight
|
|
reads over the neutral's pale image regions, not only over saturated
|
|
ones. Fixed near-black, not a theme token: it sits on the photo. */
|
|
box-shadow:
|
|
0 0 0 1.5px oklch(15% 0.006 95 / 0.6),
|
|
inset 0 0 0 1.5px oklch(15% 0.006 95 / 0.6);
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transform: scale(0.78);
|
|
transition:
|
|
opacity 180ms var(--ks-ease),
|
|
transform 180ms var(--ks-ease);
|
|
}
|
|
|
|
.picker-palette:has(.picker-band[data-band="primary"]:is(:hover, :focus-within))
|
|
.picker-card[data-pos="0"] .picker-ring[data-role="primary"]::after,
|
|
.picker-palette:has(.picker-band[data-band="secondary"]:is(:hover, :focus-within))
|
|
.picker-card[data-pos="0"] .picker-ring[data-role="secondary"]::after,
|
|
.picker-palette:has(.picker-band[data-band="tertiary"]:is(:hover, :focus-within))
|
|
.picker-card[data-pos="0"] .picker-ring[data-role="tertiary"]::after,
|
|
.picker-palette:has(.picker-band[data-band="neutral"]:is(:hover, :focus-within))
|
|
.picker-card[data-pos="0"] .picker-ring[data-role="neutral"]::after {
|
|
opacity: 1;
|
|
transform: scale(1);
|
|
}
|
|
|
|
.picker-card[data-pos="0"] .picker-ring {
|
|
pointer-events: auto;
|
|
}
|
|
|
|
.picker-ring:is(:focus-visible, [data-dragging]) {
|
|
z-index: 3;
|
|
}
|
|
|
|
.picker-ring[data-dragging] {
|
|
cursor: grabbing;
|
|
}
|
|
|
|
.picker-ring[data-detached] {
|
|
opacity: 0.4;
|
|
}
|
|
|
|
.picker-ring:focus-visible {
|
|
outline: none;
|
|
}
|
|
|
|
.picker-ring:focus-visible::before {
|
|
outline: 2px solid var(--ks-patina);
|
|
outline-offset: 3px;
|
|
}
|
|
|
|
.picker-deck-controls {
|
|
min-width: 260px;
|
|
display: grid;
|
|
grid-template-columns: 44px minmax(120px, 1fr) 44px;
|
|
gap: 14px;
|
|
align-items: center;
|
|
}
|
|
|
|
.picker-ring-guide {
|
|
min-height: 2.9em;
|
|
max-width: 38ch;
|
|
color: var(--ks-text-muted);
|
|
font-size: 0.82rem;
|
|
line-height: 1.45;
|
|
text-align: center;
|
|
transition: opacity 200ms var(--ks-ease);
|
|
}
|
|
|
|
.picker-ring-guide span {
|
|
display: block;
|
|
}
|
|
|
|
.picker-ring-guide[aria-hidden="true"] {
|
|
opacity: 0;
|
|
}
|
|
|
|
.picker-icon-button {
|
|
position: relative;
|
|
width: 44px;
|
|
height: 44px;
|
|
display: inline-grid;
|
|
place-items: center;
|
|
padding: 0;
|
|
background: transparent;
|
|
border: 1px solid var(--ks-rule);
|
|
border-radius: 2px;
|
|
color: var(--ks-champagne);
|
|
cursor: pointer;
|
|
transition:
|
|
background-color 180ms var(--ks-ease),
|
|
border-color 180ms var(--ks-ease),
|
|
color 180ms var(--ks-ease);
|
|
}
|
|
|
|
.picker-icon-button:hover {
|
|
background: var(--ks-graphite);
|
|
border-color: var(--ks-patina);
|
|
color: var(--ks-patina);
|
|
}
|
|
|
|
.picker-icon-button:focus-visible,
|
|
.picker-band:focus-visible,
|
|
.picker-band-tool:focus-visible,
|
|
.picker-tint:focus-visible,
|
|
.picker-tint-close:focus-visible {
|
|
outline: 2px solid var(--ks-patina);
|
|
outline-offset: 3px;
|
|
}
|
|
|
|
.picker-icon-button:disabled {
|
|
color: var(--ks-text-mute-deep);
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.picker-icon-button svg,
|
|
.picker-band-tool svg {
|
|
width: 18px;
|
|
height: 18px;
|
|
fill: none;
|
|
stroke: currentColor;
|
|
stroke-width: 1.5;
|
|
stroke-linecap: round;
|
|
stroke-linejoin: round;
|
|
}
|
|
|
|
.picker-deck-count {
|
|
color: var(--ks-text-muted);
|
|
font-family: var(--ks-mono);
|
|
font-size: var(--ks-type-mono-size);
|
|
line-height: 1.35;
|
|
text-align: center;
|
|
}
|
|
|
|
.picker-palette-panel {
|
|
position: relative;
|
|
min-width: 0;
|
|
display: grid;
|
|
gap: 18px;
|
|
padding: 30px;
|
|
background: var(--ks-lacquer-raised);
|
|
border: 1px solid var(--ks-rule);
|
|
border-radius: 2px;
|
|
}
|
|
|
|
.picker-palette-panel > legend {
|
|
position: absolute;
|
|
width: 1px;
|
|
height: 1px;
|
|
overflow: hidden;
|
|
clip: rect(0 0 0 0);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.picker-reset {
|
|
position: absolute;
|
|
top: -22px;
|
|
right: 18px;
|
|
z-index: 4;
|
|
background: var(--ks-lacquer-raised);
|
|
}
|
|
|
|
.picker-bands {
|
|
display: grid;
|
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
gap: 1px;
|
|
background: var(--ks-rule);
|
|
border: 1px solid var(--ks-rule);
|
|
}
|
|
|
|
.picker-band-item {
|
|
min-width: 0;
|
|
background: var(--ks-lacquer-raised);
|
|
}
|
|
|
|
.picker-band {
|
|
--band-color: var(--ks-graphite);
|
|
--band-ink: var(--ks-champagne);
|
|
position: relative;
|
|
height: 175px;
|
|
background: var(--band-color);
|
|
color: var(--band-ink);
|
|
}
|
|
|
|
.picker-band output {
|
|
position: absolute;
|
|
right: 14px;
|
|
bottom: 14px;
|
|
left: 14px;
|
|
font-family: var(--ks-mono);
|
|
font-size: var(--ks-type-mono-size);
|
|
font-weight: 500;
|
|
letter-spacing: 0.04em;
|
|
line-height: 1;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.picker-band-tools {
|
|
position: absolute;
|
|
top: 12px;
|
|
right: 10px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transition: opacity 180ms var(--ks-ease);
|
|
}
|
|
|
|
.picker-band:hover .picker-band-tools,
|
|
.picker-band:focus-within .picker-band-tools {
|
|
opacity: 1;
|
|
pointer-events: auto;
|
|
}
|
|
|
|
/* With the strip open the edit button is also its close button, so it is the
|
|
one tool that stays live. The other two would act on a color the strip is in
|
|
the middle of changing. */
|
|
.picker-band-item[data-tint-open] .picker-band-tool:is([data-copy-color], [data-custom-color]) {
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.picker-band-tool {
|
|
position: relative;
|
|
width: 32px;
|
|
height: 32px;
|
|
display: grid;
|
|
place-items: center;
|
|
padding: 0;
|
|
background: transparent;
|
|
border: 0;
|
|
border-radius: 2px;
|
|
color: inherit;
|
|
cursor: pointer;
|
|
opacity: 0.72;
|
|
transition: opacity 180ms var(--ks-ease);
|
|
}
|
|
|
|
.picker-band-tool:is(:hover, :focus-visible) {
|
|
opacity: 1;
|
|
}
|
|
|
|
.picker-band-tool svg {
|
|
width: 17px;
|
|
height: 17px;
|
|
}
|
|
|
|
.picker-tip::after {
|
|
content: attr(data-tip);
|
|
position: absolute;
|
|
left: 50%;
|
|
bottom: calc(100% + 8px);
|
|
z-index: 12;
|
|
width: max-content;
|
|
max-width: 210px;
|
|
padding: 7px 9px;
|
|
opacity: 0;
|
|
background: var(--ks-lacquer-raised);
|
|
border: 1px solid var(--ks-rule);
|
|
border-radius: 2px;
|
|
color: var(--ks-text);
|
|
font-size: 0.76rem;
|
|
line-height: 1.25;
|
|
pointer-events: none;
|
|
transform: translate(-50%, 3px);
|
|
transition:
|
|
opacity 180ms var(--ks-ease),
|
|
transform 180ms var(--ks-ease);
|
|
}
|
|
|
|
.picker-tip:hover::after,
|
|
.picker-tip:focus-visible::after {
|
|
opacity: 1;
|
|
transform: translate(-50%, 0);
|
|
}
|
|
|
|
.picker-band-tool.picker-tip::after {
|
|
top: 50%;
|
|
right: calc(100% + 8px);
|
|
bottom: auto;
|
|
left: auto;
|
|
transform: translate(3px, -50%);
|
|
}
|
|
|
|
.picker-band-tool.picker-tip:is(:hover, :focus-visible)::after {
|
|
transform: translate(0, -50%);
|
|
}
|
|
|
|
.picker-native-color {
|
|
position: absolute;
|
|
width: 1px;
|
|
height: 1px;
|
|
overflow: hidden;
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
}
|
|
|
|
/* Reorder grip. Dragging a band carries its color to another slot; the slots
|
|
keep their roles, so the label in the foot holds still while the band above
|
|
it travels. The affordance stays quiet because the color is the subject of
|
|
the row: no border of its own, and it strengthens on hover and on focus. */
|
|
.picker-band-grip {
|
|
position: absolute;
|
|
top: 5px;
|
|
left: 5px;
|
|
z-index: 2;
|
|
width: 36px;
|
|
height: 36px;
|
|
display: grid;
|
|
place-items: center;
|
|
padding: 0;
|
|
background: transparent;
|
|
border: 0;
|
|
border-radius: 2px;
|
|
color: inherit;
|
|
cursor: grab;
|
|
opacity: 0.42;
|
|
touch-action: none;
|
|
transition: opacity 180ms var(--ks-ease);
|
|
}
|
|
|
|
.picker-band-grip svg {
|
|
width: 22px;
|
|
height: 22px;
|
|
fill: currentColor;
|
|
stroke: none;
|
|
}
|
|
|
|
.picker-band:hover .picker-band-grip,
|
|
.picker-band:focus-within .picker-band-grip {
|
|
opacity: 0.9;
|
|
}
|
|
|
|
.picker-band-grip:focus-visible {
|
|
opacity: 1;
|
|
outline: 2px solid var(--ks-patina);
|
|
outline-offset: 3px;
|
|
}
|
|
|
|
.picker-band-grip[data-dragging] {
|
|
cursor: grabbing;
|
|
opacity: 1;
|
|
}
|
|
|
|
/* The leftmost grip sits against the panel's left edge, so its tip reads to the
|
|
side there is room on rather than to the left like the tools' tips do. */
|
|
.picker-band-grip.picker-tip::after {
|
|
top: 50%;
|
|
right: auto;
|
|
bottom: auto;
|
|
left: calc(100% + 8px);
|
|
transform: translate(-3px, -50%);
|
|
}
|
|
|
|
.picker-band-grip.picker-tip:is(:hover, :focus-visible)::after {
|
|
transform: translate(0, -50%);
|
|
}
|
|
|
|
/* The tip has said its piece by the time the band is moving, and under the
|
|
pointer it would cover the color being placed. */
|
|
.picker-band-grip[data-dragging].picker-tip::after {
|
|
opacity: 0;
|
|
transition: none;
|
|
}
|
|
|
|
/* A lifted band rides over its neighbours and carries a hairline of its own
|
|
ink, since this screen has no shadows to borrow. The ring fades rather than
|
|
cuts, so it is gone by the time the band hands its color to the slot it
|
|
landed on and the swap itself is invisible. */
|
|
.picker-band[data-dragging] {
|
|
z-index: 6;
|
|
cursor: grabbing;
|
|
}
|
|
|
|
.picker-band::after {
|
|
content: "";
|
|
position: absolute;
|
|
inset: 0;
|
|
opacity: 0;
|
|
outline: 1px solid color-mix(in srgb, currentColor 55%, transparent);
|
|
outline-offset: -1px;
|
|
pointer-events: none;
|
|
transition: opacity 140ms var(--ks-ease);
|
|
}
|
|
|
|
.picker-band[data-dragging]::after {
|
|
opacity: 1;
|
|
}
|
|
|
|
.picker-band-status {
|
|
position: absolute;
|
|
width: 1px;
|
|
height: 1px;
|
|
overflow: hidden;
|
|
clip: rect(0 0 0 0);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.picker-band-grip,
|
|
.picker-band::after {
|
|
transition: none;
|
|
}
|
|
}
|
|
|
|
.picker-band-foot {
|
|
position: relative;
|
|
height: 46px;
|
|
display: grid;
|
|
place-items: center;
|
|
}
|
|
|
|
.picker-band-foot h2 {
|
|
color: var(--ks-champagne);
|
|
font-size: var(--ks-type-title-size);
|
|
font-weight: var(--ks-type-title-weight);
|
|
line-height: var(--ks-type-title-line);
|
|
}
|
|
|
|
.picker-band-item[data-tint-open] .picker-band-foot h2 {
|
|
visibility: hidden;
|
|
}
|
|
|
|
/* The same band, restated on the strategy screen under the choices. It is a
|
|
reading of the palette and a handle to reorder it, not a place to edit it, so
|
|
the tools come off and what is left fits in the column the options leave
|
|
behind: a little over a third of the height the editable band needs. */
|
|
.picker-bands--compact .picker-band {
|
|
/* The strip's own height less the foot and the group's two hairlines. */
|
|
height: calc(var(--pk-band, 96px) - 32px);
|
|
}
|
|
|
|
/* Read from the far corner rather than under the grip: at the short end of the
|
|
band's range the swatch is not tall enough to stack them. */
|
|
.picker-bands--compact .picker-band output {
|
|
right: 8px;
|
|
bottom: 8px;
|
|
left: 8px;
|
|
text-align: right;
|
|
}
|
|
|
|
.picker-bands--compact .picker-band-grip {
|
|
top: 3px;
|
|
left: 3px;
|
|
width: 30px;
|
|
height: 30px;
|
|
}
|
|
|
|
.picker-bands--compact .picker-band-grip svg {
|
|
width: 19px;
|
|
height: 19px;
|
|
}
|
|
|
|
.picker-bands--compact .picker-band-foot {
|
|
height: 30px;
|
|
}
|
|
|
|
.picker-bands--compact .picker-band-foot h2 {
|
|
font-size: 0.96rem;
|
|
}
|
|
|
|
.picker-tint-strip {
|
|
position: absolute;
|
|
top: 9px;
|
|
left: 0;
|
|
z-index: 8;
|
|
display: flex;
|
|
background: var(--ks-lacquer-raised);
|
|
border: 1px solid var(--ks-rule);
|
|
}
|
|
|
|
.picker-band-item[data-band-item="secondary"] .picker-tint-strip {
|
|
left: -56px;
|
|
}
|
|
|
|
.picker-band-item:is([data-band-item="tertiary"], [data-band-item="neutral"]) .picker-tint-strip {
|
|
right: 0;
|
|
left: auto;
|
|
}
|
|
|
|
.picker-tint-strip[hidden] {
|
|
display: none;
|
|
}
|
|
|
|
.picker-tint,
|
|
.picker-tint-close {
|
|
position: relative;
|
|
width: 28px;
|
|
height: 28px;
|
|
padding: 0;
|
|
border: 0;
|
|
border-right: 1px solid var(--ks-rule);
|
|
border-radius: 0;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.picker-tint {
|
|
background: var(--tint-color);
|
|
}
|
|
|
|
.picker-tint[data-current]::after {
|
|
content: "";
|
|
position: absolute;
|
|
right: 8px;
|
|
bottom: 3px;
|
|
left: 8px;
|
|
height: 2px;
|
|
background: var(--ks-champagne);
|
|
}
|
|
|
|
.picker-tint-close {
|
|
background: var(--ks-lacquer-raised);
|
|
border-right: 0;
|
|
color: var(--ks-champagne);
|
|
font-size: 1.1rem;
|
|
line-height: 1;
|
|
}
|
|
|
|
.picker-preview {
|
|
--pv-primary: var(--ks-champagne);
|
|
--pv-secondary: var(--ks-patina);
|
|
--pv-tertiary: var(--ks-kinpaku);
|
|
--pv-neutral: var(--ks-lacquer-raised);
|
|
--pv-n-ink: var(--ks-champagne);
|
|
--pv-p-ink: var(--ks-champagne);
|
|
--pv-surface-2: color-mix(in oklab, var(--pv-neutral) 92%, var(--pv-n-ink));
|
|
--pv-bone: color-mix(in oklab, var(--pv-neutral) 62%, var(--pv-n-ink));
|
|
--pv-strong: color-mix(in oklab, var(--pv-neutral) 22%, var(--pv-n-ink));
|
|
--pv-secondary-wash: color-mix(in oklab, var(--pv-secondary) 28%, var(--pv-neutral));
|
|
--pv-tertiary-wash: color-mix(in oklab, var(--pv-tertiary) 22%, var(--pv-neutral));
|
|
width: 100%;
|
|
aspect-ratio: 16 / 7;
|
|
display: grid;
|
|
grid-template-columns: minmax(0, 1fr) auto;
|
|
/* A share of the frame rather than a fixed channel: on screen 03 the two
|
|
cards are drawn near twice the size they are on 01b, and a gutter that
|
|
stays at 16px there reads as a seam between them. */
|
|
gap: 3.229163%;
|
|
}
|
|
|
|
/* Two artboards floating on the page itself: a desktop card and a phone card,
|
|
both in the same shell of shared chrome. What fills their bodies is the
|
|
variant the chosen surface brings, so the palette is judged on the kind of
|
|
page it is going to dress. The phone's fixed portrait ratio keeps the wide
|
|
frame from stretching a single page mock.
|
|
|
|
Both cards are inline-size containers, so the chrome each one carries is cut
|
|
from the card it sits on. A nav band is a drawn object rather than page
|
|
length, which is why its own vertical padding is counted in `--pv-x` with
|
|
the rest: a taller frame is a longer page behind the same window, not a
|
|
thicker window. Every count below is a canonical pixel over the card's own
|
|
height, 214.78 on both, counted the way the four bodies are counted against
|
|
theirs. The two corner radii are the exception and stay in pixels: a
|
|
container measures for what is inside it, so a card cannot spend its own
|
|
`--pv-x` on itself. */
|
|
/* A line of type is drawn as a bar, and how heavy that bar reads is not settled
|
|
by its authored count: the phone spends nearly four times the share of its
|
|
card on the same number, which is why the two cards side by side can be cut
|
|
from one drawing and still disagree about how present the text is. The factor
|
|
is the room to say that, per card and without moving anything else in the
|
|
drawing. It stays 1 wherever a card was signed off, so a card only carries a
|
|
different weight where something turned it on by name. */
|
|
.pv-desktop,
|
|
.pv-phone {
|
|
--pv-text: 1;
|
|
container-type: inline-size;
|
|
min-width: 0;
|
|
min-height: 0;
|
|
overflow: hidden;
|
|
background: var(--pv-neutral);
|
|
border: 1px solid var(--ks-rule);
|
|
}
|
|
|
|
.pv-desktop {
|
|
/* 1 / 1.745162, this card's width over its height at the component's authored
|
|
16 / 7, measured on the built picker. */
|
|
--pv-x: 0.573013cqw;
|
|
display: grid;
|
|
grid-template-rows: auto 1fr;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.pv-phone {
|
|
/* 1 / 0.468718: the portrait card is narrower than it is tall, so the same
|
|
count spends a larger share of this card's width than of the desktop's. */
|
|
--pv-x: 2.133479cqw;
|
|
height: 100%;
|
|
aspect-ratio: 9 / 19;
|
|
display: grid;
|
|
grid-template-rows: auto 1fr auto;
|
|
border-radius: 10px;
|
|
}
|
|
|
|
.pv-nav {
|
|
display: grid;
|
|
grid-template-columns: auto 1fr auto;
|
|
gap: calc(4.6559 * var(--pv-x));
|
|
align-items: center;
|
|
padding: calc(4.6559 * var(--pv-x)) calc(5.58708 * var(--pv-x));
|
|
}
|
|
|
|
.pv-logo {
|
|
width: calc(3.72472 * var(--pv-x));
|
|
aspect-ratio: 1;
|
|
background: var(--pv-primary);
|
|
border-radius: 50%;
|
|
}
|
|
|
|
.pv-nav-bars {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: calc(3.72472 * var(--pv-x));
|
|
}
|
|
|
|
.pv-nav-bars i {
|
|
display: block;
|
|
width: calc(8.38062 * var(--pv-x));
|
|
height: calc(1.39677 * var(--pv-x));
|
|
background: var(--pv-bone);
|
|
border-radius: 2px;
|
|
}
|
|
|
|
.pv-pill {
|
|
width: calc(14.89888 * var(--pv-x));
|
|
height: calc(5.58708 * var(--pv-x));
|
|
background: var(--pv-primary);
|
|
border-radius: 2px;
|
|
}
|
|
|
|
/* The hero is a size container so the page it draws scales with the card rather
|
|
than sitting at one drawn size inside it. Its counts are canonical pixels
|
|
divided by the box's own height, 164.78. Down the page they are spent in
|
|
`cqh`, so a taller card is a longer hero rather than a magnified one; across
|
|
it they are spent in `--pv-x` against the width, as the ops variant below
|
|
sets out, so a bar keeps its thickness when the frame changes shape.
|
|
Its own padding is a percentage, since a container cannot measure itself. */
|
|
.pv-hero {
|
|
container-type: size;
|
|
min-height: 0;
|
|
display: grid;
|
|
grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
|
|
gap: 8%;
|
|
align-items: center;
|
|
padding: 1.06729% 8% 3.735179%;
|
|
/* 1 / 1.910772, this box's width over its height. */
|
|
--pv-x: 0.523349cqw;
|
|
}
|
|
|
|
.pv-hero-copy {
|
|
display: grid;
|
|
gap: 4.85493cqh;
|
|
}
|
|
|
|
.pv-eyebrow {
|
|
width: 22%;
|
|
height: calc(2.42746 * var(--pv-text) * var(--pv-x));
|
|
background: var(--pv-tertiary);
|
|
border-radius: 2px;
|
|
}
|
|
|
|
/* The headline is display type set in the brand color, not permanent black
|
|
ink: it must visibly react when the primary changes. */
|
|
.pv-headline {
|
|
display: grid;
|
|
gap: 4.24806cqh;
|
|
margin-block: 1.21374cqh 1.8206cqh;
|
|
}
|
|
|
|
.pv-headline i {
|
|
display: block;
|
|
width: 88%;
|
|
height: calc(4.85492 * var(--pv-text) * var(--pv-x));
|
|
background: color-mix(in oklab, var(--pv-primary) 80%, var(--pv-strong));
|
|
border-radius: 2px;
|
|
}
|
|
|
|
.pv-headline i:last-child {
|
|
width: 58%;
|
|
}
|
|
|
|
.pv-copy-bar {
|
|
width: 64%;
|
|
height: calc(3.03433 * var(--pv-text) * var(--pv-x));
|
|
background: var(--pv-bone);
|
|
border-radius: 2px;
|
|
}
|
|
|
|
.pv-actions {
|
|
display: flex;
|
|
gap: calc(4.24806 * var(--pv-x));
|
|
margin-top: 6.06866cqh;
|
|
}
|
|
|
|
/* Buttons are objects on the page rather than page rhythm, so they keep their
|
|
drawn size and the room above them takes the extra height instead. */
|
|
.pv-actions i {
|
|
width: calc(23.06088 * var(--pv-x));
|
|
height: calc(7.88925 * var(--pv-x));
|
|
background: var(--pv-primary);
|
|
border-radius: 2px;
|
|
}
|
|
|
|
.pv-actions i:last-child {
|
|
background: transparent;
|
|
border: 1px solid var(--pv-secondary);
|
|
}
|
|
|
|
.pv-image {
|
|
position: relative;
|
|
width: 100%;
|
|
aspect-ratio: 4 / 3;
|
|
background: linear-gradient(135deg, var(--pv-secondary-wash), color-mix(in oklab, var(--pv-secondary) 52%, var(--pv-neutral)));
|
|
border: 1px solid var(--pv-secondary);
|
|
border-radius: 2px;
|
|
}
|
|
|
|
.pv-image::after {
|
|
content: "";
|
|
position: absolute;
|
|
top: calc(5.46179 * var(--pv-x));
|
|
right: calc(5.46179 * var(--pv-x));
|
|
width: calc(4.24806 * var(--pv-x));
|
|
aspect-ratio: 1;
|
|
background: var(--pv-tertiary);
|
|
border-radius: 50%;
|
|
}
|
|
|
|
.pv-avatar {
|
|
width: calc(4.19031 * var(--pv-x));
|
|
aspect-ratio: 1;
|
|
border: 1px solid var(--pv-bone);
|
|
border-radius: 50%;
|
|
}
|
|
|
|
/* Phone artboard: the desktop page narrowed, same hero plate over the same two
|
|
blocks, so the palette is judged making one argument at two widths. The
|
|
blocks each carry a different role, keeping the secondary and tertiary
|
|
visible as accents rather than only as washes. */
|
|
.pv-phone-top {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: calc(4.19031 * var(--pv-x)) calc(4.6559 * var(--pv-x));
|
|
background: var(--pv-surface-2);
|
|
border-bottom: 1px solid var(--ks-rule);
|
|
}
|
|
|
|
/* A size container counted the way the hero is: canonical pixels over this
|
|
padded box's own height, 145.78, spent in `cqh` down the page and in
|
|
`--pv-x` across it. The padding itself is a percentage of the phone's width,
|
|
since a container cannot measure itself. */
|
|
.pv-phone-body {
|
|
container-type: size;
|
|
min-height: 0;
|
|
display: grid;
|
|
/* The plate takes the row that flexes and the two blocks keep their drawn
|
|
size, so a narrower window shortens the hero rather than pushing a block
|
|
out through the bottom of the phone. */
|
|
grid-template-rows: minmax(0, 1fr) auto auto;
|
|
/* A percentage of this box's own height rather than a count in `cqh`: a
|
|
container unit on the container's own property is measured against the box
|
|
outside it, which here is the phone card, and the row gap would be cut from
|
|
the wrong page. */
|
|
row-gap: 4.802058%;
|
|
padding: 8.940432% 9.933758% 9.933758%;
|
|
/* 1 / 0.553376, this box's width over its height. */
|
|
--pv-x: 1.807089cqw;
|
|
}
|
|
|
|
/* The desktop plate's mark is counted against a taller box than this one, so
|
|
the phone restates it. */
|
|
.pv-phone-body .pv-image::after {
|
|
top: calc(6.17364 * var(--pv-x));
|
|
right: calc(6.17364 * var(--pv-x));
|
|
width: calc(4.80172 * var(--pv-x));
|
|
}
|
|
|
|
/* Its 4 / 3 is honored wherever the row is tall enough to hold it, which is
|
|
every window past the width the component stops growing at. Below that the
|
|
plate gives up height instead of overflowing. */
|
|
.pv-phone-body > .pv-image {
|
|
align-self: center;
|
|
max-height: 100%;
|
|
}
|
|
|
|
/* A block is a drawn object rather than a share of the page, so everything
|
|
inside it is counted across the box: the row it sits in is `auto`, and a
|
|
block that took its rhythm from the card's height would grow into the plate
|
|
above it every time the frame got taller. */
|
|
.pv-mini-card {
|
|
display: grid;
|
|
gap: calc(3.4298 * var(--pv-x));
|
|
padding: calc(5.48768 * var(--pv-x)) calc(6.17364 * var(--pv-x));
|
|
background: var(--pv-surface-2);
|
|
border: 1px solid var(--ks-rule);
|
|
border-radius: 2px;
|
|
}
|
|
|
|
.pv-mini-card i,
|
|
.pv-mini-card span {
|
|
display: block;
|
|
height: calc(2.05788 * var(--pv-x));
|
|
background: var(--pv-bone);
|
|
border-radius: 2px;
|
|
}
|
|
|
|
.pv-mini-card span {
|
|
width: 62%;
|
|
height: calc(4.11576 * var(--pv-x));
|
|
background: var(--pv-secondary);
|
|
}
|
|
|
|
/* Keyed off the pair rather than a position, so the hero plate sitting above
|
|
them cannot shift which block reads as tertiary. */
|
|
.pv-mini-card + .pv-mini-card span {
|
|
background: var(--pv-tertiary);
|
|
}
|
|
|
|
.pv-tabbar {
|
|
display: flex;
|
|
justify-content: space-evenly;
|
|
align-items: center;
|
|
padding: calc(3.72472 * var(--pv-x)) 0;
|
|
border-top: 1px solid var(--ks-rule);
|
|
}
|
|
|
|
.pv-tabbar i {
|
|
width: calc(2.32795 * var(--pv-x));
|
|
aspect-ratio: 1;
|
|
background: var(--pv-bone);
|
|
border-radius: 50%;
|
|
}
|
|
|
|
.pv-tabbar i:first-child {
|
|
background: var(--pv-primary);
|
|
}
|
|
|
|
/* ── Screen 01b: which surfaces ──────────────────────────────
|
|
Four tiles, one per mode the skill recognizes, chosen before any visual
|
|
question so every later answer knows what kind of page it is dressing.
|
|
Multi-select: a product is allowed to be an app with a landing page and
|
|
docs. All four previews are drawn in the one demo palette set on the frame
|
|
below: color has not been chosen yet, so no tile is allowed to read as a
|
|
better color decision than the three beside it. */
|
|
/* Everything on this screen is cut from one measure: the width the palette
|
|
screen's preview is currently rendering at, and the scale this tile reduces
|
|
it to. Every tile shows that component itself under a transform rather than
|
|
a second copy tuned by hand, which is the only way the two screens can
|
|
agree pixel for pixel.
|
|
|
|
--pk-pv-natural mirrors the palette screen's own preview width, which grows
|
|
at 5/12 of the viewport and pins at 495.5px once the window passes 1500px.
|
|
Measured at 1280 / 1366 / 1440 / 1512 wide, where the line is exact. Re-measure
|
|
it if screen 02's grid ever changes. The scale steps by viewport height
|
|
because CSS cannot divide one length by another to reach the unitless number
|
|
scale() needs, and the frame's height is derived from both so the reduced
|
|
component fills the frame exactly. */
|
|
.picker-modes {
|
|
--pk-pv-natural: min(495.5px, calc(41.667vw - 129.5px));
|
|
--pk-pv-scale: 0.62;
|
|
--pk-mode-frame: calc(var(--pk-pv-natural) * 7 / 16 * var(--pk-pv-scale));
|
|
--pk-mode-pad: clamp(14px, 2svh, 24px);
|
|
--pk-mode-gap: clamp(16px, 2.2svh, 24px);
|
|
display: grid;
|
|
gap: clamp(16px, 2.6svh, 36px);
|
|
justify-items: center;
|
|
padding-block: clamp(20px, 3svh, 44px) clamp(14px, 2.2svh, 34px);
|
|
}
|
|
|
|
@media (min-height: 1040px) {
|
|
.picker-modes {
|
|
--pk-pv-scale: 0.68;
|
|
}
|
|
}
|
|
|
|
/* Short windows spend their room on the frames rather than on the chrome
|
|
around them: below roughly 100px tall the drawn previews start pressing
|
|
their bars against the frame edge, so the page padding and gutters give way
|
|
first. */
|
|
@media (max-height: 760px) {
|
|
.picker-modes {
|
|
--pk-pv-scale: 0.58;
|
|
--pk-mode-pad: 12px;
|
|
--pk-mode-gap: 12px;
|
|
gap: 14px;
|
|
padding-block: 14px 10px;
|
|
}
|
|
}
|
|
|
|
/* A wide short window is the hard case: the reference width has already pinned
|
|
at its maximum, so only the scale is left to give. Narrow short windows need
|
|
no such step, because their reference width is smaller to begin with. */
|
|
@media (max-height: 760px) and (min-width: 1500px) {
|
|
.picker-modes {
|
|
--pk-pv-scale: 0.5;
|
|
}
|
|
}
|
|
|
|
@media (max-height: 700px) and (min-width: 1500px) {
|
|
.picker-modes {
|
|
--pk-pv-scale: 0.47;
|
|
}
|
|
}
|
|
|
|
.picker-modes-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
/* Both rows take the taller row's height, so a goal or chip row that wraps
|
|
in one tile cannot leave the other three sitting shorter than it. */
|
|
grid-template-rows: repeat(2, minmax(0, 1fr));
|
|
gap: var(--pk-mode-gap);
|
|
/* Two tiles wide at the frame's largest, so a tall viewport never drives a
|
|
frame wider than the tile holding it. Below that the frame is matted by
|
|
the tile rather than the tile shrinking, because a narrower tile wraps
|
|
the chips and goal onto extra lines and costs more height than the
|
|
matting saves. */
|
|
width: min(100%, 860px);
|
|
margin: 0;
|
|
padding: 0;
|
|
border: 0;
|
|
}
|
|
|
|
.picker-modes-grid legend {
|
|
position: absolute;
|
|
width: 1px;
|
|
height: 1px;
|
|
overflow: hidden;
|
|
clip: rect(0 0 0 0);
|
|
clip-path: inset(50%);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.picker-mode-tile {
|
|
position: relative;
|
|
display: grid;
|
|
grid-template-rows: auto 1fr;
|
|
gap: clamp(12px, 1.9svh, 20px);
|
|
padding: var(--pk-mode-pad);
|
|
background: var(--ks-lacquer-raised);
|
|
border: 1px solid var(--ks-rule);
|
|
cursor: pointer;
|
|
transition:
|
|
border-color 180ms var(--ks-ease),
|
|
background-color 180ms var(--ks-ease);
|
|
}
|
|
|
|
.picker-mode-tile input {
|
|
position: absolute;
|
|
width: 1px;
|
|
height: 1px;
|
|
overflow: hidden;
|
|
clip: rect(0 0 0 0);
|
|
clip-path: inset(50%);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.picker-mode-tile:hover {
|
|
border-color: color-mix(in oklab, var(--ks-patina) 45%, var(--ks-rule));
|
|
}
|
|
|
|
.picker-mode-tile:has(input:checked) {
|
|
background: color-mix(in oklab, var(--ks-patina) 9%, var(--ks-lacquer-raised));
|
|
border-color: var(--ks-patina);
|
|
}
|
|
|
|
.picker-mode-tile:has(input:focus-visible) {
|
|
outline: 2px solid var(--ks-patina-deep);
|
|
outline-offset: -2px;
|
|
z-index: 1;
|
|
}
|
|
|
|
/* The box every tile's preview is matted into: one measure, cut from the
|
|
palette screen's own preview width and handed to all four modes. */
|
|
.picker-mode-preview {
|
|
/* The height is the budget and the width follows it, at the same 16 / 7 the
|
|
palette screen's preview uses. Letting the tile set the width instead
|
|
stretched the desktop card to about five times its own ratio. */
|
|
height: var(--pk-mode-frame);
|
|
aspect-ratio: 16 / 7;
|
|
width: auto;
|
|
max-width: 100%;
|
|
justify-self: center;
|
|
}
|
|
|
|
/* Every mode shows the palette screen's preview itself, at that component's own
|
|
canonical size, reduced by a single transform. Nothing inside is restated
|
|
here: a second set of sizes tuned to this smaller box is how the two screens
|
|
drifted apart in the first place. The frame is sized from the same scale, so
|
|
the reduced component fills it exactly. */
|
|
.picker-mode-preview--exact {
|
|
display: block;
|
|
position: relative;
|
|
}
|
|
|
|
.picker-mode-preview--exact > .picker-preview {
|
|
/* The demo palette every tile is drawn in. It lives here rather than inline
|
|
on each tile so the four cannot drift: the palette screen has not been
|
|
reached yet, and without it the component falls back to the picker's own
|
|
dark chrome.
|
|
|
|
These four are hex and outside DESIGN.md on purpose. They stand in for the
|
|
palette the visitor is about to pick, so they are an example rather than a
|
|
system color, which is the carve-out DESIGN.md's OKLCH-Only Rule already
|
|
names. Do not move them into DESIGN.md: that file mirrors
|
|
kinpaku-tokens.css, and admitting a teal here would make every stray teal
|
|
in the repo pass. `detect skill/scripts/picker/` reports the first three as
|
|
design-system-color and is expected to; --pv-neutral lands 4 channels from
|
|
the documented neutral-94, inside the rule's 6-channel tolerance, so it
|
|
never registers. CLAUDE.md's Picker anti-pattern gate holds the count. */
|
|
--pv-primary: #195856;
|
|
--pv-secondary: #5f7c7a;
|
|
--pv-tertiary: #3a958b;
|
|
--pv-neutral: #edefef;
|
|
--pv-n-ink: var(--pk-ink-dark);
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: var(--pk-pv-natural);
|
|
transform: scale(var(--pk-pv-scale));
|
|
transform-origin: top left;
|
|
}
|
|
|
|
/* ── Operate variant of the preview ──────────────────────────
|
|
Same shell, same nav, same phone chrome as the landing tile. The body is an
|
|
app screen instead: a section rail, a work area holding a chart above a
|
|
selectable list, and a settings panel of fields and switches. Sizes come from
|
|
measuring the reference illustration and mapping its card box onto this one,
|
|
the same way the docs variant below was built. */
|
|
.picker-preview--ops {
|
|
--po-ink: var(--pv-bone);
|
|
--po-ink-soft: color-mix(in oklab, var(--pv-bone) 72%, var(--pv-neutral));
|
|
--po-wash: color-mix(in oklab, var(--pv-tertiary) 12%, var(--pv-neutral));
|
|
--po-rule: color-mix(in oklab, var(--pv-neutral) 86%, var(--pv-n-ink));
|
|
}
|
|
|
|
.picker-preview--ops .pv-nav {
|
|
border-bottom: 1px solid var(--po-rule);
|
|
}
|
|
|
|
/* Both bodies are size containers so the screen inside them scales with the
|
|
card rather than spilling out of it. Every measure below is a canonical pixel
|
|
read off the reference illustration, divided by the body height it sits in:
|
|
181.78 on the desktop, 133.77 inside the phone's padded box. Vertical ones
|
|
spend that count in `cqh`, so a taller card is a taller page. Horizontal ones
|
|
spend it in `--pv-x`, so the same count holds when the frame's proportions
|
|
move: the width of a column and the thickness of a bar are not the frame's
|
|
height to give away. The chrome all four variants share (nav, phone top bar,
|
|
tab bar) is counted on the cards themselves, above. */
|
|
.po-body {
|
|
container-type: size;
|
|
min-height: 0;
|
|
display: grid;
|
|
/* Percentages, not a container unit: a container cannot measure itself, and
|
|
the three panels are horizontal dimensions anyway. */
|
|
grid-template-columns: 18.68% 51.84% minmax(0, 1fr);
|
|
/* One canonical measure counted against this body's width rather than its
|
|
height: 1 / 2.061974, this box's width over its height at the component's
|
|
authored 16 / 7, measured on the built picker. Every horizontal measure is
|
|
then a fixed fraction of the width it has to fit inside, so no column can
|
|
be squeezed out and no row can reach past the card no matter what the frame
|
|
does with its height. Each body is a different box, so each restates it.
|
|
|
|
The count is what the reference illustration was measured in, so a value
|
|
stays comparable to its vertical neighbours: `13.23` is the same 13.23 in
|
|
both axes. */
|
|
--pv-x: 0.484972cqw;
|
|
}
|
|
|
|
.po-side {
|
|
display: grid;
|
|
align-content: start;
|
|
gap: 5.25cqh;
|
|
padding: 6.38cqh calc(2.86 * var(--pv-x)) 0 calc(2.22 * var(--pv-x));
|
|
border-right: 1px solid var(--po-rule);
|
|
}
|
|
|
|
.po-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: calc(3.39 * var(--pv-x));
|
|
padding-left: calc(2.84 * var(--pv-x));
|
|
}
|
|
|
|
.po-dot {
|
|
flex: none;
|
|
width: calc(4.07 * var(--pv-x));
|
|
aspect-ratio: 1;
|
|
background: var(--po-ink);
|
|
border-radius: 50%;
|
|
}
|
|
|
|
.po-line {
|
|
width: calc(13.23 * var(--pv-x));
|
|
height: calc(1.53 * var(--pv-text) * var(--pv-x));
|
|
background: var(--po-ink);
|
|
border-radius: 2px;
|
|
}
|
|
|
|
/* The open section is a filled plate, not a tinted row: it is the mark that
|
|
tells a reader this rail belongs to an app and not to a document. */
|
|
.po-item--on {
|
|
padding-block: 2.97cqh;
|
|
background: var(--pv-primary);
|
|
border-radius: 2px;
|
|
}
|
|
|
|
.po-item--on .po-dot,
|
|
.po-item--on .po-line {
|
|
background: var(--pv-neutral);
|
|
}
|
|
|
|
.po-main {
|
|
min-width: 0;
|
|
display: grid;
|
|
align-content: start;
|
|
padding: 8.14cqh calc(4.68 * var(--pv-x)) 0 calc(5.23 * var(--pv-x));
|
|
border-right: 1px solid var(--po-rule);
|
|
}
|
|
|
|
/* The bars stand on the content box and the axis rules it, so nothing can come
|
|
off the baseline at any window width. The padding below the axis is the zone
|
|
the category ticks hang in: they are positioned against the chart rather than
|
|
against a bar so no element ends up with a scrolling overflow. */
|
|
.po-chart {
|
|
position: relative;
|
|
display: flex;
|
|
align-items: flex-end;
|
|
gap: calc(7.23 * var(--pv-x));
|
|
height: 33.26cqh;
|
|
margin-inline: calc(9.52 * var(--pv-x)) calc(14.63 * var(--pv-x));
|
|
/* The bottom is the tick zone rather than page rhythm, so it is measured with
|
|
the ticks it holds. */
|
|
padding: 0 calc(4.29 * var(--pv-x)) calc(2.56 * var(--pv-x)) calc(4.46 * var(--pv-x));
|
|
}
|
|
|
|
.po-chart::before {
|
|
content: "";
|
|
position: absolute;
|
|
right: 0;
|
|
bottom: calc(2.56 * var(--pv-x));
|
|
left: 0;
|
|
height: 1px;
|
|
background: var(--po-ink-soft);
|
|
}
|
|
|
|
.po-chart i {
|
|
flex: none;
|
|
width: calc(6.92 * var(--pv-x));
|
|
height: var(--h);
|
|
background: var(--po-wash);
|
|
}
|
|
|
|
.po-chart .po-peak {
|
|
background: var(--pv-primary);
|
|
}
|
|
|
|
/* No `left`, so the tick takes the static position it would have had inside its
|
|
bar and the margin walks it to that bar's centre. */
|
|
.po-chart i::after {
|
|
content: "";
|
|
position: absolute;
|
|
bottom: 0;
|
|
width: 1px;
|
|
height: calc(1.29 * var(--pv-x));
|
|
margin-left: calc(3.19 * var(--pv-x));
|
|
background: var(--po-ink-soft);
|
|
}
|
|
|
|
.po-divider {
|
|
height: 1px;
|
|
margin-top: 6.46cqh;
|
|
background: var(--po-rule);
|
|
}
|
|
|
|
.po-rows {
|
|
display: grid;
|
|
gap: 0.5cqh;
|
|
margin-top: 0.85cqh;
|
|
}
|
|
|
|
.po-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: calc(4.9 * var(--pv-x));
|
|
height: 11.61cqh;
|
|
padding-left: calc(3.41 * var(--pv-x));
|
|
}
|
|
|
|
.po-row .po-dot {
|
|
width: calc(3.85 * var(--pv-x));
|
|
}
|
|
|
|
/* The column widths differ per cell, so they arrive on the element. Only the
|
|
count does: a container unit reached through a second custom property is
|
|
dropped when it arrives in an inline style, which left every cell here at
|
|
auto width and so at nothing, since a cell has no content to be wide for.
|
|
Spending the unit in the sheet keeps the drawn width and leaves the markup
|
|
carrying the same counts the rest of the drawing is authored in. */
|
|
.po-cell {
|
|
flex: none;
|
|
width: calc(var(--w) * var(--pv-x));
|
|
height: calc(1.45 * var(--pv-text) * var(--pv-x));
|
|
background: var(--po-ink-soft);
|
|
border-radius: 2px;
|
|
}
|
|
|
|
/* The reference's two column gaps differ by less than half a canonical pixel,
|
|
so one step carries both. */
|
|
.po-cell + .po-cell {
|
|
margin-left: calc(12.71 * var(--pv-x));
|
|
}
|
|
|
|
.po-row--on {
|
|
background: var(--po-wash);
|
|
}
|
|
|
|
.po-row--on .po-dot {
|
|
background: var(--pv-primary);
|
|
}
|
|
|
|
.po-panel {
|
|
min-width: 0;
|
|
display: grid;
|
|
align-content: start;
|
|
padding: 6.82cqh calc(6.71 * var(--pv-x)) 0 calc(7.21 * var(--pv-x));
|
|
}
|
|
|
|
.po-heading {
|
|
width: calc(17.38 * var(--pv-x));
|
|
height: calc(1.62 * var(--pv-text) * var(--pv-x));
|
|
background: var(--pv-primary);
|
|
border-radius: 2px;
|
|
}
|
|
|
|
/* The controls in this panel are drawn objects rather than page rhythm, so the
|
|
field, the switches and the toggles keep their measured size while the
|
|
margins between them are what a taller card spends its extra height on. */
|
|
.po-field {
|
|
display: flex;
|
|
align-items: center;
|
|
height: calc(8.75 * var(--pv-x));
|
|
margin-top: 5.81cqh;
|
|
border: 1px solid var(--pv-primary);
|
|
border-radius: 2px;
|
|
}
|
|
|
|
/* A square stood on its corner, two borders lit. Half of it falls outside the
|
|
drawn chevron, so the margins are placing the tip rather than the box. */
|
|
.po-chev {
|
|
align-self: start;
|
|
width: calc(2 * var(--pv-x));
|
|
aspect-ratio: 1;
|
|
margin: calc(1.86 * var(--pv-x)) calc(3.14 * var(--pv-x)) 0 auto;
|
|
border-right: 1px solid var(--po-ink);
|
|
border-bottom: 1px solid var(--po-ink);
|
|
transform: rotate(45deg);
|
|
}
|
|
|
|
.po-switch {
|
|
display: flex;
|
|
align-items: center;
|
|
height: calc(5.34 * var(--pv-x));
|
|
margin-top: 8.39cqh;
|
|
}
|
|
|
|
.po-switch + .po-switch {
|
|
margin-top: 6.19cqh;
|
|
}
|
|
|
|
.po-label {
|
|
flex: none;
|
|
width: calc(17.22 * var(--pv-x));
|
|
height: calc(1.62 * var(--pv-text) * var(--pv-x));
|
|
background: var(--po-ink);
|
|
border-radius: 2px;
|
|
}
|
|
|
|
.po-toggle {
|
|
position: relative;
|
|
flex: none;
|
|
width: calc(10.29 * var(--pv-x));
|
|
height: calc(5.34 * var(--pv-x));
|
|
margin-left: auto;
|
|
background: var(--po-ink);
|
|
border-radius: 999px;
|
|
}
|
|
|
|
.po-toggle--on {
|
|
background: var(--pv-primary);
|
|
}
|
|
|
|
.po-toggle::after {
|
|
content: "";
|
|
position: absolute;
|
|
top: calc(0.66 * var(--pv-x));
|
|
left: calc(0.66 * var(--pv-x));
|
|
width: calc(4.02 * var(--pv-x));
|
|
aspect-ratio: 1;
|
|
background: var(--pv-neutral);
|
|
border-radius: 50%;
|
|
}
|
|
|
|
.po-toggle--on::after {
|
|
right: calc(0.66 * var(--pv-x));
|
|
left: auto;
|
|
}
|
|
|
|
/* The phone is the same screen narrowed: one chart, the same field, two setting
|
|
rows. Its padded box is 133.77 tall, so every shared measure above is
|
|
restated here against that number. The padding itself is a percentage of the
|
|
phone's width, since a container cannot measure itself.
|
|
|
|
The single column is stated rather than left implicit. The chart's five bars
|
|
and four gaps come to the full width of this box, and an implicit `auto`
|
|
column takes its floor from its widest item: it opens past the card and
|
|
carries the field and the setting rows out through the right edge with it. */
|
|
.po-phone-body {
|
|
container-type: size;
|
|
min-height: 0;
|
|
display: grid;
|
|
grid-template-columns: minmax(0, 1fr);
|
|
align-content: start;
|
|
padding: 16.19% 4.57% 14.63% 4.27%;
|
|
--pv-x: 1.457438cqw;
|
|
}
|
|
|
|
.po-phone-body .po-chart {
|
|
gap: calc(6.79 * var(--pv-x));
|
|
height: 36.93cqh;
|
|
margin-inline: calc(0.67 * var(--pv-x)) calc(2.92 * var(--pv-x));
|
|
padding: 0 calc(3.51 * var(--pv-x)) calc(3.36 * var(--pv-x)) calc(3.36 * var(--pv-x));
|
|
}
|
|
|
|
.po-phone-body .po-chart::before {
|
|
bottom: calc(3.36 * var(--pv-x));
|
|
}
|
|
|
|
.po-phone-body .po-chart i {
|
|
width: calc(6.21 * var(--pv-x));
|
|
}
|
|
|
|
.po-phone-body .po-chart i::after {
|
|
height: calc(1.94 * var(--pv-x));
|
|
margin-left: calc(2.73 * var(--pv-x));
|
|
}
|
|
|
|
.po-phone-body .po-field {
|
|
height: calc(11.81 * var(--pv-x));
|
|
margin-top: 10.47cqh;
|
|
}
|
|
|
|
.po-phone-body .po-chev {
|
|
width: calc(2.77 * var(--pv-x));
|
|
margin: calc(2.65 * var(--pv-x)) calc(5.03 * var(--pv-x)) 0 auto;
|
|
}
|
|
|
|
.po-prow {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: calc(4.49 * var(--pv-x));
|
|
height: 18.47cqh;
|
|
padding-left: calc(2.24 * var(--pv-x));
|
|
}
|
|
|
|
.po-field + .po-prow {
|
|
margin-top: 3.85cqh;
|
|
}
|
|
|
|
.po-prow + .po-prow {
|
|
border-top: 1px solid var(--po-rule);
|
|
}
|
|
|
|
.po-phone-body .po-dot {
|
|
width: calc(5.76 * var(--pv-x));
|
|
}
|
|
|
|
.po-phone-body .po-label {
|
|
width: calc(18.62 * var(--pv-x));
|
|
height: calc(2.17 * var(--pv-x));
|
|
}
|
|
|
|
.po-phone-body .po-toggle {
|
|
width: calc(12.64 * var(--pv-x));
|
|
height: calc(7.25 * var(--pv-x));
|
|
}
|
|
|
|
.po-phone-body .po-toggle::after {
|
|
top: calc(0.9 * var(--pv-x));
|
|
left: calc(0.9 * var(--pv-x));
|
|
width: calc(5.46 * var(--pv-x));
|
|
}
|
|
|
|
.po-phone-body .po-toggle--on::after {
|
|
right: calc(0.9 * var(--pv-x));
|
|
left: auto;
|
|
}
|
|
|
|
/* The row that opens a subpage points instead of switching: same square, the
|
|
other two borders lit. */
|
|
.po-phone-body .po-chev--next {
|
|
align-self: center;
|
|
width: calc(2.99 * var(--pv-x));
|
|
margin: 0 calc(4.86 * var(--pv-x)) 0 auto;
|
|
border-top: 1px solid var(--po-ink);
|
|
border-bottom: 0;
|
|
}
|
|
|
|
/* ── Docs variant of the preview ─────────────────────────────
|
|
Same shell as the landing preview, same nav, same phone chrome, so all four
|
|
tiles read as one drawing system. The body is a documentation page instead:
|
|
a section rail beside a measured column of prose, and a called-out note at
|
|
the end of it. Sizes are canonical pixels, matching how the rest of the
|
|
component is authored, and the proportions come from measuring the reference
|
|
illustration and mapping its card box onto this one. */
|
|
.picker-preview--docs {
|
|
--pd-ink: var(--pv-bone);
|
|
--pd-strong: color-mix(in oklab, var(--pv-primary) 80%, var(--pv-strong));
|
|
--pd-rule: color-mix(in oklab, var(--pv-neutral) 86%, var(--pv-n-ink));
|
|
}
|
|
|
|
.picker-preview--docs .pv-nav {
|
|
border-bottom: 1px solid var(--pd-rule);
|
|
}
|
|
|
|
/* Both bodies are size containers so the page inside them scales with the card
|
|
rather than spilling out of it. Every measure below is a canonical pixel
|
|
divided by the body height it sits in: 181.78 on the desktop, 146.5 inside
|
|
the phone. Vertical ones spend that count in `cqh`, so a taller card is a
|
|
longer article rather than a magnified one, and horizontal ones spend it in
|
|
`--pv-x` against the width, as the ops variant above sets out. The chrome
|
|
around both bodies is counted on the cards themselves. */
|
|
.pd-body {
|
|
container-type: size;
|
|
min-height: 0;
|
|
display: grid;
|
|
/* A percentage, not a container unit: a container cannot measure itself, and
|
|
the rail is a horizontal dimension anyway. */
|
|
grid-template-columns: 23.75% minmax(0, 1fr);
|
|
--pv-x: 0.484972cqw;
|
|
}
|
|
|
|
.pd-side {
|
|
display: grid;
|
|
align-content: start;
|
|
gap: 8.69cqh;
|
|
padding: 9.24cqh 0 0 calc(7.15 * var(--pv-x));
|
|
border-right: 1px solid var(--pd-rule);
|
|
}
|
|
|
|
.pd-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: calc(3.03 * var(--pv-x));
|
|
}
|
|
|
|
.pd-item i {
|
|
width: calc(2.48 * var(--pv-x));
|
|
aspect-ratio: 1;
|
|
background: var(--pd-ink);
|
|
border-radius: 50%;
|
|
}
|
|
|
|
.pd-item span {
|
|
width: calc(19.36 * var(--pv-x));
|
|
height: calc(1.27 * var(--pv-text) * var(--pv-x));
|
|
background: var(--pd-ink);
|
|
border-radius: 2px;
|
|
}
|
|
|
|
/* The open section reads in the palette rather than in ink, so the rail shows
|
|
where the reader is without needing a highlight plate. */
|
|
.pd-item--on i {
|
|
background: var(--pv-primary);
|
|
}
|
|
|
|
.pd-item--on span {
|
|
background: var(--pv-tertiary);
|
|
}
|
|
|
|
/* The right padding is the measure: prose stops well short of the card edge,
|
|
which is what makes the column read as a document rather than as a page. */
|
|
.pd-main {
|
|
min-width: 0;
|
|
display: grid;
|
|
align-content: start;
|
|
padding: 9.74cqh calc(50.06 * var(--pv-x)) 0 calc(16.94 * var(--pv-x));
|
|
}
|
|
|
|
.pd-heading {
|
|
width: var(--w, 100%);
|
|
height: calc(2.53 * var(--pv-text) * var(--pv-x));
|
|
background: var(--pd-strong);
|
|
border-radius: 2px;
|
|
}
|
|
|
|
.pd-para {
|
|
display: grid;
|
|
gap: 3.74cqh;
|
|
}
|
|
|
|
.pd-para--lede {
|
|
margin-top: 6.38cqh;
|
|
}
|
|
|
|
/* Two passage gaps, not one: the reference opens a slightly narrower gap after
|
|
the lede than it does between the passages that follow. */
|
|
.pd-para + .pd-para {
|
|
margin-top: 9.9cqh;
|
|
}
|
|
|
|
.pd-para--lede + .pd-para {
|
|
margin-top: 9.46cqh;
|
|
}
|
|
|
|
.pd-para i {
|
|
width: var(--w, 100%);
|
|
height: calc(1.1 * var(--pv-text) * var(--pv-x));
|
|
background: var(--pd-ink);
|
|
border-radius: 2px;
|
|
}
|
|
|
|
.pd-note {
|
|
display: flex;
|
|
gap: calc(3.41 * var(--pv-x));
|
|
height: 14.03cqh;
|
|
margin-top: 7.37cqh;
|
|
padding: 3.52cqh calc(4.4 * var(--pv-x)) 0 calc(5.17 * var(--pv-x));
|
|
background: var(--pv-surface-2);
|
|
border-left: calc(1.27 * var(--pv-x)) solid var(--pv-primary);
|
|
border-radius: 0 2px 2px 0;
|
|
}
|
|
|
|
.pd-note-dot {
|
|
flex: none;
|
|
align-self: start;
|
|
width: calc(2.64 * var(--pv-x));
|
|
aspect-ratio: 1;
|
|
background: var(--pv-tertiary);
|
|
border-radius: 50%;
|
|
}
|
|
|
|
.pd-note-lines {
|
|
min-width: 0;
|
|
flex: 1;
|
|
display: grid;
|
|
gap: 3.69cqh;
|
|
align-content: start;
|
|
}
|
|
|
|
.pd-note-lines i {
|
|
width: var(--w, 100%);
|
|
height: calc(0.83 * var(--pv-text) * var(--pv-x));
|
|
background: var(--pv-tertiary);
|
|
border-radius: 2px;
|
|
}
|
|
|
|
/* The phone is the same document narrowed: breadcrumb, heading, the same two
|
|
passages, the same note. */
|
|
/* The phone body's own padding is a percentage of the phone's width, since a
|
|
container cannot measure itself. Everything inside it is measured against the
|
|
padded box, in `cqh` down the page and in `--pv-x` across it. The single
|
|
column is stated so a child that asks for more width than the box has cannot
|
|
open the column and carry its siblings out through the card edge. */
|
|
.pd-phone-body {
|
|
container-type: size;
|
|
min-height: 0;
|
|
display: grid;
|
|
grid-template-columns: minmax(0, 1fr);
|
|
align-content: start;
|
|
padding: 8.84% 11.42% 9.33% 10.23%;
|
|
--pv-x: 1.857cqw;
|
|
}
|
|
|
|
.pd-crumb {
|
|
width: calc(13.11 * var(--pv-x));
|
|
height: calc(1.77 * var(--pv-x));
|
|
margin-inline: auto;
|
|
background: var(--pd-ink);
|
|
border-radius: 2px;
|
|
}
|
|
|
|
.pd-phone-body .pd-heading {
|
|
height: calc(3.14 * var(--pv-x));
|
|
margin-top: 9.62cqh;
|
|
}
|
|
|
|
.pd-phone-body .pd-para {
|
|
gap: 4.1cqh;
|
|
}
|
|
|
|
.pd-phone-body .pd-para i {
|
|
height: calc(1.37 * var(--pv-x));
|
|
}
|
|
|
|
.pd-phone-body .pd-para--lede {
|
|
margin-top: 6.55cqh;
|
|
gap: 3.62cqh;
|
|
}
|
|
|
|
.pd-phone-body .pd-para + .pd-para {
|
|
margin-top: 10.03cqh;
|
|
}
|
|
|
|
.pd-phone-body .pd-para--lede + .pd-para {
|
|
margin-top: 11.4cqh;
|
|
}
|
|
|
|
.pd-phone-body .pd-note {
|
|
height: 14.27cqh;
|
|
gap: calc(3.34 * var(--pv-x));
|
|
margin-top: 6.76cqh;
|
|
padding: 4.37cqh calc(1.37 * var(--pv-x)) 0 calc(3.14 * var(--pv-x));
|
|
border-left-width: calc(1.23 * var(--pv-x));
|
|
}
|
|
|
|
.pd-phone-body .pd-note-dot {
|
|
width: calc(3.34 * var(--pv-x));
|
|
}
|
|
|
|
.pd-phone-body .pd-note-lines {
|
|
gap: 2.8cqh;
|
|
}
|
|
|
|
.pd-phone-body .pd-note-lines i {
|
|
height: calc(1.37 * var(--pv-x));
|
|
}
|
|
|
|
/* ── Gallery variant of the preview ──────────────────────────
|
|
Same shell, same nav, same phone chrome as the other tiles. The body is a
|
|
portfolio index instead: two shots set alternately against their captions,
|
|
and a carousel rail under them saying the set continues. Sizes come from
|
|
measuring the reference illustration and mapping its card box onto this one,
|
|
the same way the docs variant was built.
|
|
|
|
Both bodies are size containers so the page inside scales with the card
|
|
rather than spilling out of it. Every measure below is a canonical pixel
|
|
divided by the body height it sits in: 182.78 on the desktop, 148.53 inside
|
|
the phone's padded box. Vertical ones spend that count in `cqh`, which is
|
|
what makes a taller card show taller plates rather than a zoomed index, and
|
|
horizontal ones spend it in `--pv-x` against the width, as the ops variant
|
|
above sets out. The chrome around both bodies is counted on the cards
|
|
themselves. */
|
|
.picker-preview--gallery {
|
|
--pg-ink: var(--pv-bone);
|
|
}
|
|
|
|
/* The work is the page, so the plates carry the drawn size and the shared
|
|
4 / 3 gives way to it. */
|
|
.picker-preview--gallery .pv-image {
|
|
aspect-ratio: auto;
|
|
}
|
|
|
|
/* The illustration marks each shot in the brand color rather than the accent,
|
|
which is what ties the two rows back to the nav. */
|
|
/* The mark is a fixed inset from the plate's own corner, so both insets are
|
|
measured with it rather than with the page. */
|
|
.picker-preview--gallery .pv-image::after {
|
|
top: calc(3.41 * var(--pv-x));
|
|
right: calc(3.15 * var(--pv-x));
|
|
width: calc(3.37 * var(--pv-x));
|
|
background: var(--pv-primary);
|
|
}
|
|
|
|
.pg-body {
|
|
container-type: size;
|
|
min-height: 0;
|
|
display: grid;
|
|
align-content: start;
|
|
--pv-x: 0.48764cqw;
|
|
}
|
|
|
|
/* The insets differ per row because the illustration staggers the pair rather
|
|
than mirroring it, which is what keeps the index from reading as a grid.
|
|
The plate is the drawn column and the caption takes what is left, so a frame
|
|
that changes shape spends it on the plate's height and never on the caption's
|
|
width. */
|
|
.pg-row {
|
|
display: grid;
|
|
grid-template-columns: calc(73.39 * var(--pv-x)) minmax(0, 1fr);
|
|
column-gap: calc(10.55 * var(--pv-x));
|
|
height: 37.31cqh;
|
|
margin-top: 2.31cqh;
|
|
padding-inline: calc(31.34 * var(--pv-x)) calc(32.13 * var(--pv-x));
|
|
}
|
|
|
|
.pg-row--flip {
|
|
grid-template-columns: minmax(0, 1fr) calc(73.39 * var(--pv-x));
|
|
column-gap: calc(11.18 * var(--pv-x));
|
|
margin-top: 7.28cqh;
|
|
padding-inline: calc(34.97 * var(--pv-x)) calc(33.07 * var(--pv-x));
|
|
}
|
|
|
|
.pg-row .pv-image {
|
|
height: 100%;
|
|
}
|
|
|
|
.pg-cap {
|
|
display: grid;
|
|
align-content: start;
|
|
gap: 3.1cqh;
|
|
padding-top: 12.39cqh;
|
|
}
|
|
|
|
.pg-cap i {
|
|
width: var(--w, 100%);
|
|
height: calc(1.5 * var(--pv-text) * var(--pv-x));
|
|
background: var(--pg-ink);
|
|
border-radius: 2px;
|
|
}
|
|
|
|
.pg-cap i.pg-cap-title {
|
|
height: calc(2.01 * var(--pv-text) * var(--pv-x));
|
|
margin-bottom: 0.93cqh;
|
|
}
|
|
|
|
/* The insets are measured to the chevrons' drawn tips, which sit outside their
|
|
layout boxes once the squares below are turned. The rail's own height is the
|
|
arrows it holds rather than a share of the page, so a taller card gives its
|
|
room to the plates above and leaves this strip alone. */
|
|
.pg-rail {
|
|
display: flex;
|
|
align-items: center;
|
|
height: calc(3.25 * var(--pv-x));
|
|
margin-top: 4.95cqh;
|
|
padding-inline: calc(7.78 * var(--pv-x)) calc(8.24 * var(--pv-x));
|
|
}
|
|
|
|
/* A square stood on its corner: two borders draw the chevron without pulling
|
|
in an inline SVG nothing else in the component uses. */
|
|
.pg-arrow {
|
|
flex: none;
|
|
width: calc(2.3 * var(--pv-x));
|
|
aspect-ratio: 1;
|
|
border-left: 1px solid var(--pg-ink);
|
|
border-bottom: 1px solid var(--pg-ink);
|
|
transform: rotate(45deg);
|
|
}
|
|
|
|
.pg-arrow--next {
|
|
border-right: 1px solid var(--pg-ink);
|
|
border-top: 1px solid var(--pg-ink);
|
|
border-bottom: 0;
|
|
border-left: 0;
|
|
}
|
|
|
|
.pg-track {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: calc(3.7 * var(--pv-x));
|
|
margin-left: calc(7.26 * var(--pv-x));
|
|
margin-right: auto;
|
|
}
|
|
|
|
/* Segment widths arrive as counts for the reason the table cells above do. */
|
|
.pg-track i {
|
|
flex: none;
|
|
width: calc(var(--w) * var(--pv-x));
|
|
height: calc(1.48 * var(--pv-x));
|
|
background: var(--pg-ink);
|
|
border-radius: 2px;
|
|
}
|
|
|
|
.pg-track i.pg-track-on {
|
|
background: var(--pv-primary);
|
|
}
|
|
|
|
/* The phone is the same index at one column: shot, caption, shot, caption. */
|
|
/* Its own padding is a percentage of the phone's width, since a container
|
|
cannot measure itself. Everything inside is measured against the padded box,
|
|
in `cqh` down the page and in `--pv-x` across it, and the single column is
|
|
stated so no child can open it wider than the phone. */
|
|
.pg-phone-body {
|
|
container-type: size;
|
|
min-height: 0;
|
|
display: grid;
|
|
grid-template-columns: minmax(0, 1fr);
|
|
align-content: start;
|
|
padding: 5.65% 8.34% 10.49%;
|
|
--pv-x: 1.770721cqw;
|
|
}
|
|
|
|
.pg-phone-body .pv-image {
|
|
height: 33.27cqh;
|
|
}
|
|
|
|
.pg-phone-body > .pg-cap + .pv-image {
|
|
height: 31.95cqh;
|
|
margin-top: 5.59cqh;
|
|
}
|
|
|
|
/* The phone container is shorter than the desktop one, so every shared measure
|
|
has to be restated against it. */
|
|
.pg-phone-body .pv-image::after {
|
|
top: calc(3.02 * var(--pv-x));
|
|
right: calc(3.26 * var(--pv-x));
|
|
width: calc(3.4 * var(--pv-x));
|
|
}
|
|
|
|
.pg-phone-body .pg-cap {
|
|
gap: 2.93cqh;
|
|
margin-top: 4.15cqh;
|
|
padding-top: 0;
|
|
}
|
|
|
|
.pg-phone-body > .pg-cap ~ .pg-cap {
|
|
margin-top: 3.68cqh;
|
|
}
|
|
|
|
.pg-phone-body .pg-cap i {
|
|
height: calc(1.52 * var(--pv-x));
|
|
}
|
|
|
|
/* At this size the caption's first line is a shorter line, not a heavier one. */
|
|
.pg-phone-body .pg-cap i.pg-cap-title {
|
|
height: calc(1.52 * var(--pv-x));
|
|
margin-bottom: 0.28cqh;
|
|
}
|
|
|
|
/* Three page dots rather than four tabs, closer together than the tab bar's
|
|
even spread. */
|
|
.picker-preview--gallery .pv-tabbar {
|
|
justify-content: center;
|
|
gap: calc(5.12149 * var(--pv-x));
|
|
}
|
|
|
|
.picker-mode-copy {
|
|
display: grid;
|
|
gap: 10px;
|
|
align-content: start;
|
|
}
|
|
|
|
.picker-mode-goal {
|
|
color: var(--ks-text-muted);
|
|
font-size: 16px;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
/* The examples name the tile now that the mode word is gone, so they lead the
|
|
copy block and carry its strongest weight. Sized to keep a four-chip row
|
|
within two lines at the narrowest tile the grid produces. */
|
|
.picker-mode-pills {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 6px;
|
|
}
|
|
|
|
.picker-mode-pills i {
|
|
padding: 4px 9px;
|
|
color: var(--ks-champagne);
|
|
font-size: 12.5px;
|
|
font-style: normal;
|
|
letter-spacing: 0.015em;
|
|
border: 1px solid color-mix(in oklab, var(--ks-champagne) 24%, var(--ks-rule));
|
|
border-radius: 2px;
|
|
transition:
|
|
color 180ms var(--ks-ease),
|
|
border-color 180ms var(--ks-ease);
|
|
}
|
|
|
|
.picker-mode-tile:hover .picker-mode-pills i {
|
|
border-color: color-mix(in oklab, var(--ks-patina) 30%, var(--ks-rule));
|
|
}
|
|
|
|
.picker-mode-tile:has(input:checked) .picker-mode-pills i {
|
|
color: color-mix(in oklab, var(--ks-patina) 85%, var(--ks-champagne));
|
|
border-color: color-mix(in oklab, var(--ks-patina) 55%, var(--ks-rule));
|
|
}
|
|
|
|
/* The wording here is doing careful work: every preview in the interview is
|
|
an isolating instrument, not a sketch of the deliverable, and this is the
|
|
one place the visitor is told so before meeting the rest of them. */
|
|
.picker-modes-note {
|
|
max-width: 52ch;
|
|
color: var(--ks-text-muted);
|
|
font-size: 1.08rem;
|
|
line-height: 1.5;
|
|
text-align: center;
|
|
text-wrap: balance;
|
|
}
|
|
|
|
.picker-modes-note strong,
|
|
.picker-mode-goal strong {
|
|
color: var(--ks-text);
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* Screen 03 mirrors screen 02's rhythm: the same centered headline class,
|
|
the same vertical gaps, options left and preview right. */
|
|
.picker-strategy {
|
|
display: grid;
|
|
gap: 48px;
|
|
padding-block: clamp(32px, 5svh, 48px) clamp(24px, 3.5svh, 48px);
|
|
}
|
|
|
|
.picker-strategy-grid {
|
|
/* Named so the option panel can cap itself against the same budget: its
|
|
height no longer comes from the column, so nothing else would stop it. */
|
|
--pk-column: calc(100svh - 346px);
|
|
max-height: var(--pk-column);
|
|
display: grid;
|
|
grid-template-columns: minmax(340px, 420px) minmax(0, 1fr);
|
|
gap: 50px;
|
|
align-items: stretch;
|
|
}
|
|
|
|
/* One raised panel, hairline rows — same vocabulary as the palette bands
|
|
on screen 02, not four floating SaaS cards. Screen 04 reuses the base
|
|
shell; screen 03 opts into the choices treatment below. */
|
|
.picker-strategy-options {
|
|
min-width: 0;
|
|
display: grid;
|
|
gap: 0;
|
|
padding: 0;
|
|
background: var(--ks-lacquer-raised);
|
|
border: 1px solid var(--ks-rule);
|
|
border-radius: 2px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.picker-strategy-options > legend,
|
|
.picker-strategy-option input {
|
|
position: absolute;
|
|
width: 1px;
|
|
height: 1px;
|
|
overflow: hidden;
|
|
clip: rect(0 0 0 0);
|
|
clip-path: inset(50%);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.picker-strategy-option {
|
|
position: relative;
|
|
display: grid;
|
|
gap: 6px;
|
|
align-items: start;
|
|
padding: 20px 24px 20px 28px;
|
|
background: transparent;
|
|
border: 0;
|
|
border-bottom: 1px solid var(--ks-rule);
|
|
border-radius: 0;
|
|
cursor: pointer;
|
|
transition:
|
|
background-color 180ms var(--ks-ease),
|
|
color 180ms var(--ks-ease);
|
|
}
|
|
|
|
.picker-strategy-option:last-child {
|
|
border-bottom: 0;
|
|
}
|
|
|
|
/* ============================================================
|
|
The standard option row. Every list of choices in the picker wears this
|
|
and nothing else: a patina spine that previews under the cursor and fills
|
|
in once the answer is committed, a wash of the same patina behind the row
|
|
at two strengths, and the row's own heading taking the patina in both
|
|
states. It came from screen 04's type rail; screens 03 and 05 read these
|
|
same rules.
|
|
|
|
A new question brings its own copy scale and its own row content. It does
|
|
not bring its own state colors, its own spine, or its own hover. If a
|
|
screen appears to need a different treatment, change it here for all of
|
|
them rather than adding a fourth variant.
|
|
============================================================ */
|
|
.picker-strategy-option::before {
|
|
content: "";
|
|
position: absolute;
|
|
inset: 0 auto 0 0;
|
|
width: 3px;
|
|
background: transparent;
|
|
transition: background-color 180ms var(--ks-ease);
|
|
}
|
|
|
|
/* Mixes are keyed to --ks-patina, not --ks-patina-deep: the deep oxide sits at
|
|
49% lightness and a single-digit mix of it into an 11% panel is invisible. */
|
|
.picker-strategy-option:hover {
|
|
background: color-mix(in oklab, var(--ks-patina) 9%, var(--ks-lacquer-raised));
|
|
}
|
|
|
|
.picker-strategy-option:hover::before {
|
|
background: color-mix(in oklab, var(--ks-patina) 55%, transparent);
|
|
}
|
|
|
|
.picker-strategy-option:has(input:checked) {
|
|
background: color-mix(in oklab, var(--ks-patina) 14%, var(--ks-lacquer-raised));
|
|
}
|
|
|
|
.picker-strategy-option:has(input:checked)::before {
|
|
background: var(--ks-patina);
|
|
}
|
|
|
|
/* Whatever names the row is what takes the accent: a strategy label, a scale
|
|
name, or the face a font pair is set in. */
|
|
.picker-strategy-option:hover :is(.picker-strategy-title, .picker-type-sample-heading),
|
|
.picker-strategy-option:has(input:checked) :is(.picker-strategy-title, .picker-type-sample-heading) {
|
|
color: var(--ks-patina);
|
|
}
|
|
|
|
.picker-strategy-option:has(input:focus-visible) {
|
|
outline: 2px solid var(--ks-patina-deep);
|
|
outline-offset: -2px;
|
|
z-index: 1;
|
|
}
|
|
|
|
.picker-strategy-copy {
|
|
display: grid;
|
|
gap: 6px;
|
|
}
|
|
|
|
/* Scoped to the copy block the two were drawn for. Both classes are also the
|
|
handles the per-surface machinery reads a row's name and its description
|
|
through, and the font rail names itself in the pair's own faces, so away from
|
|
this block they are hooks and carry no design of their own. */
|
|
.picker-strategy-copy > .picker-strategy-title {
|
|
color: var(--ks-champagne);
|
|
font-family: var(--ks-font-display);
|
|
font-size: 1.22rem;
|
|
font-weight: 400;
|
|
line-height: 1.15;
|
|
letter-spacing: -0.01em;
|
|
transition: color 180ms var(--ks-ease);
|
|
}
|
|
|
|
.picker-strategy-copy > .picker-strategy-desc {
|
|
color: var(--ks-text-muted);
|
|
font-size: 0.88rem;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
/* Screen 03 uses the same rail shell as screen 04: scrollable fieldset inside
|
|
a height-capped column. Row rhythm, hover, and checked states come from the
|
|
standard option row above; only the copy scale differs, because these are
|
|
strategy labels and not font specimens. Four rows share the preview column's
|
|
height equally so a one-line description does not leave a short band beside
|
|
a taller neighbor. */
|
|
.picker-strategy-grid > .picker-type-rail {
|
|
min-height: 0;
|
|
height: 100%;
|
|
grid-template-rows: minmax(0, 1fr);
|
|
}
|
|
|
|
/* Screen 03 carries the palette under its choices and the buttons under its
|
|
frame, and the two pairs sit on the same two rows: the choices beside the
|
|
frame, then the palette beside the buttons. Both columns subgrid onto them, so
|
|
the frame's bottom edge is the options panel's bottom edge by construction
|
|
rather than by two sizing formulas agreeing. The row gap is the rail's own
|
|
former 14px, which now also sets the air between the frame and the CTA.
|
|
|
|
Everything this screen shows lives inside the grid, so what the grid may
|
|
spend is the window less whatever the block spends around it: the padding,
|
|
the question, and the gap under it. --pk-chrome is that sum, measured by
|
|
fitStrategyColumn() rather than counted here, because the question is one or
|
|
two lines at a size that tracks the window's width. The literal is the value
|
|
before the script has measured,
|
|
and it is the number this rule used to carry outright; at laptop heights it
|
|
was short enough that the screen ran past the fold.
|
|
|
|
--pk-column, which the later question screens still read, is measured for a
|
|
grid with the buttons underneath it and leaves this one 90px short of what it
|
|
now owns. */
|
|
.picker-screen[data-screen="03"] .picker-strategy-grid {
|
|
max-height: calc(100svh - var(--pk-chrome, 346px));
|
|
}
|
|
|
|
.picker-screen[data-screen="03"] .picker-strategy-grid > .picker-type-rail {
|
|
height: auto;
|
|
align-self: start;
|
|
grid-template-rows: auto auto auto;
|
|
}
|
|
|
|
.picker-strategy-choices.picker-type-options {
|
|
align-self: start;
|
|
max-height: var(--pk-column);
|
|
/* Four is the largest option count in the run, so the row is a quarter of the
|
|
column whether a screen has four of them or three, and one formula gives
|
|
every screen in the run the same band. Below 432px of column they shrink
|
|
together; the row padding absorbs it before the copy does. The 2px is the
|
|
panel's own hairline, which the column budget does not account for. */
|
|
grid-template-rows: none;
|
|
grid-auto-rows: min(108px, calc((var(--pk-column) - 2px) / 4));
|
|
}
|
|
|
|
/* Centred rather than top-aligned: whatever the row has left over after the
|
|
label and its sentence reads as air around them, not as a gap underneath. */
|
|
.picker-strategy-choices.picker-type-options .picker-strategy-option {
|
|
align-content: center;
|
|
}
|
|
|
|
.picker-strategy-choices .picker-strategy-copy {
|
|
gap: 2px;
|
|
}
|
|
|
|
.picker-strategy-choices .picker-strategy-title {
|
|
color: var(--ks-champagne);
|
|
font-family: var(--ks-font);
|
|
font-size: 24px;
|
|
font-weight: 400;
|
|
line-height: 1.15;
|
|
letter-spacing: -0.015em;
|
|
transition: color 180ms var(--ks-ease);
|
|
}
|
|
|
|
.picker-strategy-choices .picker-strategy-desc {
|
|
font-size: 15px;
|
|
line-height: 1.4;
|
|
max-width: 34ch;
|
|
}
|
|
|
|
/* The wireframe landing page every option screen previews its answer on, at
|
|
desktop and phone sizes. Each screen remaps the slots its own question owns:
|
|
color on 03, glyphs on 04, then motion, grid, dividers, radius, and shadow.
|
|
Answers travel forward, so by the last screen the artboard is showing every
|
|
decision made so far rather than one at a time.
|
|
|
|
Two of the color slots name an ink rather than a fill: a filled button's
|
|
label has to come from the fill it sits on, and which fill that is changes
|
|
with the strategy. --pv-t-ink is set alongside the palette by the picker
|
|
script; the fallback here matches the gold that stands in before a palette
|
|
lands. */
|
|
.picker-artboard {
|
|
--pv-primary: var(--ks-champagne);
|
|
--pv-secondary: var(--ks-patina);
|
|
--pv-tertiary: var(--ks-kinpaku);
|
|
--pv-neutral: var(--ks-lacquer-raised);
|
|
--pv-n-ink: var(--ks-champagne);
|
|
--pv-p-ink: var(--ks-champagne);
|
|
--pv-t-ink: var(--ks-dark-ink);
|
|
/* The accents again, moved in lightness until they can be read as words on
|
|
the ground named in the suffix. The script publishes them; these values
|
|
stand in for the run before a palette is committed, where the accents are
|
|
the picker's own and already legible. */
|
|
--pv-p-on-n: var(--pv-primary);
|
|
--pv-t-on-n: var(--pv-tertiary);
|
|
--pv-t-on-p: var(--pv-tertiary);
|
|
--pv-p-on-p: var(--pk-ink-dark);
|
|
--pv-t-on-t: var(--pk-ink-light);
|
|
--pv-p-on-i: var(--pk-ink-light);
|
|
--pv-surface-2: color-mix(in oklab, var(--pv-neutral) 92%, var(--pv-n-ink));
|
|
--pv-bone: color-mix(in oklab, var(--pv-neutral) 62%, var(--pv-n-ink));
|
|
--pv-muted: color-mix(in oklab, var(--pv-neutral) 84%, var(--pv-n-ink));
|
|
--pv-strong: color-mix(in oklab, var(--pv-neutral) 22%, var(--pv-n-ink));
|
|
--pvs-ground: var(--pv-neutral);
|
|
--pvs-ink: var(--pv-n-ink);
|
|
--pvs-surface: var(--pv-surface-2);
|
|
--pvs-headline: var(--pv-strong);
|
|
--pvs-eyebrow: var(--pv-bone);
|
|
--pvs-copy: var(--pv-muted);
|
|
--pvs-title: var(--pv-strong);
|
|
--pvs-cta: var(--pv-primary);
|
|
--pvs-cta-ink: var(--pv-p-ink);
|
|
--pvs-cta-text: var(--pv-p-on-p);
|
|
--pvs-signal: var(--pv-primary);
|
|
--pvs-signal-ink: var(--pv-p-ink);
|
|
/* Three slots the wireframe never reads: where screen 04 turns a bar into
|
|
glyphs, the slot that filled the bar is not the one that can set the
|
|
type. The mix percentages are the same idea for running copy, tightened
|
|
by whichever strategy leaves the least room between ground and ink. */
|
|
--pvs-headline-text: var(--pvs-headline);
|
|
--pvs-mark-text: var(--pv-p-on-n);
|
|
--pvs-accent-text: var(--pv-p-on-n);
|
|
--pvs-signal-text: var(--pv-p-on-p);
|
|
--pvs-copy-mix: 34%;
|
|
--pvs-bars-mix: 42%;
|
|
--pvs-ghost: color-mix(in oklab, var(--pv-neutral) 88%, var(--pv-secondary));
|
|
--pvs-ghost-edge: color-mix(in oklab, var(--pv-secondary) 55%, var(--pv-neutral));
|
|
--pvs-image: var(--pv-surface-2);
|
|
--pvs-image-a: color-mix(in oklab, var(--pv-secondary) 26%, var(--pv-neutral));
|
|
--pvs-image-b: color-mix(in oklab, var(--pv-secondary) 50%, var(--pv-neutral));
|
|
--pvs-image-edge: color-mix(in oklab, var(--pv-secondary) 58%, var(--pv-neutral));
|
|
--pvs-bars: var(--pv-muted);
|
|
--pvs-rule: var(--ks-rule);
|
|
--pvs-accent-a: var(--pv-bone);
|
|
--pvs-accent-b: var(--pv-bone);
|
|
--pvs-accent-c: var(--pv-primary);
|
|
--pvs-accent-d: var(--pv-bone);
|
|
--ps-proof-gap: 25px;
|
|
|
|
/* Geometry slots. Every default below is the value the artboard already had,
|
|
so screens 03 and 04 are unchanged by their existence. A later screen
|
|
answers its question by rewriting a handful of them.
|
|
|
|
Radius is four slots rather than one because a wireframe bar standing in
|
|
for a line of text must never round like a button: at pill radius it stops
|
|
reading as text and starts reading as a tag. So controls, surfaces, and
|
|
text bars move independently, and the dot stays a dot unless a screen
|
|
says otherwise. */
|
|
--pvs-radius-frame: 4px;
|
|
--pvs-radius-frame-phone: 5px;
|
|
--pvs-radius-surface: 2px;
|
|
--pvs-radius-control: 2px;
|
|
--pvs-radius-bar: 2px;
|
|
--pvs-radius-dot: 50%;
|
|
|
|
/* Section separation. The frame is the browser window and stays put; the
|
|
dividers are the page's own, and are what a boundary answer removes. */
|
|
--pvs-frame-w: 1px;
|
|
--pvs-frame-c: var(--pvs-rule);
|
|
--pvs-divider-w: 1px;
|
|
--pvs-divider-c: var(--pvs-rule);
|
|
|
|
/* Section grounds and element containers. Transparent by default, which is
|
|
why they cost nothing until a screen fills them. */
|
|
--pvs-band: transparent;
|
|
--pvs-band-alt: transparent;
|
|
--pvs-panel: transparent;
|
|
--pvs-panel-edge: transparent;
|
|
--pvs-panel-edge-w: 0px;
|
|
--pvs-panel-pad: 0px;
|
|
|
|
--pvs-shadow-card: none;
|
|
--pvs-shadow-control: none;
|
|
--pvs-shadow-surface: none;
|
|
--pvs-shadow-chrome: none;
|
|
|
|
/* Page grid. Gutters are percentages of the artboard, so they hold at both
|
|
sizes and at any zoom. */
|
|
--pvs-gutter: 5.5%;
|
|
--pvs-gutter-end: 3.2%;
|
|
--pvs-rows: 9.4% 50.6% 9% 22.7% 8.3%;
|
|
--pvs-nav-cols: auto 1fr auto;
|
|
--pvs-hero-cols: 35.8% minmax(0, 1fr);
|
|
--pvs-hero-gap: 11.3%;
|
|
--pvs-editorial-cols: 27.9% minmax(0, 1fr);
|
|
--pvs-editorial-gap: 6%;
|
|
--pvs-gallery-cols: 1.12fr 0.9fr 0.9fr 0.9fr;
|
|
--pvs-gallery-gap: 3%;
|
|
--pvs-section-align: center;
|
|
|
|
width: 100%;
|
|
aspect-ratio: 1.865 / 1;
|
|
display: grid;
|
|
grid-template-columns: minmax(0, 1fr) auto;
|
|
gap: 5.25%;
|
|
}
|
|
|
|
/* One artboard filling the frame, for a screen whose answer only reads at
|
|
desktop width. It keeps the paired frame's ratio rather than taking a
|
|
taller one of its own: the preview then occupies the same block on every
|
|
screen, and the CTA underneath does not move as you page through. */
|
|
.picker-artboard--solo {
|
|
aspect-ratio: 1.865 / 1;
|
|
grid-template-columns: minmax(0, 1fr);
|
|
gap: 0;
|
|
}
|
|
|
|
/* ============================================================
|
|
The strategy stage and the column it sits in. Screen 03 colors the surfaces
|
|
chosen on screen 01b, so the frame holds one drawing per chosen surface and
|
|
shows one at a time. The tabs sit on the frame's top right corner rather than
|
|
in a row of their own, and the buttons sit under it, so the column is the
|
|
whole answer to the question rather than a preview with the commitment parked
|
|
somewhere else on the page.
|
|
|
|
The frame takes the height of the row the choices set instead of a ratio of
|
|
its own, so it is wider than tall here and squarer on a short viewport. Three
|
|
of the four drawings key their horizontal measures to their own width and so
|
|
hold either way. Persuade is still drawn in pixels and sits loose in a frame
|
|
this size.
|
|
============================================================ */
|
|
.picker-strategy-stage {
|
|
min-width: 0;
|
|
min-height: 0;
|
|
display: grid;
|
|
grid-template-rows: auto minmax(0, 1fr);
|
|
}
|
|
|
|
.picker-strategy-stage > .picker-preview {
|
|
/* Every chosen surface is mounted and all but one are display: none, so the
|
|
row is named rather than left to auto-placement: a frame where two are
|
|
briefly shown puts them on top of each other instead of stacking the stage
|
|
into a third row and taking the column past the choices' bottom edge. */
|
|
grid-row: 2;
|
|
/* The frame runs down to the bottom of the choices beside it, so it takes the
|
|
row's height rather than the 16 / 7 it carries everywhere else. */
|
|
aspect-ratio: auto;
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
/* The tiles and the palette screen show the drawing at a fraction of this
|
|
size, and this stage is the one place a visitor reads the desktop card as a
|
|
page rather than as a thumbnail. At that size the tool, the document and the
|
|
index draw their body text as hairlines beside their own phone card, which
|
|
spends nearly four times the share of its card on the same count. The step is
|
|
on the thickness only, so the page behind the window is unchanged: same
|
|
measure, same rhythm, same everything the counts were measured for.
|
|
|
|
The landing page is left at its authored weight. Its counts are the largest
|
|
of the four, and at this size they already read as type; a headline bar taken
|
|
any heavier stops being display type and becomes a slab. The nav is left out
|
|
for the same kind of reason: it is the chrome all four cards share, and a
|
|
visitor moving between tabs should not watch it change weight. */
|
|
.picker-strategy-stage > .picker-preview:is(.picker-preview--ops, .picker-preview--docs, .picker-preview--gallery) .pv-desktop {
|
|
--pv-text: 1.35;
|
|
}
|
|
|
|
/* One drawing is shown and the rest stay mounted, so switching costs an
|
|
attribute rather than a rebuild. [hidden] is a display: none that the
|
|
component's own display: grid outranks, so it is restated here. */
|
|
.picker-preview[hidden] {
|
|
display: none;
|
|
}
|
|
|
|
/* The group only appears with a second surface to switch to; a single surface
|
|
is already the only thing the frame can be showing.
|
|
|
|
In the flow, on the stage's first row, right-aligned over the frame. The row
|
|
is what reserves the strip's height, so nothing downstream has to guess at it
|
|
and the question above keeps the gap every screen shares.
|
|
|
|
On the edge rather than inside the frame. Every drawing puts its nav across
|
|
the frame's top and the strip is taller than that band, so sitting inside
|
|
covers the one place the primary shows up as chrome. Outside it the strip
|
|
also keeps its own ground: raised lacquer and a hairline are read against the
|
|
page, not against a drawing repainted in whatever palette and strategy were
|
|
chosen. The 1px pull is the join, landing the strip's bottom hairline on the
|
|
frame's top one, and the z-index is what keeps that edge visible, since the
|
|
drawings raise plates of their own that would otherwise paint over it. */
|
|
.picker-surface-tabs {
|
|
position: relative;
|
|
z-index: 1;
|
|
grid-row: 1;
|
|
justify-self: end;
|
|
margin-bottom: 16px;
|
|
display: flex;
|
|
gap: 3px;
|
|
padding: 5px;
|
|
background: var(--ks-lacquer-raised);
|
|
border: 1px solid var(--ks-rule);
|
|
border-radius: 2px;
|
|
}
|
|
|
|
.picker-surface-tabs[hidden] {
|
|
display: none;
|
|
}
|
|
|
|
/* One line: the dot and the surface this tab names. The picker's chrome
|
|
control, the same 0.8rem on the same tracking as `.picker-type-custom`, since
|
|
a strip docked on a frame's corner names which drawing is showing and should
|
|
not read as the page's loudest button. */
|
|
.picker-surface-tab {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 13px;
|
|
padding: 11px 15px;
|
|
color: var(--ks-text-muted);
|
|
background: transparent;
|
|
border: 0;
|
|
border-radius: 2px;
|
|
font-family: inherit;
|
|
font-size: 1rem;
|
|
letter-spacing: 0.04em;
|
|
line-height: 1.15;
|
|
white-space: nowrap;
|
|
cursor: pointer;
|
|
transition:
|
|
background-color 180ms var(--ks-ease),
|
|
color 180ms var(--ks-ease);
|
|
}
|
|
|
|
.picker-surface-tab:hover {
|
|
color: var(--ks-champagne);
|
|
background: color-mix(in oklab, var(--ks-patina) 9%, var(--ks-lacquer-raised));
|
|
}
|
|
|
|
.picker-surface-tab[aria-pressed="true"] {
|
|
color: var(--ks-champagne);
|
|
background: color-mix(in oklab, var(--ks-patina) 14%, var(--ks-lacquer-raised));
|
|
}
|
|
|
|
.picker-surface-tab:focus-visible {
|
|
outline: 2px solid var(--ks-patina-deep);
|
|
outline-offset: -2px;
|
|
}
|
|
|
|
/* The whole report on state: filled once the surface has been answered
|
|
deliberately, hollow while it is still carrying the default it was given. */
|
|
.picker-surface-dot {
|
|
flex: none;
|
|
width: 10px;
|
|
height: 10px;
|
|
border: 1px solid color-mix(in oklab, var(--ks-patina) 55%, transparent);
|
|
border-radius: 50%;
|
|
}
|
|
|
|
/* The open tab's dot, checked the way a radio is. The plate behind the tab
|
|
already says which one is open, but a row of hollow dots reads as a row of
|
|
unanswered questions and the plate is too quiet to argue with it. Kept above
|
|
the fill so the answered report still outranks this on the open tab. */
|
|
.picker-surface-tab[aria-pressed="true"] .picker-surface-dot {
|
|
border-color: var(--ks-patina);
|
|
background: radial-gradient(circle at center, var(--ks-patina) 2px, transparent 2px);
|
|
}
|
|
|
|
.picker-surface-tab[data-set="yes"] .picker-surface-dot {
|
|
background: var(--ks-patina);
|
|
border-color: var(--ks-patina);
|
|
}
|
|
|
|
/* An option the surface in the frame cannot carry. The row keeps its place so
|
|
the list does not reshuffle as you move between surfaces, and its sentence
|
|
is replaced by the reason it is out. */
|
|
.picker-strategy-option.is-blocked {
|
|
opacity: 0.42;
|
|
/* Deaf to the cursor as well as to the click, so a row the surface cannot
|
|
carry cannot preview itself either. It also keeps the hover and checked
|
|
branches below written the way the artboard's are: no blocked row can be
|
|
the one being hovered, so neither branch has to ask. */
|
|
pointer-events: none;
|
|
}
|
|
|
|
/* The type boards under Restrained, which is the answer no strategy block
|
|
below rewrites. Left to the artboard's own defaults these slots sit on bone
|
|
and the near-neutral surface step, so the font screen drew a page the chosen
|
|
palette had never reached while the drawing on screen 03 kept its color under
|
|
the same answer. Restrained still means mostly neutral: the mixes stay on the
|
|
quiet end and the ground is untouched. Declared ahead of the strategy section
|
|
so the three answers that do rewrite these slots still win. */
|
|
.picker-preview-type {
|
|
--pvs-ghost: color-mix(in oklab, var(--pv-primary) 14%, var(--pv-neutral));
|
|
--pvs-ghost-edge: color-mix(in oklab, var(--pv-primary) 42%, var(--pv-neutral));
|
|
--pvs-image: color-mix(in oklab, var(--pv-primary) 22%, var(--pv-neutral));
|
|
--pvs-image-a: color-mix(in oklab, var(--pv-primary) 26%, var(--pv-neutral));
|
|
--pvs-image-b: color-mix(in oklab, var(--pv-primary) 48%, var(--pv-neutral));
|
|
--pvs-image-edge: color-mix(in oklab, var(--pv-primary) 40%, var(--pv-neutral));
|
|
--pvs-accent-a: var(--pv-primary);
|
|
--pvs-accent-b: color-mix(in oklab, var(--pv-primary) 62%, var(--pv-neutral));
|
|
--pvs-accent-c: color-mix(in oklab, var(--pv-primary) 38%, var(--pv-neutral));
|
|
--pvs-accent-d: color-mix(in oklab, var(--pv-primary) 20%, var(--pv-neutral));
|
|
}
|
|
|
|
/* ============================================================
|
|
The strategy, applied to the surface in the frame.
|
|
|
|
The drawings read the palette through role names, and every shade they mix
|
|
is mixed from those same names, so a strategy is a remapping of the roles
|
|
rather than a second set of rules per drawing. Redistributing four roles
|
|
reaches all four surfaces at once, and a new surface arrives already
|
|
answering to it.
|
|
|
|
The committed colors are painted onto the stage under --pkc-*, which gives
|
|
each remap a fixed handle on what was chosen. Reading --pv-* instead would
|
|
read whatever the remap had just written, which is how drenched would end
|
|
up mixing its ground out of its own ink.
|
|
============================================================ */
|
|
.picker-strategy-stage > .picker-preview {
|
|
--pv-primary: var(--pkc-primary, var(--ks-champagne));
|
|
--pv-secondary: var(--pkc-secondary, var(--ks-patina));
|
|
--pv-tertiary: var(--pkc-tertiary, var(--ks-kinpaku));
|
|
--pv-neutral: var(--pkc-neutral, var(--ks-lacquer-raised));
|
|
--pv-n-ink: var(--pkc-n-ink, var(--ks-champagne));
|
|
--pv-p-ink: var(--pkc-p-ink, var(--ks-champagne));
|
|
}
|
|
|
|
/* Restrained: the page is its neutral and the primary is the only color that
|
|
arrives. The other two stand down into the ground rather than disappearing,
|
|
so what they were holding is still legible as structure. */
|
|
#picker-form:has(.picker-strategy-option:hover input[name="color-strategy"][value="restrained"]) .picker-strategy-stage > .picker-preview,
|
|
#picker-form:not(:has(.picker-strategy-option:hover input[name="color-strategy"])):has(input[name="color-strategy"][value="restrained"]:checked) .picker-strategy-stage > .picker-preview {
|
|
--pv-secondary: color-mix(in oklab, var(--pkc-neutral) 78%, var(--pkc-n-ink));
|
|
--pv-tertiary: color-mix(in oklab, var(--pkc-neutral) 86%, var(--pkc-n-ink));
|
|
--pv-t-ink: var(--pkc-n-ink);
|
|
--pv-t-on-t: var(--pkc-n-ink);
|
|
--pv-t-on-n: color-mix(in oklab, var(--pkc-n-ink) 62%, var(--pkc-neutral));
|
|
--pv-t-on-p: var(--pkc-p-ink);
|
|
}
|
|
|
|
/* Committed: one color doing all the work the other two were doing, and a
|
|
trace of it in the paper as well. Without the paper, a surface whose accents
|
|
are small (a dashboard, a doc) separates from restrained by a few tinted
|
|
pixels, which is not what "does real work across the page" describes. */
|
|
#picker-form:has(.picker-strategy-option:hover input[name="color-strategy"][value="committed"]) .picker-strategy-stage > .picker-preview,
|
|
#picker-form:not(:has(.picker-strategy-option:hover input[name="color-strategy"])):has(input[name="color-strategy"][value="committed"]:checked) .picker-strategy-stage > .picker-preview {
|
|
--pv-neutral: color-mix(in oklab, var(--pkc-neutral) 95%, var(--pkc-primary));
|
|
--pv-secondary: var(--pkc-primary);
|
|
--pv-tertiary: var(--pkc-primary);
|
|
--pv-t-ink: var(--pkc-p-ink);
|
|
--pv-t-on-t: var(--pkc-p-on-p);
|
|
--pv-t-on-n: var(--pkc-p-on-n);
|
|
--pv-t-on-p: var(--pkc-p-on-p);
|
|
}
|
|
|
|
/* Full palette is the drawing as it was authored: four roles, each already
|
|
holding a different part of the page. It needs no block of its own, which
|
|
is the same reason the artboard's restrained does not have one. */
|
|
|
|
/* Drenched: the ground becomes the primary, and everything that was picked out
|
|
in the primary is now picked out against it in ink. The shades the drawings
|
|
mix from the neutral follow the ground without being told, which is what
|
|
keeps the images and panels tinted rather than left as white holes. */
|
|
#picker-form:has(.picker-strategy-option:hover input[name="color-strategy"][value="drenched"]) .picker-strategy-stage > .picker-preview,
|
|
#picker-form:not(:has(.picker-strategy-option:hover input[name="color-strategy"])):has(input[name="color-strategy"][value="drenched"]:checked) .picker-strategy-stage > .picker-preview {
|
|
--pv-neutral: var(--pkc-primary);
|
|
--pv-n-ink: var(--pkc-p-ink);
|
|
--pv-primary: var(--pkc-p-ink);
|
|
--pv-p-ink: var(--pkc-primary);
|
|
--pv-p-on-n: var(--pkc-p-ink);
|
|
--pv-p-on-p: var(--pkc-primary);
|
|
--pv-secondary: var(--pkc-tertiary);
|
|
--pv-tertiary: var(--pkc-tertiary);
|
|
--pv-t-on-n: var(--pkc-t-on-p);
|
|
}
|
|
|
|
/* Hovering an option previews its strategy before the click commits it.
|
|
While any strategy option is hovered, the hovered branch drives the
|
|
preview and the checked branch stands down; hovering Restrained needs
|
|
no block of its own because standing down lands on the defaults.
|
|
|
|
The last two selectors in each block carry the committed answer onto every
|
|
later screen's artboard, so a screen deciding motion or radius shows it on
|
|
the page the visitor actually chose. Only the committed answer travels: a
|
|
hover preview belongs to the screen whose options are under the cursor,
|
|
which is what the no-hover guard on the [data-artboard] selector is for.
|
|
Both hold themselves at one class of specificity on purpose, so the
|
|
artboard's own defaults are still beaten on source order while the
|
|
reading-contrast slots the type preview declares further down still win. */
|
|
#picker-form:has(.picker-strategy-option:hover input[name="color-strategy"][value="committed"]) .picker-strategy-preview:not(.picker-preview-type),
|
|
#picker-form:not(:has(.picker-strategy-option:hover input[name="color-strategy"])):has(input[name="color-strategy"][value="committed"]:checked) .picker-strategy-preview:not(.picker-preview-type),
|
|
:where(#picker-form:has(input[name="color-strategy"][value="committed"]:checked)) .picker-preview-type,
|
|
:where(#picker-form:not(:has(.picker-strategy-option:hover input[name="color-strategy"])):has(input[name="color-strategy"][value="committed"]:checked)) [data-artboard] {
|
|
--pvs-headline: color-mix(in oklab, var(--pv-primary) 80%, var(--pv-strong));
|
|
--pvs-headline-text: var(--pv-p-on-n);
|
|
--pvs-eyebrow: var(--pv-primary);
|
|
--pvs-ghost: color-mix(in oklab, var(--pv-primary) 14%, var(--pv-neutral));
|
|
--pvs-ghost-edge: var(--pv-primary);
|
|
--pvs-image: var(--pv-primary);
|
|
--pvs-image-a: var(--pv-primary);
|
|
--pvs-image-b: color-mix(in oklab, var(--pv-primary) 72%, var(--pv-neutral));
|
|
--pvs-image-edge: var(--pv-primary);
|
|
--pvs-accent-a: var(--pv-primary);
|
|
--pvs-accent-b: color-mix(in oklab, var(--pv-primary) 70%, var(--pv-neutral));
|
|
--pvs-accent-c: var(--pv-primary);
|
|
--pvs-accent-d: color-mix(in oklab, var(--pv-primary) 18%, var(--pv-neutral));
|
|
}
|
|
|
|
/* Full palette: every role on duty. Primary carries the headline, image,
|
|
and CTA; secondary the ghost action and gallery; tertiary the eyebrow,
|
|
signal, and accents; neutral stays the ground. */
|
|
#picker-form:has(.picker-strategy-option:hover input[name="color-strategy"][value="full-palette"]) .picker-strategy-preview:not(.picker-preview-type),
|
|
#picker-form:not(:has(.picker-strategy-option:hover input[name="color-strategy"])):has(input[name="color-strategy"][value="full-palette"]:checked) .picker-strategy-preview:not(.picker-preview-type),
|
|
:where(#picker-form:has(input[name="color-strategy"][value="full-palette"]:checked)) .picker-preview-type,
|
|
:where(#picker-form:not(:has(.picker-strategy-option:hover input[name="color-strategy"])):has(input[name="color-strategy"][value="full-palette"]:checked)) [data-artboard] {
|
|
--pvs-headline: color-mix(in oklab, var(--pv-primary) 80%, var(--pv-strong));
|
|
--pvs-headline-text: var(--pv-p-on-n);
|
|
--pvs-accent-text: var(--pv-t-on-n);
|
|
--pvs-eyebrow: var(--pv-tertiary);
|
|
--pvs-ghost: color-mix(in oklab, var(--pv-secondary) 18%, var(--pv-neutral));
|
|
--pvs-ghost-edge: var(--pv-secondary);
|
|
--pvs-image: var(--pv-primary);
|
|
--pvs-image-a: var(--pv-primary);
|
|
--pvs-image-b: color-mix(in oklab, var(--pv-secondary) 32%, var(--pv-primary));
|
|
--pvs-image-edge: var(--pv-primary);
|
|
--pvs-signal: var(--pv-tertiary);
|
|
--pvs-signal-ink: var(--pv-t-ink);
|
|
--pvs-signal-text: var(--pv-t-on-t);
|
|
--pvs-accent-a: var(--pv-secondary);
|
|
--pvs-accent-b: color-mix(in oklab, var(--pv-secondary) 42%, var(--pv-neutral));
|
|
--pvs-accent-c: var(--pv-primary);
|
|
--pvs-accent-d: var(--pv-tertiary);
|
|
}
|
|
|
|
/* Drenched: the primary is the page. Everything on top is either ink,
|
|
an ink wash of the primary, or the secondary and tertiary doing accent
|
|
work; nothing is left bare white and the hairlines tint with the ground. */
|
|
#picker-form:has(.picker-strategy-option:hover input[name="color-strategy"][value="drenched"]) .picker-strategy-preview:not(.picker-preview-type),
|
|
#picker-form:not(:has(.picker-strategy-option:hover input[name="color-strategy"])):has(input[name="color-strategy"][value="drenched"]:checked) .picker-strategy-preview:not(.picker-preview-type),
|
|
:where(#picker-form:has(input[name="color-strategy"][value="drenched"]:checked)) .picker-preview-type,
|
|
:where(#picker-form:not(:has(.picker-strategy-option:hover input[name="color-strategy"])):has(input[name="color-strategy"][value="drenched"]:checked)) [data-artboard] {
|
|
--pvs-ground: var(--pv-primary);
|
|
--pvs-ink: var(--pv-p-ink);
|
|
--pvs-surface: color-mix(in oklab, var(--pv-p-ink) 10%, var(--pv-primary));
|
|
--pvs-headline: var(--pv-p-ink);
|
|
--pvs-eyebrow: color-mix(in oklab, var(--pv-p-ink) 70%, var(--pv-primary));
|
|
--pvs-copy: color-mix(in oklab, var(--pv-p-ink) 55%, var(--pv-primary));
|
|
--pvs-title: var(--pv-p-ink);
|
|
--pvs-cta: var(--pv-p-ink);
|
|
--pvs-cta-ink: var(--pv-primary);
|
|
--pvs-cta-text: var(--pv-p-on-i);
|
|
--pvs-mark-text: var(--pv-p-ink);
|
|
--pvs-accent-text: var(--pv-t-on-p);
|
|
/* A saturated primary sits far from both ends of the range, so copy mixed
|
|
toward it at the paper percentages stops being readable. The drenched
|
|
page holds its running text closer to the ink. */
|
|
--pvs-copy-mix: 14%;
|
|
--pvs-bars-mix: 16%;
|
|
--pvs-ghost: color-mix(in oklab, var(--pv-p-ink) 22%, var(--pv-primary));
|
|
--pvs-ghost-edge: color-mix(in oklab, var(--pv-p-ink) 45%, var(--pv-primary));
|
|
--pvs-image: color-mix(in oklab, var(--pv-p-ink) 20%, var(--pv-primary));
|
|
--pvs-image-a: color-mix(in oklab, var(--pv-p-ink) 16%, var(--pv-primary));
|
|
--pvs-image-b: color-mix(in oklab, var(--pv-p-ink) 28%, var(--pv-primary));
|
|
--pvs-image-edge: color-mix(in oklab, var(--pv-p-ink) 40%, var(--pv-primary));
|
|
--pvs-signal: var(--pv-tertiary);
|
|
--pvs-signal-ink: var(--pv-t-ink);
|
|
--pvs-signal-text: var(--pv-t-on-t);
|
|
--pvs-bars: color-mix(in oklab, var(--pv-p-ink) 45%, var(--pv-primary));
|
|
--pvs-rule: color-mix(in oklab, var(--pv-p-ink) 28%, var(--pv-primary));
|
|
--pvs-accent-a: var(--pv-secondary);
|
|
--pvs-accent-b: var(--pv-tertiary);
|
|
--pvs-accent-c: color-mix(in oklab, var(--pv-p-ink) 30%, var(--pv-primary));
|
|
--pvs-accent-d: color-mix(in oklab, var(--pv-p-ink) 14%, var(--pv-primary));
|
|
}
|
|
|
|
.ps-desktop,
|
|
.ps-phone {
|
|
min-width: 0;
|
|
min-height: 0;
|
|
overflow: hidden;
|
|
background-color: var(--pvs-ground);
|
|
border: var(--pvs-frame-w) solid var(--pvs-frame-c);
|
|
}
|
|
|
|
.ps-desktop {
|
|
display: grid;
|
|
grid-template-rows: var(--pvs-rows);
|
|
border-radius: var(--pvs-radius-frame);
|
|
}
|
|
|
|
.ps-phone {
|
|
height: 100%;
|
|
aspect-ratio: 8 / 19;
|
|
align-self: stretch;
|
|
display: grid;
|
|
grid-template-rows: 7.2% 1fr 7.5%;
|
|
border-radius: var(--pvs-radius-frame-phone);
|
|
}
|
|
|
|
.ps-nav {
|
|
display: grid;
|
|
grid-template-columns: var(--pvs-nav-cols);
|
|
gap: 24px;
|
|
align-items: center;
|
|
padding: 0 var(--pvs-gutter);
|
|
background-color: var(--pvs-band);
|
|
border-bottom: var(--pvs-divider-w) solid var(--pvs-divider-c);
|
|
box-shadow: var(--pvs-shadow-chrome);
|
|
}
|
|
|
|
.ps-brand-block {
|
|
width: clamp(8px, 1.15vw, 12px);
|
|
height: auto;
|
|
aspect-ratio: 1;
|
|
background-color: var(--pvs-cta);
|
|
border-radius: var(--pvs-radius-dot);
|
|
}
|
|
|
|
.ps-nav-bars {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: 27px;
|
|
}
|
|
|
|
.ps-nav-bars i {
|
|
display: block;
|
|
width: clamp(18px, 7%, 34px);
|
|
height: 4px;
|
|
background-color: var(--pvs-bars);
|
|
border-radius: var(--pvs-radius-bar);
|
|
}
|
|
|
|
.ps-nav-action {
|
|
width: clamp(34px, 3.4vw, 48px);
|
|
height: clamp(7px, 26%, 13px);
|
|
background-color: var(--pvs-signal);
|
|
border-radius: var(--pvs-radius-control);
|
|
box-shadow: var(--pvs-shadow-control);
|
|
}
|
|
|
|
.ps-hero {
|
|
min-height: 0;
|
|
display: grid;
|
|
grid-template-columns: var(--pvs-hero-cols);
|
|
gap: var(--pvs-hero-gap);
|
|
align-items: var(--pvs-section-align);
|
|
padding: 2% var(--pvs-gutter-end) 2% var(--pvs-gutter);
|
|
}
|
|
|
|
.ps-hero-copy {
|
|
display: grid;
|
|
gap: 0;
|
|
}
|
|
|
|
.ps-eyebrow {
|
|
width: 54%;
|
|
height: 10px;
|
|
margin-bottom: 14px;
|
|
background-color: var(--pvs-eyebrow);
|
|
border-radius: var(--pvs-radius-bar);
|
|
}
|
|
|
|
.ps-headline {
|
|
display: grid;
|
|
gap: 8px;
|
|
margin: 0 0 14px;
|
|
}
|
|
|
|
.ps-headline i {
|
|
display: block;
|
|
width: 100%;
|
|
height: 15px;
|
|
background-color: var(--pvs-headline);
|
|
border-radius: var(--pvs-radius-bar);
|
|
}
|
|
|
|
.ps-headline i:last-child {
|
|
width: 72%;
|
|
}
|
|
|
|
.ps-copy {
|
|
display: grid;
|
|
gap: 8px;
|
|
margin-bottom: 18px;
|
|
}
|
|
|
|
.ps-copy i {
|
|
display: block;
|
|
width: 64%;
|
|
height: 8px;
|
|
background-color: var(--pvs-copy);
|
|
border-radius: var(--pvs-radius-bar);
|
|
}
|
|
|
|
.ps-copy i:last-child {
|
|
width: 48%;
|
|
}
|
|
|
|
.ps-actions {
|
|
display: flex;
|
|
gap: clamp(5px, 1vw, 10px);
|
|
margin-top: 0;
|
|
}
|
|
|
|
.ps-actions i {
|
|
width: clamp(42px, 42%, 86px);
|
|
height: clamp(12px, 2.35vw, 29px);
|
|
background-color: var(--pvs-cta);
|
|
border-radius: var(--pvs-radius-control);
|
|
box-shadow: var(--pvs-shadow-control);
|
|
}
|
|
|
|
.ps-actions i:last-child {
|
|
background-color: var(--pvs-ghost);
|
|
border: 1px solid var(--pvs-ghost-edge);
|
|
}
|
|
|
|
.ps-image {
|
|
position: relative;
|
|
width: 100%;
|
|
height: 100%;
|
|
min-height: 0;
|
|
background-color: var(--pvs-image);
|
|
background-image: linear-gradient(135deg, var(--pvs-image-a), var(--pvs-image-b));
|
|
border: 1px solid var(--pvs-image-edge);
|
|
border-radius: var(--pvs-radius-surface);
|
|
box-shadow: var(--pvs-shadow-surface);
|
|
}
|
|
|
|
.ps-image::after {
|
|
content: "";
|
|
position: absolute;
|
|
top: 8%;
|
|
right: 8%;
|
|
width: clamp(6px, 1.1vw, 10px);
|
|
aspect-ratio: 1;
|
|
background-color: var(--pvs-accent-d);
|
|
border-radius: var(--pvs-radius-dot);
|
|
}
|
|
|
|
.ps-proof {
|
|
min-width: 0;
|
|
display: grid;
|
|
grid-template-columns: 1.05fr 1fr 1fr 1.15fr;
|
|
align-items: stretch;
|
|
column-gap: var(--ps-proof-gap);
|
|
padding: 2% var(--pvs-gutter-end) 2% var(--pvs-gutter);
|
|
background-color: var(--pvs-band-alt);
|
|
position: relative;
|
|
}
|
|
|
|
.ps-proof::before,
|
|
.ps-proof::after {
|
|
content: "";
|
|
position: absolute;
|
|
left: var(--pvs-gutter);
|
|
right: var(--pvs-gutter-end);
|
|
height: var(--pvs-divider-w);
|
|
background-color: var(--pvs-divider-c);
|
|
}
|
|
|
|
.ps-proof::before {
|
|
top: 0;
|
|
}
|
|
|
|
.ps-proof::after {
|
|
bottom: 0;
|
|
}
|
|
|
|
.ps-proof-item {
|
|
min-width: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10%;
|
|
height: 100%;
|
|
padding: 0;
|
|
}
|
|
|
|
.ps-proof-item + .ps-proof-item {
|
|
border-left: var(--pvs-divider-w) solid var(--pvs-divider-c);
|
|
}
|
|
|
|
.ps-proof-item i {
|
|
flex: none;
|
|
width: clamp(7px, 1.5vw, 13px);
|
|
aspect-ratio: 1;
|
|
background-color: var(--pvs-accent-a);
|
|
border-radius: var(--pvs-radius-dot);
|
|
}
|
|
|
|
.picker-artboard:not(.picker-preview-type) .ps-proof {
|
|
grid-template-columns:
|
|
minmax(0, 1fr) auto
|
|
minmax(0, 1fr) auto
|
|
minmax(0, 1fr) auto
|
|
minmax(0, 1fr);
|
|
}
|
|
|
|
.picker-artboard:not(.picker-preview-type) .ps-proof-item + .ps-proof-item {
|
|
border-left: 0;
|
|
}
|
|
|
|
.picker-artboard:not(.picker-preview-type) .ps-proof-divider {
|
|
width: var(--pvs-divider-w);
|
|
align-self: stretch;
|
|
background-color: var(--pvs-divider-c);
|
|
}
|
|
|
|
.picker-artboard:not(.picker-preview-type) .ps-phone-body .ps-proof {
|
|
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
|
|
}
|
|
|
|
.picker-artboard:not(.picker-preview-type) .ps-proof-item:nth-of-type(2) i {
|
|
background-color: var(--pvs-accent-b);
|
|
}
|
|
|
|
.picker-artboard:not(.picker-preview-type) .ps-proof-item:nth-of-type(3) i {
|
|
background-color: var(--pvs-accent-c);
|
|
}
|
|
|
|
.picker-artboard:not(.picker-preview-type) .ps-proof-item:nth-of-type(4) i {
|
|
background-color: var(--pvs-accent-d);
|
|
}
|
|
|
|
/* The label takes whatever the dot leaves, so a claim reads as one line of
|
|
the cell rather than a bar sized to a share of it. */
|
|
.ps-proof-item span {
|
|
flex: 1;
|
|
height: 6px;
|
|
background-color: var(--pvs-bars);
|
|
border-radius: var(--pvs-radius-bar);
|
|
}
|
|
|
|
.ps-editorial {
|
|
min-height: 0;
|
|
display: grid;
|
|
grid-template-columns: var(--pvs-editorial-cols);
|
|
gap: var(--pvs-editorial-gap);
|
|
align-items: var(--pvs-section-align);
|
|
padding: 2.2% var(--pvs-gutter);
|
|
}
|
|
|
|
.ps-editorial-copy {
|
|
display: grid;
|
|
gap: clamp(3px, 0.55vw, 6px);
|
|
}
|
|
|
|
.ps-editorial-copy strong {
|
|
width: 78%;
|
|
height: 11px;
|
|
background-color: var(--pvs-title);
|
|
border-radius: var(--pvs-radius-bar);
|
|
}
|
|
|
|
.ps-editorial-copy span {
|
|
display: grid;
|
|
gap: 6px;
|
|
}
|
|
|
|
.ps-editorial-copy span i {
|
|
display: block;
|
|
width: 90%;
|
|
height: 6px;
|
|
background-color: var(--pvs-bars);
|
|
border-radius: var(--pvs-radius-bar);
|
|
}
|
|
|
|
.ps-editorial-copy span i:last-child {
|
|
width: 80%;
|
|
}
|
|
|
|
.ps-editorial-copy > em {
|
|
width: 30%;
|
|
height: 6px;
|
|
margin-top: 3px;
|
|
background-color: var(--pvs-signal);
|
|
border-radius: var(--pvs-radius-bar);
|
|
}
|
|
|
|
.ps-gallery {
|
|
min-width: 0;
|
|
height: 100%;
|
|
display: grid;
|
|
grid-template-columns: var(--pvs-gallery-cols);
|
|
gap: var(--pvs-gallery-gap);
|
|
}
|
|
|
|
.ps-gallery-item {
|
|
min-width: 0;
|
|
display: grid;
|
|
grid-template-rows: minmax(0, 1fr) auto;
|
|
gap: 12px;
|
|
padding: var(--pvs-panel-pad);
|
|
background-color: var(--pvs-panel);
|
|
border: var(--pvs-panel-edge-w) solid var(--pvs-panel-edge);
|
|
border-radius: var(--pvs-radius-surface);
|
|
box-shadow: var(--pvs-shadow-card);
|
|
}
|
|
|
|
.ps-desktop .ps-editorial-copy {
|
|
gap: 9px;
|
|
}
|
|
|
|
.ps-desktop .ps-editorial-copy strong {
|
|
height: 11px;
|
|
}
|
|
|
|
.ps-desktop .ps-editorial-copy > em {
|
|
height: 5px;
|
|
margin-top: 9px;
|
|
}
|
|
|
|
.ps-gallery-item > i {
|
|
display: block;
|
|
min-height: 0;
|
|
background-color: var(--pvs-accent-a);
|
|
border-radius: var(--pvs-radius-surface);
|
|
}
|
|
|
|
.ps-gallery-item:nth-child(2) > i {
|
|
background-color: var(--pvs-accent-b);
|
|
}
|
|
|
|
.ps-gallery-item:nth-child(3) > i {
|
|
background-color: var(--pvs-accent-c);
|
|
}
|
|
|
|
.ps-gallery-item:nth-child(4) > i {
|
|
background-color: var(--pvs-accent-d);
|
|
}
|
|
|
|
.ps-gallery-item span {
|
|
display: grid;
|
|
gap: 6px;
|
|
}
|
|
|
|
.ps-gallery-item b {
|
|
display: block;
|
|
width: 76%;
|
|
height: 6px;
|
|
background-color: var(--pvs-bars);
|
|
border-radius: var(--pvs-radius-bar);
|
|
}
|
|
|
|
.ps-gallery-item b:last-child {
|
|
width: 58%;
|
|
}
|
|
|
|
.ps-footer {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding-inline: var(--pvs-gutter);
|
|
background-color: var(--pvs-band);
|
|
border-top: var(--pvs-divider-w) solid var(--pvs-divider-c);
|
|
}
|
|
|
|
.ps-footer-links {
|
|
display: flex;
|
|
gap: clamp(8px, 2vw, 18px);
|
|
}
|
|
|
|
.ps-footer-links i,
|
|
.ps-phone-footer i {
|
|
width: clamp(12px, 3vw, 28px);
|
|
height: 4px;
|
|
background-color: var(--pvs-bars);
|
|
border-radius: var(--pvs-radius-bar);
|
|
}
|
|
|
|
.ps-footer-mark {
|
|
width: 7%;
|
|
height: 5px;
|
|
background-color: var(--pvs-bars);
|
|
border-radius: var(--pvs-radius-bar);
|
|
}
|
|
|
|
/* Phone artboard: the same site on mobile. */
|
|
.ps-phone-top {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
min-height: 0;
|
|
padding: 0 6%;
|
|
background-color: var(--pvs-surface);
|
|
border-bottom: var(--pvs-divider-w) solid var(--pvs-divider-c);
|
|
box-shadow: var(--pvs-shadow-chrome);
|
|
}
|
|
|
|
.ps-phone-top .ps-brand-block {
|
|
width: 9px;
|
|
}
|
|
|
|
.ps-phone-top .ps-nav-action {
|
|
width: 16%;
|
|
height: 6px;
|
|
}
|
|
|
|
/* The picture is the one row here whose height is a share of the frame rather
|
|
than a count of what is in it, so it is the row that gives when the frame is
|
|
shorter than the page: the bars below it are drawn at fixed pixel heights and
|
|
do not shrink with the window. Capped at its share rather than set to it, so
|
|
a tall frame is unchanged and a short one takes the difference out of the
|
|
picture instead of pushing the card row through the footer. The card row asks
|
|
for its own height first and takes the leftover after: as a bare fraction it
|
|
was handed whatever the rows above had finished with, which on a short frame
|
|
was a third of what a card is. */
|
|
.ps-phone-body {
|
|
min-height: 0;
|
|
display: grid;
|
|
grid-template-rows: minmax(0, 31%) auto auto auto 34px auto minmax(min-content, 1fr);
|
|
gap: 6px;
|
|
padding: 6%;
|
|
}
|
|
|
|
.ps-phone-body .ps-image {
|
|
margin: 0;
|
|
min-height: 0;
|
|
}
|
|
|
|
.ps-phone-body .ps-headline i {
|
|
height: clamp(5px, 0.75vw, 8px);
|
|
}
|
|
|
|
.ps-phone-body .ps-headline {
|
|
margin: 8px 0 0;
|
|
gap: 7px;
|
|
}
|
|
|
|
.ps-phone-body .ps-headline i:first-child {
|
|
width: 54%;
|
|
height: 9px;
|
|
}
|
|
|
|
.ps-phone-body .ps-headline i:last-child {
|
|
width: 82%;
|
|
height: 8px;
|
|
}
|
|
|
|
.ps-phone-body .ps-copy {
|
|
margin: 4px 0 0;
|
|
gap: 7px;
|
|
}
|
|
|
|
.ps-phone-body .ps-copy i {
|
|
width: 68%;
|
|
height: 6px;
|
|
}
|
|
|
|
.ps-phone-body .ps-copy i:last-child {
|
|
display: block;
|
|
width: 52%;
|
|
}
|
|
|
|
.ps-phone-body .ps-actions {
|
|
gap: 7px;
|
|
margin-top: 6px;
|
|
}
|
|
|
|
.ps-phone-body .ps-actions i {
|
|
width: 43%;
|
|
height: 18px;
|
|
}
|
|
|
|
.ps-phone-body .ps-proof {
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
align-items: stretch;
|
|
margin-inline: 0;
|
|
margin-top: 5px;
|
|
padding: 2% 3.2% 2% 5.5%;
|
|
}
|
|
|
|
.ps-phone-body .ps-proof-item {
|
|
height: 100%;
|
|
padding: 0;
|
|
}
|
|
|
|
.ps-phone-body .ps-proof-item i {
|
|
width: 10px;
|
|
}
|
|
|
|
.ps-phone-body .ps-proof-item span {
|
|
height: 6px;
|
|
}
|
|
|
|
.ps-phone-body .ps-editorial-copy strong {
|
|
width: 38%;
|
|
height: 7px;
|
|
}
|
|
|
|
.ps-phone-body .ps-editorial-copy {
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.ps-phone-body .ps-editorial-copy span i {
|
|
height: 6px;
|
|
}
|
|
|
|
.ps-phone-body .ps-proof::before,
|
|
.ps-phone-body .ps-proof::after {
|
|
left: 0;
|
|
right: 0;
|
|
}
|
|
|
|
.ps-phone-body .ps-gallery {
|
|
grid-template-columns: 1.05fr 0.95fr;
|
|
gap: 10%;
|
|
margin-left: 1px;
|
|
margin-top: 6px;
|
|
}
|
|
|
|
.ps-phone-body .ps-gallery-item {
|
|
grid-template-rows: 36px auto;
|
|
gap: 6px;
|
|
}
|
|
|
|
.ps-phone-body .ps-gallery-item b {
|
|
height: 5px;
|
|
}
|
|
|
|
.ps-phone-body .ps-gallery-item span {
|
|
align-content: start;
|
|
gap: 6px;
|
|
}
|
|
|
|
.ps-phone-footer {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-around;
|
|
min-height: 0;
|
|
padding-inline: 6%;
|
|
background-color: var(--pvs-surface);
|
|
border-top: var(--pvs-divider-w) solid var(--pvs-divider-c);
|
|
}
|
|
|
|
.picker-artboard:not(.picker-preview-type) :is(
|
|
.ps-desktop,
|
|
.ps-phone,
|
|
.ps-nav,
|
|
.ps-proof,
|
|
.ps-proof::before,
|
|
.ps-proof::after,
|
|
.ps-proof-item,
|
|
.ps-footer,
|
|
.ps-phone-top,
|
|
.ps-phone-footer,
|
|
.ps-brand-block,
|
|
.ps-nav-bars i,
|
|
.ps-nav-action,
|
|
.ps-eyebrow,
|
|
.ps-headline i,
|
|
.ps-copy i,
|
|
.ps-actions i,
|
|
.ps-image,
|
|
.ps-image::after,
|
|
.ps-proof-item i,
|
|
.ps-proof-item span,
|
|
.ps-editorial-copy strong,
|
|
.ps-editorial-copy span i,
|
|
.ps-editorial-copy > em,
|
|
.ps-gallery,
|
|
.ps-gallery-item,
|
|
.ps-gallery-item > i,
|
|
.ps-gallery-item b,
|
|
.ps-footer-links i,
|
|
.ps-footer-mark,
|
|
.ps-phone-footer i,
|
|
.ps-ops-rail,
|
|
.ps-ops-main,
|
|
.ps-ops-item,
|
|
.ps-ops-chart,
|
|
.ps-ops-table,
|
|
.ps-ops-row,
|
|
.ps-ops-panel,
|
|
.ps-ops-plot > i,
|
|
.ps-docs-rail,
|
|
.ps-docs-note,
|
|
.ps-index-row,
|
|
.ps-index-rail
|
|
) {
|
|
/* Width, margin, and padding ride along so the boundary answers morph rather
|
|
than snap: a hairline grows out of nothing, the proof band slides off the
|
|
page margins, and a card's padding opens instead of jumping. */
|
|
transition:
|
|
background-color 280ms var(--ks-ease),
|
|
background-image 280ms var(--ks-ease),
|
|
border-color 280ms var(--ks-ease),
|
|
border-width 280ms var(--ks-ease),
|
|
border-radius 280ms var(--ks-ease),
|
|
box-shadow 280ms var(--ks-ease),
|
|
margin 280ms var(--ks-ease),
|
|
padding 280ms var(--ks-ease);
|
|
}
|
|
|
|
.picker-preview-type :is(
|
|
.ps-desktop,
|
|
.ps-phone,
|
|
.ps-nav,
|
|
.ps-proof,
|
|
.ps-proof::before,
|
|
.ps-proof::after,
|
|
.ps-proof-item,
|
|
.ps-footer,
|
|
.ps-phone-top,
|
|
.ps-phone-footer,
|
|
.ps-brand-block,
|
|
.ps-nav-bars i,
|
|
.ps-nav-action,
|
|
.ps-eyebrow,
|
|
.ps-headline i,
|
|
.ps-copy i,
|
|
.ps-actions i,
|
|
.ps-image,
|
|
.ps-image::after,
|
|
.ps-proof-item i,
|
|
.ps-proof-item span,
|
|
.ps-editorial-copy strong,
|
|
.ps-editorial-copy > em,
|
|
.ps-gallery-item > i,
|
|
.ps-gallery-item b,
|
|
.ps-footer-links i,
|
|
.ps-footer-mark,
|
|
.ps-phone-footer i,
|
|
.pt-headline,
|
|
.pt-body,
|
|
.pt-section-body
|
|
) {
|
|
transition:
|
|
background-color 280ms var(--ks-ease),
|
|
background-image 280ms var(--ks-ease),
|
|
border-color 280ms var(--ks-ease),
|
|
color 280ms var(--ks-ease);
|
|
}
|
|
|
|
/* Every screen past the start ends the same way: the commitment centered and
|
|
foiled, retreat parked underneath it and deliberately quiet. */
|
|
.picker-actions-stack {
|
|
display: grid;
|
|
justify-items: center;
|
|
gap: 14px;
|
|
}
|
|
|
|
.picker-back {
|
|
padding: 4px 8px;
|
|
border: 0;
|
|
background: none;
|
|
color: var(--ks-text-muted);
|
|
font-family: var(--ks-font);
|
|
font-size: 0.86rem;
|
|
letter-spacing: 0.02em;
|
|
cursor: pointer;
|
|
transition: color 180ms var(--ks-ease);
|
|
}
|
|
|
|
.picker-back:hover {
|
|
color: var(--ks-champagne);
|
|
}
|
|
|
|
.picker-back:focus-visible {
|
|
outline: 1px solid var(--ks-kinpaku);
|
|
outline-offset: 3px;
|
|
}
|
|
|
|
/* Screen 04 mirrors screen 03's frame exactly: same centered headline, same
|
|
options-left preview-right grid, same dual artboards. Only two things are
|
|
its own: the option cards are large type specimens, and the wireframe's
|
|
headline and body are real text so the pair is judged as glyphs. */
|
|
.picker-type {
|
|
display: grid;
|
|
gap: 48px;
|
|
padding-block: clamp(32px, 5svh, 48px) clamp(24px, 3.5svh, 48px);
|
|
}
|
|
|
|
/* Six pair cards are taller than four strategy rows, and left to themselves
|
|
they set the row height and push Select this pair below the fold. The row
|
|
is capped against the viewport instead, leaving the list to scroll inside
|
|
it; the subtraction covers the title above and the CTA stack below. */
|
|
.picker-type-grid {
|
|
max-height: calc(100svh - 346px);
|
|
display: grid;
|
|
grid-template-columns: minmax(340px, 420px) minmax(0, 1fr);
|
|
gap: 50px;
|
|
align-items: stretch;
|
|
}
|
|
|
|
/* The frame's own box on the font screen. Every chosen surface gets a board and
|
|
they are all mounted, so the row is named rather than left to auto-placement:
|
|
four boards would otherwise stack into four rows and take the column past the
|
|
rail beside it. The tab strip takes the row above them, and the stage is also
|
|
the node the chosen pair's faces are written to, which is how one write sets
|
|
all four boards. */
|
|
.picker-type-stage {
|
|
/* The chosen pair is the run's answer rather than one board's, so the two
|
|
faces are declared here and written here, and every board is set in them
|
|
by inheriting. Declared on a board instead, these would outrank the write
|
|
and the boards would all draw in the picker's own faces. */
|
|
--pt-heading: var(--ks-font-display);
|
|
--pt-body: var(--ks-font);
|
|
--pt-heading-weight: 600;
|
|
|
|
min-width: 0;
|
|
min-height: 0;
|
|
display: grid;
|
|
grid-template-columns: minmax(0, 1fr);
|
|
grid-template-rows: auto minmax(0, 1fr);
|
|
}
|
|
|
|
.picker-type-stage > .picker-artboard {
|
|
grid-row: 2;
|
|
/* Both sides are named so the declared ratio has nothing left to decide. It
|
|
still does the asking: with no height of its own the stage measures the
|
|
ratio, which is what the row across from the rail is offered, and the taller
|
|
of the two wins. Leave the height out and the ratio answers instead of the
|
|
row, and the board comes up short of the rail beside it. */
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
/* One board is shown and the rest stay mounted, so switching costs an attribute
|
|
rather than a rebuild. [hidden] is a display: none that the component's own
|
|
display: grid outranks, so it is restated here. */
|
|
.picker-artboard[hidden] {
|
|
display: none;
|
|
}
|
|
|
|
/* The same box for a question screen that draws a board per surface, which is
|
|
screen 06 today. The font stage above carries the pair's two faces as well,
|
|
and that is the whole of the difference between them. */
|
|
.picker-board-stage {
|
|
min-width: 0;
|
|
min-height: 0;
|
|
display: grid;
|
|
grid-template-columns: minmax(0, 1fr);
|
|
grid-template-rows: auto minmax(0, 1fr);
|
|
}
|
|
|
|
.picker-board-stage > .picker-artboard {
|
|
grid-row: 2;
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
/* The rail keeps the row height owned by the preview: the fieldset scrolls
|
|
inside it instead of stretching the page when six tall cards stack up. The
|
|
control row underneath is what tells you the list runs past its frame. On
|
|
wide screens the rail stops growing at 570px so a tall preview does not
|
|
turn six font cards into a page-length column. */
|
|
.picker-type-rail {
|
|
min-width: 0;
|
|
min-height: 0;
|
|
display: grid;
|
|
grid-template-rows: minmax(0, 1fr) auto;
|
|
gap: 14px;
|
|
}
|
|
|
|
@media (min-width: 1200px) {
|
|
.picker-type-rail {
|
|
max-height: 570px;
|
|
}
|
|
}
|
|
|
|
.picker-type-options {
|
|
min-height: 0;
|
|
overflow-y: auto;
|
|
align-content: start;
|
|
scrollbar-width: thin;
|
|
scrollbar-color: color-mix(in oklab, var(--ks-patina-deep) 35%, transparent) transparent;
|
|
}
|
|
|
|
/* The rail keeps the chosen pair in the first row, and it makes that move only
|
|
once the pointer and focus have both left the list, so no row is ever pulled
|
|
out from under a cursor and arrow keys can cross the group without it
|
|
re-sorting between presses. Waiting costs the user the sight of the travel,
|
|
so the row that has taken the top says so once: its wash and its spine come
|
|
up bright and settle to the resting checked state. Both keyframes animate
|
|
color and nothing else, so the acknowledgement cannot move the list it is
|
|
drawing the eye to. */
|
|
@keyframes picker-pair-hoisted {
|
|
from {
|
|
background-color: color-mix(in oklab, var(--ks-patina) 32%, var(--ks-lacquer-raised));
|
|
}
|
|
}
|
|
|
|
@keyframes picker-pair-hoisted-spine {
|
|
from {
|
|
background-color: var(--ks-patina-pale);
|
|
}
|
|
}
|
|
|
|
.picker-type-option[data-hoisted] {
|
|
animation: picker-pair-hoisted 640ms var(--ks-ease);
|
|
}
|
|
|
|
.picker-type-option[data-hoisted]::before {
|
|
animation: picker-pair-hoisted-spine 640ms var(--ks-ease);
|
|
}
|
|
|
|
.picker-type-controls {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
}
|
|
|
|
/* Screen 05 keeps screen 04's frame and swaps the artboards for a specimen
|
|
sheet. It is the one question that needs explaining before it can be
|
|
answered, so the title carries a lede; every other screen asks something the
|
|
options themselves make obvious. */
|
|
.picker-scale {
|
|
display: grid;
|
|
gap: clamp(24px, 4.4svh, 40px);
|
|
padding-block: clamp(32px, 5svh, 48px) clamp(24px, 3.5svh, 48px);
|
|
}
|
|
|
|
.picker-question-head {
|
|
display: grid;
|
|
justify-items: center;
|
|
gap: clamp(12px, 2svh, 18px);
|
|
}
|
|
|
|
/* Screen 05 is the only question that carries a lede, and one sentence at
|
|
caption size under a display headline reads as a footnote. Set above the
|
|
body size instead, with the measure pulled in so the sentence breaks into
|
|
two balanced lines rather than one long ruler. */
|
|
.picker-question-lede {
|
|
max-width: 46ch;
|
|
color: var(--ks-text);
|
|
font-size: 1.18rem;
|
|
line-height: 1.5;
|
|
text-align: center;
|
|
text-wrap: balance;
|
|
}
|
|
|
|
/* Eight intervals and a lede leave less room than screen 04 has, so the sheet
|
|
is capped harder. The subtraction covers the title, the lede, and the CTA
|
|
stack, which is what keeps Select this scale above the fold.
|
|
|
|
Three columns share 1388px at the container's cap, and the two previews are
|
|
what pay for the third. The rail goes first: its widest row is the ratio and
|
|
Augmented Fourth on one line, which needs 300px of column and not the 420 it
|
|
was taking, and its description was already wrapping. The sheet gives the
|
|
rest. It can afford to because its sample string shortened to the scale's
|
|
name in the same pass, and a name is under half the width of the sentence
|
|
that was there, so the sheet renders its steps larger in 538px than it did in
|
|
918. The reading column is last and smallest: 45 characters of lorem at the
|
|
base step is a measure, and more than that is width the other two need. */
|
|
.picker-scale-grid {
|
|
max-height: calc(100svh - 420px);
|
|
display: grid;
|
|
grid-template-columns: minmax(300px, 352px) minmax(0, 1.25fr) minmax(0, 1fr);
|
|
gap: clamp(22px, 2.2vw, 34px);
|
|
align-items: stretch;
|
|
}
|
|
|
|
/* Eight rows scroll rather than share the height four strategy rows can split,
|
|
so this list takes screen 04's rail and screen 03's copy scale, not screen
|
|
03's fixed four-row grid. States come from the standard option row; the only
|
|
thing this list adds is the ratio sitting in front of the name. */
|
|
.picker-scale-options .picker-strategy-copy {
|
|
gap: 4px;
|
|
}
|
|
|
|
.picker-scale-options .picker-strategy-title {
|
|
display: flex;
|
|
align-items: baseline;
|
|
font-family: var(--ks-font);
|
|
font-size: 20px;
|
|
letter-spacing: -0.015em;
|
|
}
|
|
|
|
.picker-scale-options .picker-strategy-desc {
|
|
max-width: 38ch;
|
|
font-size: 14px;
|
|
line-height: 1.45;
|
|
}
|
|
|
|
/* The ratio leads the name: it is the thing being chosen, and eight of them
|
|
in a column read as a ladder when the numbers line up. */
|
|
.picker-scale-ratio {
|
|
margin-inline-end: 12px;
|
|
color: var(--ks-text-faint);
|
|
font-family: var(--ks-mono);
|
|
font-size: 0.94rem;
|
|
font-weight: 400;
|
|
font-variant-numeric: tabular-nums;
|
|
letter-spacing: 0;
|
|
transition: color 180ms var(--ks-ease);
|
|
}
|
|
|
|
/* The ratio answers to the same two states as the name it sits in front of,
|
|
held one step back so the name still leads the row. */
|
|
.picker-strategy-option:hover .picker-scale-ratio,
|
|
.picker-strategy-option:has(input:checked) .picker-scale-ratio {
|
|
color: color-mix(in oklab, var(--ks-patina) 70%, var(--ks-champagne));
|
|
}
|
|
|
|
.picker-scale-note {
|
|
color: var(--ks-text-faint);
|
|
font-size: 0.76rem;
|
|
letter-spacing: 0.04em;
|
|
}
|
|
|
|
/* The sheet is picker chrome, not an artboard: it answers a question about
|
|
size, and every other screen already showed the palette. So it belongs to
|
|
the picker's own dark theme and reads the --ks tokens directly, matching the
|
|
raised panel the options sit in on the left. The one thing it borrows from
|
|
the run is the pair chosen on screen 04, because a scale is judged in the
|
|
faces that will set it.
|
|
|
|
Rows are not equal: the top three are the ones being judged, so they take a
|
|
larger share of the sheet and leave the small steps as the tail they are. */
|
|
.picker-scale-sheet {
|
|
--ts-fit: 1;
|
|
--pt-heading: var(--ks-font-display);
|
|
--pt-body: var(--ks-font);
|
|
--pt-heading-weight: 400;
|
|
min-width: 0;
|
|
min-height: 0;
|
|
display: grid;
|
|
grid-template-rows: 1.34fr 1.2fr 1.1fr repeat(4, minmax(0, 1fr));
|
|
padding: clamp(14px, 2.4%, 28px) clamp(18px, 3.2%, 36px);
|
|
background: var(--ks-lacquer-raised);
|
|
color: var(--ks-champagne);
|
|
border: 1px solid var(--ks-rule);
|
|
border-radius: 2px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* Fixed side columns, because each row is its own grid: sized to their content
|
|
they would land wherever that row's specimen and numbers happened to end,
|
|
and the tag column would step left and right down the sheet and again on
|
|
every font change. Rem holds them still; the sheet's own face does not.
|
|
|
|
The gutters answer to the row rather than the viewport, because the row is
|
|
one of three columns now and a viewport-scaled gutter was spending 48px of a
|
|
538px sheet on air the numbers did not ask for. */
|
|
.ts-row {
|
|
min-width: 0;
|
|
min-height: 0;
|
|
display: grid;
|
|
grid-template-columns: minmax(0, 1fr) 2.4rem 4.4rem;
|
|
align-items: center;
|
|
gap: clamp(12px, 3%, 24px);
|
|
border-bottom: 1px dotted var(--ks-rule);
|
|
}
|
|
|
|
.ts-row:last-child {
|
|
border-bottom: 0;
|
|
}
|
|
|
|
.ts-sample {
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
color: var(--ks-text-muted);
|
|
font-family: var(--pt-heading);
|
|
font-weight: var(--pt-heading-weight);
|
|
font-size: calc(var(--ts-size) * var(--ts-fit) * 1px);
|
|
line-height: 1.12;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* The three steps a display scale is actually chosen on. They carry the
|
|
brightest ink in the sheet; everything below them is there to show the
|
|
ratio continuing. */
|
|
.ts-row[data-scale-row="6"] .ts-sample,
|
|
.ts-row[data-scale-row="5"] .ts-sample,
|
|
.ts-row[data-scale-row="4"] .ts-sample {
|
|
color: var(--ks-champagne);
|
|
}
|
|
|
|
.ts-row[data-scale-row="0"] .ts-sample {
|
|
color: var(--ks-text-faint);
|
|
font-family: var(--pt-body);
|
|
font-weight: 400;
|
|
}
|
|
|
|
/* The tag and the numbers hold one size while everything to their left moves,
|
|
which is what makes the movement legible. Both are trimmed to their glyphs
|
|
so they center against the specimen rather than against a line box whose
|
|
height belongs to the font's metrics.
|
|
|
|
The tag is not chrome. It is the name of the step being judged, so it takes
|
|
the sheet's brightest ink and enough weight to be read across the row
|
|
without leaning in. The numbers under it are reference, and sit one step
|
|
back, but a size that needs a squint is not reference either. */
|
|
.ts-tag {
|
|
color: var(--ks-champagne);
|
|
font-family: var(--ks-font);
|
|
font-size: 1.02rem;
|
|
font-weight: 600;
|
|
font-variant-numeric: tabular-nums;
|
|
letter-spacing: 0.04em;
|
|
text-box: trim-both cap alphabetic;
|
|
}
|
|
|
|
.ts-values {
|
|
display: grid;
|
|
justify-items: end;
|
|
gap: 5px;
|
|
font-family: var(--ks-mono);
|
|
font-size: 0.8rem;
|
|
font-variant-numeric: tabular-nums;
|
|
line-height: 1.25;
|
|
}
|
|
|
|
.ts-values b,
|
|
.ts-values i {
|
|
text-box: trim-both cap alphabetic;
|
|
}
|
|
|
|
.ts-values b {
|
|
color: var(--ks-text-muted);
|
|
font-weight: 500;
|
|
}
|
|
|
|
.ts-values i {
|
|
color: var(--ks-text-faint);
|
|
font-style: normal;
|
|
}
|
|
|
|
/* The sheet answers what each step measures. This column answers what the steps
|
|
do to each other once a page is built out of them, which is a different
|
|
question and the one a scale is actually chosen on. Same panel, same ink, same
|
|
sizes: it reads the step values syncTypeScale computed for the sheet, so one
|
|
scale cannot render two ways on one screen.
|
|
|
|
What it deliberately has none of is color, and no chrome past the panel it
|
|
sits in. A palette here would ask screen 03's question again inside screen
|
|
05's, and a specimen that has to be read past its own decoration is not
|
|
showing a type scale. The annotation is held to the same rule: thirteen
|
|
accented marks on a colorless field were the brightest thing on the screen,
|
|
which put the loudest ink on the layer that is only there to be looked up.
|
|
Mono, faint, and out in the margin says annotation three times over. */
|
|
.picker-scale-specimen {
|
|
--pt-heading: var(--ks-font-display);
|
|
--pt-body: var(--ks-font);
|
|
--pt-heading-weight: 400;
|
|
/* One rhythm unit for the column, taken from the step the running text sets
|
|
at, so the air between components is measured in the scale being judged
|
|
rather than against it. */
|
|
--sp-rhythm: calc(var(--ts-step-0) * 1px);
|
|
/* The margin the annotation hangs in. Reserved as padding so a label can
|
|
never be measured against the line it names. Sized to the longest label
|
|
the column sets, five mono characters, plus enough slack that a fallback
|
|
mono face with wider glyphs still clears the panel edge. */
|
|
--sp-rail: 2.6rem;
|
|
--sp-rail-gap: 0.85rem;
|
|
--sp-pad: clamp(18px, 3.2%, 36px);
|
|
min-width: 0;
|
|
min-height: 0;
|
|
display: grid;
|
|
align-content: start;
|
|
/* Two intervals, not one. A page is read in sections, and a column set at a
|
|
single interval has none: the heading, its paragraph, and its list arrive
|
|
as three unrelated blocks, which is the one thing a specimen of a scale
|
|
must not say. Blocks inside a section sit close; the break before a
|
|
heading is the wide one. Both are counted in the base step, so the cadence
|
|
holds while every size in the column moves. */
|
|
gap: calc(var(--sp-rhythm) * 1.5);
|
|
padding-block: calc(clamp(14px, 2.4%, 28px) + 10px) clamp(14px, 2.4%, 28px);
|
|
padding-inline: calc(var(--sp-rail) + var(--sp-rail-gap) + 8px) 16px;
|
|
background: var(--ks-lacquer-raised);
|
|
color: var(--ks-text);
|
|
border: 1px solid var(--ks-rule);
|
|
border-radius: 2px;
|
|
overflow-y: auto;
|
|
/* Reaching the end of the specimen should not then carry the page with it. */
|
|
overscroll-behavior: contain;
|
|
/* Screen 04's list says it scrolls with a thin patina track, and the same
|
|
track says it here. What that list adds on top, arrows that page by a row,
|
|
has nothing to page through in a column of prose. */
|
|
scrollbar-width: thin;
|
|
scrollbar-color: color-mix(in oklab, var(--ks-patina-deep) 35%, transparent) transparent;
|
|
}
|
|
|
|
/* Both preview columns take the rail's shape, a panel with a line under it, so
|
|
the three panels end on one edge. What goes in that line is what the column
|
|
owes the reader: the specimen hides more than either list on this screen and
|
|
an overlay scrollbar says so only once the reader has already guessed, so it
|
|
gets the rail's arrows; the sheet hides nothing and gets the sentence that
|
|
explains why its numbers and its rendered sizes disagree, which is also the
|
|
one place that sentence has ever been next to the numbers. */
|
|
.picker-scale-column {
|
|
min-width: 0;
|
|
min-height: 0;
|
|
display: grid;
|
|
grid-template-rows: minmax(0, 1fr) auto;
|
|
gap: 14px;
|
|
}
|
|
|
|
/* A column of prose is the one item in this row whose content is taller than
|
|
any viewport, so left alone it takes the whole cap and the rail, which stops
|
|
at 570, ends 90px short of it. Taking the rail's ceiling keeps the columns
|
|
landing on one line and keeps the row the height the CTA above the fold was
|
|
calibrated against. */
|
|
@media (min-width: 1200px) {
|
|
.picker-scale-column {
|
|
max-height: 570px;
|
|
}
|
|
}
|
|
|
|
/* One sentence is shorter than the 44px buttons the other two lines are made
|
|
of, and a line that measures less takes height back from its panel, which
|
|
loses the shared edge the shape exists for. */
|
|
.picker-scale-column .picker-type-controls {
|
|
min-height: 44px;
|
|
}
|
|
|
|
/* A grid item's automatic minimum is its min-content width, so one word of the
|
|
display heading at the widest ratio can hold this column open past the panel
|
|
and hand it a horizontal scrollbar. Zeroing that floor and letting the word
|
|
break keeps every overflow on the axis the column already scrolls. */
|
|
.picker-scale-specimen > * {
|
|
min-width: 0;
|
|
overflow-wrap: break-word;
|
|
}
|
|
|
|
/* Headings take the display face and running text the body face, which is the
|
|
division screen 04 just committed to. */
|
|
.picker-scale-specimen :is(.sp-h1, .sp-h2, .sp-h3, .sp-h4) {
|
|
margin: 0;
|
|
color: var(--ks-champagne);
|
|
font-family: var(--pt-heading);
|
|
font-weight: var(--pt-heading-weight);
|
|
line-height: 1.14;
|
|
letter-spacing: -0.01em;
|
|
text-wrap: balance;
|
|
}
|
|
|
|
.sp-h1 {
|
|
font-size: calc(var(--ts-step-6) * 1px);
|
|
}
|
|
|
|
.sp-h2 {
|
|
font-size: calc(var(--ts-step-5) * 1px);
|
|
}
|
|
|
|
.sp-h3 {
|
|
font-size: calc(var(--ts-step-4) * 1px);
|
|
}
|
|
|
|
.sp-h4 {
|
|
font-size: calc(var(--ts-step-3) * 1px);
|
|
}
|
|
|
|
/* A heading belongs to what follows it, so the break in the column goes above
|
|
it and not below. The column gap alone reads as a list of unrelated blocks. */
|
|
.picker-scale-specimen :is(.sp-h2, .sp-h3, .sp-h4) {
|
|
margin-block-start: calc(var(--sp-rhythm) * 2.4);
|
|
}
|
|
|
|
/* Which component each block is, said in the annotation layer rather than in
|
|
the specimen: the mono face where the specimen is proportional, the same
|
|
faint ink the sheet's reference numbers take, and the smallest step the kit
|
|
labels anything at.
|
|
|
|
It hangs in the reserved margin instead of entering the block. A float or a
|
|
right inset both shorten the line box they share, and this column exists to
|
|
show how a ratio wraps: at the widest ratios a floated label was breaking
|
|
the display heading onto a third line and leaving one character alone on the
|
|
first, so the annotation was deciding the texture it was there to describe.
|
|
Out here it decides nothing, and thirteen marks that were ragged against
|
|
thirteen different line endings share one edge. */
|
|
.picker-scale-specimen [data-sp-label] {
|
|
position: relative;
|
|
}
|
|
|
|
.picker-scale-specimen [data-sp-label]::before {
|
|
content: attr(data-sp-label);
|
|
position: absolute;
|
|
top: 0;
|
|
padding-top: 6px;
|
|
inset-inline-end: calc(100% + var(--sp-rail-gap));
|
|
color: var(--ks-text-faint);
|
|
font-family: var(--ks-mono);
|
|
font-size: var(--ks-type-eyebrow-size);
|
|
font-weight: 500;
|
|
letter-spacing: 0.08em;
|
|
line-height: 1.5;
|
|
text-transform: uppercase;
|
|
white-space: nowrap;
|
|
/* Trimmed so the label's cap, not its leading, is what lands on the block's
|
|
top edge. The blocks above and below it are set at whatever the ratio
|
|
dealt, so any alignment measured from a line box moves with the scale. */
|
|
text-box: trim-both cap alphabetic;
|
|
}
|
|
|
|
.picker-scale-specimen :is(.sp-lede, .sp-p, .sp-list, .sp-quote) {
|
|
margin: 0;
|
|
font-family: var(--pt-body);
|
|
font-weight: 400;
|
|
}
|
|
|
|
.sp-lede {
|
|
color: var(--ks-champagne);
|
|
font-size: calc(var(--ts-step-1) * 1px);
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.sp-p {
|
|
font-size: calc(var(--ts-step-0) * 1px);
|
|
line-height: 1.62;
|
|
}
|
|
|
|
.picker-scale-specimen strong {
|
|
color: var(--ks-champagne);
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* Clear of the descenders the body face will have, whichever face it is. */
|
|
.picker-scale-specimen u {
|
|
text-decoration-thickness: from-font;
|
|
text-underline-offset: 0.16em;
|
|
}
|
|
|
|
.sp-list {
|
|
padding-inline-start: calc(var(--sp-rhythm) * 1.5);
|
|
font-size: calc(var(--ts-step-0) * 1px);
|
|
line-height: 1.58;
|
|
}
|
|
|
|
.sp-list li + li {
|
|
margin-block-start: calc(var(--sp-rhythm) * 0.5);
|
|
}
|
|
|
|
.sp-list-ordered {
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
.sp-list::marker,
|
|
.sp-list li::marker {
|
|
color: var(--ks-text-faint);
|
|
}
|
|
|
|
/* The rule is the quote's only mark. It is a typographic convention rather
|
|
than picker chrome, which is why it survives the no-decoration rule that
|
|
removed everything else from this column. */
|
|
.sp-quote {
|
|
padding-inline-start: calc(var(--sp-rhythm) * 1.2);
|
|
border-inline-start: 1px solid var(--ks-rule);
|
|
}
|
|
|
|
.sp-quote p {
|
|
margin: 0;
|
|
color: var(--ks-champagne);
|
|
font-size: calc(var(--ts-step-2) * 1px);
|
|
line-height: 1.42;
|
|
}
|
|
|
|
.sp-cite {
|
|
display: block;
|
|
margin-block-start: calc(var(--sp-rhythm) * 0.7);
|
|
color: var(--ks-text-muted);
|
|
font-size: calc(var(--ts-step-0) * 1px);
|
|
font-style: normal;
|
|
letter-spacing: 0.02em;
|
|
}
|
|
|
|
/* The mono face is not part of the pair screen 04 deals, so code takes the
|
|
kit's own stack. Guessing a third face here would put a claim in the
|
|
specimen that the run never made. */
|
|
.sp-code,
|
|
.sp-pre {
|
|
/* Mono runs large against a proportional face set at the same size. */
|
|
font-size: calc(var(--ts-step-0) * 0.94px);
|
|
font-family: var(--ks-mono);
|
|
}
|
|
|
|
.sp-code {
|
|
color: var(--ks-champagne);
|
|
}
|
|
|
|
/* The block wraps instead of scrolling sideways, and not for taste: an
|
|
overflowing box is a scroll container, a scroll container's automatic minimum
|
|
height is zero, and this column always has more content than height. The grid
|
|
spends that overflow on the one track it is allowed to shrink, so a code block
|
|
with overflow-x lands two pixels tall and the snippet disappears. */
|
|
.sp-pre {
|
|
margin: 0;
|
|
padding: 14px 16px;
|
|
color: var(--ks-text-muted);
|
|
border: 1px solid var(--ks-rule);
|
|
border-radius: 2px;
|
|
line-height: 1.55;
|
|
white-space: pre-wrap;
|
|
}
|
|
|
|
/* ============================================================
|
|
Screen 11: the icon set. Screen 05's frame again, because the question has
|
|
the same shape: more candidates than fit the rail, and one specimen that
|
|
redraws as the cursor moves down the list.
|
|
============================================================ */
|
|
.picker-icons {
|
|
display: grid;
|
|
gap: clamp(24px, 4.4svh, 40px);
|
|
padding-block: clamp(32px, 5svh, 48px) clamp(24px, 3.5svh, 48px);
|
|
}
|
|
|
|
.picker-icons-grid {
|
|
max-height: calc(100svh - 420px);
|
|
display: grid;
|
|
grid-template-columns: minmax(340px, 420px) minmax(0, 1fr);
|
|
gap: 50px;
|
|
align-items: stretch;
|
|
}
|
|
|
|
/* Eleven rows scroll, so this list takes screen 04's rail rather than screen
|
|
03's fixed row split. States come from the standard option row; what the
|
|
row adds is the pack's own numbers and the way out to its site. */
|
|
.picker-icon-options .picker-strategy-option {
|
|
padding-block: 16px;
|
|
/* The link sits in this gutter. */
|
|
padding-inline-end: 60px;
|
|
}
|
|
|
|
.picker-icon-options .picker-strategy-copy {
|
|
gap: 5px;
|
|
}
|
|
|
|
.picker-icon-options .picker-strategy-title {
|
|
font-family: var(--ks-font);
|
|
font-size: 21px;
|
|
letter-spacing: -0.015em;
|
|
}
|
|
|
|
.picker-icon-options .picker-strategy-desc {
|
|
max-width: 40ch;
|
|
font-size: 13px;
|
|
line-height: 1.45;
|
|
}
|
|
|
|
/* Grid, weight, license: the three facts that decide this question before any
|
|
description does. Measurements, so they are set in the mono face. */
|
|
.picker-icon-meta {
|
|
color: var(--ks-text-faint);
|
|
font-family: var(--ks-mono);
|
|
font-size: 0.7rem;
|
|
}
|
|
|
|
/* An anchor inside a <label> still checks the radio on its way to the new tab,
|
|
so the way out is a sibling of the row and sits above it. */
|
|
.picker-icon-row {
|
|
position: relative;
|
|
display: grid;
|
|
}
|
|
|
|
.picker-icon-options .picker-icon-row:not(:last-child) .picker-strategy-option {
|
|
border-bottom: 1px solid var(--ks-rule);
|
|
}
|
|
|
|
.picker-icon-out {
|
|
position: absolute;
|
|
top: 14px;
|
|
right: 16px;
|
|
width: 30px;
|
|
height: 30px;
|
|
display: grid;
|
|
place-items: center;
|
|
color: var(--ks-text-mute-deep);
|
|
border: 1px solid transparent;
|
|
border-radius: 2px;
|
|
transition:
|
|
color 180ms var(--ks-ease),
|
|
border-color 180ms var(--ks-ease);
|
|
}
|
|
|
|
.picker-icon-out svg {
|
|
width: 15px;
|
|
height: 15px;
|
|
fill: none;
|
|
stroke: currentColor;
|
|
stroke-width: 1.4;
|
|
stroke-linecap: round;
|
|
stroke-linejoin: round;
|
|
}
|
|
|
|
.picker-icon-out:hover {
|
|
color: var(--ks-patina);
|
|
border-color: var(--ks-rule);
|
|
}
|
|
|
|
.picker-icon-out:focus-visible {
|
|
outline: 2px solid var(--ks-patina);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
/* Like the scale sheet, the specimen is picker chrome in the picker's own
|
|
theme: it answers a question about drawing, and the palette has had its
|
|
screens already. One field divided by hairlines, not twenty-four bordered
|
|
tiles on a dark ground, which is the icon grid this project spends its
|
|
detector telling people not to ship. */
|
|
.picker-icon-sheet {
|
|
min-width: 0;
|
|
min-height: 0;
|
|
display: grid;
|
|
grid-template-rows: minmax(0, 1fr) auto;
|
|
background: var(--ks-lacquer-raised);
|
|
border: 1px solid var(--ks-rule);
|
|
border-radius: 2px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.picker-icon-strip {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16px;
|
|
/* A label row, so it gives height back to the field on a short screen instead
|
|
of holding a comfortable padding the specimen has to pay for. */
|
|
padding: clamp(9px, 1.5svh, 14px) clamp(16px, 2.2%, 24px);
|
|
}
|
|
|
|
/* The division is the grid's own gap over a rule-colored ground, so it reads as
|
|
one continuous hairline and no cell owns an edge. */
|
|
.picker-icon-field {
|
|
min-height: 0;
|
|
display: grid;
|
|
grid-template-columns: repeat(6, minmax(0, 1fr));
|
|
grid-auto-rows: minmax(0, 1fr);
|
|
gap: 1px;
|
|
background: var(--ks-rule);
|
|
border-bottom: 1px solid var(--ks-rule);
|
|
}
|
|
|
|
/* Nothing to divide when the fetch fails, so the field drops the hairlines and
|
|
carries the reason in the space the glyphs would have used. */
|
|
.picker-icon-field[data-empty] {
|
|
grid-template-columns: minmax(0, 1fr);
|
|
place-items: center;
|
|
padding: 24px;
|
|
background: var(--ks-lacquer-raised);
|
|
color: var(--ks-text-muted);
|
|
text-align: center;
|
|
}
|
|
|
|
.picker-icon-cell {
|
|
min-width: 0;
|
|
min-height: 0;
|
|
display: grid;
|
|
place-items: center;
|
|
background: var(--ks-lacquer-raised);
|
|
color: var(--ks-champagne);
|
|
}
|
|
|
|
.picker-icon-cell svg {
|
|
width: clamp(22px, 2.2vw, 32px);
|
|
height: auto;
|
|
}
|
|
|
|
/* The frame is short and wide before it is anything else, so on a laptop the
|
|
same twenty-four glyphs lie down into three long rows rather than four
|
|
letterbox ones. Nothing is dropped and nothing is scaled down. */
|
|
@media (max-height: 830px) {
|
|
.picker-icon-field {
|
|
grid-template-columns: repeat(8, minmax(0, 1fr));
|
|
}
|
|
}
|
|
|
|
/* A pack is chosen for interface work, and interface work happens at 16px.
|
|
The row underneath is the same twenty-four glyphs at the size they will
|
|
actually be read, which is where a busy set gives itself away. */
|
|
.picker-icon-strip-label {
|
|
flex: none;
|
|
color: var(--ks-text-faint);
|
|
font-family: var(--ks-mono);
|
|
font-size: 0.7rem;
|
|
}
|
|
|
|
.picker-icon-strip-row {
|
|
min-width: 0;
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 8px 11px;
|
|
color: var(--ks-text-muted);
|
|
}
|
|
|
|
.picker-icon-strip-row svg {
|
|
width: 16px;
|
|
height: 16px;
|
|
}
|
|
|
|
.picker-type-scroll {
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
|
|
.picker-type-custom {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
height: 36px;
|
|
padding: 0 16px;
|
|
background: transparent;
|
|
border: 1px solid var(--ks-rule);
|
|
border-radius: 2px;
|
|
color: var(--ks-text-muted);
|
|
font-family: var(--ks-font);
|
|
font-size: 0.8rem;
|
|
letter-spacing: 0.04em;
|
|
cursor: pointer;
|
|
transition:
|
|
color 180ms var(--ks-ease),
|
|
border-color 180ms var(--ks-ease),
|
|
background-color 180ms var(--ks-ease);
|
|
}
|
|
|
|
.picker-type-custom svg {
|
|
width: 14px;
|
|
height: 14px;
|
|
fill: none;
|
|
stroke: currentColor;
|
|
stroke-width: 1.4;
|
|
stroke-linecap: square;
|
|
}
|
|
|
|
.picker-type-custom:hover {
|
|
color: var(--ks-champagne);
|
|
border-color: color-mix(in oklab, var(--ks-patina) 45%, transparent);
|
|
background: color-mix(in oklab, var(--ks-patina) 8%, transparent);
|
|
}
|
|
|
|
.picker-type-custom:focus-visible {
|
|
outline: 1px solid var(--ks-kinpaku);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
/* Custom font sheet. A native <dialog> carries the modal behaviour, so the
|
|
styling only has to dress it. */
|
|
.picker-modal {
|
|
width: min(520px, calc(100vw - 48px));
|
|
margin: auto;
|
|
padding: 0;
|
|
background: var(--ks-lacquer-raised);
|
|
border: 1px solid var(--ks-rule);
|
|
border-radius: 2px;
|
|
color: var(--ks-text);
|
|
}
|
|
|
|
.picker-modal::backdrop {
|
|
background: oklch(2% 0.004 95 / 0.72);
|
|
backdrop-filter: blur(3px);
|
|
}
|
|
|
|
.picker-modal-inner {
|
|
display: grid;
|
|
gap: 24px;
|
|
padding: 32px;
|
|
}
|
|
|
|
.picker-modal-head {
|
|
display: grid;
|
|
gap: 8px;
|
|
}
|
|
|
|
.picker-modal-head h2 {
|
|
color: var(--ks-champagne);
|
|
font-family: var(--ks-font);
|
|
font-size: 1.7rem;
|
|
font-weight: 400;
|
|
line-height: 1.1;
|
|
}
|
|
|
|
.picker-modal-head p {
|
|
color: var(--ks-text-muted);
|
|
font-size: 0.9rem;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.picker-modal-field {
|
|
display: grid;
|
|
gap: 8px;
|
|
padding: 20px;
|
|
border: 1px solid var(--ks-rule);
|
|
border-radius: 2px;
|
|
}
|
|
|
|
.picker-modal-field legend {
|
|
padding-inline: 8px;
|
|
margin-inline-start: -8px;
|
|
color: var(--ks-kinpaku);
|
|
font-size: 0.68rem;
|
|
letter-spacing: 0.16em;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.picker-modal-label {
|
|
color: var(--ks-text-muted);
|
|
font-size: 0.78rem;
|
|
letter-spacing: 0.02em;
|
|
}
|
|
|
|
.picker-modal-input {
|
|
height: 40px;
|
|
padding: 0 12px;
|
|
background: var(--ks-lacquer-deep);
|
|
border: 1px solid var(--ks-rule);
|
|
border-radius: 2px;
|
|
color: var(--ks-champagne);
|
|
font-family: var(--ks-font);
|
|
font-size: 0.88rem;
|
|
}
|
|
|
|
.picker-modal-input::placeholder {
|
|
color: color-mix(in oklab, var(--ks-text-muted) 65%, transparent);
|
|
}
|
|
|
|
.picker-modal-input:focus-visible {
|
|
outline: 1px solid var(--ks-patina);
|
|
outline-offset: 1px;
|
|
border-color: transparent;
|
|
}
|
|
|
|
.picker-modal-or {
|
|
color: var(--ks-text-muted);
|
|
font-size: 0.68rem;
|
|
letter-spacing: 0.16em;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.picker-modal-file-block {
|
|
display: grid;
|
|
gap: 10px;
|
|
}
|
|
|
|
.picker-modal-file {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
min-width: 0;
|
|
}
|
|
|
|
.picker-modal-file input {
|
|
position: absolute;
|
|
width: 1px;
|
|
height: 1px;
|
|
overflow: hidden;
|
|
clip-path: inset(50%);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.picker-modal-file-button {
|
|
flex: none;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
padding: 8px 14px;
|
|
border: 1px solid var(--ks-rule);
|
|
border-radius: 2px;
|
|
color: var(--ks-champagne);
|
|
font-size: 0.8rem;
|
|
cursor: pointer;
|
|
transition: border-color 180ms var(--ks-ease), color 180ms var(--ks-ease);
|
|
}
|
|
|
|
.picker-modal-file:hover .picker-modal-file-button {
|
|
border-color: color-mix(in oklab, var(--ks-patina) 45%, transparent);
|
|
color: var(--ks-patina);
|
|
}
|
|
|
|
.picker-modal-file input:focus-visible + .picker-modal-file-button {
|
|
outline: 1px solid var(--ks-kinpaku);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
.picker-modal-file-list {
|
|
display: grid;
|
|
gap: 6px;
|
|
margin: 0;
|
|
padding: 0;
|
|
list-style: none;
|
|
}
|
|
|
|
.picker-modal-file-item {
|
|
display: grid;
|
|
grid-template-columns: minmax(0, 1fr) auto;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 8px 10px;
|
|
background: var(--ks-lacquer-deep);
|
|
border: 1px solid var(--ks-rule);
|
|
border-radius: 2px;
|
|
}
|
|
|
|
.picker-modal-file-item span {
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
color: var(--ks-text);
|
|
font-size: 0.8rem;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.picker-modal-file-remove {
|
|
flex: none;
|
|
width: 24px;
|
|
height: 24px;
|
|
padding: 0;
|
|
border: 0;
|
|
border-radius: 2px;
|
|
background: transparent;
|
|
color: var(--ks-text-muted);
|
|
font-size: 1.1rem;
|
|
line-height: 1;
|
|
cursor: pointer;
|
|
transition: color 180ms var(--ks-ease), background-color 180ms var(--ks-ease);
|
|
}
|
|
|
|
.picker-modal-file-remove:hover {
|
|
color: var(--ks-champagne);
|
|
background: color-mix(in oklab, var(--ks-patina) 10%, transparent);
|
|
}
|
|
|
|
.picker-modal-file-remove:focus-visible {
|
|
outline: 1px solid var(--ks-kinpaku);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
.picker-modal-status:empty {
|
|
display: none;
|
|
}
|
|
|
|
.picker-modal-status {
|
|
color: var(--ks-patina);
|
|
font-size: 0.82rem;
|
|
}
|
|
|
|
.picker-modal-hint:empty {
|
|
display: none;
|
|
}
|
|
|
|
/* What the sheet is still waiting for, in the voice of the field labels above
|
|
it rather than an error. Nothing is wrong yet: the form is unfinished, so
|
|
this is muted caption ink and carries no vermilion and no icon. It sits in
|
|
the footer because that is where the button it explains is. */
|
|
.picker-modal-hint {
|
|
color: var(--ks-text-muted);
|
|
font-size: 0.82rem;
|
|
line-height: 1.45;
|
|
}
|
|
|
|
/* Cancel then commit, both hard against the right padding edge. The dialog is a
|
|
fixed 520px behind a 1200px width gate and the pair needs 247px of it, so
|
|
wrapping can only ever fire on a stray long label; nowrap keeps the two
|
|
together and lets them shrink rather than stranding the commit button on a
|
|
line of its own under Cancel. */
|
|
.picker-modal-actions {
|
|
display: flex;
|
|
flex-wrap: nowrap;
|
|
align-items: center;
|
|
justify-content: flex-end;
|
|
gap: 18px;
|
|
}
|
|
|
|
/* Screen 04 takes the standard option row and only tightens it: a specimen
|
|
needs less air around it than a label plus a sentence. */
|
|
.picker-type-options .picker-strategy-option {
|
|
gap: 0;
|
|
padding: 18px 26px 18px 28px;
|
|
}
|
|
|
|
.picker-type-copy {
|
|
min-width: 0;
|
|
display: grid;
|
|
gap: 2px;
|
|
}
|
|
|
|
/* Each face names itself in its own glyphs: the display family large, the
|
|
text family under it at reading size. An uploaded pair can be named after
|
|
its file, so both lines are allowed to break inside a word rather than
|
|
push the marker out of the row. */
|
|
.picker-type-sample-heading {
|
|
min-width: 0;
|
|
color: var(--ks-champagne);
|
|
font-family: var(--pair-heading, var(--ks-font-display));
|
|
font-size: 26px;
|
|
font-weight: var(--pair-heading-weight, 400);
|
|
line-height: 1.15;
|
|
letter-spacing: -0.015em;
|
|
overflow-wrap: anywhere;
|
|
transition: color 180ms var(--ks-ease);
|
|
}
|
|
|
|
.picker-type-sample-body {
|
|
min-width: 0;
|
|
color: var(--ks-text);
|
|
font-family: var(--pair-body, var(--ks-font));
|
|
font-size: 15px;
|
|
font-weight: var(--pair-body-weight, 400);
|
|
line-height: 1.4;
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
/* The role trails the family it names, on the same baseline and in the UI
|
|
font, the way a foundry annotates a specimen. Leading it would put the
|
|
chrome where the face belongs: the reader is judging glyphs first and only
|
|
then asking which slot they fill. */
|
|
.picker-type-role {
|
|
margin-left: 9px;
|
|
color: var(--ks-text-faint);
|
|
font-family: var(--ks-font);
|
|
font-size: 11px;
|
|
font-weight: 400;
|
|
line-height: 1.2;
|
|
letter-spacing: 0.02em;
|
|
white-space: nowrap;
|
|
transition: color 180ms var(--ks-ease);
|
|
}
|
|
|
|
/* The markers take the row's accent so they belong to the selected state,
|
|
mixed back toward faint ink so they never rise to the specimen's level. */
|
|
.picker-strategy-option:hover .picker-type-role,
|
|
.picker-strategy-option:has(input:checked) .picker-type-role {
|
|
color: color-mix(in oklab, var(--ks-patina) 45%, var(--ks-text-faint));
|
|
}
|
|
|
|
/* The descriptor is a phrase about the pairing, so it is set as one. Tracked
|
|
caps read as a category label and made a short sentence look shouted. */
|
|
.picker-type-desc {
|
|
margin-top: 7px;
|
|
color: var(--ks-text-muted);
|
|
font-family: var(--ks-font);
|
|
font-size: 13px;
|
|
font-weight: 400;
|
|
line-height: 1.45;
|
|
}
|
|
|
|
/* One golden-ratio ladder rules every piece of text on the artboards:
|
|
micro-copy sits one step below the body size, the headline two steps
|
|
above it. The phone artboard re-bases the same ladder smaller, so both
|
|
artboards stay on scale. A future type-scale step replaces the ratio;
|
|
until then the preview follows this one everywhere.
|
|
|
|
The structural boards are on it too. They set no glyphs, but the app shell,
|
|
the document, and the index are drawn out of this ladder's spacing steps, and
|
|
a board that resolved none of them would drop every rule that reaches for one.
|
|
The ink slots below stay where they are: those exist because a bar and a
|
|
printed line need different contrast, and a structural board is all bars. */
|
|
.picker-preview-type,
|
|
[data-carry] {
|
|
--pt-ratio: 1.618;
|
|
--pt-base: clamp(0.72rem, 0.86vw, 0.84rem);
|
|
}
|
|
|
|
.picker-preview-type {
|
|
/* Slot colors were mixed for bars, where a pale fill still reads as a
|
|
shape. Once those bars carry glyphs the same mix disappears, so the type
|
|
artboards re-derive every ink slot at reading contrast. The mix is taken
|
|
against the ground and the ink the strategy left in place, not against
|
|
the neutral: under Drenched the page is the primary, and running text
|
|
mixed toward a neutral it no longer sits on would go invisible. */
|
|
--pvs-copy: color-mix(in oklab, var(--pvs-ground) var(--pvs-copy-mix), var(--pvs-ink));
|
|
--pvs-bars: color-mix(in oklab, var(--pvs-ground) var(--pvs-bars-mix), var(--pvs-ink));
|
|
}
|
|
|
|
/* Drenched is the one page that raises a surface out of its own primary
|
|
rather than out of paper. A bar could take a heavy wash of the page ink and
|
|
still read as a shape; a label printed on that wash cannot, so the second
|
|
button keeps its fill near the ground and lets the border say button, and
|
|
the phone's chrome bands lift by less than they do in the wireframe. */
|
|
:where(#picker-form:has(input[name="color-strategy"][value="drenched"]:checked)) .picker-preview-type {
|
|
--pvs-ghost: color-mix(in oklab, var(--pv-p-ink) 12%, var(--pv-primary));
|
|
--pvs-surface: color-mix(in oklab, var(--pv-p-ink) 5%, var(--pv-primary));
|
|
}
|
|
|
|
/* The phone's chrome bands are the other raised surface, and their labels are
|
|
mixed for the ground the rest of the page sets type on. Same derivation,
|
|
taken against the band they actually sit on. */
|
|
.picker-preview-type :is(.ps-phone-top, .ps-phone-footer) {
|
|
--pvs-bars: color-mix(in oklab, var(--pvs-surface) var(--pvs-bars-mix), var(--pvs-ink));
|
|
}
|
|
|
|
/* The derived steps are declared on both artboard roots because custom
|
|
properties resolve their var() references where they are declared: the
|
|
phone re-bases --pt-base, so it needs its own ladder computation. The
|
|
micro step is floored rather than divided: strict scale fidelity puts it
|
|
under 8px, and this preview exists to be read.
|
|
|
|
The spacing scale hangs off the same base, so an artboard that sets its
|
|
type smaller gets proportionally tighter bands without a second set of
|
|
numbers. The gutter is the one exception: it is a share of the frame,
|
|
because a band's inset reads against the frame edge, not the type. */
|
|
.picker-preview-type,
|
|
.picker-preview-type .ps-phone,
|
|
[data-carry],
|
|
[data-carry] .ps-phone {
|
|
--pt-micro: max(0.62rem, calc(var(--pt-base) / var(--pt-ratio)));
|
|
--pt-title: calc(var(--pt-base) * var(--pt-ratio));
|
|
--pt-display: calc(var(--pt-base) * var(--pt-ratio) * var(--pt-ratio));
|
|
--ps-band-y: calc(var(--pt-base) * 0.9);
|
|
--ps-gap-xs: calc(var(--pt-base) * 0.4);
|
|
--ps-gap-sm: calc(var(--pt-base) * 0.75);
|
|
--ps-gap-md: calc(var(--pt-base) * 1.25);
|
|
--ps-gap-lg: calc(var(--pt-base) * 2);
|
|
}
|
|
|
|
.picker-preview-type {
|
|
--ps-gutter: 5.5%;
|
|
}
|
|
|
|
.picker-preview-type .ps-phone {
|
|
--ps-gutter: 6%;
|
|
grid-template-rows: 7.2% 1fr;
|
|
}
|
|
|
|
/* On a structural board the band inset is the drawn field's own margin, and it is
|
|
a constant on purpose. What the layout answer moves on a shell, a document, or
|
|
an index is the proportion of their regions, and the margin is only there so
|
|
those regions can be checked against the field. Hung off the layout answer
|
|
instead, the balanced page would take the artboard's 5.5 and 3.2, and every
|
|
region edge would land three pixels shy of the line it was drawn to meet:
|
|
read as sloppiness on a tool, where on the landing page the same lean is the
|
|
answer being shown. */
|
|
[data-carry] {
|
|
--ps-gutter: 6%;
|
|
}
|
|
|
|
[data-carry] .ps-phone {
|
|
--ps-gutter: var(--pvs-phone-gutter, 6%);
|
|
}
|
|
|
|
/* Centering a text box centers its em box, and every family puts its caps
|
|
somewhere else inside that box, so a centered label moves each time the
|
|
pair changes. Trimming to the cap and baseline edges makes the box the
|
|
glyphs, which is what the eye is aligning to. Browsers without it fall
|
|
back to today's line-box centering. */
|
|
.picker-preview-type :is(
|
|
.ps-brand-block,
|
|
.ps-nav-bars i,
|
|
.ps-nav-action,
|
|
.ps-actions i,
|
|
.ps-proof-item span,
|
|
.ps-footer-links i,
|
|
.ps-footer-mark,
|
|
.ps-phone-footer i,
|
|
.ps-gallery-item b
|
|
) {
|
|
text-box: trim-both cap alphabetic;
|
|
}
|
|
|
|
/* One rhythm down the column: the headline, the sentence, and the buttons are
|
|
spaced by the same scale rather than three margins that each drifted. */
|
|
.picker-preview-type .ps-hero-copy {
|
|
align-content: center;
|
|
gap: var(--ps-gap-md);
|
|
}
|
|
|
|
/* The pair is the subject and the image is a placeholder for one, so the copy
|
|
takes the column and the image takes what it needs to read as a picture. */
|
|
.picker-preview-type .ps-hero {
|
|
grid-template-columns: minmax(0, 1fr) 34%;
|
|
gap: var(--ps-gap-lg);
|
|
padding: var(--ps-band-y) var(--ps-gutter);
|
|
}
|
|
|
|
/* The column sets the width and the ratio sets the height, so the picture
|
|
stays a picture instead of stretching to whatever height the band has left.
|
|
The band is only a ceiling, for the pair whose copy runs long. */
|
|
.picker-preview-type .ps-hero > .ps-image {
|
|
width: 100%;
|
|
height: auto;
|
|
aspect-ratio: 4 / 5;
|
|
max-height: 100%;
|
|
align-self: center;
|
|
}
|
|
|
|
/* The artboard's height comes from the frame's aspect ratio, so the bands stay
|
|
proportional and each one has to hold its text rather than push the next
|
|
one down. Real copy needs more of the page than bar glyphs did: the hero
|
|
gives height to the editorial band, which now carries a wrapping title. */
|
|
.picker-preview-type .ps-desktop {
|
|
grid-template-rows: 9.4% 45% 9% 28.3% 8.3%;
|
|
}
|
|
|
|
/* The editorial band splits differently once its bars are words. Four cards
|
|
left the copy column at 147px, under the 169px the widest pairing needs to
|
|
set a four-word title in two lines, and left the last card a pixel short of
|
|
its label. Three cards, in even columns, clears both with room to spare. */
|
|
/* The two columns start together at the top of the row, and the row itself is
|
|
what centers in the band, so a pair that sets a shorter title does not leave
|
|
the whole section sitting against the rule above it. */
|
|
.picker-preview-type .ps-editorial {
|
|
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
|
gap: var(--ps-gap-lg);
|
|
align-content: center;
|
|
align-items: start;
|
|
padding: var(--ps-band-y) var(--ps-gutter);
|
|
}
|
|
|
|
.picker-preview-type .ps-editorial-copy {
|
|
gap: var(--ps-gap-sm);
|
|
}
|
|
|
|
.picker-preview-type .ps-desktop .ps-gallery {
|
|
height: auto;
|
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
gap: var(--ps-gap-md);
|
|
/* The cards keep their column's width so the three squares are one size;
|
|
what centers is their contents, below. Shrinking the card to its label
|
|
handed each square a different width. */
|
|
justify-items: stretch;
|
|
}
|
|
|
|
.picker-preview-type .ps-desktop .ps-gallery-item {
|
|
justify-items: center;
|
|
text-align: center;
|
|
}
|
|
|
|
.picker-preview-type .ps-desktop .ps-gallery-item span {
|
|
align-items: center;
|
|
}
|
|
|
|
/* The thumbnail stands in for a picture, not for the band: left to fill the
|
|
leftover height it grew taller than the copy beside it. A square sized off
|
|
the type scale keeps the three cards reading as a set of labels. */
|
|
.picker-preview-type .ps-gallery-item {
|
|
grid-template-rows: auto auto;
|
|
gap: var(--ps-gap-sm);
|
|
align-content: start;
|
|
}
|
|
|
|
.picker-preview-type .ps-gallery-item > i {
|
|
width: 100%;
|
|
max-width: calc(var(--pt-base) * 4);
|
|
aspect-ratio: 1;
|
|
}
|
|
|
|
.picker-preview-type .ps-desktop .ps-gallery-item > i {
|
|
margin-inline: auto;
|
|
}
|
|
|
|
/* Words set taller than bars, and the phone is the narrowest frame they have
|
|
to fit. Its card row goes: nav, hero, proof and a section of running copy
|
|
already show every face at both sizes, and the row that was left over is
|
|
the one that would clip.
|
|
|
|
Every row after the image sizes to its own content. The band that held the
|
|
proof row at a fixed 34px was the one place a tall pair had nowhere to go.
|
|
Whatever height is left over is spread between the sections rather than
|
|
pooled under the last one, so a compact pair reads as a page with air in it
|
|
and a wide one closes up to its own gap without spilling past the frame.
|
|
|
|
The picture keeps a cap rather than a share, for the reason under the shared
|
|
phone body: it is the only row here that is not a count of its own contents,
|
|
so it is the row that gives first when the frame runs short. */
|
|
.picker-preview-type .ps-phone-body {
|
|
grid-template-rows: minmax(0, 18%) repeat(5, auto);
|
|
gap: var(--ps-gap-sm);
|
|
align-content: space-between;
|
|
padding: var(--ps-band-y) var(--ps-gutter);
|
|
}
|
|
|
|
.picker-preview-type[data-surface="persuade"] .ps-phone-body > .ps-image {
|
|
display: none;
|
|
}
|
|
|
|
.picker-preview-type[data-surface="persuade"] .ps-phone-body {
|
|
grid-template-rows: repeat(5, auto);
|
|
gap: 0;
|
|
align-content: start;
|
|
}
|
|
|
|
.picker-preview-type[data-surface="persuade"] .ps-phone-body .pt-headline {
|
|
font-size: 26px;
|
|
line-height: 1.15;
|
|
margin-block-end: 8px;
|
|
}
|
|
|
|
.picker-preview-type[data-surface="persuade"] .ps-phone-body .pt-body {
|
|
margin-block-end: var(--ps-gap-md);
|
|
}
|
|
|
|
.picker-preview-type[data-surface="persuade"] .ps-phone-body .ps-actions {
|
|
margin-block-end: var(--ps-gap-lg);
|
|
}
|
|
|
|
.picker-preview-type[data-surface="persuade"] .ps-phone-body .ps-proof {
|
|
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
align-items: center;
|
|
column-gap: var(--ps-gap-md);
|
|
margin-block-end: var(--ps-gap-md);
|
|
}
|
|
|
|
.picker-preview-type[data-surface="persuade"] .ps-phone-body .ps-proof-divider,
|
|
.picker-preview-type[data-surface="persuade"] .ps-phone-body .ps-proof-item:nth-of-type(2) {
|
|
display: none;
|
|
}
|
|
|
|
.picker-preview-type[data-surface="persuade"] .ps-phone-body .ps-proof-item:first-of-type {
|
|
grid-column: 2;
|
|
justify-content: center;
|
|
}
|
|
|
|
.picker-preview-type[data-surface="persuade"] .ps-phone-body .ps-proof::before,
|
|
.picker-preview-type[data-surface="persuade"] .ps-phone-body .ps-proof::after {
|
|
content: "";
|
|
position: static;
|
|
width: 5px;
|
|
height: 5px;
|
|
background: none;
|
|
border-left: 1px solid color-mix(in oklab, var(--pvs-bars) 38%, transparent);
|
|
border-bottom: 1px solid color-mix(in oklab, var(--pvs-bars) 38%, transparent);
|
|
align-self: center;
|
|
}
|
|
|
|
.picker-preview-type[data-surface="persuade"] .ps-phone-body .ps-proof::before {
|
|
grid-column: 1;
|
|
transform: rotate(45deg);
|
|
}
|
|
|
|
.picker-preview-type[data-surface="persuade"] .ps-phone-body .ps-proof::after {
|
|
grid-column: 3;
|
|
transform: rotate(-135deg);
|
|
}
|
|
|
|
.picker-preview-type[data-surface="persuade"] .ps-phone-body .ps-editorial-copy {
|
|
gap: var(--ps-gap-xs);
|
|
}
|
|
|
|
.picker-preview-type[data-surface="persuade"] .ps-phone-body .ps-editorial-copy strong {
|
|
font-size: calc(var(--pt-title) * 1.12);
|
|
}
|
|
|
|
.picker-preview-type[data-surface="persuade"] .ps-phone-body .pt-section-body {
|
|
font-size: var(--pt-base);
|
|
}
|
|
|
|
/* One gap owns the spacing down the phone. The margins underneath it were a
|
|
second, unrelated scale, and every element that carried one landed on a
|
|
different interval from its neighbour. */
|
|
.picker-preview-type .ps-phone-body > * {
|
|
margin: 0;
|
|
}
|
|
|
|
.picker-preview-type .ps-phone-body .ps-actions {
|
|
gap: var(--ps-gap-sm);
|
|
}
|
|
|
|
/* The two buttons divide the row rather than each asking for the width its own
|
|
label wants. A pair of labels nobody wrote to a length was setting the widest
|
|
line on the handset, and a nowrap line is a width the column has to find:
|
|
the row came out wider than the phone, and every band under it was drawn to
|
|
the row instead of to the frame.
|
|
|
|
Two even columns rather than two flexible children, because a flex item still
|
|
reports what its label wants when its container is asked for a minimum, and
|
|
the minimum is exactly what was overflowing here. A label longer than half the
|
|
row ends in an ellipsis; the clip margin is the house allowance for a
|
|
descender under text-box trimming. */
|
|
.picker-preview-type .ps-phone-body .ps-actions {
|
|
display: grid;
|
|
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
|
}
|
|
|
|
.picker-preview-type .ps-phone-body .ps-actions i {
|
|
min-width: 0;
|
|
padding-inline: 0.8em;
|
|
overflow: clip;
|
|
overflow-clip-margin: 0.4em;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
/* The band already sits inside the body's gutter; its own inset put it a
|
|
step to the right of everything it lines up with. */
|
|
.picker-preview-type .ps-phone-body .ps-proof {
|
|
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
|
|
padding-inline: 0;
|
|
}
|
|
|
|
.picker-preview-type .ps-phone-body .ps-editorial-copy {
|
|
align-content: start;
|
|
}
|
|
|
|
.picker-preview-type .ps-phone-top,
|
|
.picker-preview-type .ps-phone-footer {
|
|
padding-inline: var(--ps-gutter);
|
|
}
|
|
|
|
/* The card row stays off the handset. The widest pairs set the headline in
|
|
three lines here, and the cards are the section that would be pushed past
|
|
the frame; every face the phone has to prove is already shown above. */
|
|
.picker-preview-type .ps-phone-body .ps-gallery {
|
|
display: none;
|
|
}
|
|
|
|
/* Asked of the board rather than the window, for the reason the docs and ops
|
|
boards are: the ladder floors while the frame keeps shrinking, so a short
|
|
frame holds fewer blocks at the same reading size. The ops and docs boards
|
|
name their own container over this one. */
|
|
.picker-preview-type {
|
|
container-type: size;
|
|
container-name: ps-type-board;
|
|
}
|
|
|
|
/* Below this the handset is holding more sections than the frame has room for,
|
|
and the picture it takes them out of has already given everything it has. The
|
|
section pair is what goes, on the same terms as the card row above: its
|
|
heading face is the headline's and its body face is the lede's, both already
|
|
set on this page, and the desktop beside it keeps the section in full. */
|
|
@container ps-type-board (height < 500px) {
|
|
.picker-preview-type .ps-phone-body .ps-editorial-copy {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
.pt-headline {
|
|
max-width: 16ch;
|
|
margin: 0;
|
|
color: var(--pvs-headline-text);
|
|
font-family: var(--pt-heading);
|
|
font-size: var(--pt-display);
|
|
font-weight: var(--pt-heading-weight);
|
|
line-height: 1.15;
|
|
letter-spacing: -0.02em;
|
|
overflow-wrap: break-word;
|
|
text-wrap: balance;
|
|
}
|
|
|
|
.pt-body {
|
|
max-width: 42ch;
|
|
margin: 0;
|
|
color: var(--pvs-copy);
|
|
font-family: var(--pt-body);
|
|
font-size: var(--pt-base);
|
|
line-height: 1.55;
|
|
}
|
|
|
|
/* The section paragraph is one element, so its leading is the face's own and
|
|
runs unbroken from the first line to the last. It was two stacked bars in
|
|
the wireframe, and two elements set two lines with the container's gap
|
|
between them, which read as a pair of unrelated lines rather than a
|
|
paragraph. Micro size over a near-full-column measure wants more leading
|
|
than the single-line labels around it, hence the step past their 1.4.
|
|
|
|
No measure cap: the artboard is a fixed-proportion frame with a ceiling of
|
|
its own, so the copy column stops growing before the line does. Measured
|
|
67 to 75 characters on the desktop board and 38 to 51 on the handset,
|
|
across every width from 1280 up. */
|
|
.pt-section-body {
|
|
margin: 0;
|
|
color: var(--pvs-copy);
|
|
font-family: var(--pt-body);
|
|
font-size: var(--pt-micro);
|
|
line-height: 1.6;
|
|
}
|
|
|
|
/* Phone artboard type: the same ladder re-based to handset scale, held above
|
|
the legibility floor rather than scaled straight down. */
|
|
.picker-preview-type .ps-phone,
|
|
[data-carry] .ps-phone {
|
|
--pt-base: clamp(0.66rem, 0.72vw, 0.74rem);
|
|
}
|
|
|
|
.ps-phone .pt-headline,
|
|
.ps-phone .pt-body {
|
|
max-width: none;
|
|
margin: 0;
|
|
}
|
|
|
|
/* On the type screen the wireframe's bar glyphs become micro-copy, so the
|
|
pair is judged on real glyphs in every slot: nav, buttons, proof row,
|
|
cards, footer. Each element keeps the color slot its bar used, moved
|
|
from background to ink; sizes come from the ladder above.
|
|
|
|
Overflow stays visible: a bar had to be kept inside its box, but a line of
|
|
text hangs its descenders below the baseline, and the trimmed box ends at
|
|
the baseline. Hiding the overflow would cut the tail off every g and p, so
|
|
the proof label, which does have to truncate, clips with a margin instead.
|
|
|
|
The nowrap below is the default for a one-line label, and the card labels
|
|
opt back out of it further down. Every selector in this list is one class
|
|
and one type, so the rules that override this one win on source order.
|
|
Adding a more specific one here (a descendant pair, say) silently raises
|
|
the whole :is() above them and puts the clipping back. */
|
|
.picker-preview-type :is(
|
|
.ps-nav-bars i,
|
|
.ps-proof-item span,
|
|
.ps-editorial-copy > em,
|
|
.ps-gallery-item b,
|
|
.ps-footer-links i,
|
|
.ps-footer-mark,
|
|
.ps-phone-footer i
|
|
) {
|
|
width: auto;
|
|
height: auto;
|
|
overflow: visible;
|
|
background: none;
|
|
border-radius: 0;
|
|
font-family: var(--pt-body);
|
|
font-size: var(--pt-micro);
|
|
font-style: normal;
|
|
font-weight: 500;
|
|
line-height: 1.4;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.picker-preview-type .ps-nav-bars i,
|
|
.picker-preview-type .ps-footer-links i,
|
|
.picker-preview-type .ps-footer-mark,
|
|
.picker-preview-type .ps-phone-footer i {
|
|
color: var(--pvs-bars);
|
|
}
|
|
|
|
/* Same construction as the nav: one baseline across the row, the row itself
|
|
centered in the band, both edges on the shared gutter. */
|
|
.picker-preview-type .ps-footer {
|
|
display: none;
|
|
}
|
|
|
|
.picker-preview-type .ps-phone-footer {
|
|
display: none;
|
|
}
|
|
|
|
.picker-preview-type .ps-phone .pt-headline {
|
|
font-size: var(--pt-title);
|
|
}
|
|
|
|
.picker-preview-type .ps-phone-top .ps-brand-block {
|
|
font-size: var(--pt-micro);
|
|
}
|
|
|
|
.picker-preview-type .ps-footer-links {
|
|
align-items: baseline;
|
|
gap: var(--ps-gap-md);
|
|
}
|
|
|
|
/* The mark, the links, and the button are three sizes of the same row, so
|
|
they hang from one baseline instead of being centered against each other;
|
|
the row as a whole is what centers in the band. The button is a shape
|
|
rather than a line of running text, so it centers on the row. */
|
|
.picker-preview-type .ps-nav {
|
|
gap: var(--ps-gap-lg);
|
|
align-content: center;
|
|
align-items: center;
|
|
padding-inline: var(--ps-gutter);
|
|
}
|
|
|
|
.picker-preview-type .ps-nav-bars {
|
|
justify-content: flex-start;
|
|
align-items: baseline;
|
|
gap: var(--ps-gap-md);
|
|
}
|
|
|
|
.picker-preview-type .ps-nav-action {
|
|
align-self: center;
|
|
}
|
|
|
|
/* The lettermark takes the heading face: a logo is display type. The circle's
|
|
square ratio has to go with its fill, or the mark keeps a box taller than
|
|
its glyphs and rides above the links it should sit beside. */
|
|
.picker-preview-type .ps-brand-block {
|
|
width: auto;
|
|
height: auto;
|
|
aspect-ratio: auto;
|
|
background: none;
|
|
border-radius: 0;
|
|
color: var(--pvs-mark-text);
|
|
font-family: var(--pt-heading);
|
|
font-size: var(--pt-title);
|
|
font-weight: var(--pt-heading-weight);
|
|
letter-spacing: 0.04em;
|
|
line-height: 1;
|
|
}
|
|
|
|
/* Buttons keep their fills; a filled label takes the ink of the fill it sits
|
|
on, which is a different color per strategy: Drenched paints the button in
|
|
the page's own ink, and Full palette hands the nav button to the tertiary.
|
|
The label is centered by the box being cut to the glyphs, so this stays a
|
|
block: a grid container has no line box of its own to trim. The filled
|
|
button carries a transparent border so it resolves to the same height as
|
|
the outlined one sitting next to it.
|
|
|
|
The hero buttons are `<i>`, so the style is stated rather than inherited:
|
|
the slots that reset the UA italic in one shared list cannot take these two,
|
|
because that list also clears the background and the radius and a button is
|
|
the fill it sits on. The nav button beside them is a `div` and never had the
|
|
italic to lose, which is how the pair came to be set two ways. */
|
|
.picker-preview-type .ps-nav-action,
|
|
.picker-preview-type .ps-actions i {
|
|
width: auto;
|
|
height: auto;
|
|
display: block;
|
|
padding: 0.7em 1.3em;
|
|
border: 1px solid transparent;
|
|
color: var(--pvs-cta-text);
|
|
font-family: var(--pt-body);
|
|
font-size: var(--pt-micro);
|
|
font-style: normal;
|
|
font-weight: 600;
|
|
line-height: 1;
|
|
text-align: center;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* The nav button is the one fill a strategy can hand to the tertiary, so its
|
|
label reads that fill's reading ink and not the hero button's. */
|
|
.picker-preview-type .ps-nav-action {
|
|
color: var(--pvs-signal-text);
|
|
}
|
|
|
|
.picker-preview-type .ps-actions {
|
|
align-items: center;
|
|
gap: var(--ps-gap-sm);
|
|
}
|
|
|
|
/* The ghost button sits on the ground, so ground-colored ink would erase it. */
|
|
.picker-preview-type .ps-actions i:last-child {
|
|
color: var(--pvs-title);
|
|
}
|
|
|
|
/* The band's rules become its own borders, so they run the full width of the
|
|
frame and meet the nav's bottom edge and the footer's top edge instead of
|
|
stopping short of the right margin. */
|
|
.picker-preview-type .ps-proof {
|
|
grid-template-columns:
|
|
minmax(0, 1fr) auto
|
|
minmax(0, 1fr) auto
|
|
minmax(0, 1fr) auto
|
|
minmax(0, 1fr);
|
|
padding: var(--ps-band-y) var(--ps-gutter);
|
|
border-block: 1px solid var(--pvs-rule);
|
|
}
|
|
|
|
.picker-preview-type .ps-proof::before,
|
|
.picker-preview-type .ps-proof::after {
|
|
content: none;
|
|
}
|
|
|
|
/* A share of the cell put a different gap in every column. */
|
|
.picker-preview-type .ps-proof-item {
|
|
gap: var(--ps-gap-sm);
|
|
}
|
|
|
|
.picker-preview-type .ps-proof-item i {
|
|
width: calc(var(--pt-base) * 0.55);
|
|
}
|
|
|
|
.picker-preview-type .ps-proof-item + .ps-proof-item {
|
|
border-left: 0;
|
|
}
|
|
|
|
.picker-preview-type .ps-proof-divider {
|
|
width: 1px;
|
|
align-self: stretch;
|
|
background-color: var(--pvs-rule);
|
|
}
|
|
|
|
.picker-preview-type .ps-phone-body .ps-proof {
|
|
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
|
|
}
|
|
|
|
.picker-preview-type .ps-proof-item:nth-of-type(2) i {
|
|
background-color: var(--pvs-accent-b);
|
|
}
|
|
|
|
.picker-preview-type .ps-proof-item:nth-of-type(3) i {
|
|
background-color: var(--pvs-accent-c);
|
|
}
|
|
|
|
.picker-preview-type .ps-proof-item:nth-of-type(4) i {
|
|
background-color: var(--pvs-accent-d);
|
|
}
|
|
|
|
/* A chip is one line. The columns are even and the row is read across, so the
|
|
label that ran long took a second line and set the whole band to two
|
|
heights at once. Clipped rather than hidden because the box above is trimmed
|
|
to the baseline: the clip margin is the room the tail of a g or a y needs,
|
|
and the ellipsis still lands on the inline edge. */
|
|
.picker-preview-type .ps-proof-item span {
|
|
min-width: 0;
|
|
overflow: clip;
|
|
overflow-clip-margin: 0.4em;
|
|
color: var(--pvs-bars);
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* Wraps rather than truncates: a section title is the product's own words and
|
|
its length is not ours to guarantee. */
|
|
.picker-preview-type .ps-editorial-copy strong {
|
|
width: auto;
|
|
height: auto;
|
|
overflow: visible;
|
|
background: none;
|
|
color: var(--pvs-title);
|
|
font-family: var(--pt-heading);
|
|
font-size: var(--pt-title);
|
|
font-weight: var(--pt-heading-weight);
|
|
line-height: 1.25;
|
|
white-space: normal;
|
|
text-wrap: balance;
|
|
}
|
|
|
|
.picker-preview-type .ps-editorial-copy > em {
|
|
width: auto;
|
|
height: auto;
|
|
margin-top: var(--ps-gap-xs);
|
|
background: none;
|
|
color: var(--pvs-accent-text);
|
|
}
|
|
|
|
.picker-preview-type .ps-gallery-item span {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
}
|
|
|
|
/* Card labels wrap instead of truncating: a two-line label still reads, and a
|
|
clipped word is exactly the kind of flaw this preview exists to rule out. */
|
|
.picker-preview-type .ps-gallery-item b {
|
|
overflow: visible;
|
|
color: var(--pvs-bars);
|
|
white-space: normal;
|
|
text-box: unset;
|
|
display: block;
|
|
}
|
|
|
|
.picker-preview-type .ps-gallery-item b:first-child,
|
|
.picker-preview-type .ps-gallery-item [data-type-gallery-title] {
|
|
color: var(--pvs-title);
|
|
line-height: 1.38;
|
|
}
|
|
|
|
.picker-preview-type .ps-gallery-item b:last-child {
|
|
line-height: 1.45;
|
|
}
|
|
|
|
/* ============================================================
|
|
The App UI board.
|
|
|
|
Same frame, same nav band, same footer as the landing board; only the two
|
|
bodies change. The wireframe on the surface tile is what it is drawn from,
|
|
column for column: a rail, a working column, a settings panel, at the
|
|
18.68 / 51.84 / rest the drawing already uses.
|
|
|
|
What it refuses to add is a display line: nothing on a dashboard is set at
|
|
35px, and a board that opened with one would be recommending a pair for a
|
|
page this surface never has. The largest step here is the section title, and
|
|
the face doing the most work is the body face between 10 and 12px.
|
|
|
|
The chart is the one element on the board that is still mostly a drawing.
|
|
Its bars carry no glyphs and prove nothing about a pair, so they are held to
|
|
a share of the plot and are there for the two pieces of text they bracket:
|
|
the heading above them and the category labels below, which is the tightest
|
|
slot on any of the boards. Both are what a chart really costs a pairing.
|
|
============================================================ */
|
|
.picker-preview-type--ops .ps-desktop {
|
|
grid-template-rows: 9.4% minmax(0, 1fr) 8.3%;
|
|
}
|
|
|
|
/* The board is asked its own height, the way the docs board is, because that is
|
|
what the working column has to fit inside and it is not the window's: the
|
|
frame shrinks with the window while the type ladder floors, so a short frame
|
|
holds the same four blocks at a larger relative size. What a short frame
|
|
spends less on hangs off the query at the end of this section. */
|
|
.picker-preview-type--ops {
|
|
container-type: size;
|
|
container-name: ps-ops-board;
|
|
}
|
|
|
|
.ps-ops {
|
|
min-width: 0;
|
|
min-height: 0;
|
|
display: grid;
|
|
grid-template-columns: 18.68% 51.84% minmax(0, 1fr);
|
|
}
|
|
|
|
.ps-ops-rail,
|
|
.ps-ops-main {
|
|
border-right: 1px solid var(--pvs-rule);
|
|
}
|
|
|
|
.ps-ops-rail,
|
|
.ps-ops-panel {
|
|
min-width: 0;
|
|
display: grid;
|
|
align-content: start;
|
|
gap: var(--ps-gap-sm);
|
|
padding: var(--ps-band-y) calc(var(--pt-base) * 0.7);
|
|
}
|
|
|
|
/* The working column runs to the bottom of the band rather than stopping partway
|
|
down it: whatever a pair leaves over is spread between the four blocks instead
|
|
of pooling under the table, which is how the docs and portfolio boards already
|
|
fill. It takes no height gate, unlike the docs column, because the inset it
|
|
would spend on a tall frame is already there on every frame (this padding is
|
|
symmetric) and because `space-between` is `start` again the moment the free
|
|
space runs out, so a short frame keeps the drawing it has.
|
|
|
|
What it does not do is gain a block on a tall frame the way the docs column
|
|
gains its list. That column was short a structural element; this one draws the
|
|
four a dashboard is made of, so a fifth would be filling for its own sake. Its
|
|
table is also filled from the run's own item slots, and a row past their count
|
|
would come up blank.
|
|
|
|
`min-height: 0` for the same reason the docs column carries it: without it the
|
|
column takes its automatic minimum height from the table and grows past the
|
|
row, which pushes the footer out of the frame. Held to the band, the
|
|
widest-setting pair runs to the band's edge instead. */
|
|
.ps-ops-main {
|
|
min-width: 0;
|
|
min-height: 0;
|
|
display: grid;
|
|
align-content: space-between;
|
|
gap: var(--ps-gap-lg);
|
|
padding: var(--ps-band-y) calc(var(--pt-base) * 1.1);
|
|
}
|
|
|
|
/* Chrome on a tool is one line at the smallest step there is, and a rail item
|
|
or a table cell that took two would be a different control. Holding them to
|
|
one means a pair that sets them too wide reports it as an overflow rather
|
|
than as a second line nobody reads as a fault. */
|
|
.picker-preview-type--ops :is(
|
|
.ps-ops-item b,
|
|
.ps-ops-row b,
|
|
.ps-ops-field b,
|
|
.ps-ops-switch b,
|
|
.ps-ops-lanes b
|
|
) {
|
|
min-width: 0;
|
|
color: var(--pvs-bars);
|
|
font-family: var(--pt-body);
|
|
font-size: var(--pt-micro);
|
|
font-weight: 500;
|
|
line-height: 1.4;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.ps-ops-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: calc(var(--pt-base) * 0.5);
|
|
padding: calc(var(--pt-base) * 0.35) calc(var(--pt-base) * 0.45);
|
|
border-radius: var(--pvs-radius-bar);
|
|
}
|
|
|
|
.ps-ops-item > i {
|
|
flex: none;
|
|
width: calc(var(--pt-base) * 0.4);
|
|
aspect-ratio: 1;
|
|
background-color: var(--pvs-accent-a);
|
|
border-radius: 50%;
|
|
}
|
|
|
|
/* The open section is a filled plate rather than a tinted row, which is the
|
|
mark that tells a reader this rail belongs to a tool and not to a document.
|
|
Its label is the one place a rail sets the body face's semibold. */
|
|
.ps-ops-item--on {
|
|
background-color: var(--pvs-cta);
|
|
}
|
|
|
|
.picker-preview-type--ops .ps-ops-item--on b {
|
|
color: var(--pvs-cta-text);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.ps-ops-item--on > i {
|
|
background-color: var(--pvs-cta-text);
|
|
}
|
|
|
|
.ps-ops-title {
|
|
color: var(--pvs-title);
|
|
font-family: var(--pt-heading);
|
|
font-size: var(--pt-title);
|
|
font-weight: var(--pt-heading-weight);
|
|
line-height: 1.2;
|
|
overflow-wrap: break-word;
|
|
}
|
|
|
|
.ps-ops-metrics {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
gap: var(--ps-gap-sm);
|
|
}
|
|
|
|
.ps-ops-metric {
|
|
min-width: 0;
|
|
display: grid;
|
|
gap: var(--ps-gap-xs);
|
|
}
|
|
|
|
/* The figure is set in the body face, because on a tool it is the body face
|
|
that has to draw numbers: a column of them lines up or it does not, and a
|
|
pair whose text figures step up and down loses a table the moment there is
|
|
more than one row of it. */
|
|
.ps-ops-metric b {
|
|
color: var(--pvs-title);
|
|
font-family: var(--pt-body);
|
|
font-size: var(--pt-title);
|
|
font-weight: 600;
|
|
font-variant-numeric: tabular-nums lining-nums;
|
|
line-height: 1.1;
|
|
}
|
|
|
|
/* Three of these sit side by side in a third of the column each, so the label
|
|
is the one micro line on the board that has to be allowed to take two. */
|
|
.picker-preview-type--ops .ps-ops-metric span {
|
|
color: var(--pvs-bars);
|
|
font-family: var(--pt-body);
|
|
font-size: var(--pt-micro);
|
|
line-height: 1.35;
|
|
}
|
|
|
|
.ps-ops-chart {
|
|
min-width: 0;
|
|
display: grid;
|
|
gap: var(--ps-gap-sm);
|
|
}
|
|
|
|
/* A chart title is a heading, so it takes the heading face, and it takes it at
|
|
the reading step rather than the title step: the section title above it is
|
|
already the largest thing on the board, and a second line at that size would
|
|
read as a second section. Two sizes of the same face is the useful thing here
|
|
anyway, because a display face that holds at 22px and falls apart at 13 is
|
|
exactly the fault this board exists to surface. */
|
|
.ps-ops-chart-title {
|
|
color: var(--pvs-title);
|
|
font-family: var(--pt-heading);
|
|
font-size: var(--pt-base);
|
|
font-weight: var(--pt-heading-weight);
|
|
line-height: 1.3;
|
|
overflow-wrap: break-word;
|
|
}
|
|
|
|
/* The bars and the labels are two grids over one column structure, so a label
|
|
is under its own bar without either row measuring the other. Implicit columns
|
|
rather than a stated count, because the handset draws three of them and the
|
|
desktop five and neither should have to restate it here.
|
|
|
|
The axis is the plot's own bottom border, so the bars stand on it at every
|
|
width. Their height is a share of the plot rather than a value, which is what
|
|
keeps this a drawing of a chart: nothing here is a reading of data. */
|
|
.ps-ops-plot,
|
|
.ps-ops-lanes {
|
|
display: grid;
|
|
grid-auto-flow: column;
|
|
grid-auto-columns: minmax(0, 1fr);
|
|
gap: calc(var(--pt-base) * 0.7);
|
|
}
|
|
|
|
.ps-ops-plot {
|
|
height: calc(var(--pt-base) * 5.5);
|
|
align-items: end;
|
|
border-bottom: 1px solid var(--pvs-rule);
|
|
}
|
|
|
|
/* The lanes that are not the leading one take the fill an unselected row takes,
|
|
which is the same slot the wireframe chart on the surface tile fills its bars
|
|
with. It is the one slot that stays a light step off the ground under every
|
|
strategy and moves only in hue, and five bars are an area where the board's
|
|
accents are points: at accent weight the chart would outrank the table it
|
|
sits above. */
|
|
.ps-ops-plot > i {
|
|
height: var(--h);
|
|
background-color: var(--pvs-ghost);
|
|
}
|
|
|
|
/* The leading lane is the only bar a schematic chart has to say anything with,
|
|
so it takes the fill the board already spends on a chosen state: the open
|
|
rail plate and the switch that is on. Under Drenched that fill is the page's
|
|
own ink, which is the one reading of primary that stays visible when the
|
|
ground is the primary. */
|
|
.ps-ops-plot > .ps-ops-bar--lead {
|
|
background-color: var(--pvs-cta);
|
|
}
|
|
|
|
/* Centered under the bar, and the one micro label on this board that is a
|
|
caption rather than a row: it has its bar's width and no more, which is why
|
|
the words it is filled with are single and short. */
|
|
.picker-preview-type--ops .ps-ops-lanes b {
|
|
text-align: center;
|
|
}
|
|
|
|
/* Three columns over a handset's width come out square at the desktop's plot
|
|
height, and a square is a tile rather than a bar. The gap is spent on both
|
|
rows or the labels walk off the bars they name. */
|
|
.picker-preview-type--ops .ps-phone-body :is(.ps-ops-plot, .ps-ops-lanes),
|
|
[data-carry][data-surface="operate"] .ps-phone-body :is(.ps-ops-plot, .ps-ops-lanes) {
|
|
gap: calc(var(--pt-base) * 2);
|
|
}
|
|
|
|
.picker-preview-type--ops .ps-phone-body .ps-ops-plot,
|
|
[data-carry][data-surface="operate"] .ps-phone-body .ps-ops-plot {
|
|
height: calc(var(--pt-base) * 7);
|
|
}
|
|
|
|
.ps-ops-table {
|
|
display: grid;
|
|
border-top: 1px solid var(--pvs-rule);
|
|
}
|
|
|
|
.ps-ops-row {
|
|
display: grid;
|
|
grid-template-columns: auto minmax(0, 1fr) auto auto;
|
|
gap: calc(var(--pt-base) * 0.7);
|
|
align-items: center;
|
|
padding: calc(var(--pt-base) * 0.55) calc(var(--pt-base) * 0.3);
|
|
}
|
|
|
|
.ps-ops-row > i {
|
|
width: calc(var(--pt-base) * 0.4);
|
|
aspect-ratio: 1;
|
|
background-color: var(--pvs-accent-c);
|
|
border-radius: 50%;
|
|
}
|
|
|
|
.picker-preview-type--ops .ps-ops-row b:first-of-type {
|
|
color: var(--pvs-title);
|
|
}
|
|
|
|
.picker-preview-type--ops .ps-ops-row b:last-of-type {
|
|
text-align: end;
|
|
font-variant-numeric: tabular-nums lining-nums;
|
|
}
|
|
|
|
/* The heading row is the semibold an Operate product leans on hardest, and it
|
|
sits directly over the cells it names, so any drift between the two weights
|
|
of the same face shows up here first. */
|
|
.picker-preview-type--ops .ps-ops-row--head b {
|
|
color: var(--pvs-copy);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.ps-ops-row--head > i {
|
|
background-color: transparent;
|
|
}
|
|
|
|
.ps-ops-row--on {
|
|
background-color: var(--pvs-ghost);
|
|
}
|
|
|
|
/* A settings panel's heading is a UI label rather than a section title, so it
|
|
takes the body face at reading size and the weight that goes with the job.
|
|
No other board sets that combination, and it is the one an app spends most
|
|
of its chrome on. */
|
|
.ps-ops-panel-title {
|
|
color: var(--pvs-title);
|
|
font-family: var(--pt-body);
|
|
font-size: var(--pt-base);
|
|
font-weight: 600;
|
|
line-height: 1.3;
|
|
}
|
|
|
|
.ps-ops-field {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: calc(var(--pt-base) * 0.4);
|
|
padding: calc(var(--pt-base) * 0.4) calc(var(--pt-base) * 0.5);
|
|
border: 1px solid var(--pvs-ghost-edge);
|
|
border-radius: var(--pvs-radius-bar);
|
|
}
|
|
|
|
.ps-ops-chev {
|
|
flex: none;
|
|
width: calc(var(--pt-base) * 0.32);
|
|
aspect-ratio: 1;
|
|
margin-left: auto;
|
|
border-right: 1px solid var(--pvs-copy);
|
|
border-bottom: 1px solid var(--pvs-copy);
|
|
transform: translateY(-25%) rotate(45deg);
|
|
}
|
|
|
|
.ps-ops-switch {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: calc(var(--pt-base) * 0.4);
|
|
padding-block: calc(var(--pt-base) * 0.3);
|
|
}
|
|
|
|
.ps-ops-toggle {
|
|
flex: none;
|
|
position: relative;
|
|
width: calc(var(--pt-base) * 1.5);
|
|
height: calc(var(--pt-base) * 0.8);
|
|
margin-left: auto;
|
|
background-color: var(--pvs-bars);
|
|
border-radius: 999px;
|
|
}
|
|
|
|
.ps-ops-toggle--on {
|
|
background-color: var(--pvs-cta);
|
|
}
|
|
|
|
.ps-ops-toggle::after {
|
|
content: "";
|
|
position: absolute;
|
|
top: 12.5%;
|
|
left: 8%;
|
|
height: 75%;
|
|
aspect-ratio: 1;
|
|
background-color: var(--pvs-ground);
|
|
border-radius: 50%;
|
|
}
|
|
|
|
.ps-ops-toggle--on::after {
|
|
right: 8%;
|
|
left: auto;
|
|
}
|
|
|
|
/* The handset takes the same screen narrowed: the title, one figure, the rows
|
|
cut to what they are and what they come to, and the two switches. The rail
|
|
and the panel are chrome a phone gives to a drawer, and a drawer is not on
|
|
screen. */
|
|
/* The structural boards are on the same selectors from here down wherever the
|
|
rule is about the shape of the body rather than the setting of its words. A
|
|
handset holding four blocks of a tool holds them the same way whether they are
|
|
printed or drawn, and writing that twice is how two boards claiming to be the
|
|
same anatomy drift apart. */
|
|
.picker-preview-type--ops .ps-phone-body,
|
|
[data-carry][data-surface="operate"] .ps-phone-body {
|
|
grid-template-rows: repeat(4, auto);
|
|
align-content: space-between;
|
|
gap: var(--ps-gap-md);
|
|
}
|
|
|
|
.picker-preview-type--ops .ps-phone-body .ps-ops-row,
|
|
[data-carry][data-surface="operate"] .ps-phone-body .ps-ops-row {
|
|
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
}
|
|
|
|
.picker-preview-type--ops .ps-phone-body .ps-ops-panel,
|
|
[data-carry][data-surface="operate"] .ps-phone-body .ps-ops-panel {
|
|
gap: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
/* A frame with less room than the drawing needs. The plot is the one block here
|
|
whose height is a count rather than its contents, so it is the block that
|
|
gives: the chart keeps its title, its bars, and its labels, and loses the air
|
|
between them, which is the reduction it already makes on the handset. The
|
|
column closes to the middle step at the same time.
|
|
|
|
Shortening rather than dropping, because a dashboard without its chart is the
|
|
one thing this board exists to show a pair against, and 1280 by 800 is a
|
|
laptop rather than an edge case. */
|
|
@container ps-ops-board (height < 500px) {
|
|
.picker-preview-type--ops .ps-ops-main {
|
|
gap: var(--ps-gap-md);
|
|
}
|
|
|
|
.picker-preview-type--ops .ps-ops-plot {
|
|
height: calc(var(--pt-base) * 2.8);
|
|
}
|
|
|
|
.picker-preview-type--ops .ps-phone-body,
|
|
[data-carry][data-surface="operate"] .ps-phone-body {
|
|
gap: var(--ps-gap-sm);
|
|
}
|
|
|
|
.picker-preview-type--ops .ps-phone-body .ps-ops-plot,
|
|
[data-carry][data-surface="operate"] .ps-phone-body .ps-ops-plot {
|
|
height: calc(var(--pt-base) * 4.4);
|
|
}
|
|
}
|
|
|
|
/* The bottom of the range, where the ladder has floored and the frame is still
|
|
shrinking. The handset is the frame that runs out first, and the plot is the
|
|
block that gives on it for the same reason it gives above: four blocks at
|
|
their own heights and one whose height is a number. */
|
|
@container ps-ops-board (height < 400px) {
|
|
.picker-preview-type--ops .ps-phone-body .ps-ops-plot,
|
|
[data-carry][data-surface="operate"] .ps-phone-body .ps-ops-plot {
|
|
height: calc(var(--pt-base) * 3.2);
|
|
}
|
|
}
|
|
|
|
/* ============================================================
|
|
The Docs board.
|
|
|
|
Drawn from the wireframe on the surface tile: a section rail at the 23.75%
|
|
the drawing sets, and a document beside it. Where it departs is the count
|
|
of headings. The drawing carries one; this board sets a page title and a
|
|
subsection heading together, because the relationship between two heading
|
|
levels is the thing a documentation pair is actually chosen on, and a
|
|
specimen showing one level can never be judged on it.
|
|
|
|
The other departure is the measure. The drawing stops its prose at roughly
|
|
46 characters, which is a picture of a document rather than a document. A
|
|
body face is judged on the texture of a block at a reading measure, so the
|
|
prose is capped in characters instead: the cap is set in the chosen face,
|
|
so a wide-set family takes the column and a narrow one leaves the drawing's
|
|
whitespace of its own accord.
|
|
============================================================ */
|
|
.picker-preview-type--read .ps-desktop {
|
|
grid-template-rows: 9.4% minmax(0, 1fr) 8.3%;
|
|
}
|
|
|
|
/* The board is asked its own height because that is what the document has to
|
|
fill, and it is not the window's: the frame shrinks with the window while
|
|
the type ladder floors, so a short frame holds fewer blocks at the same
|
|
reading size. Everything the fuller page needs hangs off the query below. */
|
|
.picker-preview-type--read {
|
|
container-type: size;
|
|
container-name: ps-docs-board;
|
|
}
|
|
|
|
.ps-docs {
|
|
min-width: 0;
|
|
min-height: 0;
|
|
display: grid;
|
|
grid-template-columns: 23.75% minmax(0, 1fr);
|
|
}
|
|
|
|
.ps-docs-rail {
|
|
min-width: 0;
|
|
display: grid;
|
|
align-content: start;
|
|
gap: var(--ps-gap-sm);
|
|
padding: var(--ps-band-y) calc(var(--pt-base) * 0.6) 0 var(--ps-gutter);
|
|
border-right: 1px solid var(--pvs-rule);
|
|
}
|
|
|
|
.ps-docs-item {
|
|
min-width: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: calc(var(--pt-base) * 0.5);
|
|
}
|
|
|
|
.ps-docs-item > i {
|
|
flex: none;
|
|
width: calc(var(--pt-base) * 0.34);
|
|
aspect-ratio: 1;
|
|
background-color: var(--pvs-bars);
|
|
border-radius: 50%;
|
|
}
|
|
|
|
.picker-preview-type--read .ps-docs-item b {
|
|
min-width: 0;
|
|
color: var(--pvs-bars);
|
|
font-family: var(--pt-body);
|
|
font-size: var(--pt-micro);
|
|
font-weight: 500;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
/* The open section is a recolor, not a plate. A reader is somewhere in a
|
|
document rather than in one of its modes, and a filled plate would say the
|
|
rail is a set of switches. */
|
|
.ps-docs-item--on > i {
|
|
background-color: var(--pvs-accent-c);
|
|
}
|
|
|
|
.picker-preview-type--read .ps-docs-item--on b {
|
|
color: var(--pvs-accent-text);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.ps-docs-main {
|
|
min-width: 0;
|
|
/* Without it the column takes its automatic minimum height from the copy and
|
|
grows past the row, which pushes the footer out of the frame. Held to the
|
|
band, the widest-setting pair runs to the band's edge instead. */
|
|
min-height: 0;
|
|
display: grid;
|
|
align-content: start;
|
|
padding: var(--ps-band-y) 6% 0 calc(var(--pt-base) * 1.6);
|
|
}
|
|
|
|
.picker-preview-type--read .ps-docs-main .pt-headline {
|
|
max-width: 22ch;
|
|
}
|
|
|
|
.picker-preview-type--read :is(.ps-docs-lede, .ps-docs-para) {
|
|
max-width: 70ch;
|
|
margin: 0;
|
|
color: var(--pvs-copy);
|
|
font-family: var(--pt-body);
|
|
font-size: var(--pt-base);
|
|
line-height: 1.65;
|
|
}
|
|
|
|
/* The lede is the one paragraph a documentation page sets apart, and it is set
|
|
apart by weight rather than by size so the block below it keeps the leading
|
|
the family drew for it. */
|
|
.picker-preview-type--read .ps-docs-lede {
|
|
margin-top: calc(var(--pt-base) * 0.9);
|
|
color: var(--pvs-title);
|
|
font-weight: 500;
|
|
}
|
|
|
|
.picker-preview-type--read .ps-docs-para {
|
|
margin-top: calc(var(--pt-base) * 0.7);
|
|
}
|
|
|
|
/* A heading belongs to the passage under it, so that one step stays shorter
|
|
than the step between two passages. Left at the paragraph interval the
|
|
subsection floated between the block above it and the block below. */
|
|
.picker-preview-type--read .ps-docs-sub + .ps-docs-para {
|
|
margin-top: calc(var(--pt-base) * 0.55);
|
|
}
|
|
|
|
.ps-docs-sub {
|
|
margin-top: calc(var(--pt-base) * 1.5);
|
|
color: var(--pvs-title);
|
|
font-family: var(--pt-heading);
|
|
font-size: var(--pt-title);
|
|
font-weight: var(--pt-heading-weight);
|
|
line-height: 1.25;
|
|
overflow-wrap: break-word;
|
|
}
|
|
|
|
/* The marker is the list's own, so it takes the ink the rail's markers take
|
|
rather than a drawn element that would have to be positioned against a line
|
|
whose height changes with the pair. Off until the frame is tall enough to
|
|
hold it, which is what the container query below decides. */
|
|
.picker-preview-type--read .ps-docs-list {
|
|
display: none;
|
|
margin: calc(var(--pt-base) * 1.2) 0 0;
|
|
padding-left: calc(var(--pt-base) * 1.2);
|
|
color: var(--pvs-copy);
|
|
font-family: var(--pt-body);
|
|
font-size: var(--pt-base);
|
|
line-height: 1.55;
|
|
}
|
|
|
|
.picker-preview-type--read .ps-docs-list li {
|
|
max-width: 62ch;
|
|
}
|
|
|
|
.picker-preview-type--read .ps-docs-list li + li {
|
|
margin-top: calc(var(--pt-base) * 0.45);
|
|
}
|
|
|
|
.picker-preview-type--read .ps-docs-list li::marker {
|
|
color: var(--pvs-bars);
|
|
}
|
|
|
|
/* A callout is the one place a documentation page prints body copy on a ground
|
|
that is not the page, which is where a light body weight stops holding.
|
|
|
|
The accent is the box's own border. It was a border plus a round marker sized
|
|
off the type scale, and the marker was a flex item with an auto height, so
|
|
the box it drew was the flex line rather than the circle it was declared as:
|
|
a stretched ellipse whose length changed with the callout, detached from the
|
|
edge by the padding. A border cannot leave its box or fall short of it. */
|
|
.picker-preview-type--read .ps-docs-note {
|
|
margin-top: calc(var(--pt-base) * 1.3);
|
|
padding: calc(var(--pt-base) * 0.7) calc(var(--pt-base) * 0.9);
|
|
background-color: var(--pvs-ghost);
|
|
border-left: calc(var(--pt-base) * 0.25) solid var(--pvs-accent-c);
|
|
}
|
|
|
|
.ps-docs-note-copy {
|
|
min-width: 0;
|
|
display: grid;
|
|
gap: calc(var(--pt-base) * 0.2);
|
|
}
|
|
|
|
.picker-preview-type--read .ps-docs-note-copy b {
|
|
color: var(--pvs-accent-text);
|
|
font-family: var(--pt-body);
|
|
font-size: var(--pt-micro);
|
|
font-weight: 600;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.picker-preview-type--read .ps-docs-note-copy > span {
|
|
color: var(--pvs-copy);
|
|
font-family: var(--pt-body);
|
|
font-size: var(--pt-micro);
|
|
line-height: 1.5;
|
|
}
|
|
|
|
/* The handset is the same document with the rail folded into a crumb, which is
|
|
how the drawing puts it too. One passage rather than two: the list is what
|
|
the width is spent on instead, and past that the note leaves the card. The
|
|
blocks carry the rhythm, so the gap stays at zero. */
|
|
.picker-preview-type--read .ps-phone-body,
|
|
[data-carry][data-surface="read"] .ps-phone-body {
|
|
grid-template-rows: repeat(6, auto);
|
|
align-content: start;
|
|
gap: 0;
|
|
}
|
|
|
|
.ps-docs-crumb {
|
|
color: var(--pvs-bars);
|
|
font-family: var(--pt-body);
|
|
font-size: var(--pt-micro);
|
|
font-weight: 500;
|
|
text-align: center;
|
|
}
|
|
|
|
.picker-preview-type--read .ps-phone-body .pt-headline {
|
|
margin-top: calc(var(--pt-base) * 1);
|
|
font-size: var(--pt-title);
|
|
}
|
|
|
|
/* The phone re-bases the type ladder smaller, so the step that separates a
|
|
passage from the heading over it has to be restated against the new base or
|
|
the two run together. */
|
|
.picker-preview-type--read .ps-phone-body .ps-docs-sub {
|
|
margin-top: calc(var(--pt-base) * 2.1);
|
|
}
|
|
|
|
/* A frame with room for the whole page. The document runs to the bottom of the
|
|
band instead of stopping partway down it and leaving the footer standing on
|
|
white: the list arrives, the column takes an inset under it, and whatever a
|
|
pair leaves over is spread between the blocks rather than pooled under the
|
|
last one, which is how the landing and portfolio handsets already fill.
|
|
|
|
Below this the band and the reading size have pulled apart far enough that
|
|
the original five blocks are the page: the type ladder floors on a small
|
|
window while the frame keeps shrinking, so there is no slack to spread and
|
|
the sixth block would run into the footer. */
|
|
@container ps-docs-board (height >= 545px) {
|
|
.picker-preview-type--read .ps-docs-main {
|
|
align-content: space-between;
|
|
padding-bottom: var(--ps-band-y);
|
|
}
|
|
|
|
.picker-preview-type--read .ps-docs-list {
|
|
display: block;
|
|
}
|
|
|
|
.picker-preview-type--read .ps-phone-body {
|
|
align-content: space-between;
|
|
}
|
|
}
|
|
|
|
/* Two steps further down, where the handset's five remaining blocks are already
|
|
more than the frame holds. The callout is the block that goes: it is an aside
|
|
to the passage above it rather than part of the document's own ladder, and
|
|
everything above it is either a heading level or the running text the pair is
|
|
judged on. */
|
|
@container ps-docs-board (height < 440px) {
|
|
.picker-preview-type--read .ps-phone-body .ps-docs-note,
|
|
[data-carry][data-surface="read"] .ps-phone-body .ps-docs-note {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
/* ============================================================
|
|
The Portfolio board.
|
|
|
|
The sparsest of the four drawings, so it is the one that changes most. The
|
|
drawing staggers two plates and a carousel rail under them, and it keeps both
|
|
plates: this is also the one surface whose heading face is chosen to be looked
|
|
at rather than read past, so the page title is added above them rather than
|
|
paid for out of the second entry.
|
|
|
|
Both entry rows are an equal share of the band and the plate takes its row,
|
|
which is the construction the handset below already uses. That is what fills
|
|
the height: a plate held to its own ratio left the leftover pooled around one
|
|
centred row, and a band that has to hold whatever a pair's caption comes to
|
|
cannot be filled by a shape sized off its width.
|
|
|
|
The tracked caption meta is deliberate and it is the only tracked label in
|
|
the picker. An `experience` body face is asked to hold up letter-spaced and
|
|
uppercased, and an instruction a specimen cannot test is decoration.
|
|
============================================================ */
|
|
.picker-preview-type--experience .ps-desktop {
|
|
grid-template-rows: 9.4% minmax(0, 1fr) 8.3%;
|
|
}
|
|
|
|
.ps-index {
|
|
min-width: 0;
|
|
min-height: 0;
|
|
display: grid;
|
|
grid-template-rows: auto minmax(0, 1fr) minmax(0, 1fr) auto;
|
|
gap: var(--ps-gap-md);
|
|
padding: var(--ps-band-y) var(--ps-gutter);
|
|
}
|
|
|
|
.picker-preview-type--experience .ps-index > .pt-headline {
|
|
max-width: 20ch;
|
|
}
|
|
|
|
.ps-index-row {
|
|
min-height: 0;
|
|
display: grid;
|
|
grid-template-columns: 42% minmax(0, 1fr);
|
|
column-gap: 5.14%;
|
|
}
|
|
|
|
/* The plate changes sides on the second entry by placement, so both rows stay
|
|
one element order and the caption keeps reading after the work it names. */
|
|
.ps-index-row--flip {
|
|
grid-template-columns: minmax(0, 1fr) 42%;
|
|
}
|
|
|
|
.ps-index-row--flip > .ps-image {
|
|
grid-area: 1 / 2;
|
|
}
|
|
|
|
.ps-index-row--flip > .ps-index-cap {
|
|
grid-area: 1 / 1;
|
|
}
|
|
|
|
.ps-index-cap {
|
|
min-width: 0;
|
|
display: grid;
|
|
align-content: center;
|
|
gap: calc(var(--pt-base) * 0.35);
|
|
}
|
|
|
|
.ps-index-title {
|
|
color: var(--pvs-title);
|
|
font-family: var(--pt-heading);
|
|
font-size: var(--pt-title);
|
|
font-weight: var(--pt-heading-weight);
|
|
line-height: 1.2;
|
|
overflow-wrap: break-word;
|
|
}
|
|
|
|
.picker-preview-type--experience .ps-index-meta {
|
|
color: var(--pvs-bars);
|
|
font-family: var(--pt-body);
|
|
font-size: var(--pt-micro);
|
|
font-weight: 500;
|
|
line-height: 1.4;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.08em;
|
|
}
|
|
|
|
.picker-preview-type--experience .ps-index-note {
|
|
max-width: 46ch;
|
|
margin-top: calc(var(--pt-base) * 0.3);
|
|
color: var(--pvs-copy);
|
|
font-family: var(--pt-body);
|
|
font-size: var(--pt-micro);
|
|
line-height: 1.55;
|
|
}
|
|
|
|
.ps-index-rail {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: calc(var(--pt-base) * 0.9);
|
|
}
|
|
|
|
/* A square stood on its corner, the same construction the drawing uses, so the
|
|
board takes no inline SVG for two marks. */
|
|
.ps-index-arrow {
|
|
flex: none;
|
|
width: calc(var(--pt-base) * 0.42);
|
|
aspect-ratio: 1;
|
|
border-left: 1px solid var(--pvs-bars);
|
|
border-bottom: 1px solid var(--pvs-bars);
|
|
transform: rotate(45deg);
|
|
}
|
|
|
|
.ps-index-arrow--next {
|
|
margin-left: auto;
|
|
border-right: 1px solid var(--pvs-bars);
|
|
border-top: 1px solid var(--pvs-bars);
|
|
border-bottom: 0;
|
|
border-left: 0;
|
|
}
|
|
|
|
.ps-index-track {
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: calc(var(--pt-base) * 1.1);
|
|
}
|
|
|
|
.picker-preview-type--experience .ps-index-track b {
|
|
color: var(--pvs-bars);
|
|
font-family: var(--pt-body);
|
|
font-size: var(--pt-micro);
|
|
font-weight: 500;
|
|
line-height: 1.4;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.picker-preview-type--experience .ps-index-stop--on {
|
|
color: var(--pvs-title);
|
|
}
|
|
|
|
/* The handset drops the title and the rail and shows the work: two plates,
|
|
each with the caption the desktop row carries. */
|
|
.picker-preview-type--experience .ps-phone-body,
|
|
[data-carry][data-surface="experience"] .ps-phone-body {
|
|
grid-template-rows: minmax(0, 1fr) auto minmax(0, 1fr) auto;
|
|
gap: var(--ps-gap-sm);
|
|
}
|
|
|
|
.picker-preview-type--experience .ps-phone-body .ps-index-cap,
|
|
[data-carry][data-surface="experience"] .ps-phone-body .ps-index-cap {
|
|
padding-top: 0;
|
|
}
|
|
|
|
/* ============================================================
|
|
The structural boards: the four bodies drawn as wireframes.
|
|
|
|
Screens 07 to 10 ask what a page is made of rather than what it is set in, and
|
|
they ask it of each surface the run chose. The three bodies above this were
|
|
built for the font screen, where every slot holds a printed line, and the font
|
|
screen is the only place a slot gets filled. So the same anatomy is drawn a
|
|
second way here: the tree, the class names, and the geometry are the ones
|
|
above, and what changes is that a slot is a bar.
|
|
|
|
Everything below is scoped to [data-carry], the attribute that marks a board
|
|
as one accumulating the structural answers. That is exactly screens 07 to 10,
|
|
so nothing here can reach the color screen's tiles, the font screen's
|
|
specimens, or the motion screen's scenes, all of which draw the same bodies
|
|
under their own terms.
|
|
============================================================ */
|
|
|
|
/* A single body between the chrome, so three bands rather than the landing
|
|
page's five. Declared on the row property rather than through --pvs-rows
|
|
because the layout answer owns that variable for the page it describes, and
|
|
the answer this board is showing must not be able to leave it with two empty
|
|
rows. The same construction the font screen's boards use. */
|
|
[data-carry]:is([data-surface="operate"], [data-surface="read"], [data-surface="experience"]) .ps-desktop {
|
|
grid-template-rows: 9.4% minmax(0, 1fr) 8.3%;
|
|
}
|
|
|
|
/* ── A word, drawn ───────────────────────────────────────────
|
|
One declaration for every slot that holds a line of type upstairs. The height
|
|
ladder underneath is the type ladder read as thicknesses: a figure and a
|
|
heading are the two things on a tool you are meant to see from across a room,
|
|
a row cell and a rail label are the smallest marks the board makes, and a
|
|
running line sits between them. Widths are stated per slot, because a column
|
|
of bars all the same length is a table and not prose. */
|
|
[data-carry] :is(
|
|
.ps-ops-title,
|
|
.ps-ops-item b,
|
|
.ps-ops-metric b,
|
|
.ps-ops-metric span,
|
|
.ps-ops-chart-title,
|
|
.ps-ops-lanes b,
|
|
.ps-ops-row b,
|
|
.ps-ops-panel-title,
|
|
.ps-ops-field b,
|
|
.ps-ops-switch b,
|
|
.ps-docs-item b,
|
|
.ps-docs-sub,
|
|
.ps-docs-crumb,
|
|
.ps-docs-list li,
|
|
.ps-docs-note-copy b,
|
|
.ps-docs-main .pt-headline i,
|
|
.ps-phone-body .pt-headline i,
|
|
.ps-docs-lede i,
|
|
.ps-docs-para i,
|
|
.ps-docs-note-copy > span i
|
|
) {
|
|
display: block;
|
|
background-color: var(--pvs-bars);
|
|
border-radius: var(--pvs-radius-bar);
|
|
}
|
|
|
|
/* The two headings, at the two weights the ladder gives them. */
|
|
[data-carry] :is(.ps-ops-title, .ps-docs-main .pt-headline i, .ps-phone-body .pt-headline i) {
|
|
height: calc(var(--pt-base) * 0.72);
|
|
background-color: var(--pvs-headline);
|
|
}
|
|
|
|
[data-carry] .ps-ops-title {
|
|
width: 38%;
|
|
}
|
|
|
|
[data-carry] :is(.ps-docs-sub, .ps-ops-chart-title, .ps-ops-panel-title) {
|
|
height: calc(var(--pt-base) * 0.5);
|
|
background-color: var(--pvs-title);
|
|
}
|
|
|
|
[data-carry] .ps-docs-sub {
|
|
width: 44%;
|
|
}
|
|
|
|
[data-carry] .ps-ops-chart-title {
|
|
width: 46%;
|
|
}
|
|
|
|
[data-carry] .ps-ops-panel-title {
|
|
width: 62%;
|
|
}
|
|
|
|
/* Running copy: a stack of lines with a short last one, so a passage reads as a
|
|
passage. The headline takes the same treatment at its own weight.
|
|
|
|
Each gap is the line-height the printed version sets, less the thickness of
|
|
the bar, so a stack of n bars stands exactly as tall as the n lines it replaces
|
|
and the block leaves the same room behind it. Guessing the gap instead is what
|
|
made these boards float apart in a band sized for text: a bar is a quarter the
|
|
height of the line it stands in for, and four blocks each short by that much
|
|
is a page of holes. */
|
|
[data-carry] :is(.ps-docs-main, .ps-phone-body) .pt-headline {
|
|
display: grid;
|
|
gap: calc(var(--pt-base) * 1.3);
|
|
margin: 0;
|
|
}
|
|
|
|
[data-carry] :is(.ps-docs-main, .ps-phone-body) .pt-headline i:last-child {
|
|
width: 58%;
|
|
}
|
|
|
|
/* The steps between blocks are the printed board's own, restated because the
|
|
bars replace the elements those margins were declared on. Kept identical so a
|
|
document reads with the same rhythm whether it is set or drawn. */
|
|
[data-carry] :is(.ps-docs-lede, .ps-docs-para) {
|
|
display: grid;
|
|
gap: calc(var(--pt-base) * 1.25);
|
|
}
|
|
|
|
[data-carry] .ps-docs-lede {
|
|
margin: calc(var(--pt-base) * 0.9) 0 0;
|
|
}
|
|
|
|
[data-carry] .ps-docs-para {
|
|
margin: calc(var(--pt-base) * 0.7) 0 0;
|
|
}
|
|
|
|
[data-carry] .ps-docs-sub + .ps-docs-para {
|
|
margin-top: calc(var(--pt-base) * 0.55);
|
|
}
|
|
|
|
[data-carry] .ps-docs-note-copy > span {
|
|
display: grid;
|
|
gap: calc(var(--pt-base) * 0.67);
|
|
margin: 0;
|
|
}
|
|
|
|
[data-carry] :is(.ps-docs-lede, .ps-docs-para) i {
|
|
height: calc(var(--pt-base) * 0.3);
|
|
background-color: var(--pvs-copy);
|
|
}
|
|
|
|
[data-carry] .ps-docs-lede i:last-child,
|
|
[data-carry] .ps-docs-para i:last-child {
|
|
width: 62%;
|
|
}
|
|
|
|
[data-carry] .ps-docs-note-copy > span i {
|
|
height: calc(var(--pt-base) * 0.26);
|
|
}
|
|
|
|
[data-carry] .ps-docs-note-copy > span i:last-child {
|
|
width: 54%;
|
|
}
|
|
|
|
[data-carry] .ps-docs-note-copy b {
|
|
width: 42%;
|
|
height: calc(var(--pt-base) * 0.3);
|
|
background-color: var(--pvs-accent-c);
|
|
}
|
|
|
|
[data-carry] .ps-docs-crumb {
|
|
width: 46%;
|
|
height: calc(var(--pt-base) * 0.26);
|
|
margin-inline: auto;
|
|
}
|
|
|
|
/* A list item is one ragged line against its marker, and the marker is the
|
|
rail's own dot rather than a ::marker, which has no box a bar can stand on. */
|
|
[data-carry] .ps-docs-list {
|
|
display: grid;
|
|
gap: calc(var(--pt-base) * 1.7);
|
|
margin: calc(var(--pt-base) * 1.2) 0 0;
|
|
padding: 0;
|
|
list-style: none;
|
|
}
|
|
|
|
[data-carry] .ps-docs-note {
|
|
margin-top: calc(var(--pt-base) * 1.3);
|
|
}
|
|
|
|
[data-carry] .ps-docs-list li {
|
|
position: relative;
|
|
height: calc(var(--pt-base) * 0.3);
|
|
margin-left: calc(var(--pt-base) * 1.1);
|
|
background-color: var(--pvs-copy);
|
|
}
|
|
|
|
[data-carry] .ps-docs-list li:nth-child(2) {
|
|
width: 82%;
|
|
}
|
|
|
|
[data-carry] .ps-docs-list li::before {
|
|
content: "";
|
|
position: absolute;
|
|
top: 0;
|
|
left: calc(var(--pt-base) * -1.1);
|
|
width: calc(var(--pt-base) * 0.3);
|
|
aspect-ratio: 1;
|
|
background-color: var(--pvs-accent-c);
|
|
border-radius: var(--pvs-radius-dot);
|
|
}
|
|
|
|
/* The tool's own labels. A rail item and a table cell are the smallest marks on
|
|
the board, and their lengths are staggered so neither column reads as a
|
|
ruled form. */
|
|
[data-carry] :is(.ps-ops-item b, .ps-docs-item b, .ps-ops-lanes b, .ps-ops-row b, .ps-ops-field b, .ps-ops-switch b, .ps-ops-metric span) {
|
|
height: calc(var(--pt-base) * 0.26);
|
|
}
|
|
|
|
[data-carry] :is(.ps-ops-item, .ps-docs-item) b {
|
|
flex: 1 1 auto;
|
|
min-width: 0;
|
|
}
|
|
|
|
[data-carry] .ps-ops-item b {
|
|
width: 74%;
|
|
}
|
|
|
|
/* The open plate is a fill, so the label on it takes the ink that fill was paired
|
|
with rather than the page's bar tone, which would be reading a light mark
|
|
against a dark ground and hoping. */
|
|
[data-carry] .ps-ops-item--on b {
|
|
background-color: var(--pvs-cta-ink);
|
|
}
|
|
|
|
[data-carry] .ps-docs-item b {
|
|
width: 72%;
|
|
}
|
|
|
|
[data-carry] .ps-docs-item:nth-of-type(2) b {
|
|
width: 88%;
|
|
}
|
|
|
|
[data-carry] .ps-docs-item:nth-of-type(3) b {
|
|
width: 60%;
|
|
}
|
|
|
|
[data-carry] .ps-docs-item:nth-of-type(4) b {
|
|
width: 80%;
|
|
}
|
|
|
|
[data-carry] .ps-docs-item--on b {
|
|
background-color: var(--pvs-accent-c);
|
|
}
|
|
|
|
[data-carry] .ps-ops-item:nth-of-type(2) b {
|
|
width: 58%;
|
|
}
|
|
|
|
[data-carry] .ps-ops-item:nth-of-type(3) b {
|
|
width: 82%;
|
|
}
|
|
|
|
[data-carry] .ps-ops-metric b {
|
|
width: 56%;
|
|
height: calc(var(--pt-base) * 0.62);
|
|
background-color: var(--pvs-title);
|
|
}
|
|
|
|
[data-carry] .ps-ops-metric span {
|
|
width: 88%;
|
|
}
|
|
|
|
[data-carry] .ps-ops-lanes b {
|
|
width: 68%;
|
|
margin-inline: auto;
|
|
}
|
|
|
|
/* The row's outer tracks are sized by their contents, so a cell that carries no
|
|
words has to state the width the words would have taken. */
|
|
[data-carry] .ps-ops-row b:first-of-type {
|
|
width: calc(var(--pt-base) * 4.4);
|
|
background-color: var(--pvs-title);
|
|
}
|
|
|
|
[data-carry] .ps-ops-row b:nth-of-type(2) {
|
|
width: calc(var(--pt-base) * 3);
|
|
}
|
|
|
|
[data-carry] .ps-ops-row b:last-of-type {
|
|
width: calc(var(--pt-base) * 2.2);
|
|
margin-left: auto;
|
|
}
|
|
|
|
[data-carry] .ps-ops-row--head b {
|
|
background-color: var(--pvs-copy);
|
|
}
|
|
|
|
[data-carry] .ps-ops-field b {
|
|
width: 64%;
|
|
}
|
|
|
|
[data-carry] .ps-ops-switch b {
|
|
width: 56%;
|
|
}
|
|
|
|
[data-carry] .ps-ops-switch:nth-of-type(3) b {
|
|
width: 70%;
|
|
}
|
|
|
|
/* ── The index, drawn ────────────────────────────────────────
|
|
The caption stand-ins the wireframe index already declares. They are styled
|
|
here rather than borrowed from the motion screen, whose copies are scoped to
|
|
its own scenes and carry the fixed pixel sizes those scenes animate. */
|
|
[data-carry] :is(.ps-index-head, .ps-index-name, .ps-index-tag, .ps-index-lines b) {
|
|
display: block;
|
|
background-color: var(--pvs-bars);
|
|
border-radius: var(--pvs-radius-bar);
|
|
}
|
|
|
|
[data-carry] .ps-index-head {
|
|
width: 28%;
|
|
height: calc(var(--pt-base) * 0.72);
|
|
background-color: var(--pvs-headline);
|
|
}
|
|
|
|
[data-carry] .ps-index-name {
|
|
width: 68%;
|
|
height: calc(var(--pt-base) * 0.52);
|
|
background-color: var(--pvs-title);
|
|
}
|
|
|
|
[data-carry] .ps-index-tag {
|
|
width: 34%;
|
|
height: calc(var(--pt-base) * 0.24);
|
|
}
|
|
|
|
[data-carry] .ps-index-lines {
|
|
display: grid;
|
|
gap: calc(var(--pt-base) * 0.7);
|
|
margin-top: calc(var(--pt-base) * 0.3);
|
|
}
|
|
|
|
[data-carry] .ps-index-lines b {
|
|
width: 78%;
|
|
height: calc(var(--pt-base) * 0.26);
|
|
background-color: var(--pvs-copy);
|
|
}
|
|
|
|
[data-carry] .ps-index-lines b:last-child {
|
|
width: 52%;
|
|
}
|
|
|
|
[data-carry] .ps-index-track b {
|
|
width: calc(var(--pt-base) * 1.5);
|
|
height: calc(var(--pt-base) * 0.24);
|
|
background-color: var(--pvs-bars);
|
|
border-radius: var(--pvs-radius-bar);
|
|
}
|
|
|
|
[data-carry] .ps-index-stop--on {
|
|
background-color: var(--pvs-cta);
|
|
}
|
|
|
|
/* ── Shape and lift on the three bodies ──────────────────────
|
|
The corner and depth answers reach the landing page through the slots the
|
|
drawing has always read. These are the same slots on the parts the other
|
|
three bodies are made of, so one answer lands on every board.
|
|
|
|
Which slot a part takes is the part's own kind, not its size: a rail plate
|
|
and a switch are controls, a card and a callout are surfaces, and a bar
|
|
standing in for a line of text is never either, because at control radius it
|
|
stops reading as text and starts reading as a tag. */
|
|
[data-carry] :is(.ps-ops-item, .ps-ops-field, .ps-ops-row) {
|
|
border-radius: var(--pvs-radius-control);
|
|
}
|
|
|
|
[data-carry] :is(.ps-ops-chart, .ps-ops-table, .ps-ops-panel, .ps-ops-rail, .ps-docs-note, .ps-docs-rail, .ps-index-cap) {
|
|
border-radius: var(--pvs-radius-surface);
|
|
}
|
|
|
|
/* A switch is the most control-shaped thing a tool has, so it is where this
|
|
answer reads hardest: fully round under Pill, and squared under Sharp, where a
|
|
drafted page should have no round control left on it. The knob follows the
|
|
track, because a square switch holding a circle is neither answer. Both were
|
|
fixed at 999px, which is the pill answer already given away. */
|
|
[data-carry] :is(.ps-ops-toggle, .ps-ops-toggle::after) {
|
|
border-radius: var(--pvs-radius-control);
|
|
}
|
|
|
|
/* A bar stands on the axis, so only the two corners off it can take a radius.
|
|
Read off the surface slot rather than the control slot: a fully round bar at
|
|
Pill would be a lozenge floating over the baseline it is measured from. */
|
|
[data-carry] .ps-ops-plot > i {
|
|
border-radius: var(--pvs-radius-surface) var(--pvs-radius-surface) 0 0;
|
|
}
|
|
|
|
/* Which of these actually carries a ground is the boundary answer's business, so
|
|
the depth screen withholds the lift where the answer left the box transparent.
|
|
The slots are wired here; the gating is in depth.css beside the answer that
|
|
knows it. */
|
|
[data-carry] :is(.ps-ops-chart, .ps-ops-table, .ps-docs-note) {
|
|
box-shadow: var(--pvs-shadow-card);
|
|
}
|
|
|
|
[data-carry] :is(.ps-ops-item--on, .ps-ops-field, .ps-ops-toggle) {
|
|
box-shadow: var(--pvs-shadow-control);
|
|
}
|
|
|
|
[data-carry] :is(.ps-ops-rail, .ps-ops-panel, .ps-docs-rail) {
|
|
box-shadow: var(--pvs-shadow-surface);
|
|
}
|
|
|
|
/* ── The bands the answer divides ────────────────────────────
|
|
The layout answer's column tracks, per body. Every track is a whole number of
|
|
the field's columns, and the halves added on are the halves of a gutter that
|
|
put a region's dividing line down the middle of a gutter rather than against
|
|
one edge of it: a shell's regions abut and are told apart by a rule, where a
|
|
page's blocks are told apart by air. Defaults are the shape the drawing had
|
|
before the answer was asked, so a board is never left without one. */
|
|
[data-carry] .ps-ops {
|
|
grid-template-columns: var(--pvs-shell-cols, calc(var(--pvs-w2, 15.3333%) + var(--pvs-gut, 1.6%) / 2) calc(var(--pvs-w7, 57.6667%) + var(--pvs-gut, 1.6%)) minmax(0, 1fr));
|
|
column-gap: 0;
|
|
padding-inline: var(--ps-gutter);
|
|
}
|
|
|
|
[data-carry] .ps-docs {
|
|
grid-template-columns: var(--pvs-measure-cols, calc(var(--pvs-w3, 23.8%) + var(--pvs-gut, 1.6%) / 2) minmax(0, 1fr));
|
|
padding-inline: var(--ps-gutter);
|
|
}
|
|
|
|
/* The metrics row is the shell's second reading of the answer: three equal
|
|
thirds under a strict grid, and the lead figure taking more than its
|
|
neighbours once the answer allows an emphasis. */
|
|
[data-carry] .ps-ops-metrics {
|
|
grid-template-columns: var(--pvs-metric-cols, repeat(3, minmax(0, 1fr)));
|
|
}
|
|
|
|
[data-carry] .ps-index-row {
|
|
grid-template-columns: var(--pvs-entry-cols, var(--pvs-w6, 49.2%) minmax(0, 1fr));
|
|
column-gap: var(--pvs-gut, 1.6%);
|
|
}
|
|
|
|
[data-carry] .ps-index-row--flip {
|
|
grid-template-columns: var(--pvs-entry-flip-cols, minmax(0, 1fr) var(--pvs-w6, 49.2%));
|
|
}
|
|
|
|
/* The rail and the panel start on the page's margin like the nav's mark above
|
|
them, so their own inset is spent on the inside edge only and the dividing
|
|
line lands on the column line rather than a text inset past it. */
|
|
[data-carry] .ps-ops-rail {
|
|
padding-left: 0;
|
|
}
|
|
|
|
[data-carry] .ps-ops-panel {
|
|
padding-right: 0;
|
|
}
|
|
|
|
[data-carry] .ps-docs-rail {
|
|
padding-left: 0;
|
|
}
|
|
|
|
/* The measure starts on the next column's own left edge, which is half a gutter
|
|
past the line the rail's rule stands on. A text inset chosen for looks would
|
|
put every line of the document a few points off the grid it is being checked
|
|
against, and this is the one screen where that is the fault on show. */
|
|
[data-carry] .ps-docs-main {
|
|
padding-inline: calc(var(--pvs-gut, 1.6%) / 2) 0;
|
|
}
|
|
|
|
/* ── Filling the band ────────────────────────────────────────
|
|
The trap the docs board hit: a column holding less than its band pools all the
|
|
slack under the last block and leaves the footer standing on white. Bars are
|
|
shorter than the lines they stand in for, so these boards have more slack than
|
|
the specimens do, and every one of them spreads it between the blocks instead.
|
|
|
|
Under the query rather than always, and asked of the board's own height rather
|
|
than the window's, because the ladder floors on a small window while the frame
|
|
keeps shrinking: below this there is no slack to spread and forcing the spread
|
|
would push the last block through the footer. */
|
|
[data-carry][data-surface="read"] {
|
|
container-type: size;
|
|
container-name: ps-docs-board;
|
|
}
|
|
|
|
[data-carry][data-surface="operate"] {
|
|
container-type: size;
|
|
container-name: ps-ops-board;
|
|
}
|
|
|
|
@container ps-docs-board (height >= 300px) {
|
|
[data-carry] .ps-docs-main {
|
|
align-content: space-between;
|
|
padding-bottom: var(--ps-band-y);
|
|
}
|
|
|
|
[data-carry] .ps-phone-body:has(.ps-docs-list) {
|
|
align-content: space-between;
|
|
}
|
|
}
|
|
|
|
/* The tool fills its pane differently from the document, and the difference is
|
|
real rather than a taste: a chart and a table are the two blocks on a
|
|
dashboard that are *given* their height rather than taking it from what is in
|
|
them, so the slack goes into them instead of into the gaps between blocks.
|
|
Spreading the gaps here left a shell whose four blocks floated apart with
|
|
nothing between them, which is the same emptiness the docs fix was chasing. */
|
|
@container ps-ops-board (height >= 300px) {
|
|
[data-carry] .ps-ops-main {
|
|
grid-template-rows: auto auto minmax(0, 1fr) minmax(0, 1fr);
|
|
align-content: stretch;
|
|
padding-bottom: var(--ps-band-y);
|
|
}
|
|
|
|
[data-carry] .ps-ops-chart {
|
|
grid-template-rows: auto minmax(0, 1fr) auto;
|
|
}
|
|
|
|
[data-carry] .ps-ops-plot {
|
|
height: auto;
|
|
min-height: 0;
|
|
}
|
|
|
|
[data-carry] .ps-ops-table {
|
|
align-content: space-between;
|
|
}
|
|
|
|
/* Same reading on the handset: the plot is the block whose height is a count
|
|
rather than its contents, so it is the one that takes up the slack. */
|
|
[data-carry][data-surface="operate"] .ps-phone-body {
|
|
grid-template-rows: auto auto minmax(0, 1fr) auto auto;
|
|
align-content: stretch;
|
|
}
|
|
|
|
[data-carry][data-surface="operate"] .ps-phone-body .ps-ops-plot {
|
|
height: auto;
|
|
min-height: 0;
|
|
}
|
|
}
|
|
|
|
.picker-palette-hint {
|
|
min-height: 3.2em;
|
|
padding-inline-end: 48px;
|
|
color: var(--ks-text-muted);
|
|
font-size: 0.84rem;
|
|
line-height: 1.6;
|
|
opacity: 1;
|
|
transition: opacity 180ms var(--ks-ease);
|
|
}
|
|
|
|
.picker-palette-hint.is-changing {
|
|
opacity: 0;
|
|
}
|
|
|
|
.picker-select {
|
|
width: auto;
|
|
}
|
|
|
|
.picker-placeholder {
|
|
display: grid;
|
|
justify-items: start;
|
|
gap: 28px;
|
|
color: var(--ks-text-muted);
|
|
}
|
|
|
|
/* Finish screen: the review copy plus the loader that covers the save and
|
|
the handoff into the design context document. */
|
|
.picker-finish {
|
|
max-width: 560px;
|
|
gap: 18px;
|
|
}
|
|
|
|
.picker-finish-lede {
|
|
margin: 0;
|
|
color: var(--ks-text-muted);
|
|
font-size: 1.05rem;
|
|
line-height: 1.6;
|
|
text-wrap: pretty;
|
|
}
|
|
|
|
.picker-finish-loader {
|
|
width: min(360px, 100%);
|
|
height: 2px;
|
|
margin-top: 10px;
|
|
background: color-mix(in oklab, var(--ks-text) 14%, transparent);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.picker-finish-loader i {
|
|
display: block;
|
|
height: 100%;
|
|
width: 38%;
|
|
background: var(--ks-kinpaku);
|
|
animation: picker-finish-sweep 1.4s var(--ks-ease) infinite;
|
|
}
|
|
|
|
.picker-finish-loader[data-stalled] i {
|
|
animation-play-state: paused;
|
|
opacity: 0.35;
|
|
}
|
|
|
|
@keyframes picker-finish-sweep {
|
|
from { translate: -100% 0; }
|
|
to { translate: 380% 0; }
|
|
}
|
|
|
|
.picker-finish-status {
|
|
margin: 0;
|
|
min-height: 1.6em;
|
|
color: var(--ks-text-faint);
|
|
font-size: 0.84rem;
|
|
letter-spacing: 0.04em;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.picker-finish-error {
|
|
margin: 0;
|
|
color: var(--ks-text-muted);
|
|
font-size: 0.94rem;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
/* The stack's own display beats the hidden attribute's UA rule, so the error
|
|
state needs the attribute spelled out. */
|
|
.picker-finish [data-doc-error][hidden] {
|
|
display: none;
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.picker-finish-loader i { animation: none; translate: 0 0; width: 100%; opacity: 0.5; }
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.picker-hero-art,
|
|
.picker-progress,
|
|
.picker-progress span,
|
|
.picker-card,
|
|
.picker-ring,
|
|
.picker-ring::after,
|
|
.picker-ring-guide,
|
|
.picker-icon-button,
|
|
.picker-band-tools,
|
|
.picker-tip::after,
|
|
.picker-palette-hint,
|
|
.picker-strategy-option,
|
|
.picker-artboard:not(.picker-preview-type) :is(
|
|
.ps-desktop,
|
|
.ps-phone,
|
|
.ps-nav,
|
|
.ps-proof,
|
|
.ps-proof-item,
|
|
.ps-footer,
|
|
.ps-phone-top,
|
|
.ps-phone-footer,
|
|
.ps-brand-block,
|
|
.ps-nav-bars i,
|
|
.ps-nav-action,
|
|
.ps-eyebrow,
|
|
.ps-headline i,
|
|
.ps-copy i,
|
|
.ps-actions i,
|
|
.ps-image,
|
|
.ps-proof-item i,
|
|
.ps-proof-item span,
|
|
.ps-editorial-copy strong,
|
|
.ps-editorial-copy span i,
|
|
.ps-editorial-copy > em,
|
|
.ps-gallery-item > i,
|
|
.ps-gallery-item b,
|
|
.ps-footer-links i,
|
|
.ps-footer-mark,
|
|
.ps-phone-footer i,
|
|
.ps-ops-rail,
|
|
.ps-ops-main,
|
|
.ps-ops-item,
|
|
.ps-ops-chart,
|
|
.ps-ops-table,
|
|
.ps-ops-row,
|
|
.ps-ops-panel,
|
|
.ps-ops-plot > i,
|
|
.ps-docs-rail,
|
|
.ps-docs-note,
|
|
.ps-index-row,
|
|
.ps-index-rail
|
|
),
|
|
.picker-preview-type :is(
|
|
.ps-desktop,
|
|
.ps-phone,
|
|
.ps-nav,
|
|
.ps-proof,
|
|
.ps-proof-item,
|
|
.ps-footer,
|
|
.ps-phone-top,
|
|
.ps-phone-footer,
|
|
.ps-brand-block,
|
|
.ps-nav-bars i,
|
|
.ps-nav-action,
|
|
.ps-eyebrow,
|
|
.ps-headline i,
|
|
.ps-copy i,
|
|
.ps-actions i,
|
|
.ps-image,
|
|
.ps-proof-item i,
|
|
.ps-proof-item span,
|
|
.ps-editorial-copy strong,
|
|
.ps-editorial-copy > em,
|
|
.ps-gallery-item > i,
|
|
.ps-gallery-item b,
|
|
.ps-footer-links i,
|
|
.ps-footer-mark,
|
|
.ps-phone-footer i,
|
|
.pt-headline,
|
|
.pt-body,
|
|
.pt-section-body
|
|
) {
|
|
transition: none;
|
|
}
|
|
|
|
.picker-card[data-exit] {
|
|
animation: none;
|
|
}
|
|
|
|
.picker-type-option[data-hoisted],
|
|
.picker-type-option[data-hoisted]::before {
|
|
animation: none;
|
|
}
|
|
}
|
|
|
|
.picker-width-gate {
|
|
display: none;
|
|
}
|
|
|
|
@media (max-width: 1199px) {
|
|
.picker-width-gate {
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 10;
|
|
display: grid;
|
|
place-items: center;
|
|
padding: 40px;
|
|
background: var(--ks-lacquer);
|
|
color: var(--ks-text);
|
|
text-align: center;
|
|
}
|
|
|
|
.picker-width-gate > div {
|
|
max-width: 560px;
|
|
display: grid;
|
|
gap: 12px;
|
|
justify-items: center;
|
|
}
|
|
|
|
.picker-width-gate h2 {
|
|
color: var(--ks-champagne);
|
|
font-family: var(--ks-font-display);
|
|
font-size: var(--ks-type-headline-size);
|
|
font-weight: var(--ks-type-headline-weight);
|
|
line-height: var(--ks-type-headline-line);
|
|
}
|
|
|
|
.picker-width-gate p {
|
|
color: var(--ks-text-muted);
|
|
}
|
|
|
|
.picker-copy-link {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 14px;
|
|
width: min(100%, 480px);
|
|
min-width: 0;
|
|
margin-top: 14px;
|
|
padding: 13px 12px 13px 18px;
|
|
background: var(--ks-lacquer-raised);
|
|
border: 1px solid var(--ks-rule);
|
|
border-radius: 2px;
|
|
text-align: left;
|
|
}
|
|
|
|
.picker-copy-link code {
|
|
flex: 1 1 auto;
|
|
min-width: 18ch;
|
|
overflow-x: auto;
|
|
color: var(--ks-patina);
|
|
font-family: var(--ks-mono);
|
|
font-size: var(--ks-type-body-size);
|
|
letter-spacing: 0;
|
|
white-space: nowrap;
|
|
background: transparent;
|
|
border: 0;
|
|
padding: 0;
|
|
user-select: all;
|
|
}
|
|
|
|
.picker-copy-link-button {
|
|
position: relative;
|
|
flex: none;
|
|
display: inline-grid;
|
|
place-items: center;
|
|
width: 40px;
|
|
height: 40px;
|
|
padding: 0;
|
|
border: 1px solid var(--ks-rule);
|
|
border-radius: 2px;
|
|
background: transparent;
|
|
color: var(--ks-patina);
|
|
cursor: pointer;
|
|
transition:
|
|
background 180ms var(--ks-ease),
|
|
border-color 180ms var(--ks-ease),
|
|
color 180ms var(--ks-ease),
|
|
transform 120ms var(--ks-ease);
|
|
}
|
|
|
|
.picker-copy-link-button:hover,
|
|
.picker-copy-link-button.is-copied {
|
|
border-color: currentColor;
|
|
background: var(--ks-lacquer);
|
|
}
|
|
|
|
.picker-copy-link-button:active {
|
|
transform: scale(0.96);
|
|
}
|
|
|
|
.picker-copy-link-button:focus-visible {
|
|
outline: 2px solid var(--ks-patina);
|
|
outline-offset: 3px;
|
|
}
|
|
|
|
.picker-copy-link-icon,
|
|
.picker-copy-link-check {
|
|
grid-area: 1 / 1;
|
|
transition:
|
|
opacity 140ms var(--ks-ease),
|
|
transform 180ms var(--ks-ease);
|
|
}
|
|
|
|
.picker-copy-link-icon {
|
|
opacity: 1;
|
|
transform: scale(1);
|
|
}
|
|
|
|
.picker-copy-link-check {
|
|
opacity: 0;
|
|
transform: scale(0.86);
|
|
}
|
|
|
|
.picker-copy-link-button.is-copied .picker-copy-link-icon {
|
|
opacity: 0;
|
|
transform: scale(0.82);
|
|
}
|
|
|
|
.picker-copy-link-button.is-copied .picker-copy-link-check {
|
|
opacity: 1;
|
|
transform: scale(1);
|
|
}
|
|
|
|
.picker-copy-link-button span {
|
|
position: absolute;
|
|
width: 1px;
|
|
height: 1px;
|
|
overflow: hidden;
|
|
clip: rect(0 0 0 0);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.picker-copy-status {
|
|
min-height: 1.5em;
|
|
font-size: var(--ks-type-mono-size);
|
|
}
|
|
|
|
@media (max-width: 520px) {
|
|
.picker-width-gate {
|
|
padding-inline: 24px;
|
|
}
|
|
|
|
.picker-copy-link {
|
|
gap: 10px;
|
|
padding-left: 14px;
|
|
}
|
|
|
|
.picker-copy-link code {
|
|
overflow-x: visible;
|
|
overflow-wrap: anywhere;
|
|
white-space: normal;
|
|
}
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.picker-copy-link-button,
|
|
.picker-copy-link-icon,
|
|
.picker-copy-link-check {
|
|
transition: none;
|
|
}
|
|
}
|
|
}
|