:root{
  --bg:#1b1b1d; --panel:#202023; --band:#26262a;
  --line:#2e2e33; --line2:#4a4a52;
  --fg:#e6e6e8; --fg2:#9a9aa3;
  --acc:#4cc2ff; --on-acc:#06080c;
  --ok:#22c55e; --warn:#f59e0b; --bad:#ef4444;
  --scrim:rgba(6,9,16,.8);
  --mono:ui-monospace,Menlo,Consolas,monospace;
  --fs1:11px; --fs2:12px; --fs3:14px; --fs4:16px; --fs5:24px;
  --s1:4px; --s2:8px; --s3:12px; --s4:16px; --s5:24px;
  --r:8px; --top:52px;
  /* Dark throughout: scrollbars, form controls and the browser's own chrome
     inside the page are drawn dark too, not left white. */
  color-scheme:dark;
  scrollbar-color:var(--line2) transparent;
}
*{box-sizing:border-box;margin:0}
html,body{height:100%}
/* The page does not zoom. A pinch on the Access screen magnifies the Machine's
   picture, and a browser magnifying this page underneath it moves the picture
   out from under the finger doing the magnifying -- so the two gestures are
   the same gesture with two answers. touch-action also takes double-tap zoom
   out of the way, which the screen needs for a double-tap-and-hold drag; the
   meta tag beside it settles the pinch. */
html{touch-action:manipulation;-webkit-text-size-adjust:100%}
body{background:var(--bg);color:var(--fg);
  font:var(--fs3)/1.5 -apple-system,system-ui,"Segoe UI",Roboto,sans-serif}
a{color:inherit;text-decoration:none}
/* A display rule from this sheet outranks the browser hiding the element, so
   the attribute has to win explicitly. */
[hidden]{display:none!important}
svg{width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.8;
  stroke-linecap:round;stroke-linejoin:round;flex:none}

.page{padding:var(--s5) var(--s5) 60px;width:100%;min-width:0}
h1{font-size:var(--fs5);font-weight:600}
h2{font-size:var(--fs3);font-weight:600;margin:var(--s5) 0 var(--s3)}
.desc{color:var(--fg2);max-width:70ch;margin:2px 0 18px}
.desc a{color:var(--acc)}
.tools{display:flex;gap:var(--s3);align-items:center;flex-wrap:wrap;margin-bottom:var(--s4)}
.tools input{flex:1 1 220px;width:auto;max-width:400px}
/* A filter in the toolbar sits at its own size beside the search and the
   segments. Without this it takes the width:100% every input, select and
   textarea is given for a form field, and one control fills the row. */
.tools select{flex:0 0 auto;width:auto;max-width:14rem}
.right{margin-left:auto}

.btn{display:inline-flex;align-items:center;gap:var(--s2);font:inherit;
  padding:var(--s2) var(--s3);border-radius:var(--r);border:1px solid var(--line);
  background:var(--panel);color:var(--fg);cursor:pointer;white-space:nowrap}
.btn:hover{border-color:var(--line2)}
.btn.p{background:var(--acc);border-color:var(--acc);color:var(--on-acc);font-weight:600}
.btn.p:hover{filter:brightness(1.15)}
.btn.d{color:var(--bad)}
.btn.icon{padding:var(--s2)}
.btn[disabled]{opacity:.4;cursor:default}

/* Sized to its own labels, never to whatever holds it. Without align-self, a
   flex-column parent (a dialog's .mbody, .stack on /join) stretches this to
   the full column width by default -- and since nothing inside pushes back,
   all of the leftover space lands after the last label, which has no border
   between it and that space, so the last option alone reads as huge. */
.seg{display:inline-flex;align-self:flex-start;border:1px solid var(--line);border-radius:var(--r);
  overflow:hidden;background:var(--panel)}
.seg button{padding:var(--s2) var(--s3);cursor:pointer;color:var(--fg2);font:inherit;
  border:0;border-right:1px solid var(--line);background:transparent}
.seg button:last-child{border-right:0}
.seg button.on{color:var(--fg);background:var(--band)}

/* An OS picker, not a toolbar filter: it stands in for the icon-button row
   next to it (Downloads, Add Machine, /join), so it takes the same full
   width and splits it evenly instead of hugging its own labels. */
.seg.wide{display:flex;align-self:stretch;width:100%}
.seg.wide button{flex:1;text-align:center}

