/* The customer portal's stylesheet.
 *
 * THIS WAS AN INLINE <style> BLOCK IN base.html UNTIL 2026-09-05, AND IT WAS BLOCKED IN PRODUCTION.
 * The Caddyfile serves `style-src 'self'` with no 'unsafe-inline', which is correct and stays. A page
 * whose entire stylesheet is inline is therefore a page with no stylesheet at all, and that is exactly
 * what a customer arriving from their licence e-mail was getting: Times New Roman on a white ground,
 * 8px body margin, no layout.
 *
 * MEASURED before the move, in a real browser through the real proxy:
 *   document.styleSheets.length ... 0     (one <style> tag present, zero applied)
 *   body font-family ............. "Times New Roman"
 *   body background .............. rgba(0, 0, 0, 0)
 *   .wrap max-width .............. none   (44rem intended)
 * plus two console errors naming the directive that blocked it.
 *
 * It went unnoticed because local development runs DJANGO_DEBUG=1 without the proxy, so the header that
 * breaks the page is the one header no developer ever sees. ops/edge_test.py now asks the question from
 * OUTSIDE the edge, which is the only place it is askable.
 *
 * SO: no rule may move back into a template, and no element may carry a style= attribute. A CSP hash
 * cannot rescue a style attribute at all without 'unsafe-hashes', and the utilities at the bottom of
 * this file exist precisely so nobody needs one.
 *
 * AND THIS IS THE ONLY STYLESHEET THE PORTAL MAY LINK. ops/edge_test.py asserts the served page carries
 * exactly one <link rel="stylesheet">, matched with `rel` BEFORE `href`, in double quotes. A second
 * sheet for print, for fonts, or for one page fails the gate. Everything lives here.
 *
 * ------------------------------------------------------------------------------------------------
 * THE ACCOUNT IS A TOOL, NOT A BRAND SURFACE. 2026-09-06, and this reverses a decision taken earlier
 * the same day, so the reversal is written here rather than left for somebody to find as a
 * contradiction.
 *
 * The first version of this rewrite copied the marketing site exactly: the deck's near black ground,
 * the phosphor labels, 2px corners, uppercase letterspaced everything. The reasoning was "one product,
 * one look", and the controller looked at it and asked for Apple and Stripe instead. They are right,
 * and the argument is about what each surface is FOR rather than about taste.
 *
 * The site is atmosphere. It is a night airfield, it is meant to be felt, and somebody reads it once
 * before deciding. THIS page is where a person manages a licence, reads what they were charged, moves
 * an installation to a new machine and closes an account. It is scanned, not felt, and it is opened
 * when something needs doing or has gone wrong. A dark cinematic ground makes every one of those jobs
 * slower. Stripe's dashboard, Linear's settings and Apple's account pages are all light and quiet for
 * the same reason, sitting behind marketing that is not.
 *
 * WHAT CARRIES THE BRAND ACROSS instead of the palette: the same three typefaces, the same mark, and
 * the orange, which appears exactly once per page and only in the mark. A near black primary button is
 * deliberate rather than a compromise; on a white ground an orange one shouts, and restraint is most
 * of what this look is.
 *
 * LIGHT ONLY, and no prefers-color-scheme block. Same reasoning as the site's single theme, pointed
 * the other way: a page that changes its ground depending on the reader's operating system is two
 * designs, and only one of them was drawn.
 */

/* ------------------------------------------------------------------------------------------------
 * TYPEFACES. Latin subset, copied from server/site/assets/fonts/, because `default-src 'self'` means
 * this host cannot fetch them from the site's origin and `font-src` falls back to the same directive,
 * which does not include `data:`. Five faces, 128 KB, in fonts/ beside this file.
 *
 * WhiteNoise's ManifestStaticFilesStorage rewrites the url() below to hashed names at collectstatic,
 * which is also why a missing face breaks the image build rather than the page.
 */
