/*
  Duka administration portal.

  Deliberately not storefront.css, and not a variation on it. That stylesheet is a 420px phone frame
  for shoppers on a slow Android connection; this is two operators on laptops reading a table of
  everyone who can reach every merchant's data. Sharing it would have put a permissions matrix
  inside a phone.

  Kept from the house style, because it is right rather than because it is the house style:
  elevation is a 1px line on a white surface, never a shadow.

  Departed from, deliberately: the warm clay palette (this is not a shop and should never be
  mistaken for one), the 420px column, the 10-14px radii, the 44px phone touch targets, and
  Public Sans.

  One idea carries the design. The portal's subject is grants - who may do what - so every screen
  renders access as the same seven columns in the same order, split into the two bands the
  permission set actually has: two grants that let you SEE something, five that let you CHANGE
  something. Two accounts can then be compared by shape without reading a word, and the checkbox
  that edits a grant sits in the very cell that displays it, so switching to edit moves nothing.

  Contrast, measured rather than assumed (WCAG 2.1 relative luminance):
    ink #12232B on paper #FFFFFF ............ 16.15:1
    ink #12232B on ground #E7EAEC ........... 13.36:1
    ink-2 #4A5C64 on paper .................. 6.99:1
    ink-2 #4A5C64 on ground ................. 5.78:1
    signal #0B5E63 on paper ................. 7.51:1   (and paper on signal, for buttons)
    grant #175C3C on grant-bg #E4EFE9 ....... 6.81:1
    refuse #8A2B2B on refuse-bg #F6E7E7 ..... 7.11:1
    caution #7A5200 on caution-bg #F6EEDD ... 6.05:1
    field-line #6E7C82 on paper ............. 4.31:1   (>= 3:1, WCAG 1.4.11 non-text)
    focus ring signal on paper / ground ..... 7.51:1 / 6.22:1
    paper #FFFFFF on caution #7A5200 ........ 6.92:1   (the impersonation banner; see .acting)
*/

:root {
    color-scheme: light;

    --ground: #E7EAEC;
    --paper: #FFFFFF;

    --ink: #12232B;
    --ink-2: #4A5C64;

    --rule: #DDE3E6;
    --rule-2: #C7D0D4;
    --field-line: #6E7C82;

    --signal: #0B5E63;
    --signal-2: #083F43;

    --grant: #175C3C;
    --grant-bg: #E4EFE9;
    --refuse: #8A2B2B;
    --refuse-bg: #F6E7E7;
    --caution: #7A5200;
    --caution-bg: #F6EEDD;

    /* Near-square. An instrument, not an app. One value, everywhere. */
    --r: 3px;
}

* { box-sizing: border-box; }

[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
    margin: 0;
    background: var(--ground);
    color: var(--ink);
    font-family: "IBM Plex Sans", "Segoe UI", system-ui, -apple-system, sans-serif;
    font-size: 15px;
    font-weight: 400;
    line-height: 1.55;
}

a { color: var(--signal); text-underline-offset: 3px; }

a:hover { color: var(--signal-2); }

button, input, select, textarea { font: inherit; color: inherit; }

/*
   One focus treatment for the whole portal, on :focus-visible so a mouse click does not paint it.
   Three pixels rather than two: these screens are read at arm's length on a laptop, and the ring is
   the only thing that says where the keyboard is.
*/
:focus-visible {
    outline: 3px solid var(--signal);
    outline-offset: 2px;
}

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

/* ---------- impersonation ---------- */

/*
   The one place in this portal that fills a whole band with a warning colour rather than tinting a
   box with it - .caution and .state--warn use --caution as restrained text on a pale ground, which
   reads as "worth noting". This has to read as "you are not looking at your own screen", so it is
   the solid colour, full width, ahead of even the top bar - the first thing painted and the first
   thing seen on every page of the request, chromed or not.
*/
.acting {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    width: 100%;
    padding: 0.65rem 1.75rem;
    background: var(--caution);
    color: var(--paper);
    font-weight: 500;
}

.acting__dot {
    flex: none;
    width: 0.55rem;
    height: 0.55rem;
    border-radius: 50%;
    background: var(--paper);
}

.acting__out {
    margin-left: auto;
    flex: none;
    color: var(--paper);
    font-weight: 600;
    white-space: nowrap;
}

.acting__out:hover { color: var(--ground); }

/* ---------- chrome ---------- */

.top {
    background: var(--paper);
    border-bottom: 1px solid var(--rule-2);
}

.top__in {
    display: flex;
    align-items: stretch;
    gap: 2rem;
    max-width: 78rem;
    margin: 0 auto;
    padding: 0 1.75rem;
}

