/* The stylesheet for everything Memolok serves.
 *
 * Canonical for **both** surfaces now: the generated content pages, and the
 * application pages (/login, /forgot-password, /reset-password,
 * /password-ready, /request-access) — plus memolok-auth's consent screen,
 * which links this same file across containers the way it already links
 * /assets/logo_white.svg. There is one styling system, where there used to be
 * two and six copies of one of them.
 *
 * One theme, light, rendered unconditionally: there is no
 * `prefers-color-scheme` query anywhere in this file.
 *
 * TWO RULES THIS FILE MUST KEEP:
 *
 * 1. **Every colour lives in a custom property. No literal colour in any rule.**
 *    The difference between "a variable swap in `:root`" and "a second rewrite
 *    of the prose system" is decided by whether the values are in variables
 *    now. This holds even where a literal would be shorter — especially for the
 *    callout tints and the form components, which used to hardcode theirs.
 *
 * 2. The edge CSP (`memolok-edge/traefik/dynamic/headers.yml`) is a hard
 *    boundary: no `@import`, no `url()` to any external origin
 *    (`default-src 'none'`), and no webfont from anywhere but this origin
 *    (`font-src 'self'`) — hence the system font stack rather than a
 *    downloaded family. Once every page links this file instead of carrying an
 *    inline <style>, `style-src` drops `'unsafe-inline'`, which is the whole
 *    reason the two systems had to become one.
 */

:root {
  color-scheme: light;

  /* --- ground and ink ---------------------------------------------------- */
  --page: #fff;
  /* Lifted surfaces: cards, code, table headers. Alpha rather than a mixed
   * colour so anything sitting on the header gradient lifts by the same amount
   * as something sitting on the flat page. */
  --wash: rgba(16, 24, 48, 0.05);
  --wash-strong: rgba(16, 24, 48, 0.08);

  --ink: #121a2e;
  --ink-soft: #4a5570;
  /* Both users of this token (.hint, .note) are the smallest text on the site
   * at 0.78-0.8rem, so AA wants 4.5:1 and there is no large-text exemption to
   * lean on. */
  --ink-faint: #5c657c;

  --rule: rgba(16, 24, 48, 0.14);
  --rule-strong: rgba(16, 24, 48, 0.20);

  --accent: #3049c9;
  /* The hover state, darker than --accent so a hovered link comes forward
   * rather than receding toward the page. */
  --accent-bright: #1f33a0;
  /* Text placed *on* the accent — the primary button. */
  --on-accent: #ffffff;

  /* The header band. Kept as chrome rather than as the page ground: a
   * full-page gradient behind long-form prose drifts in tone from the top of
   * the article to the bottom. */
  --band-from: #dde5f8;
  --band-to: #fff;

  /* --- message states ---------------------------------------------------- */
  --danger-wash: rgba(176, 26, 26, 0.10);
  --danger-rule: rgba(176, 26, 26, 0.35);
  --danger-ink: #a11b1b;

  --notice-wash: rgba(48, 73, 201, 0.10);
  --notice-rule: rgba(48, 73, 201, 0.32);
  --notice-ink: #2a3f9e;

  /* --- form controls ----------------------------------------------------- */
  --field: #ffffff;
  --field-rule: rgba(16, 24, 48, 0.22);
  --label-ink: #3c4763;

  /* --- callout tints -----------------------------------------------------
   * Up here rather than on the variant classes, so that a palette change
   * overrides `:root` alone instead of restating the variant rules too.
   *
   * The fill is a low-alpha overlay of the variant's own accent rather than a
   * pastel: a saturated tint reads as a stain on the page rather than as
   * emphasis. */
  --callout-generic-accent: #5c6784;
  --callout-generic-wash: rgba(92, 103, 132, 0.09);

  --callout-note-accent: #3049c9;
  --callout-note-wash: rgba(48, 73, 201, 0.09);

  --callout-tip-accent: #1d7a53;
  --callout-tip-wash: rgba(29, 122, 83, 0.10);

  --callout-important-accent: #6b3fbf;
  --callout-important-wash: rgba(107, 63, 191, 0.10);

  --callout-warning-accent: #9a6410;
  --callout-warning-wash: rgba(154, 100, 16, 0.10);

  --callout-example-accent: #2f6b87;
  --callout-example-wash: rgba(47, 107, 135, 0.10);

  --font-prose: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-code: ui-monospace, SFMono-Regular, "Cascadia Mono", Menlo, Consolas, monospace;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  background: var(--page);
  color: var(--ink);
  font-family: var(--font-prose);
  font-size: 17px;
  line-height: 1.65;

  /* Keeps the footer at the bottom of the viewport on a short page rather than
   * stranded halfway up it. The form pages need this most — they used to be
   * centred in the viewport and would otherwise sit in the top third with a
   * large empty area beneath — but a thin content page benefits equally. */
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

/* `flex: 1 0 auto` is what pushes the footer down. `width: 100%` is what keeps
 * these the width they claim to be: a flex item with `margin: 0 auto` on the
 * cross axis loses the default stretch, so without it the column shrinks to fit
 * its content and stops lining up with everything else on the page. */
.prose,
.form-column,
.page-footer {
  width: 100%;
}

.prose,
.form-column {
  flex: 1 0 auto;
}

/* --- header ---------------------------------------------------------------
 * The band carries the gradient the login and consent screens used to own.
 * That is what makes the two surfaces read as one product rather than as one
 * palette.
 */

.site-header {
  background: radial-gradient(120% 300% at 20% 0%, var(--band-from), var(--band-to) 70%);
  border-bottom: 1px solid var(--rule);
  padding: 18px 24px;
  /* Wraps rather than scrolls or truncates: on a narrow screen the nav drops
   * under the logo, which is the one behaviour that needs no media query and
   * cannot hide a link. */
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 28px;
}

/* The markup names `logo_white.svg`, and memolok-auth's consent screen links
 * this stylesheet from another container with markup of its own naming the
 * same file. `logo_black.svg` is a different drawing — a black wordmark with
 * the brand gradient on the mark — so no `filter` can produce it; swapping it
 * in `content` is what keeps the change inside this one file. */
.site-logo {
  display: block;
  width: 176px;
  height: auto;
  content: url("/assets/logo_black.svg");
}

.site-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 20px;
  font-size: 0.92rem;
}