/* What a list is narrowed to, said out loud beside the button that set it.
   Each one removes itself, because the way out of a filter should be where
   the filter is, not back inside the dialog that made it. */
.chips{display:inline-flex;flex-wrap:wrap;gap:var(--s2);align-items:center}
.chip{display:inline-flex;align-items:center;gap:var(--s1);cursor:pointer;
  font:inherit;color:var(--fg2);background:var(--band);
  border:1px solid var(--line);border-radius:999px;padding:var(--s1) var(--s3)}
.chip b{color:var(--fg);font-weight:600}
.chip:hover{color:var(--fg);border-color:var(--line2)}
.chip svg{width:12px;height:12px;opacity:.7}

input,select,textarea{font:inherit;color:var(--fg);background:var(--panel);
  border:1px solid var(--line);border-radius:var(--r);padding:var(--s2) var(--s3);
  width:100%;outline:none}
input:focus,select:focus,textarea:focus{outline:2px solid var(--line2);outline-offset:1px}
/* A textarea's rows attribute otherwise sets its own height, so one field
   with rows="12" sat taller than every other field around it before anybody
   had typed a line. The handle in the corner is what rows is for -- drag it
   open. Starts the same height as an input, whatever rows says. */
textarea{height:calc(var(--fs3)*1.5 + var(--s2)*2 + 2px);
  min-height:calc(var(--fs3)*1.5 + var(--s2)*2 + 2px);resize:vertical}

.field{display:flex;flex-direction:column;gap:var(--s1)}
.field>label{font-size:var(--fs1);letter-spacing:.5px;text-transform:uppercase;
  color:var(--fg2);font-weight:600}
.hint{color:var(--fg2);font-size:var(--fs2)}

.dot{width:8px;height:8px;border-radius:50%;background:var(--fg2);
  display:inline-block;flex:none}
.dot.ok{background:var(--ok)} .dot.warn{background:var(--warn)} .dot.bad{background:var(--bad)}

.b{display:inline-flex;align-items:center;gap:var(--s1);font-size:var(--fs2);
  padding:2px var(--s2);border-radius:var(--r);background:var(--band);
  color:var(--fg2);border:1px solid var(--line);white-space:nowrap}
.b.ok{color:var(--ok)} .b.warn{color:var(--warn)}
/* The accented badge the owner row and the Connection you are reading this on
   both ask for. It had no rule, so both drew as an ordinary grey one. */
.b.acc{color:var(--acc);border-color:var(--acc)}
.hint.warn{color:var(--warn)}
.hint.bad{color:var(--bad)} .b.bad{color:var(--bad)}

.tw{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);overflow:auto}
/* A list on a page scrolls inside itself, so its sideways scrollbar is on
   screen wherever the page is scrolled to, not only at the page's bottom. The
   header row stays at the top of it. */
#pageBody .tw{max-height:calc(100dvh - 160px)}
#pageBody .tw th{position:sticky;top:0;z-index:1}
table{width:100%;border-collapse:collapse}
th{background:var(--band);text-align:left;font-size:var(--fs1);letter-spacing:.5px;
  text-transform:uppercase;color:var(--fg2);font-weight:600;
  padding:var(--s3) var(--s4);white-space:nowrap}
td{padding:var(--s3) var(--s4);border-bottom:1px solid var(--line);
  vertical-align:middle;white-space:nowrap}
tbody tr:last-child td{border-bottom:0}
tr.link{cursor:pointer} tr.link:hover td{background:var(--band)}
/* A row's way in stays on screen however far the table is scrolled sideways:
   its cell is pinned to the right edge, with the rows passing under it. */
td.pin,.tw:has(td.pin) th:last-child{position:sticky;right:0;background:var(--panel);
  box-shadow:inset 1px 0 0 var(--line)}
.tw:has(td.pin) th:last-child{z-index:2;background:var(--band)}
td.wide{white-space:normal;min-width:220px}
td.end{text-align:right}
/* A cell that ends a row puts its buttons at the end. text-align does not
   move flex items, so a row of them sat at the left of a cell that had
   already been told to sit at the right. */
td.end .row{justify-content:flex-end}
th.sort{cursor:pointer;user-select:none}
th.sort:hover,th.sort.on{color:var(--fg)}
th.sort::after{content:"";display:inline-block;width:0;height:0;
  margin-left:var(--s1);vertical-align:middle;
  border-left:4px solid transparent;border-right:4px solid transparent;
  border-top:4px solid currentColor;opacity:0}
