@font-face {
  font-family: klfont;
  src: url('/static/klfont.woff') format('woff');
}

:root {
  --pico-font-family: klfont;
}

button {
  --pico-font-weight: 700;
}

/* A switch is taller than the text beside it, so a note trailing one runs into
   the label's own line. Give it a line of its own, as Pico already does for the
   note under a text field. */
label:has(>[role="switch"])>small {
  display: block;
  margin-top: .1rem;
}

body>header .container {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

/* ==================== header ==================== */

.brand {
  text-decoration: none;
  color: inherit;
}

/* Pico gives nav items a block padding that a 32px avatar pushes past, so the
   icon row sets its own alignment rather than inheriting a text baseline. */
body>header nav ul.icons {
  align-items: center;
}

body>header nav ul.icons .dropdown summary {
  margin-bottom: 0;
}

.avatar {
  display: grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  object-fit: cover;
}

.avatar-initial {
  background: var(--pico-primary-background);
  color: var(--pico-primary-inverse);
  font-weight: 600;
  font-size: .85rem;
}

/* The identity line is a label, not a choice: it sits in the same list as the
   sign-out control, so it has to opt out of looking clickable. */
.account-identity {
  padding: var(--pico-form-element-spacing-vertical) var(--pico-form-element-spacing-horizontal);
  line-height: 1.3;
  cursor: default;
}

.account-identity small {
  display: block;
  color: var(--pico-muted-color);
}

.account ul form {
  margin: 0;
  padding: 0 var(--pico-form-element-spacing-horizontal) var(--pico-form-element-spacing-vertical);
}

.account ul form button {
  width: 100%;
  margin: 0;
}

/* ==================== landing ==================== */

.hero {
  padding: clamp(2rem, 8vw, 5rem) 0 2rem;
  max-width: 46rem;
}

.hero h1 {
  font-size: clamp(2rem, 6vw, 3.25rem);
  line-height: 1.1;
  margin-bottom: .5rem;
}

.hero p {
  font-size: 1.125rem;
  color: var(--pico-muted-color);
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: .75rem;
  margin-top: 1.5rem;
}

.hero-actions [role="button"] {
  margin: 0;
}

/* ==================== cards ==================== */

/* auto-fit rather than a fixed column count: the same rule carries three feature
   cards on the landing page and however many projects a person happens to have. */
.feature-grid,
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
  gap: 1rem;
  margin-bottom: 2rem;
}

.card {
  margin: 0;
  padding: 1.25rem;
}

.card h3 {
  font-size: 1.1rem;
  margin-bottom: .5rem;
}

.card p {
  margin-bottom: 0;
  color: var(--pico-muted-color);
}

.card-grid .card {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 1rem;
}

.card-grid .card [role="button"] {
  margin: 0;
  align-self: flex-start;
}

/* ==================== addons ==================== */

/* Wider than the shared card: one holds a fingerprint, a digest and a couple of
   forms, none of which fit in 17rem. */
.addons-grid {
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 26rem), 1fr));
}

/* Rows below a heading, so they stack from the top rather than spreading to fill
   the tallest card in the row. */
.card-grid .addons-card {
  justify-content: flex-start;
  gap: .75rem;
}

.addons-card header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  margin: 0;
  padding: 0;
  background: none;
  border: 0;
}

.addons-card header h3 {
  margin: 0;
}

.addons-card header button {
  width: auto;
  margin: 0;
  padding: .35rem .75rem;
  font-size: .85rem;
}

.addons-card dl {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: .5rem .75rem;
  margin: 0;
}

.addons-card dt {
  color: var(--pico-muted-color);
  font-size: .85rem;
}

/* `min-width` so a repository URL wraps inside its column instead of widening
   the card past its track. */
.addons-card dd {
  margin: 0;
  min-width: 0;
  overflow-wrap: anywhere;
}

.addons-card details,
.addons-card summary {
  margin-bottom: 0;
}

.addons-card form {
  margin-top: .5rem;
}

/* Under the value rather than trailing it: a commit's date and where it was read
   from is a line of its own in a 26rem track. Direct children only — the archive's
   size sits inside a summary beside the filename. */
.addons-card dd>small {
  display: block;
}

.addons-card .digest {
  display: block;
  font-size: .75rem;
}