.site-nav a {
  color: var(--ink-soft);
  text-decoration: none;
}

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

/* --- prose column -------------------------------------------------------- */

.prose {
  max-width: 60rem;
  margin: 0 auto;
  /* No bottom padding: the last block's own margin is what separates the prose
   * from the footer's hairline. */
  padding: 40px 24px 0;
}

.prose > :first-child {
  margin-top: 0;
}

h1,
h2,
h3,
h4 {
  line-height: 1.25;
  font-weight: 650;
  margin: 2.2em 0 0.6em;
}

h1 {
  font-size: 2.05rem;
  margin-top: 0.4em;
}

h2 {
  font-size: 1.5rem;
  padding-top: 0.5em;
  border-top: 1px solid var(--rule);
}

h3 {
  font-size: 1.18rem;
}

h4 {
  font-size: 1rem;
  color: var(--ink-soft);
}

p,
ul,
ol {
  margin: 0 0 1.1em;
}

li {
  margin-bottom: 0.35em;
}

a {
  color: var(--accent);
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}

a:hover {
  color: var(--accent-bright);
}

hr {
  border: none;
  border-top: 1px solid var(--rule);
  margin: 2.5em 0;
}

/* Screenshots are the bulk of the corpus and are wider than the text column
 * on a phone. */
img {
  max-width: 100%;
  height: auto;
}

/* The header illustration every content page opens with, alone in its first
 * paragraph. It is artwork rather than a screenshot, so nothing in it needs the
 * full column to be legible — and at full column width it pushes the page's
 * heading below the fold on a laptop screen. */
.prose > p:first-child > img:only-child {
  display: block;
  max-width: min(100%, 25.6rem);
  margin: 1rem auto 5rem;
}

/* --- code ---------------------------------------------------------------- */

code {
  font-family: var(--font-code);
  font-size: 0.88em;
  background: var(--wash);
  border: 1px solid var(--rule);
  border-radius: 4px;
  padding: 0.1em 0.35em;
}