th.sort:hover::after{opacity:.4}
th.sort.on::after{opacity:1}
th.sort.on.asc::after{border-top:0;border-bottom:4px solid currentColor}

.sub{display:block;color:var(--fg2);font-size:var(--fs2)}
.stack{display:flex;flex-direction:column;gap:var(--s1)}
.row{display:flex;gap:var(--s2);align-items:center;flex-wrap:wrap}
/* A cell does not wrap its text, so a row inside one does not wrap either: a
   status light above its own word is not a narrower column, it is a taller
   one. The column that is allowed to be long keeps the default. */
td .row{flex-wrap:nowrap}
td.wide .row{flex-wrap:wrap}
.m{color:var(--fg2)}
.mono{font-family:var(--mono);font-size:var(--fs2)}

/* The space between panels belongs to the page, not to the panels. It used to
   live on .card alone, so a card after a card was spaced and a card after a
   table or a grid was not -- which is why Billing and Policy had panels with
   their borders touching. */
.page>*+*{margin-top:var(--s3)}
/* The body of a screen is wrapped in one element so it can be replaced on its
   own when the data arrives, without the heading and toolbar above it being
   redrawn. Its children space themselves as they did when they were the
   page's own, which is what that wrapper must not change. */
#pageBody>*+*{margin-top:var(--s3)}
#pageBody>.grid{margin-top:var(--s3)}
/* A screen with no toolbar leaves the element empty rather than leaving it
   out, so that a screen never gains or loses a row between renders. */
.tools:empty{display:none}
.card{background:var(--panel);border:1px solid var(--line);
  border-radius:var(--r);padding:var(--s4)}
.card h2{margin:0}
.card>*+*{margin-top:var(--s3)}
.grid{display:grid;gap:var(--s3);grid-template-columns:repeat(auto-fit,minmax(280px,1fr))}

.code{position:relative;display:block;font-family:var(--mono);font-size:var(--fs2);
  background:var(--bg);border:1px solid var(--line);border-radius:var(--r);
  padding:var(--s3);padding-right:calc(var(--s4) + var(--s5));
  white-space:pre-wrap;overflow-wrap:anywhere}
/* A code in a list is a value in a column, not a block to copy from. It
   keeps the typeface and loses the rest: no room held back for a copy button
   that is not there, and never broken across lines -- a join code split down
   the middle of a narrow screen made every row as tall as its digits. */
td .code,.rows>.r .code{display:inline-flex;align-items:center;gap:var(--s2);
  padding:var(--s1) var(--s2);white-space:nowrap;overflow-wrap:normal}
/* Beside the code rather than over it: a block holds the copy button in its
   top corner and keeps padding clear for it, which a cell this size has none
   to spare. The same goes for a code given as one value among a dialog's
   facts, which sits on the right of its row like the others. */
td .code .copy,.rows>.r .code .copy{position:static}

.code.big{font-size:var(--fs5);letter-spacing:.12em;text-align:center;
  padding:var(--s4);padding-right:calc(var(--s4) + var(--s5))}
/* Always there. It used to appear on hover, which meant a block of code did
   not look copyable until you were already pointing at it, and on a touch
   screen there is no hover at all -- the exception underneath said so. */
.code .copy{position:absolute;top:var(--s1);right:var(--s1)}
.code .copy.done{color:var(--ok)}

.empty{border:1px dashed var(--line2);border-radius:var(--r);
  padding:40px var(--s5);text-align:center;color:var(--fg2)}
.empty b{display:block;color:var(--fg);margin-bottom:var(--s1)}

/* The count on the left, the rows-per-page beside it, and the arrows pushed
   to the far right -- which the markup had always meant and the stylesheet
   never did: it styled .right while the markup said .r, so the whole row sat
   in a heap on the left. */
.pager{display:flex;align-items:center;gap:var(--s3);margin-top:var(--s3);
  color:var(--fg2);font-size:var(--fs2)}
.pager .right{display:flex;gap:var(--s2);align-items:center}
.pager select{width:auto;padding:var(--s2) var(--s3);font-size:var(--fs2)}

.check{display:grid;grid-template-columns:auto 1fr;gap:var(--s1) var(--s2);
  align-items:center;cursor:pointer;font-weight:600}