@font-face{font-family:'Barlow';font-style:normal;font-weight:300;font-display:optional;src:url("fonts/barlow-300.de218f435ec7.woff2") format('woff2')}
@font-face{font-family:'Barlow';font-style:normal;font-weight:400;font-display:optional;src:url("fonts/barlow-400.81419bf5abb5.woff2") format('woff2')}
@font-face{font-family:'Barlow';font-style:normal;font-weight:600;font-display:optional;src:url("fonts/barlow-600.a1e2a2d7d4c0.woff2") format('woff2')}
@font-face{font-family:'Barlow Condensed';font-style:normal;font-weight:500;font-display:optional;src:url("fonts/barlow-condensed-500.24f826a7cf9c.woff2") format('woff2')}
@font-face{font-family:'Barlow Condensed';font-style:normal;font-weight:600;font-display:optional;src:url("fonts/barlow-condensed-600.ba4513495282.woff2") format('woff2')}
@font-face{font-family:'JetBrains Mono';font-style:normal;font-weight:500;font-display:optional;src:url("fonts/jetbrains-mono-500.b636a65da4f0.woff2") format('woff2')}
/* font-display IS `optional` AND NOT `swap`, and the pages preload every face above. The full
 * reasoning is at the top of site/assets/site.css, which made the same change on the same day for the
 * same report: `swap` has a zero length block period, so a hard refresh paints in the machine's own
 * sans and then snaps to Barlow, which is what the controller saw. `optional` makes that swap
 * impossible rather than unlikely; the preloads are what make sure the real font is what gets
 * committed. Neither half works without the other. */

/* ------------------------------------------------------------------------------------------------
 * TOKENS
 *
 * Two greys and one ink, which is most of this look. The page is a very light grey and the cards are
 * pure white, so a card reads as raised without needing a heavy shadow to say so; that grouping is
 * the pattern Apple's settings and Stripe's dashboard both use, and it does more work than any border.
 *
 * SEMANTIC COLOUR IS SEPARATE FROM THE BRAND. Green, amber and red say what state a licence is in.
 * Orange says Airside. They are never swapped, and orange never means "good".
 */
:root{
  --page:#F6F6F7;        /* the ground the cards sit on */
  --card:#FFFFFF;
  --sunk:#F2F2F4;        /* an inset field or a code chip */

  --ink:#18181B;         /* text */
  --ink-2:#52525B;       /* secondary text, still comfortably readable */
  --ink-3:#8A8A94;       /* labels and captions */

  --line:#E4E4E8;        /* hairline */
  --line-2:#EDEDF0;      /* a divider INSIDE a card, lighter than its border */

  --brand:#FF6B1A;       /* Theme.Orange. The mark, and nothing else */
  --ok:#137A45;
  --warn:#8A5A00;
  --bad:#B4231C;
  --ok-bg:#E9F6EF;
  --warn-bg:#FDF4E3;
  --bad-bg:#FCECEA;

  --ui:"Barlow",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  --sign:"Barlow Condensed","Barlow",-apple-system,"Segoe UI",sans-serif;
  --mono:"JetBrains Mono","Cascadia Mono",ui-monospace,Consolas,monospace;

  --r:12px;              /* a card */
  --r-s:8px;             /* a control */

  /* Two shadows, both nearly invisible on their own. A card that announces itself with a shadow is a
   * card from a template; these are here so an edge does not disappear on a low contrast display. */
  --shadow:0 1px 2px rgba(24,24,27,.04), 0 1px 3px rgba(24,24,27,.06);
  --shadow-lift:0 1px 2px rgba(24,24,27,.05), 0 4px 12px rgba(24,24,27,.07);

  --s1:4px; --s2:8px; --s3:12px; --s4:16px; --s5:24px; --s6:36px; --s7:56px; --s8:80px;
  --ease:cubic-bezier(.16,1,.3,1);
  --quick:150ms;
}

*{box-sizing:border-box}

/* Every digit that sits in a column. A number that is not tabular changes width and shoves whatever is
 * beside it, which here means a machine fingerprint next to a date. */
.key,.mono,code,.row-val,.fact dd{font-variant-numeric:tabular-nums}

[hidden]{display:none!important}

body{
  margin:0;
  background:var(--page);
  color:var(--ink);
  font:400 16px/1.6 var(--ui);
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
}

/* ------------------------------------------------------------------------------------------------
 * SHELL. Narrow, centred, and generous at the top. The width is the measure that makes a row of
 * label, value and control legible without the control drifting a screen away from what it acts on.
 */
.wrap{max-width:44rem;margin:0 auto;padding:var(--s7) var(--s5) var(--s8)}

.top{display:flex;align-items:center;gap:var(--s3);margin-bottom:var(--s7);flex-wrap:wrap}
.mark{display:block;width:20px;height:20px}
.wordmark{
  font-family:var(--sign);font-weight:600;font-size:17px;
  letter-spacing:.16em;text-transform:uppercase;color:var(--ink);
}