pre {
  background: var(--wash);
  border: 1px solid var(--rule);
  border-radius: 8px;
  padding: 14px 16px;
  overflow-x: auto;
  margin: 0 0 1.3em;
}

pre code {
  background: none;
  border: none;
  padding: 0;
  font-size: 0.86em;
}

/* --- tables --------------------------------------------------------------
 * Wrapped in nothing — the generator emits a bare <table> — so the table
 * itself scrolls rather than pushing the page sideways on a narrow screen. */

table {
  display: block;
  overflow-x: auto;
  width: 100%;
  border-collapse: collapse;
  margin: 0 0 1.4em;
  font-size: 0.95rem;
}

th,
td {
  border: 1px solid var(--rule);
  padding: 8px 12px;
  text-align: left;
  vertical-align: top;
}

th {
  background: var(--wash);
  font-weight: 650;
}

/* --- callouts ------------------------------------------------------------
 * Each variant maps two tokens and nothing else, so a retint is an edit to
 * `:root` and the shared box rules never fork.
 *
 * `.callout-generic` is the freeform case: any bracket text that is not one of
 * the known keywords. It must look deliberate rather than broken, because an
 * unrecognised title is a valid authoring choice, not an error.
 */

blockquote {
  margin: 0 0 1.4em;
  padding: 2px 18px;
  border-left: 3px solid var(--rule);
  color: var(--ink-soft);
}

.callout {
  padding: 16px 18px;
  border-left-width: 4px;
  border-left-color: var(--callout-accent);
  border-radius: 0 8px 8px 0;
  background: var(--callout-wash);
  color: var(--ink);
}

.callout > :last-child {
  margin-bottom: 0;
}

.callout-title {
  font-weight: 650;
  margin: 0 0 0.5em;
  color: var(--callout-accent, var(--ink-soft));
}

.callout-generic {
  --callout-accent: var(--callout-generic-accent);
  --callout-wash: var(--callout-generic-wash);
}

.callout-note {
  --callout-accent: var(--callout-note-accent);
  --callout-wash: var(--callout-note-wash);
}

.callout-tip {
  --callout-accent: var(--callout-tip-accent);
  --callout-wash: var(--callout-tip-wash);
}

.callout-important {
  --callout-accent: var(--callout-important-accent);
  --callout-wash: var(--callout-important-wash);
}

/* WARNING and CAUTION are distinct keywords to an author and the same colour
 * here, which is deliberate — they mean the same urgency. */
.callout-warning,
.callout-caution {
  --callout-accent: var(--callout-warning-accent);
  --callout-wash: var(--callout-warning-wash);
}

.callout-example {
  --callout-accent: var(--callout-example-accent);
  --callout-wash: var(--callout-example-wash);
}

/* --- directives ----------------------------------------------------------
 * `::: faq`, `::: glossary` and `::: term`.
 *
 * Almost everything these constructs do is invisible: the schema.org microdata
 * is the point, and a reader sees headings and prose either way. So there are no
 * tints here — nothing should suggest a callout, which is a different kind of
 * thing that fails a different way. The visible job is to keep a question
 * attached to its own answer, and to stop the structural nesting the markup
 * needs from contributing vertical space nobody asked for.
 */

.faq,
.glossary,
.term {
  margin: 1.8em 0;
}

/* Headings carry a large top margin so they separate sections of a page. Inside
 * an entry a heading is the first line of it, and the entry owns that spacing.
 * The `border-top` reset is for an `##` question: it would otherwise carry the
 * sitewide h2 hairline as well as the entry separator below, two rules a few
 * pixels apart. */
.faq-question,
.term-name {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}

.faq-answer :last-child,
.term-definition > :last-child {
  margin-bottom: 0;
}

/* A hairline between entries, so a glossary reads as a list of terms rather than
 * a run of headings. Between entries only — the first one is separated from the
 * prose above it by the container's own margin. */
.glossary .term + .term {
  margin-top: 1.5em;
  padding-top: 1.3em;
  border-top: 1px solid var(--rule);
}

/* Each question is a full-width grey bar with its answer set in beneath it, so
 * the page reads as a set of questions rather than as more body text. A flat
 * wash under a neutral top rule, never a coloured accent bar — that belongs to
 * callouts. The answer steps back to
 * --ink-soft so the questions scan as a list. */