.check input{width:16px;height:16px;padding:0;accent-color:var(--acc)}
.check .hint{grid-column:2;font-weight:400}

/* ---- the setting panel ----

   Every dialog's tab is one of these: a bordered list of rows, one row per
   setting. Two shapes, and nothing else:

     .pick    an answer beside its label -- typed, chosen, ticked, or read
              only. The control keeps its own width at the end of the row, so
              a column of them lines up whatever the labels say.
     .block   an answer under its label -- a blacklist, a script body, an
              instruction, a note. Only this shape takes the full width,
              because a line of script has nowhere else to go.

   One label treatment across both, and across the read-only rows between
   them: normal weight, one colour. A panel that changed weight halfway down
   read as two panels. */
.rows{border:1px solid var(--line);border-radius:var(--r);
  background:var(--band);overflow:hidden}
.rows>*{padding:var(--s3)}
.rows>*+*{border-top:1px solid var(--line)}
/* A row is not a button: it does not light up under the pointer, and clicking
   a label does not leave its text selected behind the control it opened. */
.pick>span,.rows>.r>.m,.rows>.block>span,.check>span{font-weight:400;
  color:var(--fg);user-select:none}

.pick{display:grid;grid-template-columns:1fr auto;gap:var(--s1) var(--s3);
  align-items:center;cursor:pointer}
/* Placed by position rather than by tag. Naming select and input left a
   segmented control and a compound row -- an operator beside its value --
   dropping onto a line of their own. */
.pick>*:first-child{grid-column:1;grid-row:1}
.pick>*:not(:first-child){grid-column:2;grid-row:1}
.pick select,.pick input{width:auto;min-width:11rem;max-width:100%}
/* Every dropdown in a column is the same width, so Screen, Files and Reach line
   up instead of each sizing to its longest option. */
.pick select{width:14rem}
.pick input.narrow{min-width:0;width:10rem}
.pick .seg{align-self:center}
.pick .row{justify-content:flex-end;gap:var(--s2)}
.pick .row select{min-width:9rem}
.pick .row input{width:7rem;min-width:0}

.rows>.block{display:flex;flex-direction:column;gap:var(--s2);align-items:stretch}
/* Two lines to start. One line reads as a field for a single answer, and
   these hold a list of them -- a rule per line, a line of script. The handle
   in the corner opens it further. */
.rows>.block textarea{height:calc(var(--fs3)*1.5*2 + var(--s2)*2 + 2px);
  min-height:calc(var(--fs3)*1.5*2 + var(--s2)*2 + 2px)}
.rows>.r{display:flex;gap:var(--s2);align-items:center}

.app{display:flex;flex-direction:column;min-height:100vh}
.topbar{position:sticky;top:0;z-index:30;height:var(--top);flex:none;
  display:flex;align-items:center;gap:var(--s3);padding:0 var(--s4);
  border-bottom:1px solid var(--line);background:var(--bg)}
.brand{display:flex;align-items:center;gap:var(--s2);font-size:var(--fs4);
  font-weight:600;margin-right:auto;color:var(--acc)}
.brand span{color:var(--fg)}
.brand svg{width:24px;height:24px}
.burger .close{display:none}
.shownav .burger .close{display:block}
.shownav .burger .open{display:none}
.reload{margin-left:auto}
.reload+.av{margin-left:var(--s2)}
.av{margin-left:auto;width:32px;height:32px;border-radius:50%;
  background:var(--acc);color:var(--on-acc);display:flex;align-items:center;
  justify-content:center;font-size:var(--fs2);font-weight:600;flex:none;cursor:pointer}

.shell{display:flex;flex:1 1 auto;min-height:0}
aside{width:224px;flex:none;background:var(--bg);border-right:1px solid var(--line);
  display:flex;flex-direction:column;position:sticky;top:var(--top);
  height:calc(100vh - var(--top))}
/* The list of places is taller than the bar on a short window, so it
   scrolls on its own and the space bar below it stays put. No visible
   track -- same as the tab strip. */
nav{padding:var(--s2);display:flex;flex-direction:column;gap:2px;
  flex:1 1 auto;min-height:0;overflow-y:auto;overflow-x:hidden;
  scrollbar-width:none;
  /* A drag that reaches the end of this list stops there. Without it the page
     behind the drawer took over the drag, and on a phone that is worse than
     nothing: scrolling the page is what makes the browser's own URL bar slide
     in and out, which resizes the window the drawer is measured against. */
  overscroll-behavior:contain}