/* THE NAVIGATION IS A ROW OF QUIET LINKS, not tabs and not pills. There are four destinations and
 * they are all the same kind of thing; a pill for each would be four buttons competing with the one
 * button on the page that actually does something. */
.top nav{margin-left:auto;display:flex;gap:var(--s5);flex-wrap:wrap}
.top nav a{
  font-size:.95rem;text-decoration:none;color:var(--ink-3);
  padding-bottom:2px;border-bottom:1.5px solid transparent;
  transition:color var(--quick) var(--ease);
}
.top nav a:hover{color:var(--ink)}
.top nav a.now{color:var(--ink);border-bottom-color:var(--ink)}
/* THE ONE ITEM IN THIS ROW THAT GOES SOMEWHERE ELSE. Four account pages and then the product's own
 * site is five links only if they are all the same kind of thing, and they are not - so the last one
 * is separated by a rule and a wider gap instead of sitting flush with its neighbours. The gap is on
 * the item rather than on the row for the same reason it is on the site's background control: a row
 * gap is the distance INSIDE a group. */
.top nav a.away{
  margin-left:var(--s3);padding-left:var(--s5);
  border-left:1px solid var(--line);border-bottom-color:transparent;
}

main{display:block}

footer{margin-top:var(--s8);padding-top:var(--s5);border-top:1px solid var(--line)}
footer nav{display:flex;gap:var(--s5);flex-wrap:wrap;margin-bottom:var(--s4)}
footer nav a{font-size:.9rem;text-decoration:none;color:var(--ink-3)}
footer nav a:hover{color:var(--ink)}
footer p{margin:0;color:var(--ink-3);font-size:.82rem;max-width:52ch;line-height:1.55}

/* ------------------------------------------------------------------------------------------------
 * TYPE. One large light heading, then small grey section labels. The labels are SENTENCE CASE and not
 * uppercase with letterspacing: that treatment belongs to the deck and the marketing site, where it
 * reads as instrumentation. Here it would read as shouting at somebody doing paperwork.
 */
h1,h2,h3,p,ul,ol,dl,dd,figure{margin:0}
ul{padding:0;list-style:none}

h1{
  font-weight:300;font-size:clamp(2rem,4vw,2.6rem);line-height:1.12;
  letter-spacing:-.022em;margin-bottom:var(--s2);text-wrap:balance;
}
h1 b{font-weight:600}

h2{
  font-size:.92rem;font-weight:600;letter-spacing:-.004em;color:var(--ink-3);
  margin:var(--s6) 0 var(--s3);
}

h3{font-size:1.02rem;font-weight:600;letter-spacing:-.008em;color:var(--ink)}

p{margin-top:var(--s3);color:var(--ink-2);max-width:62ch}
p:first-child{margin-top:0}

a{color:var(--ink);text-decoration:underline;text-decoration-color:var(--line);text-underline-offset:3px}
a:hover{text-decoration-color:var(--ink-3)}

.muted{color:var(--ink-3);font-size:.9rem}
.lede{color:var(--ink-2);font-size:1.08rem;max-width:54ch;margin-top:var(--s2)}

/* ------------------------------------------------------------------------------------------------
 * CARDS AND ROWS
 *
 * A white card on a grey page, a hairline border, and almost no shadow. Rows inside are divided by a
 * lighter rule than the card's own edge, so the group reads as one object with parts rather than as
 * a stack of separate things.
 */
.card{
  background:var(--card);
  border:1px solid var(--line);
  border-radius:var(--r);
  padding:var(--s5);
  margin-top:var(--s4);
  box-shadow:var(--shadow);
}
.card > h2:first-child{margin-top:0}
.card > h3:first-child{margin-top:0}

.row{
  display:flex;align-items:center;justify-content:space-between;gap:var(--s4);
  padding:var(--s4) 0;
  border-top:1px solid var(--line-2);
  flex-wrap:wrap;
}
.row:first-of-type{border-top:0;padding-top:0}
.row-main{min-width:0;font-size:.98rem}
.row-val{color:var(--ink-3);font-size:.88rem}
.row-act{margin-left:auto}

/* THE LICENCE KEY IS THE ONE THING ON THIS PAGE SOMEBODY COPIES, so it is set as a field rather than
 * as text: a sunken chip, selectable, wide enough not to wrap on a phone. */