.faq-item + .faq-item {
  margin-top: 1.6em;
}

.faq-question {
  font-size: 1.08rem;
  margin-bottom: 0;
  padding: 0.7em 1em;
  background: var(--wash);
  border-top: 0.1rem solid var(--rule-strong);
}

.faq-answer {
  color: var(--ink-soft);
  padding: 0.8em 1em 0;
}

/* --- page footer ---------------------------------------------------------
 * The "Last updated" witness. Same column geometry as `.prose` so the hairline
 * starts and ends where the text above it does, in `--ink-soft` so it reads as
 * metadata about the page rather than as the last sentence of it.
 */

.page-footer {
  max-width: 60rem;
  margin: 0 auto;
  padding: 0 24px 56px;
  color: var(--ink-soft);
  font-size: 0.85rem;
}

/* The hairline belongs to whichever element comes first, because the date is
 * absent on pages with no source document (the 404, the 500) and the footer
 * must not lose its separation from the prose when it is. */
.page-footer > :first-child {
  padding-top: 16px;
  border-top: 1px solid var(--rule);
}

.page-footer p {
  margin: 0;
}

.footer-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 18px;
  margin-top: 10px;
}

.page-footer > .footer-nav:first-child {
  margin-top: 0;
}

.footer-nav a {
  color: var(--ink-soft);
}

/* --- application pages ----------------------------------------------------
 * /login, /forgot-password, /reset-password, /password-ready, /request-access,
 * and memolok-auth's /auth/consent.
 *
 * These used to be viewport-centred (`body { display: flex; min-height: 100vh }`)
 * with no header at all, which is exactly what made them incompatible with the
 * prose system and forced two stylesheets. They now sit in ordinary document
 * flow under the same header band, so one file covers both surfaces.
 *
 * They get the band and a footer but **no navigation**, deliberately: the
 * consent screen is reached mid-OAuth from a third-party client, and the
 * password pages are single-task. Offering a way off them is a regression
 * wearing consistency's clothes.
 */

.form-column {
  /* One value, replacing the 380px of www's forms and the 420px of auth's
   * consent card. The consent screen is the widest thing here — a scope list
   * and two side-by-side buttons — so the single value is chosen to suit it. */
  width: 100%;
  max-width: 400px;
  margin: 0 auto;
  padding: 56px 24px 72px;
}

/* No `.brand` rule: the five form pages used to repeat the logo inside `<main>`
 * because they had no header at all. They have the shared header band now, so
 * a second logo below it would be the same image twice. */

.tagline {
  margin: 0 0 32px;
  color: var(--ink-soft);
  font-size: 0.95rem;
}

/* One name for what used to be `.panel` in password_ready and `.card` in the
 * consent screen, with identical rules under both.
 *
 * The child combinator on the form is load-bearing. On www's pages the form
 * *is* the card and is a direct child of the column. On the consent screen the
 * card is a `<div>` and the form sits inside it holding only the two buttons —
 * a descendant selector would give that form a second card treatment, nested
 * inside the first, which reads as a panel behind the buttons. */
.card,
.form-column > form {
  background: var(--wash);
  border: 1px solid var(--rule);
  border-radius: 12px;
  padding: 28px;
  backdrop-filter: blur(6px);
}