nav::-webkit-scrollbar{display:none}
nav a{display:flex;align-items:center;gap:var(--s2);flex:none;
  padding:var(--s2) var(--s3);border-radius:var(--r);color:var(--fg2)}
nav a:hover{background:var(--panel);color:var(--fg)}
nav a.on{background:var(--band);color:var(--fg)}
nav a .n{margin-left:auto;background:var(--acc);color:var(--on-acc);
  border-radius:var(--r);padding:0 var(--s2);font-size:var(--fs1);font-weight:600}
nav hr{margin:var(--s2) var(--s1);border:0;border-top:1px solid var(--line)}
.spacebar{margin-top:auto;padding:var(--s3);border-top:1px solid var(--line);
  display:flex;flex-direction:column;gap:var(--s3)}
/* The way into the one record this app has only one of. Full width, because
   it belongs to the bar rather than sitting beside something. */
.spacebtn{justify-content:flex-start;width:100%}
.main{flex:1 1 auto;min-width:0}
.drawer{display:none}

/* A list page fills the window, on a phone as much as anywhere, and only its
   list scrolls: the heading, the tools and the pager stay where they are. */
.app.fixed{height:100dvh;min-height:0;overflow:hidden}
.app.fixed .main{display:flex;flex-direction:column;min-height:0;overflow:hidden}
.app.fixed .page{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;
  padding-bottom:calc(var(--s4) + env(safe-area-inset-bottom,0px))}
.app.fixed .page>*{flex:none}
.app.fixed #pageBody{flex:1 1 auto;min-height:0;display:flex;flex-direction:column}
.app.fixed #pageBody>*{flex:none}
.app.fixed #pageBody>.tw{flex:0 1 auto;min-height:0;max-height:none}

@media(min-width:768px){
  aside{overflow:hidden;transition:width .2s}
  :root:not(.shownav) aside{width:0;border-right:0}
  nav,.spacebar{width:224px}
}
@media(max-width:767px){
  aside{position:fixed;top:var(--top);left:0;z-index:40;width:min(284px,82vw);
    height:calc(100dvh - var(--top));transform:translateX(-100%);transition:transform .2s;
    overflow:hidden;overscroll-behavior:contain}
  /* The phone's home bar sits over the bottom of the drawer, so the last
     thing in it needs room underneath to be reached. */
  .spacebar{padding-bottom:calc(var(--s3) + env(safe-area-inset-bottom,0px))}
  .shownav aside{transform:none}
  /* The page behind the drawer is held still while the drawer is open. It is
     what keeps a drag on the list from scrolling the page instead, and it
     keeps the URL bar where it is -- a URL bar that slides away mid-drag
     changes the height the drawer was drawn to and cuts the end of the list
     off the bottom of the window. */
  :root.shownav,:root.shownav body{overflow:hidden}
  .shownav .drawer{display:block;position:fixed;inset:var(--top) 0 0 0;
    background:var(--scrim);z-index:39;touch-action:none}
  .page{padding:var(--s4) var(--s4) 40px}
}

dialog{width:min(720px,100%);margin:min(10vh,64px) auto auto;padding:0;
  /* the offset above it, plus one spacing step below, or the box hangs off the bottom */
  max-height:calc(100dvh - min(10vh,64px) - var(--s5));
  overflow:hidden;background:var(--panel);color:var(--fg);
  border:1px solid var(--line);border-radius:var(--r);
  box-shadow:0 20px 60px rgba(0,0,0,.55)}
dialog[open]{display:flex;flex-direction:column}
dialog::backdrop{background:var(--scrim)}
/* Asking one question is not the same shape as editing a record: narrower,
   and sitting nearer the middle of the window than the top of it. */
.ask{width:min(440px,100%);margin:min(22vh,180px) auto auto}
.ask .mbody{gap:var(--s2)}
/* A third shape, between the two: more than one question, but nothing in it
   is a record with fields to browse -- Add Machine's code and its few
   actions, not a table of them. 720px left it mostly empty width. */
.medium{width:min(460px,100%);margin:min(14vh,110px) auto auto}
/* The row of actions is the last thing in here and the thing being reached
   for, so it gets more room under it than a field would need. */