/* The harsher half of Remove, folded away directly under it in the same header.
   Styled as a footnote to that button rather than as a section of the card: a
   summary the size of the surrounding notes, and the confirmed button in the
   delete colour the danger zone uses for the same kind of act. */
.instance header .purge {
  margin: .5rem 0 0;
  border: none;
}

.instance header .purge summary {
  color: var(--pico-muted-color);
  font-size: .85rem;
}

.instance header .purge form {
  margin: .5rem 0 0;
}

.instance header .purge button {
  margin: 0;
  --pico-background-color: var(--pico-del-color);
  --pico-border-color: var(--pico-del-color);
}

/* An instance's addons table: each cell stacks a value, a note about it and
   sometimes a form, so the annotations are lines rather than trailing text. */
.instance td small {
  display: block;
}

.instance .addons-ref form,
.instance .addons-commit form {
  margin: .35rem 0 0;
}

.instance .addons-ref button,
.instance .addons-commit button {
  width: auto;
  margin: .35rem 0 0;
  padding: .35rem .75rem;
  font-size: .85rem;
}

/* The redeploy prompt on a deployment page. Sticky on the wrapper and not on the
   bar itself: sticky holds an element only while its own parent is in view, and
   the wrapper's parent is the page. The wrapper collapses to nothing when there
   is nothing to say, so it costs no space on a deployment that is up to date. */
#pending {
  position: sticky;
  top: 0;
  /* Above the page it slides over, below nothing else — no other overlay here. */
  z-index: 10;
}

