1/* dev.site — everything that is not Pico.2 *3 * Pico 2 does the work. This file supplies the typeface, the handful of layout4 * rules Pico has no opinion about, and nothing else. Anything added here has to5 * earn its place against a Pico element or class that would have done the job.6 *7 * Colours are deliberately absent: every colour on the page comes from a8 * --pico-* variable, which is what makes a user theme a small, checkable list9 * of variable assignments rather than a stylesheet. See docs/profile-template.md.10 */1112/* Open Sans, vendored. The file is a variable font covering 300–800, but it is13 * declared twice at fixed weights instead of as a range, so that a request for14 * any other weight — Pico asks for 600 in places — snaps to 700 rather than15 * rendering an in-between instance. Two weights, by construction. */16@font-face {17 font-family: 'Open Sans';18 src: url('/vendor/fonts/open-sans-latin.woff2') format('woff2');19 font-weight: 400;20 font-style: normal;21 font-display: swap;22 unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;23}24@font-face {25 font-family: 'Open Sans';26 src: url('/vendor/fonts/open-sans-latin.woff2') format('woff2');27 font-weight: 700;28 font-style: normal;29 font-display: swap;30 unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;31}32@font-face {33 font-family: 'Open Sans';34 src: url('/vendor/fonts/open-sans-latin-ext.woff2') format('woff2');35 font-weight: 400;36 font-style: normal;37 font-display: swap;38 unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;39}40@font-face {41 font-family: 'Open Sans';42 src: url('/vendor/fonts/open-sans-latin-ext.woff2') format('woff2');43 font-weight: 700;44 font-style: normal;45 font-display: swap;46 unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;47}4849:root {50 --pico-font-family-sans-serif: 'Open Sans', system-ui, sans-serif;51 --pico-font-family: var(--pico-font-family-sans-serif);52}5354/* -- layout Pico has no opinion about ------------------------------------- */5556body > main {57 padding-block: var(--pico-block-spacing-vertical);58}5960body > header > nav {61 align-items: center;62}6364#page-footer nav {65 justify-content: flex-end;66}6768main:has(.sequence),69main:has(.sequence) + #page-footer {70 user-select: none;71}7273main:has(.sequence) :is(pre, code, #install-command) {74 user-select: text;75}7677/* Pico spaces paragraphs as standalone blocks. Inside a quote that already has78 * its own padding, use a smaller gap without adding another trailing margin. */79main:has(.sequence) blockquote > p {80 margin-block-end: 0;81}8283main:has(.sequence) blockquote > p + p {84 margin-block-start: calc(var(--pico-spacing) * 0.5);85}8687/* Dashboard labels and controls are interface, not document copy. Keep the88 * values someone may reasonably copy selectable: form input, commands, link89 * destinations, and errors worth carrying into a bug report. */90main:has(#private-only) {91 user-select: none;92}9394main:has(#private-only) :is(input, textarea, pre, code, [data-selectable], .error) {95 user-select: text;96}9798/* A profile is a directory of links and service controls. Its handle is the99 * one text value people need to carry elsewhere when sharing with its owner. */100#profile {101 user-select: none;102}103104#profile h1 {105 user-select: text;106}107108/* Pico's chevron is a gray SVG data URI, so summary colour variables cannot109 * reach it. Draw the same mark from currentColor and let open/closed summary110 * colours apply to the text and disclosure indicator together. */111details:not(.dropdown) > summary::after {112 width: 0.55rem;113 height: 0.55rem;114 margin-top: 0.15rem;115 margin-inline-end: 0.15rem;116 background-image: none;117 border-right: 0.12rem solid currentColor;118 border-bottom: 0.12rem solid currentColor;119 transform: rotate(-45deg);120}121122details:not(.dropdown)[open] > summary::after {123 transform: rotate(45deg);124}125126/* Keep each concepts table header visible until its table leaves the viewport. */127.concepts-table thead th {128 position: sticky;129 top: 0;130 z-index: 1;131 background-color: var(--pico-background-color);132}133134.concept-detail {135 margin-block: calc(var(--pico-spacing) * 0.75);136}137138.concept-detail > summary {139 line-height: 1.175;140}141142.brand {143 display: inline-flex;144 gap: calc(var(--pico-spacing) * 0.4);145 align-items: center;146}147.brand > svg {148 flex: none;149 width: 2.5rem;150 height: 2.5rem;151}152153.entries + .folder,154.folder + .folder {155 margin-top: var(--pico-block-spacing-vertical);156}157158/* A folder's summary is a label on a fold, not a heading competing with the159 * profile name. Pico already draws the chevron and handles the open state. */160.folder > summary {161 color: var(--pico-muted-color);162 font-weight: 700;163}164.folder > summary > small {165 color: var(--pico-muted-color);166 font-weight: 400;167}168/* Pico gives an open <details> a bottom margin on its content; the list already169 * carries its own rules, so it only needs to sit clear of the summary. */170.folder > .entries {171 margin-top: calc(var(--pico-spacing) * 0.5);172}173174/* A list of things on a profile: name on the left, state on the right.175 * `list-style` is set on the items, not the list: Pico styles `ul li` directly,176 * and a directly-matching rule beats an inherited one however specific. */177.entries {178 margin: 0;179 padding: 0;180}181.entries > li {182 list-style: none;183 display: flex;184 gap: var(--pico-spacing);185 align-items: center;186 justify-content: space-between;187 padding-block: calc(var(--pico-spacing) * 0.5);188 border-bottom: var(--pico-border-width) solid var(--pico-muted-border-color);189}190.entries > li:last-child {191 border-bottom: 0;192}193/* Entry actions are buttons and links; neither should carry a block margin here. */194.entries > li > :is(a, button, [role='button']) {195 margin-bottom: 0;196}197198/* A service is a button because clicking it acts on this page rather than199 * navigating — that is the correct element and it stays. It should not *look*200 * like a different class of thing, though: the row already says how you get201 * there with an arrow or its absence, and drawing one as a box and the other as202 * text would re-sort the page into kinds the model no longer has. So the button203 * is dressed as what it is: an entry in a list of sites. */204.entries > li > button {205 width: auto;206 padding: 0;207 border: 0;208 background: none;209 font: inherit;210 color: var(--pico-primary);211 text-decoration: underline;212 text-underline-offset: var(--pico-text-underline-offset);213 cursor: pointer;214}215.entries > li > button:is(:hover, :focus-visible) {216 color: var(--pico-primary-hover);217}218/* The right-hand column: where a link goes, or how a service is shared. Both are219 * secondary to the name, so both take the muted colour rather than the body one. */220.entries .state {221 white-space: nowrap;222 color: var(--pico-muted-color);223}224225/* The host, folded into the arrow until you point at its row.226 *227 * An inline grid going 0fr → 1fr animates to exactly the content's width, which228 * a max-width cannot do without a guess — and a wrong guess either clips a long229 * host or spends the transition crossing empty space. The row is right-aligned,230 * so growing the column pushes the text out to the left and leaves the arrow231 * where it was. */232.entries .host {233 display: inline-grid;234 grid-template-columns: 0fr;235 opacity: 0;236 transition: grid-template-columns 240ms ease, opacity 160ms ease;237}238.entries .host > span {239 overflow: hidden;240}241.entries > li:is(:hover, :focus-within) .host {242 grid-template-columns: 1fr;243 opacity: 1;244}245246/* Without hover there is no way to ask, so the answer is always on. */247@media (hover: none) {248 .entries .host {249 grid-template-columns: 1fr;250 opacity: 1;251 }252}253254@media (prefers-reduced-motion: reduce) {255 .entries .host {256 transition: none;257 }258}259260/* -- CLI commands and service tickets -------------------------------------- */261262.secret-command {263 display: flex;264 align-items: stretch;265 margin-bottom: calc(var(--pico-spacing) * 0.25);266 overflow: hidden;267 border: var(--pico-border-width) solid var(--pico-form-element-border-color);268 border-radius: var(--pico-border-radius);269 background: var(--pico-form-element-background-color);270}271.secret-command > code {272 flex: 1;273 align-content: center;274 min-width: 0;275 padding: var(--pico-form-element-spacing-vertical)276 var(--pico-form-element-spacing-horizontal);277 overflow: hidden;278 border: 0;279 background: transparent;280 text-overflow: ellipsis;281 white-space: nowrap;282}283.command-executable {284 color: var(--pico-primary);285 font-weight: 700;286}287.command-verb {288 color: var(--pico-ins-color);289 font-weight: 700;290}291.command-option {292 color: var(--pico-primary-hover);293}294.command-value {295 color: var(--pico-color);296}297.command-secret {298 color: var(--pico-del-color);299}300301/* The sequence diagram is the signed-out page's one bespoke element. */302.home-intro .home-wordmark {303 display: block;304 width: min(100%, 35.64rem);305 height: auto;306 fill: currentColor;307 shape-rendering: crispEdges;308}309310.home-intro h1 {311 margin-bottom: var(--pico-typography-spacing-vertical);312}313314.sequence {315 margin: var(--pico-block-spacing-vertical) 0;316}317318.command-output {319 color: var(--pico-muted-color);320}321.sequence figcaption {322 color: var(--pico-muted-color);323 font-size: 0.75rem;324 text-align: center;325}326.sequence > header {327 display: grid;328 grid-template-columns: repeat(3, minmax(0, 1fr));329 gap: calc(var(--pico-spacing) * 0.5);330}331.sequence > header > strong {332 padding: calc(var(--pico-spacing) * 0.5);333 border-radius: var(--pico-border-radius);334 background: var(--pico-form-element-background-color);335 font-size: 0.75rem;336 text-align: center;337}338.sequence > div {339 position: relative;340 height: 21.5rem;341}342.sequence > div > i {343 position: absolute;344 top: 0;345 bottom: 0;346 border-left: var(--pico-border-width) dashed var(--pico-form-element-border-color);347}348.sequence > div > i:nth-of-type(1) {349 left: 16.667%;350}351.sequence > div > i:nth-of-type(2) {352 left: 50%;353}354.sequence > div > i:nth-of-type(3) {355 left: 83.333%;356}357.sequence ol {358 position: absolute;359 inset: 0;360 margin: 0;361 padding: 0;362}363.sequence li {364 position: absolute;365 height: 1.35rem;366 padding: 0 0.25rem;367 border-top: var(--pico-border-width) solid var(--pico-primary);368 color: var(--pico-muted-color);369 font-size: clamp(0.58rem, 1.5vw, 0.7rem);370 line-height: 1.15;371 text-align: center;372 list-style: none;373}374.sequence li > span {375 position: relative;376 display: inline-block;377 padding-inline: 0.25rem;378 background: var(--pico-card-background-color);379 transform: translateY(calc(-100% - 0.2rem));380}381.sequence li::after {382 position: absolute;383 top: -0.28rem;384 content: '';385 border-block: 0.24rem solid transparent;386}387.sequence li:nth-child(1),388.sequence li:nth-child(2),389.sequence li:nth-child(3),390.sequence li:nth-child(4),391.sequence li:nth-child(5),392.sequence li:nth-child(6) {393 left: 50%;394 width: 33.333%;395}396.sequence li:nth-child(1) {397 top: 2.1rem;398 left: 16.667%;399}400.sequence li:nth-child(2) {401 top: 4.8rem;402 left: 16.667%;403}404.sequence li:nth-child(3) {405 top: 7.5rem;406}407.sequence li:nth-child(4) {408 top: 10.2rem;409}410.sequence li:nth-child(5) {411 top: 12.9rem;412}413.sequence li:nth-child(6) {414 top: 15.6rem;415}416.sequence li:nth-child(7) {417 top: 18.5rem;418 left: 16.667%;419 width: 66.666%;420 border-top-style: dashed;421 animation: sequence-flow 1.4s ease-in-out infinite alternate;422}423.sequence li:nth-child(1)::after,424.sequence li:nth-child(4)::after,425.sequence li:nth-child(6)::after {426 right: -0.05rem;427 border-left: 0.42rem solid var(--pico-primary);428}429.sequence li:nth-child(2)::after,430.sequence li:nth-child(3)::after,431.sequence li:nth-child(5)::after,432.sequence li:nth-child(7)::after {433 left: -0.05rem;434 border-right: 0.42rem solid var(--pico-primary);435}436.sequence li:nth-child(7)::before {437 position: absolute;438 top: -0.28rem;439 right: -0.05rem;440 content: '';441 border-block: 0.24rem solid transparent;442 border-left: 0.42rem solid var(--pico-primary);443}444445@keyframes sequence-flow {446 to { opacity: 0.55; }447}448449@media (prefers-reduced-motion: reduce) {450 .sequence li:nth-child(7) {451 animation: none;452 }453}454455.secret-command > button {456 flex: none;457 width: auto;458 margin: 0;459 border-radius: 0;460 white-space: nowrap;461}462.secret-copy-status {463 display: block;464 min-height: 1lh;465}466467/* Refusals and confirmations. Pico's `ins` and `del` colours, without their468 * underline and strikethrough — a rejected declaration has to stay readable. */469.error {470 color: var(--pico-del-color);471}472.ok {473 color: var(--pico-ins-color);474}475476.dashboard-list {477 margin: 0;478 padding: 0;479}480.dashboard-list > li {481 display: flex;482 gap: var(--pico-spacing);483 align-items: center;484 justify-content: space-between;485 padding-block: calc(var(--pico-spacing) * 0.5);486 border-bottom: var(--pico-border-width) solid var(--pico-muted-border-color);487 list-style: none;488}489.dashboard-list > li:last-child {490 border-bottom: 0;491}492.dashboard-list > li > div:first-child {493 display: grid;494}495.dashboard-actions {496 display: flex;497 flex-wrap: wrap;498 gap: calc(var(--pico-spacing) * 0.4);499 justify-content: end;500}501.dashboard-actions > button,502.dashboard-list > li > button {503 width: auto;504 margin: 0;505 padding: calc(var(--pico-form-element-spacing-vertical) * 0.5)506 calc(var(--pico-form-element-spacing-horizontal) * 0.65);507}508509@media (max-width: 576px) {510 .secret-command {511 align-items: stretch;512 flex-direction: column;513 }514 .secret-command > button {515 border-radius: 0;516 }517 .dashboard-list > li {518 align-items: start;519 flex-direction: column;520 }521 .dashboard-actions {522 justify-content: start;523 }524}