.medium .mbody{padding-bottom:var(--s5)}
dialog>form{flex:1 1 auto;min-height:0;display:flex;flex-direction:column}

.mhead{flex:none;display:flex;align-items:center;gap:var(--s3);
  padding:var(--s3) var(--s4);border-bottom:1px solid var(--line)}
.mhead h2{font-size:var(--fs4);font-weight:600;display:flex;
  align-items:center;gap:var(--s2);min-width:0}
.mhead .btn{margin-left:auto}
.mbody{flex:1 1 auto;min-height:0;overflow:auto;padding:var(--s4);
  display:flex;flex-direction:column;gap:var(--s3)}
.mfoot{flex:none;display:flex;align-items:center;gap:var(--s2);
  padding:var(--s3) var(--s4);border-top:1px solid var(--line)}
.mfoot .right{display:flex;gap:var(--s2)}
/* A table in a dialog has 720px, not a window. Tighter cells fit more of it
   before the row has to scroll sideways. */
.mbody th,.mbody td{padding:var(--s2) var(--s3)}

/* Six tabs at a narrow width is wider than the dialog holding them. They
   scroll sideways rather than wrapping onto a second line, which would move
   the body down and leave the row that names where you are looking split in
   two. The underline of the tab in front stays on the bottom edge either way,
   so the strip still reads as one rule under the whole dialog. */
.tabs{flex:none;display:flex;gap:2px;padding:0 var(--s4);
  border-bottom:1px solid var(--line);
  overflow-x:auto;overflow-y:hidden;scrollbar-width:none}
.tabs::-webkit-scrollbar{display:none}
.tab{flex:0 0 auto;white-space:nowrap;
  padding:var(--s2) var(--s3);border:0;border-bottom:2px solid transparent;
  background:none;color:var(--fg2);font:inherit;font-weight:600;cursor:pointer}
.tab:hover{color:var(--fg)}
.tab.on{color:var(--acc);border-bottom-color:var(--acc)}

.btn.p.d{background:var(--bad);border-color:var(--bad);color:var(--on-acc)}

/* A phone has no room for a box on top of a page, so every dialog is the
   window. .ask is named as well as dialog because it sets its own width and
   its own offset for the desktop, and a selector that does not mention it
   loses to those. */
@media(max-width:767px){
  dialog,.ask,.medium{width:100%;max-width:none;height:100dvh;max-height:none;
    margin:0;border:0;border-radius:0}
  .mhead,.mbody,.mfoot,.tabs{padding-left:var(--s3);padding-right:var(--s3)}
  /* The footer is on the bottom edge of the window now, and on a phone the
     bottom edge is where the home bar is. */
  .mfoot{padding-bottom:calc(var(--s3) + env(safe-area-inset-bottom,0px))}
}

@keyframes sp{to{transform:rotate(360deg)}}
@keyframes sh{to{background-position:-200% 0}}
@keyframes sl{to{transform:translateX(400%)}}

.spin{width:1em;height:1em;border:2px solid currentColor;
  border-right-color:transparent;border-radius:50%;
  display:inline-block;flex:none;animation:sp .6s linear infinite}
/* A busy button keeps the width its label had, so the row around it does not
   jump. The spinner that replaces the label has to be centred in that width --
   left by default, which read as the spinner belonging to whatever was
   beside it. */
.btn[aria-busy]{pointer-events:none;opacity:.8;justify-content:center}

.skel{display:block;height:1em;border-radius:var(--r);
  background:linear-gradient(90deg,var(--band) 25%,var(--line) 37%,var(--band) 63%);
  background-size:200% 100%;animation:sh 1.4s ease-in-out infinite}
.skel.n{width:38%}
.skel.inl{display:inline-block;width:2ch;vertical-align:-.1em}
/* A read-only dialog's body: the fields stay where they are, disabled. */
fieldset.ro{display:contents;border:0;margin:0;padding:0;min-width:0}

.bar{position:fixed;top:0;left:0;right:0;height:2px;z-index:60;
  overflow:hidden;opacity:0;transition:opacity .25s}
.bar.on{opacity:1}
.bar i{display:block;height:100%;width:25%;background:var(--acc);
  animation:sl 1.1s ease-in-out infinite}

@media(prefers-reduced-motion:reduce){
  .skel{animation:none;background:var(--band)}
  .bar i{animation:none;width:100%}
}