.key{
  display:inline-block;
  font-family:var(--mono);font-weight:500;font-size:1rem;letter-spacing:.02em;
  color:var(--ink);background:var(--sunk);
  border:1px solid var(--line);border-radius:var(--r-s);
  padding:var(--s2) var(--s3);
  user-select:all;word-break:break-all;
}
code,.mono{font-family:var(--mono);font-weight:500;font-size:.8rem;color:var(--ink-3)}

/* AN AMOUNT IS NOT A KEY, AND A VERSION IS NOT A KEY EITHER. Both were set in .key when the account
 * pages were written, which put a sunken, select-all, copy-me chip around two figures nobody copies.
 * The chip is a control; these are values, and they are read. The amount is the largest thing on an
 * order because it is the thing being looked up. */
.amount{font-size:1.35rem;font-weight:600;letter-spacing:-.014em;color:var(--ink)}
.amount .cur{font-size:.9rem;font-weight:600;color:var(--ink-3);margin-right:.4em;letter-spacing:.02em}
.ver{font-family:var(--mono);font-weight:500;font-size:1rem;color:var(--ink);letter-spacing:-.01em}

/* A definition list for facts that are read rather than acted on. */
.facts{
  border:1px solid var(--line);border-radius:var(--r);background:var(--card);
  margin-top:var(--s4);overflow:hidden;
}
.fact{
  display:flex;justify-content:space-between;gap:var(--s4);
  padding:var(--s4) var(--s5);
  border-top:1px solid var(--line-2);flex-wrap:wrap;
}
.fact:first-child{border-top:0}
.fact dt{color:var(--ink-3);font-size:.92rem}
.fact dd{color:var(--ink);font-size:.95rem;text-align:right}

/* A BOX INSIDE A BOX IS A MISTAKE THIS FILE SHOULD MAKE IMPOSSIBLE. A .facts list carries its own
 * border, radius and white fill so it can stand alone on the grey page; dropped inside a .card it
 * became a white panel on a white panel, hairline against hairline, which reads as a nested object
 * where there is only one. Contextual rather than a modifier class, so it cannot be forgotten at the
 * next call site. The rows keep their dividers and fall back to the card's own gutter. */
.card > .facts{border:0;background:none;border-radius:0;margin-top:var(--s2)}
.card > .facts > .fact{padding-left:0;padding-right:0}
.card > .facts > .fact:first-child{border-top:1px solid var(--line-2)}

/* ------------------------------------------------------------------------------------------------
 * STATUS. Colour is never the only signal: every tag carries its word, which is the deck's own rule.
 */
.tag{
  display:inline-block;
  font-size:.78rem;font-weight:600;letter-spacing:.01em;
  padding:3px 9px;border-radius:999px;white-space:nowrap;
}
.tag.ok{color:var(--ok);background:var(--ok-bg)}
.tag.warn{color:var(--warn);background:var(--warn-bg)}
.tag.bad{color:var(--bad);background:var(--bad-bg)}

/* A notice is the product telling the customer something, not a control. */
.notice{
  background:var(--warn-bg);border:1px solid rgba(138,90,0,.16);
  border-radius:var(--r-s);padding:var(--s3) var(--s4);margin-top:var(--s3);
}
.notice.bad{background:var(--bad-bg);border-color:rgba(180,35,28,.18)}
/* A THING THAT WENT RIGHT, and it needs saying: the one moment a customer most wants to be told
 * plainly that it worked is the moment they have just moved the address they sign in with. */
.notice.ok{background:var(--ok-bg);border-color:rgba(19,122,69,.18)}
.notice p{margin-top:0;color:var(--ink-2);font-size:.92rem;max-width:none}

/* THE ACCOUNT PAGE NO LONGER CARRIES THE LIVE NOTICES, so `.knowing` is gone with them (controller,
 * 2026-09-06). It was written the same day: the eight lines the installer shows, set as one quiet
 * card under the licence after a first attempt put them ABOVE it as eight amber warning boxes. The
 * reasoning for having them at all was that somebody opening the portal is often somebody with a
 * problem - that stands, and the answer is the site's own known limits page, which is where the list
 * is published in full and where it is read before anybody pays. THE PORTAL'S FOOTER LINK TO IT WENT
 * TOO, an hour after this paragraph first cited it as the replacement; a comment that names a route
 * somebody has since removed is the defect this session kept finding, so it is corrected rather than
 * left to be read as true.
 * Named here rather than deleted silently, for the same reason button.danger is: a rule nothing uses
 * is a rule the next reader takes for load bearing. */