.form-column > form {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

/* Prose `h1` is sized for the top of an article. Inside a card it is a heading
 * on a component, and the consent question is long enough to wrap.
 *
 * Two selectors because the heading sits in two places. memolok-auth's screens
 * put it inside the card; www's form pages get theirs from `layouts/app.html`,
 * above the card, so that one heading covers the whole page whichever state the
 * body is in. One size for both, or the same page looks like two.
 *
 * **`.card h1` is memolok-auth's heading, and this file is the only stylesheet
 * that service loads.** Editing either value here retunes the consent screen —
 * a surface with no golden, no test, and no local dev server in this repo, so
 * nothing goes red. That is not an argument for splitting the rule: the two
 * services are one visual system on purpose. It is an argument for opening
 * `memolok-auth/memolok_auth/templates/pages/consent.html` before you touch it.
 *
 * The `8px` is deliberate and shared: it is the gap between a heading and the
 * soft line that belongs to it — www's `.tagline`, auth's `.client`. Anything
 * that is not that line gets the rule below instead. */
.card h1,
.form-column > h1 {
  font-size: 1.35rem;
  margin: 0 0 8px;
}

/* A tagline is a subtitle and belongs to the heading, so it keeps the tight
 * gap above. Everything else in this column is the page's actual work — the
 * card, the form, an error standing in for one — and needs air in front of it.
 * Margins collapse, so this is the gap on the pages with no tagline. */
.form-column > h1 + :not(.tagline) {
  margin-top: 24px;
}

label {
  font-size: 0.85rem;
  color: var(--label-ink);
  display: flex;
  flex-direction: column;
  gap: 6px;
}

input {
  font: inherit;
  padding: 10px 12px;
  border-radius: 8px;
  border: 1px solid var(--field-rule);
  background: var(--field);
  color: var(--ink);
}

input:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

button {
  font: inherit;
  font-weight: 600;
  padding: 11px 12px;
  border-radius: 8px;
  border: none;
  background: var(--accent);
  color: var(--on-accent);
  cursor: pointer;
}

button:hover {
  background: var(--accent-bright);
}

/* --- messages and secondary text on the form pages ----------------------- */

.error,
.notice {
  margin: 0;
  padding: 10px 12px;
  border-radius: 8px;
  font-size: 0.85rem;
}

.error {
  background: var(--danger-wash);
  border: 1px solid var(--danger-rule);
  color: var(--danger-ink);
}

.notice {
  background: var(--notice-wash);
  border: 1px solid var(--notice-rule);
  color: var(--notice-ink);
}

.hint {
  margin: 0;
  font-size: 0.8rem;
  color: var(--ink-faint);
}

/* `.forgot` on the login page and `.back` elsewhere were the same rule under
 * two names — the only real difference across the five copied stylesheets. */
.forgot,
.back {
  margin: 0;
  font-size: 0.85rem;
  text-align: center;
}

.forgot a,
.back a {
  color: var(--ink-soft);
}

/* "hp" stands for "honeypot": present in the markup for bots, invisible and
 * unreachable for a person. Off-screen rather than `display: none` — some
 * crawlers skip filling hidden fields that way, which would defeat the point.
 * Do not "simplify" this to a display or visibility rule. */
.hp {
  position: absolute;
  left: -9999px;
  top: auto;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* --- the consent screen ---------------------------------------------------
 * Served by memolok-auth, styled here. The client name is attacker-controlled,
 * so `word-break` matters: a registration with a very long unbroken name must
 * not be able to push the card wider than its column.
 */

.client {
  margin: 0 0 24px;
  color: var(--ink-soft);
  font-size: 0.85rem;
  word-break: break-all;
}

.scopes {
  list-style: none;
  margin: 0 0 24px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.scopes li {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  font-size: 0.9rem;
  color: var(--label-ink);
}

.scopes li::before {
  content: "";
  flex: none;
  width: 6px;
  height: 6px;
  margin-top: 7px;
  border-radius: 50%;
  background: var(--accent);
}

.actions {
  display: flex;
  gap: 12px;
}

.actions button {
  flex: 1;
}

.allow {
  background: var(--accent);
  color: var(--on-accent);
}

.allow:hover {
  background: var(--accent-bright);
}

.deny {
  background: transparent;
  color: var(--label-ink);
  border: 1px solid var(--field-rule);
}

.deny:hover {
  background: var(--wash);
}

.note {
  margin: 20px 0 0;
  font-size: 0.78rem;
  color: var(--ink-faint);
  line-height: 1.5;
}

@media (max-width: 600px) {
  body {
    font-size: 16px;
  }

  /* Logo and menu stacked and centred. CSS cannot tell when a flex row has
   * wrapped, so this stacks them at the breakpoint instead: the one-line header
   * needs ~580px, so above 600px it never wraps and below it never has to. */
  .site-header {
    flex-direction: column;
  }

  .site-nav {
    justify-content: center;
  }

  .prose {
    padding: 28px 18px 0;
  }

  .page-footer {
    padding: 0 18px 44px;
  }

  .form-column {
    padding: 40px 18px 56px;
  }

  h1 {
    font-size: 1.7rem;
  }
}