.pub{max-width:420px;margin:56px auto;padding:0 var(--s4)}
.pub .brand{justify-content:center;margin-bottom:var(--s3)}
/* A way in beside the card, such as Have a join code?: room above and below,
   so it reads as its own line and not as the card's last or the footer's first. */
.pub .beside{margin:var(--s5) 0 var(--s4)}
/* The pages to be read rather than used -- About, Privacy, Terms: wider, and
   set as prose. */
.pub.doc{max-width:760px}
.doc h1{font-size:var(--fs5);font-weight:600;margin:0}
.doc h2{font-size:var(--fs4);font-weight:600;margin:var(--s5) 0 var(--s2)}
.doc p,.doc li{line-height:1.6}
.doc ul{margin:var(--s2) 0;padding-left:var(--s5)}
.doc table{width:100%}
/* The console's Guide: one card a task, set as prose. */
.guide{display:flex;flex-direction:column;gap:var(--s3);max-width:760px}
.guide h2{margin:0 0 var(--s2)}
.guide p,.guide li{line-height:1.6}
.guide ul{margin:var(--s2) 0;padding-left:var(--s5)}
/* Where the pages to be read are linked from: under a sign-in, a join, the
   console's menu. */
.legal{display:flex;gap:var(--s3);justify-content:center;flex-wrap:wrap;color:var(--fg2);font-size:var(--fs2)}
.legal a{color:inherit}


a.link{color:var(--acc)}
.warn{color:var(--warn)}
.bad{color:var(--bad)}
.half{flex:1;min-width:0}
.card.warn{border-color:var(--warn)}
.card.on{border-color:var(--acc)}
/* A list to choose one from: a radio, what it is, and what it costs at the end. */
.choice{display:grid;grid-template-columns:auto 1fr auto;gap:var(--s3);align-items:center;cursor:pointer}
.choice input{accent-color:var(--acc);width:16px;height:16px;margin:0}
.choice .sub{color:var(--fg2);font-size:var(--fs2)}
.choice.on{background:rgba(76,194,255,.06)}
.av.sm{width:28px;height:28px;margin:0;font-size:var(--fs1);
  background:var(--band);color:var(--fg)}
.btn.wide{justify-content:center;width:100%}
.centre{text-align:center}
.between{justify-content:space-between}
/* One step of a form showing at a time: sign-in's email and code steps, each
   its own div, only one ever without this. */
.wizstep{display:none}
.wizstep.on{display:flex;flex-direction:column;gap:var(--s3)}
.code.out{min-height:120px}
input.narrow,select.narrow{max-width:10rem}
.stack.loose{gap:var(--s3)}
/* A line for what happened that has nothing in it yet takes no room: an empty
   one under Use a passkey pushed the next way in a gap further down. */
.stack>.hint:empty{display:none}
.page>.grid{margin-top:var(--s3)}

/* The keys a browser keeps for itself, laid out as a keypad: even columns so
   the one people reach for -- Ctrl + Alt + Del -- sits in the same place every
   time it is opened. */
.keys{display:grid;grid-template-columns:repeat(auto-fit,minmax(9.5rem,1fr));gap:var(--s2)}
.keys>.btn{justify-content:center;width:100%}

/* The browser's own zoom, which nobody asked for: a double tap magnifies the
   page, and a phone zooms into any field whose text is under 16px -- both
   leave the app scaled and off-centre with no way back but pinching. Pinch
   zoom itself is left alone; it is the only one of the three anybody wants. */
html{-webkit-text-size-adjust:100%;text-size-adjust:100%}
body{touch-action:manipulation}
/* Only on a touch screen, which is the only place that zoom happens: a narrow
   window on a desktop keeps its fields the size of everything around them. */
@media(max-width:767px) and (pointer:coarse){
  input,select,textarea{font-size:var(--fs4)}
  .terminput input{font-size:var(--fs4)}
  textarea{height:calc(var(--fs4)*1.5 + var(--s2)*2 + 2px);
    min-height:calc(var(--fs4)*1.5 + var(--s2)*2 + 2px)}
  .rows>.block textarea{height:calc(var(--fs4)*1.5*2 + var(--s2)*2 + 2px);
    min-height:calc(var(--fs4)*1.5*2 + var(--s2)*2 + 2px)}
}