.pending-changes {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem 1rem;
  margin: 0 0 1rem;
  padding: .6rem .9rem;
  border-radius: var(--pico-border-radius);
  /* Opaque, because the page scrolls underneath it. */
  background: var(--pico-card-sectioning-background-color, var(--pico-background-color));
  border: 1px solid var(--pico-color-amber-500, #d97706);
}

.pending-changes p {
  margin: 0;
  /* Takes the row, so the button lands at the right-hand end and stays put as
     the message wraps. */
  flex: 1;
}

.pending-changes button {
  width: auto;
  margin: 0;
  padding: .35rem .9rem;
}

.pending-changes small {
  color: var(--pico-muted-color);
}

/* The disk, left to right: what cannot be collected, the room the ephemeral
   instances have, and the rest of the track, which is free. The room they have
   is a block of its own rather than a second fill from the left, because the
   question the bar answers is how full *that* is. */
.notice.storage .bar {
  display: flex;
  height: .5rem;
  margin-bottom: .6rem;
  border-radius: var(--pico-border-radius);
  /* The track is the free space; nothing is drawn for it. Slightly stronger than
     the border colour it would otherwise take, so the bar's full extent — and
     with it how much room is left — is visible on a light page. */
  background: color-mix(in srgb, var(--pico-muted-color) 22%, var(--pico-background-color));
  overflow: hidden;
}

.notice.storage .bar>span {
  /* Widths come from the server as percentages of the disk, so nothing here may
     grow or shrink them. */
  flex: none;
}

.notice.storage .durable {
  background: var(--pico-muted-color);
}

/* The room they have: the same colour as what they hold, weakened, so the two
   read as one block partly filled rather than as two unrelated things.

   Amber, as the redeploy prompt above already is on this page: what is in here
   is the impermanent part of the disk, and the page's blue is the colour of
   things you act on. Mixed towards the page's own background rather than towards
   `transparent`, because a translucent block sits over the grey track, which in
   light mode washes it out to something indistinguishable from the free space
   beside it. */
.notice.storage .allotted {
  /* The same token, and the same fallback, as the prompt's border. */
  --storage-ephemeral: var(--pico-color-amber-500, #d97706);
  background: color-mix(in srgb, var(--storage-ephemeral) 30%, var(--pico-background-color));
}

.notice.storage .allotted>span {
  display: block;
  height: 100%;
  background: var(--storage-ephemeral);
}

/* Over the budget, which the sentence below the bar explains. */
.notice.storage .allotted>.over {
  background: var(--pico-del-color);
}

/* ==================== projects ==================== */

.create-project {
  max-width: 32rem;
}

.back-link {
  color: var(--pico-muted-color);
  text-decoration: none;
}

/* Links, not buttons: each tab is a page with an address of its own, so the
   current one is marked with aria-current and styled from that rather than from
   a class the server would have to compute twice. */
.tabs {
  display: flex;
  gap: 1.5rem;
  border-bottom: 1px solid var(--pico-muted-border-color);
  margin-bottom: 2rem;
  justify-content: initial;
}

.tabs a {
  padding: .5rem 0;
  margin-bottom: -1px;
  border-bottom: 2px solid transparent;
  color: var(--pico-muted-color);
  text-decoration: none;
}

.tabs a:hover {
  color: var(--pico-color);
}

.tabs a[aria-current="page"] {
  color: var(--pico-color);
  border-bottom-color: var(--pico-primary);
}

.row-actions {
  text-align: right;
  white-space: nowrap;
}

.row-actions button {
  margin: 0;
  padding: .35rem .75rem;
  font-size: .85rem;
}

.invite-member {
  max-width: 38rem;
}

.danger-zone {
  border-top: 1px solid var(--pico-muted-border-color);
  padding-top: 1.5rem;
  margin-top: 2rem;
}

.danger-zone button {
  margin: 0;
  --pico-color: var(--pico-del-color);
  --pico-border-color: var(--pico-del-color);
}

/* ==================== job log ==================== */

.job-log {
  max-height: 70vh;
  overflow: auto;
  /* Long tofu diffs and ansible messages wrap rather than pushing the page
     sideways, but the leading whitespace that lines a plan up is kept. */
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/*
  The palette `static/js/job-log.js` renders into. Named after the terminal
  colours the tools ask for, defined here because a terminal's idea of "green"
  assumes a dark background and this page is not always one.

  The `light-dark()` pairs are the same hue at two lightnesses: the dark member
  is what a terminal would show, the light member is darkened enough to stay
  legible on paper-coloured card. Pico sets `color-scheme` from `data-theme`,
  so both follow the toggle without a media query.
*/
.job-log {
  --ansi-black: light-dark(#3b4252, #4c566a);
  --ansi-red: light-dark(#b02a26, #f07178);
  --ansi-green: light-dark(#3c7a35, #9ece6a);
  --ansi-yellow: light-dark(#8a6100, #e0af68);
  --ansi-blue: light-dark(#2d5fa8, #7aa2f7);
  --ansi-magenta: light-dark(#8a3b96, #bb9af7);
  --ansi-cyan: light-dark(#0f6f75, #7dcfff);
  --ansi-white: light-dark(#4b5563, #c0caf5);
}

.job-log .ansi-black-fg {
  color: var(--ansi-black);
}

.job-log .ansi-red-fg {
  color: var(--ansi-red);
}

.job-log .ansi-green-fg {
  color: var(--ansi-green);
}

.job-log .ansi-yellow-fg {
  color: var(--ansi-yellow);
}

.job-log .ansi-blue-fg {
  color: var(--ansi-blue);
}

.job-log .ansi-magenta-fg {
  color: var(--ansi-magenta);
}

.job-log .ansi-cyan-fg {
  color: var(--ansi-cyan);
}

.job-log .ansi-white-fg {
  color: var(--ansi-white);
}

/* Bright is the same hue; a terminal separates them by intensity, which at
   these sizes reads as noise rather than as information. */
.job-log .ansi-bright-black-fg {
  color: var(--ansi-black);
}

.job-log .ansi-bright-red-fg {
  color: var(--ansi-red);
}

.job-log .ansi-bright-green-fg {
  color: var(--ansi-green);
}

.job-log .ansi-bright-yellow-fg {
  color: var(--ansi-yellow);
}

.job-log .ansi-bright-blue-fg {
  color: var(--ansi-blue);
}

.job-log .ansi-bright-magenta-fg {
  color: var(--ansi-magenta);
}

.job-log .ansi-bright-cyan-fg {
  color: var(--ansi-cyan);
}

.job-log .ansi-bright-white-fg {
  color: var(--ansi-white);
}

.job-log :is(.ansi-black-bg, .ansi-bright-black-bg) {
  background-color: var(--ansi-black);
}

.job-log :is(.ansi-red-bg, .ansi-bright-red-bg) {
  background-color: var(--ansi-red);
}

.job-log :is(.ansi-green-bg, .ansi-bright-green-bg) {
  background-color: var(--ansi-green);
}

.job-log :is(.ansi-yellow-bg, .ansi-bright-yellow-bg) {
  background-color: var(--ansi-yellow);
}

.job-log :is(.ansi-blue-bg, .ansi-bright-blue-bg) {
  background-color: var(--ansi-blue);
}

.job-log :is(.ansi-magenta-bg, .ansi-bright-magenta-bg) {
  background-color: var(--ansi-magenta);
}

.job-log :is(.ansi-cyan-bg, .ansi-bright-cyan-bg) {
  background-color: var(--ansi-cyan);
}

.job-log :is(.ansi-white-bg, .ansi-bright-white-bg) {
  background-color: var(--ansi-white);
}

/* ==================== sign-in ==================== */

.panel {
  max-width: 32rem;
  margin: clamp(2rem, 8vw, 4rem) auto;
  text-align: center;
}

.device-code {
  font-family: var(--pico-font-family-monospace);
  font-size: clamp(1.5rem, 7vw, 2.25rem);
  letter-spacing: .25em;
  /* The letter-spacing hangs off the last glyph; pull the block back so the code
     still looks centred. */
  text-indent: .25em;
  font-weight: 600;
}

/* ==================== doctor (dev only) ==================== */

/* A results list rather than a data table: the status column is sized to its
   widest word so the check names line up, and everything else gives way to the
   detail, which is a tool's output and can be long. */
.doctor td {
  vertical-align: top;
}

.doctor-status {
  width: 1%;
  white-space: nowrap;
  font-weight: 600;
  text-transform: uppercase;
  font-size: .75rem;
  letter-spacing: .05em;
}

.doctor-status span:first-child {
  /* The glyph is decorative — the word beside it carries the meaning — so it is
     only ever a size and a colour, never the sole signal. */
  margin-right: .35em;
  font-size: 1rem;
}

.doctor-ok {
  color: var(--pico-ins-color);
}

.doctor-warn {
  color: var(--pico-color-amber-500, #d97706);
}

.doctor-fail {
  color: var(--pico-del-color);
}

.doctor-detail {
  margin: .25rem 0 0;
  /* Output arrives as one line and may be a full error; wrap it rather than
     letting it push the page sideways. */
  overflow-wrap: anywhere;
}

.doctor-detail code {
  font-size: .8125rem;
}

/* ==================== shared ==================== */

.muted {
  color: var(--pico-muted-color);
}

.error-note {
  color: var(--pico-del-color);
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

svg.icon-theme-toggle {
  --theme-toggle-duration: .4s
}

svg.icon-theme-toggle :first-child path {
  transition-duration: calc(var(--theme-toggle-duration) * .6);
  transition-property: transform, d;
  transition-timing-function: cubic-bezier(0, 0, .5, 1)
}

svg.icon-theme-toggle g circle,
svg.icon-theme-toggle g path {
  transform-origin: center;
  transition: transform calc(var(--theme-toggle-duration) * .65) cubic-bezier(0, 0, 0, 1.25) calc(var(--theme-toggle-duration) * .35)
}

/* The moon state is derived from the document theme rather than a class JS applies
   after the fact: a boosted navigation re-sends the header markup, and toggling a
   class on the freshly swapped icon would replay the sun/moon transition every time.
   An element inserted already in its final state does not transition. */
:root[data-theme="dark"] svg.icon-theme-toggle g circle {
  transform: scale(1.4);
  transition-delay: 0s
}

:root[data-theme="dark"] svg.icon-theme-toggle g path {
  transform: scale(.75);
  transition-delay: 0s
}

:root[data-theme="dark"] svg.icon-theme-toggle :first-child path {
  d: path("M-9 3h25a1 1 0 0017 13v30H0Z");
  transition-delay: calc(var(--theme-toggle-duration) * .4);
  transition-timing-function: cubic-bezier(0, 0, 0, 1.25)
}

@supports not (d:path("")) {
  :root[data-theme="dark"] svg.icon-theme-toggle :first-child path {
    transform: translate3d(-9px, 14px, 0)
  }
}

.d-none {
  display: none;
}

.code-editor {
  font-family: monospace;
}