.top__mark {
    display: flex;
    align-items: center;
    font-weight: 600;
    letter-spacing: -0.01em;
    white-space: nowrap;
}

.top__nav { display: flex; gap: 1.5rem; flex: 1; }

.top__nav a {
    display: flex;
    align-items: center;
    padding: 0.9rem 0 0.85rem;
    color: var(--ink-2);
    text-decoration: none;
    /* The active item is marked by a bar on the edge it shares with the content below it, not by a
       pill or a tinted block: the mark points at where you are, rather than decorating a link. */
    border-bottom: 3px solid transparent;
}

.top__nav a:hover { color: var(--ink); }

.top__nav a[aria-current="page"] {
    color: var(--ink);
    font-weight: 500;
    border-bottom-color: var(--signal);
}

.top__who {
    display: flex;
    align-items: center;
    gap: 1rem;
    color: var(--ink-2);
    white-space: nowrap;
}

.top__out {
    padding: 0;
    border: 0;
    background: none;
    color: var(--signal);
    cursor: pointer;
    text-decoration: underline;
    text-underline-offset: 3px;
}

.top__out:hover { color: var(--signal-2); }

.main {
    max-width: 78rem;
    margin: 0 auto;
    padding: 2.25rem 1.75rem 5rem;
}

/* Sign in and enrolment: no chrome to return to, so one column, left-aligned inside it. */
.focus {
    max-width: 33rem;
    margin: 0 auto;
    padding: 4.5rem 1.75rem 5rem;
}

/* ---------- type ---------- */

h1 {
    font-size: 1.625rem;
    font-weight: 600;
    line-height: 1.2;
    letter-spacing: -0.02em;
    margin: 0;
}

h2 {
    font-size: 1.125rem;
    font-weight: 600;
    letter-spacing: -0.01em;
    margin: 0 0 0.5rem;
}

.lede {
    max-width: 46ch;
    margin: 0.4rem 0 0;
    color: var(--ink-2);
}

.head { margin-bottom: 1.75rem; }

.lede + form, .lede + .steps, .lede + .codes { margin-top: 1.5rem; }

.meta { color: var(--ink-2); font-size: 0.8125rem; }

/*
   Monospace has exactly one job here and never any other: strings a person has to transcribe
   character by character - a base32 secret, a recovery code, a temporary password. IBM Plex Mono
   slashes its zero and keeps 1, l and I apart, which is the difference between a working
   authenticator and a support call.
*/
.mono {
    font-family: "IBM Plex Mono", ui-monospace, "Cascadia Mono", Consolas, monospace;
    font-variant-ligatures: none;
}

/* ---------- surfaces ---------- */

.panel {
    background: var(--paper);
    border: 1px solid var(--rule-2);
    border-radius: var(--r);
    padding: 1.5rem;
}

.panel + .panel { margin-top: 1.25rem; }

/*
   The bar across the top of a focus panel is the page's only ornament, and it carries state: petrol
   while the form is waiting, red when the last attempt was refused.
*/
.panel--focus { border-top: 3px solid var(--signal); padding: 1.75rem; }

.panel--refused { border-top-color: var(--refuse); }

.panel__foot {
    margin: 1.5rem -1.75rem -1.75rem;
    padding: 1rem 1.75rem;
    border-top: 1px solid var(--rule);
    background: #F7F9FA;
    border-radius: 0 0 var(--r) var(--r);
    color: var(--ink-2);
    font-size: 0.8125rem;
}

/* ---------- messages ---------- */

.alarm, .notice, .caution {
    border: 1px solid;
    border-radius: var(--r);
    padding: 0.7rem 0.85rem;
    margin: 0 0 1.25rem;
}