/* An instruction somebody has to read before they work: the words of whoever
   runs the Space, so they are set as prose rather than as another hint line
   the eye skips. */
.ackline{white-space:pre-wrap;overflow-wrap:anywhere;
  border-left:2px solid var(--acc);padding-left:var(--s3)}

/* A step in an Add Machine dialog that is one of two ways in, not a chain of
   fields to fill: the code above needs nothing else, and everything below
   the divider is the alternative to it, not a continuation of it. */
.ordivider{display:flex;align-items:center;gap:var(--s3);color:var(--fg2);
  font-size:var(--fs1);letter-spacing:.08em;text-transform:uppercase;font-weight:600}
.ordivider::before,.ordivider::after{content:"";flex:1;height:1px;background:var(--line)}
/* With no word in it, the two halves meet as one line. */
.ordivider:empty{gap:0}

.iconrow{display:flex;gap:var(--s2)}
.iconbtn{display:flex;flex-direction:column;align-items:center;gap:4px;padding:var(--s3);
  border:1px solid var(--line);border-radius:var(--r);background:var(--band);cursor:pointer;
  flex:1;color:var(--fg);font:inherit;text-align:center}
.iconbtn:hover{border-color:var(--line2)}
.iconbtn svg{width:20px;height:20px;color:var(--acc)}
.iconbtn span{font-size:var(--fs1)}
/* A command with a worded Copy beside it rather than over it: the word needs
   more room than the corner the box keeps clear for the icon. */
.code.cmd{display:flex;align-items:flex-start;gap:var(--s2);padding-right:var(--s3)}
.code.cmd>span{flex:1;min-width:0}
.code.cmd .copy{position:static;flex:none}

/* A settings page read as one form rather than several unrelated cards: one
   card, its sections divided by a rule instead of by another card's border,
   one Save at the bottom of all of them. The one exception is danger --
   destructive actions keep their own card, on purpose, so they are never
   mistaken for part of the form above them. */
.formcard{max-width:640px}
.section+.section{margin-top:var(--s5);padding-top:var(--s4);border-top:1px solid var(--line)}
.dangerzone{border-color:var(--bad)}

/* A numbered sequence, for a page that is followed top to bottom the first
   time rather than scanned for the one thing already known. */
.steps{display:flex;flex-direction:column}
.step{display:grid;grid-template-columns:32px 1fr;gap:var(--s3);padding:var(--s4) 0}
.step+.step{border-top:1px solid var(--line)}
.step>div:last-child{display:flex;flex-direction:column;gap:var(--s2)}
.step h3{font-size:var(--fs3);font-weight:600;margin:0}
.num{width:28px;height:28px;border-radius:50%;background:var(--band);border:1px solid var(--line);
  display:flex;align-items:center;justify-content:center;font-weight:700;color:var(--acc);font-size:var(--fs2)}

/* A download target: what it is, one line on what it's for, and the button --
   in that order, so "vsmachine" and "ViewScale" read as two different things
   rather than the same word twice. */
.target{display:grid;grid-template-columns:40px 1fr auto;gap:var(--s3);align-items:center}
.target .mark{width:40px;height:40px;border-radius:var(--r);background:var(--band);
  display:flex;align-items:center;justify-content:center;color:var(--acc);flex:none}
.target b{display:block}
.target span.d{color:var(--fg2);font-size:var(--fs2)}
details{margin-top:var(--s3)}
details summary{cursor:pointer;color:var(--acc);font-size:var(--fs2);list-style:none}
details summary::-webkit-details-marker{display:none}
details summary::before{content:"▸ ";display:inline-block}
details[open] summary::before{content:"▾ "}
details .code{margin-top:var(--s2)}


/* A badge that carries a mark as well as a word. Sized to the text beside it
   rather than to whatever the icon was drawn at. */
.b svg{width:14px;height:14px;flex:none}

/* A progress bar: how far a file transfer has come. */
.tilebar{background:var(--band);border-radius:999px;height:8px;overflow:hidden}
.tilebar span{display:block;height:100%;background:var(--acc);border-radius:999px}

/* Which platforms a Script covers, as marks in a column. Muted, because they
   answer "does it cover my boxes" at a glance and should not compete with the
   name beside them. */
.runson{display:inline-flex;align-items:center;gap:6px;color:var(--fg2)}
.runson svg{width:15px;height:15px}