/* WHAT CHANGED IN A BUILD IS A LIST OF CHANGES. It was one paragraph with line breaks in it, so three
 * separate things read as one long sentence and the eye had nothing to count. */
.notes{margin-top:var(--s3);display:flex;flex-direction:column;gap:var(--s2)}
.notes li{position:relative;padding-left:var(--s4);color:var(--ink-2);font-size:.95rem;line-height:1.55;max-width:62ch}
.notes li::before{content:"";position:absolute;left:2px;top:.62em;width:4px;height:4px;border-radius:50%;background:var(--ink-3)}

/* ------------------------------------------------------------------------------------------------
 * FORMS AND BUTTONS
 */
label{display:block;margin-bottom:var(--s2);font-size:.92rem;font-weight:600;color:var(--ink)}

/* A CONTROL AND ITS LABEL, SPACED AS ONE THING. `label` cannot carry the top margin itself: on the
 * sign in page it is the first child of its card and would push the field off its own edge. So the
 * space belongs to the group, and the group knows when it is first. */
.field{margin-top:var(--s5)}
.field:first-child{margin-top:0}

input[type=email],input[type=text]{
  width:100%;padding:11px var(--s3);
  border:1px solid var(--line);border-radius:var(--r-s);
  background:var(--card);color:var(--ink);
  font:400 1rem/1.4 var(--ui);
  transition:border-color var(--quick) var(--ease), box-shadow var(--quick) var(--ease);
}
input[type=email]:hover,input[type=text]:hover{border-color:#D2D2D8}
input[type=email]:focus,input[type=text]:focus{
  outline:none;border-color:var(--ink);box-shadow:0 0 0 3px rgba(24,24,27,.08);
}
/* ------------------------------------------------------------------------------------------------
 * SIX BOXES, ONE DIGIT EACH. This replaced a single wide field with letterspacing faked into it, and
 * the difference is not decoration: a person reading six characters off a screen and typing them into
 * one box has to hold their place themselves, and a box per digit holds it for them. It is also the
 * shape every phone, bank and password manager now expects, which is why `autocomplete="one-time-code"`
 * on the first box fills the whole group on iOS.
 *
 * `ch` UNITS AND NOT PIXELS, so a box is sized by the typeface it holds rather than by a number that
 * happens to look right at one weight. `aspect-ratio` keeps it square as the width follows.
 */
.otp{display:flex;gap:var(--s2);margin-top:var(--s2)}
.otp input{
  width:2.4ch;flex:1 1 0;min-width:0;max-width:3.4rem;aspect-ratio:1/1.15;
  padding:0;text-align:center;
  font-family:var(--mono);font-weight:500;font-size:1.5rem;letter-spacing:0;
  border:1px solid var(--line);border-radius:var(--r-s);
  background:var(--card);color:var(--ink);
  transition:border-color var(--quick) var(--ease), box-shadow var(--quick) var(--ease);
}
.otp input:hover{border-color:#D2D2D8}
.otp input:focus{outline:none;border-color:var(--ink);box-shadow:0 0 0 3px rgba(24,24,27,.08)}
/* NO "FILLED" STATE, AND THAT IS A CHOICE. Six identical squares give no sense of progress, so a
 * darker border on a filled box is tempting; there is no honest selector for it. `:placeholder-shown`
 * needs a placeholder these do not have, `:valid` is true of an empty box because the pattern only
 * rejects what is there, and the remaining route is a class toggled from JavaScript - which would put
 * the appearance of the field behind a script the no-script path is written to survive without. The
 * caret and the focus ring are the feedback, and they are real in both worlds. */

/* THE PRIMARY BUTTON IS NEAR BLACK, NOT ORANGE, and that is the decision that makes this page look
 * like the thing that was asked for. On a white ground an orange button is the loudest object on the
 * screen, which is right on a page that is selling and wrong on a page where somebody is releasing a
 * machine. The brand keeps the mark; the action keeps the ink. */
button,.button{
  display:inline-block;
  padding:11px var(--s5);
  font:600 .96rem/1.2 var(--ui);letter-spacing:-.004em;
  text-decoration:none;text-align:center;
  color:#fff;background:var(--ink);
  border:1px solid var(--ink);border-radius:var(--r-s);
  cursor:pointer;
  transition:background var(--quick) var(--ease), box-shadow var(--quick) var(--ease);
}
button:hover,.button:hover{background:#2C2C33;box-shadow:var(--shadow-lift)}
button:active,.button:active{background:var(--ink);box-shadow:none}

button.ghost,.button.ghost{
  background:var(--card);color:var(--ink-2);border-color:var(--line);
  padding:7px var(--s3);font-size:.88rem;
}
button.ghost:hover,.button.ghost:hover{background:var(--card);color:var(--ink);border-color:#D2D2D8;box-shadow:var(--shadow)}

/* THERE IS NO DESTRUCTIVE BUTTON ON THIS HOST ANY MORE, so the styles that dressed one are gone with
 * it: `button.danger`, `.card.grave` and `h2.grave` were written for the self service account closure
 * on 2026-09-06 and deleted the same day, when the controller decided that closing an account is a
 * request staff act on rather than something a browser can do. They are named here rather than left
 * in place because a rule nothing uses is a rule the next reader takes for load bearing, and because
 * somebody re-adding a red button should have to decide what it looks like rather than inherit it.
 * The red TOKENS stay: .tag.bad and .notice.bad still say what state a thing is in. */

button[disabled],.button[disabled]{opacity:.4;cursor:not-allowed;box-shadow:none}

button:focus-visible,.button:focus-visible,a:focus-visible{
  outline:2px solid var(--ink);outline-offset:2px;
}

form.inline{display:inline}
.actions{display:flex;gap:var(--s3);align-items:center;flex-wrap:wrap;margin-top:var(--s5)}

/* ------------------------------------------------------------------------------------------------
 * EMPTY STATES. They say what happens next rather than reporting an absence.
 */
.empty{
  background:var(--card);border:1px solid var(--line);border-radius:var(--r);
  padding:var(--s7) var(--s5);margin-top:var(--s4);text-align:center;
}
.empty p{color:var(--ink-3);margin-left:auto;margin-right:auto}
.empty .actions{justify-content:center}

/* ------------------------------------------------------------------------------------------------
 * NARROW. A row of label, value and control is the one shape that breaks first, so below this the
 * control drops under its own row rather than squeezing the label to nothing.
 */
@media (max-width:36rem){
  .wrap{padding:var(--s6) var(--s4) var(--s7)}
  .top{gap:var(--s2) var(--s3)}
  .top nav{margin-left:0;width:100%;gap:var(--s4)}
  .row{align-items:flex-start}
  .row-act{margin-left:0;width:100%;margin-top:var(--s2)}
  .fact{flex-direction:column;gap:2px}
  .fact dd{text-align:left}
  /* The boxes already flex; this only stops them growing past what a thumb needs on a phone. */
  .otp{gap:6px}
  .otp input{font-size:1.25rem;max-width:none}
}

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{transition:none!important;animation:none!important}
}

/* ------------------------------------------------------------------------------------------------ spacing utilities ----------
 * One class per style= attribute that would otherwise sit in a template, and they exist for exactly
 * that reason: a style attribute cannot be rescued by a CSP hash without 'unsafe-hashes', which would
 * weaken the whole origin.
 *
 * `.note-hint` IS NAMED IN ops/bite_manifest.txt: the entry an-element-that-carries-a-style-attribute
 * mutates the exact line `<p class="muted note-hint">The code works once and expires in a few
 * minutes.</p>` in signin.html. Renaming this class, or that line, breaks the proof that the gate
 * works. It is kept for that reason as much as for this one.
 */
.note-hint  {margin:var(--s3) 0 0}
.note-retry {margin:var(--s3) 0 0;color:var(--bad)}
.note-sub   {margin:var(--s2) 0 0}
.gap-sm     {margin-top:var(--s3)}
.gap-md     {margin-top:var(--s4)}
.gap-lg     {margin-top:var(--s5)}
/* THE BREAK AN h2 WOULD HAVE MADE. Every other page opens h1, lede, then a section label whose 36px
 * top margin separates the introduction from the content. The sign in page has no section label -
 * "Purchase e-mail" is the field's own label and a heading above it would say the same thing twice -
 * so its card followed the lede by 16px and the heading, the sentence and the form read as one dense
 * block with no hierarchy at all. This is that missing 36px, and nothing else. */
.gap-xl     {margin-top:var(--s6)}