.alarm { background: var(--refuse-bg); border-color: #E0BDBD; color: var(--refuse); }

.notice { background: var(--grant-bg); border-color: #BCD6C8; color: var(--grant); }

.caution { background: var(--caution-bg); border-color: #DFCFA6; color: var(--caution); }

/* ---------- forms ---------- */

.field { display: block; margin-bottom: 1.15rem; }

/* Label, control, hint - three siblings, so the hint never lands inside the label and becomes part
   of the control's accessible name. */
.field > label {
    display: block;
    margin-bottom: 0.3rem;
    font-weight: 500;
}

.field input[type="text"],
.field input[type="email"],
.field input[type="password"],
.field select {
    width: 100%;
    background: var(--paper);
    border: 1px solid var(--field-line);
    border-radius: var(--r);
    padding: 0.5rem 0.6rem;
    min-height: 2.5rem;
}

.field--code input, .field--otp input {
    letter-spacing: 0.28em;
    font-family: "IBM Plex Mono", ui-monospace, Consolas, monospace;
}

/* Six digits, and the box says so. */
.field--code input { max-width: 9rem; }

/* Six digits or a ten-character recovery code, so it has to hold the longer one. */
.field--otp input { max-width: 15rem; }

.field input::placeholder { color: var(--ink-2); }

.hint { margin: 0.3rem 0 0; color: var(--ink-2); font-size: 0.8125rem; }

.btn {
    display: inline-block;
    background: var(--signal);
    color: var(--paper);
    border: 1px solid var(--signal);
    border-radius: var(--r);
    padding: 0.5rem 1rem;
    min-height: 2.5rem;
    font-weight: 500;
    cursor: pointer;
    transition: background-color 120ms ease;
}

.btn:hover { background: var(--signal-2); border-color: var(--signal-2); }

.btn--quiet {
    background: var(--paper);
    color: var(--signal);
    border-color: var(--field-line);
}

.btn--quiet:hover { background: #F1F4F5; color: var(--signal-2); }

.btn--small { padding: 0.3rem 0.65rem; min-height: 2rem; font-size: 0.8125rem; }

.btn[disabled] { opacity: 0.5; cursor: not-allowed; }

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

/* ---------- the grant sheet ---------- */

.sheetwrap { overflow-x: auto; }

.sheet {
    width: 100%;
    border-collapse: collapse;
    background: var(--paper);
    border: 1px solid var(--rule-2);
    border-radius: var(--r);
}

.sheet caption {
    caption-side: top;
    text-align: left;
    color: var(--ink-2);
    padding-bottom: 0.6rem;
}

.sheet th, .sheet td {
    padding: 0.6rem 0.75rem;
    text-align: left;
    vertical-align: middle;
    border-bottom: 1px solid var(--rule);
}

.sheet thead th {
    /* Sentence case, not tracked-out capitals. A column head is a word, not a badge. */
    font-size: 0.8125rem;
    font-weight: 600;
    color: var(--ink-2);
    vertical-align: bottom;
}

.sheet thead tr:last-child th { border-bottom: 1px solid var(--rule-2); }

.sheet tbody tr:last-child th,
.sheet tbody tr:last-child td { border-bottom: 0; }

.sheet tbody tr:hover td, .sheet tbody tr:hover th { background: #F7F9FA; }

/* The two bands. A single rule between them does the work a repeated legend would. */
.sheet thead th.band {
    text-align: center;
    font-weight: 600;
    color: var(--ink);
    padding-bottom: 0.15rem;
}

.sheet .band--see,
.sheet .band--change,
.sheet .col--first { border-left: 1px solid var(--rule-2); }

.sheet .grantcell { text-align: center; width: 5.5rem; }

.grantcell input[type="checkbox"] {
    width: 1.05rem;
    height: 1.05rem;
    margin: 0;
    accent-color: var(--signal);
    cursor: pointer;
}

.grantcell input[disabled] { cursor: not-allowed; }

/*
   Read-only marks, on the overview. Filled for held, an empty well for not held - so a row of
   grants reads as a shape before it reads as words. The word is still there for a screen reader.
*/
.mark {
    display: inline-block;
    width: 1.05rem;
    height: 1.05rem;
    border-radius: var(--r);
    border: 1px solid var(--field-line);
}

.mark--on { background: var(--grant); border-color: var(--grant); }

.who__name { display: block; font-weight: 500; }

.who__mail { display: block; color: var(--ink-2); font-size: 0.8125rem; }

.sheet tbody th { font-weight: 400; }

/* Status: never colour alone. A dot and the word, always both. */
.state { display: block; white-space: nowrap; }

.state::before {
    content: "";
    display: inline-block;
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 50%;
    margin-right: 0.4rem;
    vertical-align: 0.05rem;
}

.state--on::before { background: var(--grant); }

.state--off { color: var(--refuse); }

.state--off::before { background: var(--refuse); }

/*
   Fix round 1: this carried its own font-size while its two siblings did not, which was invisible
   while it only ever labelled a secondary annotation ("No authenticator yet") beside a full-size
   primary state. The Merchants screen's status column made it a primary state in its own right
   (Published), sitting beside .state--on and .state--off in the same cell context - three peers that
   now need to read as the same kind of thing, not two full-size ones and a visibly smaller third.
*/
.state--warn { color: var(--caution); }

.state--warn::before { background: var(--caution); }

/*
   The neutral member of the trio a shop's lifecycle actually is - Draft -> Published -> Active is a
   progression, not a health signal, so Draft gets no colour opinion at all rather than borrowing
   .state--off's red, which read as "broken" for the ordinary state every merchant starts in.
*/
.state--neutral { color: var(--ink-2); }

.state--neutral::before { background: var(--ink-2); }

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

.do .btn + .btn { margin-left: 0.4rem; }

.do__self { color: var(--ink-2); font-size: 0.8125rem; }

/* ---------- choices ---------- */

fieldset { border: 0; margin: 0 0 1.15rem; padding: 0; }

legend { padding: 0; font-weight: 500; }

.choice {
    display: grid;
    grid-template-columns: 1.05rem minmax(0, 1fr);
    gap: 0.7rem;
    align-items: start;
    max-width: 40rem;
    padding: 0.55rem 0;
    cursor: pointer;
}

.choice + .choice { border-top: 1px solid var(--rule); }

.choice input[type="radio"] {
    width: 1.05rem;
    height: 1.05rem;
    margin: 0.25rem 0 0;
    accent-color: var(--signal);
}

.choice b { font-weight: 500; }

.choice .why { display: block; font-size: 0.8125rem; color: var(--ink-2); }

/* ---------- small helpers ---------- */

.pushed { margin-top: 2rem; }

.msg-after { margin: 1rem 0 0; }

.inline-form { display: inline; }

/* ---------- secrets shown once ---------- */

.secret {
    display: block;
    background: var(--ground);
    border: 1px solid var(--rule-2);
    border-radius: var(--r);
    padding: 0.7rem 0.85rem;
    font-size: 1.0625rem;
    letter-spacing: 0.08em;
    /* Wrap between the four-character groups, never inside one: a secret split mid-group is a
       secret somebody types wrong. */
    word-break: normal;
    overflow-wrap: break-word;
    user-select: all;
}

.uri {
    display: block;
    margin-top: 0.5rem;
    font-size: 0.8125rem;
    word-break: break-all;
}

.codes {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.5rem 1.25rem;
    margin: 1rem 0 0;
    padding: 0;
    list-style: none;
    counter-reset: code;
}

.codes li {
    display: flex;
    align-items: baseline;
    gap: 0.6rem;
    padding: 0.35rem 0.6rem;
    background: var(--paper);
    border: 1px solid var(--rule-2);
    border-radius: var(--r);
    font-size: 1.0625rem;
    letter-spacing: 0.06em;
}

/* The codes are a numbered list because they are a checklist to tick off, not a sequence to follow. */
.codes li::before {
    counter-increment: code;
    content: counter(code);
    color: var(--ink-2);
    font-size: 0.75rem;
    font-family: "IBM Plex Sans", system-ui, sans-serif;
    letter-spacing: 0;
    min-width: 1.1rem;
}

.steps { margin: 0; padding-left: 1.35rem; }

.steps > li { margin-bottom: 1.1rem; }

.steps > li::marker { color: var(--ink-2); font-weight: 600; }

/* ---------- overview ---------- */

.bands { display: flex; flex-wrap: wrap; gap: 2.5rem; }

.bands h2 { margin-bottom: 0.75rem; }

.bandlist { margin: 0; padding: 0; list-style: none; }

.bandlist li {
    display: grid;
    grid-template-columns: 1.05rem minmax(0, 1fr);
    gap: 0.7rem;
    align-items: start;
    padding: 0.45rem 0;
    max-width: 34rem;
}

.bandlist li + li { border-top: 1px solid var(--rule); }

.bandlist .off { color: var(--ink-2); }

.bandlist b { font-weight: 500; }

.bandlist .yn { color: var(--ink-2); font-size: 0.8125rem; margin-left: 0.4rem; }

.bandlist .why { display: block; font-size: 0.8125rem; color: var(--ink-2); }

.figures {
    margin-top: 2.5rem;
    padding-top: 1.5rem;
    border-top: 1px solid var(--rule-2);
}

.figures h3 {
    margin: 1.5rem 0 0.5rem;
    font-size: 0.9375rem;
    font-weight: 600;
}

.figures .hint { margin: 0; color: var(--ink-2); }

/* Three counts, read as a shape before they are read as numbers - the same idea as .mark on the
   grant sheet, applied to figures instead of flags. */
.stats {
    display: flex;
    flex-wrap: wrap;
    gap: 1.75rem;
    margin: 0;
}

.stat { min-width: 6rem; }

.stat dt {
    font-size: 0.8125rem;
    color: var(--ink-2);
}

.stat dd {
    margin: 0.15rem 0 0;
    font-size: 1.625rem;
    font-weight: 600;
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums;
}

.revenue {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem 1.5rem;
    margin: 0;
    padding: 0;
    list-style: none;
}

.revenue li { font-variant-numeric: tabular-nums; }

.revenue b { font-weight: 600; margin-right: 0.4rem; }

.next {
    margin-top: 2rem;
    padding-top: 1.25rem;
    border-top: 1px solid var(--rule-2);
    color: var(--ink-2);
}

.next p { max-width: 56ch; }

/* ---------- merchant filters ---------- */

/*
   A plain GET form - there is no JavaScript anywhere in this portal, so "filter" means "submit and
   reload" and the values it submitted are what has to reappear in these same fields afterwards.
*/
.filters {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 1rem 1.25rem;
    margin-bottom: 1.75rem;
}

.filters .field { margin-bottom: 0; }

.filters .field--search { flex: 1 1 16rem; min-width: 16rem; }

.filters .field--country { width: 8rem; }

.filters .field--status { width: 12rem; }

.sheet .num { text-align: right; font-variant-numeric: tabular-nums; }

/*
   The audit log's detail column holds JSON another code path wrote, not this page's own copy - it
   can be long, and it can be malformed. overflow-wrap: anywhere is what actually stops one long value
   stretching the row: it lets a break land anywhere in the string, not just at a space, which compact
   JSON has few of. max-width does nothing on its own here - a <td>'s width is not constrained by it
   under the browser's default (auto) table layout, Chrome included - so if a value somehow still ran
   the row wide, it is .sheetwrap's own overflow-x: auto that is the real fallback, scrolling the sheet
   sideways in its own wrapper rather than the page doing so. Keep overflow-wrap and .sheetwrap; do not
   assume max-width is holding anything up.
*/
.sheet .detail {
    max-width: 22rem;
    overflow-wrap: anywhere;
    color: var(--ink-2);
    font-size: 0.8125rem;
}

/* ---------- narrow windows ---------- */

/*
   Desktop-first: this portal is used on laptops and the grant sheet needs the width. Below 900px it
   stays usable rather than pretty - the sheet scrolls sideways inside its own wrapper instead of
   pushing the page.
*/
@media (max-width: 900px) {
    .acting { flex-wrap: wrap; padding: 0.65rem 1rem; }

    .acting__out { margin-left: 0; }

    .top__in { flex-wrap: wrap; gap: 1rem; padding: 0.75rem 1rem; }

    .top__nav { gap: 1.1rem; }

    .top__nav a { padding: 0.4rem 0; }

    .main { padding: 1.5rem 1rem 4rem; }

    .focus { padding: 2rem 1rem 4rem; }

    .bands { gap: 1.75rem; }

    .codes { grid-template-columns: minmax(0, 1fr); }

    .filters { align-items: stretch; }

    .filters .field--country, .filters .field--status { width: auto; }
}

/* ---------- print ---------- */

/*
   Recovery codes and a temporary password are shown once and never again, so Ctrl+P has to produce
   something worth keeping. Everything that is not the codes goes away.
*/
@media print {
    body { background: #FFFFFF; }

    /*
       Hide what cannot be used on paper, and nothing else. An earlier version hid every form and
       every field globally, which printed the "Add an administrator" heading above the empty space
       where its form had been. Whole blocks that are only for pressing carry .no-print instead.
    */
    .acting, .top, .btn, .panel__foot, .no-print { display: none !important; }

    .focus, .main { max-width: none; padding: 0; }

    .panel { border: 0; padding: 0; }

    /* The sheet must not be clipped at the paper's edge by its own scroll container. */
    .sheetwrap { overflow: visible; }

    .codes li { border-color: #999999; }
}

/*
   Enrolment's primary action. Full width and tall because it is tapped on a phone, one-handed, by
   somebody who has just been told their password alone will not get them in - and because the
   alternative to tapping it is transcribing a thirty-two character key by hand.
*/
.btn--wide {
    display: block;
    width: 100%;
    text-align: center;
    padding: 0.85rem 1rem;
    min-height: 3rem;
    text-decoration: none;
}

/*
   The key, folded away. It is still the fallback for a browser that is not on the phone, so it stays
   reachable in one click - but it is no longer the first thing the page offers, because a key that
   is typed is a key that can be mistyped.
*/
.by-hand > summary {
    cursor: pointer;
    padding: 0.4rem 0;
    color: var(--ink-2);
}

.by-hand[open] > summary { margin-bottom: 0.35rem; }
