/* ---------------------------------------------------------------------------
   xx4h branding for mokey — served as site.css at /css/styles.

   Loaded after bootstrap.min.css and mokey's own style.css, so this file is
   pure override. The tokens below are copied from src/styles/global.css in the
   xx4h.de repo; everything after them re-points Bootstrap at those tokens so
   the portal reads as part of the same site.

   Two things shape how it is written:

   - mokey vendors Bootstrap 5.1.3, which predates `data-bs-theme` and
     hard-codes its component colours, so the dark theme is written out by hand
     rather than switched on.
   - Bootstrap's utility classes (.bg-white, .text-muted, .shadow, …) are
     generated with !important, so overriding one needs !important too. Those
     are the only places it appears here.

   Colour scheme follows prefers-color-scheme until the navbar toggle pins a
   choice, which it stores in localStorage and reflects as data-theme on <html>
   (see assets/templates/header.html). data-theme must therefore beat the media
   query in both directions, which is why each palette is written twice.
   --------------------------------------------------------------------------- */

:root {
  /* System stacks, matching xx4h.de: no webfont download, and the mono stack
     is what a terminal would show. mokey's style.css asks for Roboto and its
     @font-face files ship in the image; overriding body font-family below
     means they are simply never requested. */
  --font-sans: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  --font-mono: ui-monospace, 'SFMono-Regular', 'JetBrains Mono', 'Fira Code', Menlo, Consolas, monospace;

  --bg: #fbfbfc;
  --bg-inset: #f1f1f3;
  --surface: #ffffff;
  --fg: #18181b;
  --fg-muted: #64646e;
  --fg-faint: #8e8e98;
  --border: #e0e0e4;
  --border-strong: #c8c8d0;
  --accent: #e03131;
  --accent-fg: #ffffff;
  --ok: #1a7f37;
  --danger: #b42318;
  /* xx4h.de has no warning colour — nothing there warns. mokey's "enable MFA"
     banner does, so this is the one token added rather than copied. */
  --warn: #9a6700;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0a0a0b;
    --bg-inset: #141417;
    --surface: #121215;
    --fg: #e7e7ea;
    --fg-muted: #9b9ba5;
    --fg-faint: #6b6b76;
    --border: #26262b;
    --border-strong: #3a3a42;
    --accent: #ff5555;
    --accent-fg: #0a0a0b;
    --ok: #3fb950;
    --danger: #f47067;
    --warn: #d29922;
  }
}

:root[data-theme='light'] {
  --bg: #fbfbfc;
  --bg-inset: #f1f1f3;
  --surface: #ffffff;
  --fg: #18181b;
  --fg-muted: #64646e;
  --fg-faint: #8e8e98;
  --border: #e0e0e4;
  --border-strong: #c8c8d0;
  --accent: #e03131;
  --accent-fg: #ffffff;
  --ok: #1a7f37;
  --danger: #b42318;
  --warn: #9a6700;
}

:root[data-theme='dark'] {
  --bg: #0a0a0b;
  --bg-inset: #141417;
  --surface: #121215;
  --fg: #e7e7ea;
  --fg-muted: #9b9ba5;
  --fg-faint: #6b6b76;
  --border: #26262b;
  --border-strong: #3a3a42;
  --accent: #ff5555;
  --accent-fg: #0a0a0b;
  --ok: #3fb950;
  --danger: #f47067;
  --warn: #d29922;
}

/* ---------------------------------------------------------------------------
   Base
   --------------------------------------------------------------------------- */

body {
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-sans);
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
}

h1,
h2,
h3,
h4,
h5 {
  line-height: 1.25;
  font-weight: 650;
  letter-spacing: -0.011em;
  text-wrap: balance;
}

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

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

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  box-shadow: none;
}

hr {
  border-top-color: var(--border);
  opacity: 1;
}

code,
samp {
  font-family: var(--font-mono);
  font-size: 0.9em;
  color: var(--fg);
  background: var(--bg-inset);
  border: 1px solid var(--border);
  padding: 0.1em 0.35em;
  /* SSH fingerprints are long enough to wrap; without this the box is drawn
     once across both lines and the border lands in the middle of the text. */
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
}

.text-muted {
  color: var(--fg-muted) !important;
}

.text-primary {
  color: var(--accent) !important;
}

.text-light {
  color: var(--fg) !important;
}

/* Nothing in this design is raised off the page; edges are hairlines. */
.shadow {
  box-shadow: none !important;
}

.rounded-3,
.rounded-pill {
  border-radius: 0 !important;
}

.border,
.border-top,
.border-end {
  border-color: var(--border) !important;
}

/* ---------------------------------------------------------------------------
   Header

   `navbar-inverse` and `navbar-fixed-top` are Bootstrap 3 class names that
   survived into mokey's markup; Bootstrap 5 defines neither, so the bar is a
   plain static block that mokey's style.css paints blue. Sticky positioning
   has to be asked for here, and .main-content's 100px top padding (which was
   compensating for a fixed bar that never was one) comes back down to the
   spacing xx4h.de uses.
   --------------------------------------------------------------------------- */

.navbar-inverse {
  position: sticky;
  top: 0;
  z-index: 1030;
  background-color: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--border);
  padding-block: 0;
}

.navbar-inverse .container {
  display: flex;
  align-items: center;
  gap: 1rem;
  min-height: 3.25rem;
}

.navbar-brand {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0;
  margin: 0;
  color: var(--fg);
  text-decoration: none;
}

.navbar-brand:hover {
  color: var(--fg);
}

.brand-mark {
  width: 1.5rem;
  height: 1.5rem;
  flex: none;
  fill: var(--accent);
}

/* The logo <img> in the header mokey renders for itself, when
   site.templates_dir is unset and assets/templates/header.html is therefore
   never read. It needs an explicit size and flex: none — the container above
   is a flex row, and an image with no CSS size gets shrunk to nothing in one.
   Without this rule the stock navbar comes out empty, which is exactly what
   it did. */
#site-logo {
  /* Sized so the baked-in wordmark lands at roughly the same size as the live
     one above, i.e. 1rem for "xx4h" and 0.75rem for the suffix. */
  height: 2.25rem;
  width: auto;
  flex: none;
}

.brand-name {
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 1rem;
  letter-spacing: -0.02em;
}

.brand-sub {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--fg-faint);
}

.brand-sub::before {
  content: '/ ';
}

/* The portal is one page deep, so the sub-label is the first thing to go. */
@media (max-width: 22rem) {
  .brand-sub {
    display: none;
  }
}

/* ---------------------------------------------------------------------------
   Theme toggle

   The mark's halves show the *chosen* scheme's page background — light over
   dark on auto, one colour across both halves when pinned. Fixed literals (the
   two --bg values), not tokens: they must not follow the active theme. The
   hairline stroke keeps the light half visible on a light header and the dark
   half on a dark one.
   --------------------------------------------------------------------------- */

.theme-toggle {
  margin-inline-start: auto;
  display: inline-flex;
  align-items: center;
  border: none;
  background: transparent;
  padding: 0.3rem 0.4rem;
  cursor: pointer;
  line-height: 1;
}

.theme-toggle__mark {
  display: block;
  width: 1.35rem;
  height: 1.35rem;
}

.theme-toggle__half {
  fill: #fbfbfc;
  stroke: var(--border-strong);
  stroke-width: 16;
}

.theme-toggle__half--dark {
  fill: #0a0a0b;
}

.theme-toggle[data-mode='light'] .theme-toggle__half--dark {
  fill: #fbfbfc;
}

.theme-toggle[data-mode='dark'] .theme-toggle__half--light {
  fill: #0a0a0b;
}

/* ---------------------------------------------------------------------------
   Page shell and cards
   --------------------------------------------------------------------------- */

.main-content {
  padding-top: clamp(2rem, 6vw, 3.5rem);
  padding-bottom: clamp(2rem, 6vw, 3.5rem);
}

.bg-white {
  background-color: var(--surface) !important;
}

/* The account shell (index.html) and the login/signup card. Both arrive as
   rounded, shadowed white boxes; here they are flat panels with a hairline. */
.bg-white.shadow,
.login-card {
  border: 1px solid var(--border);
  box-shadow: none;
}

.login-card {
  background: var(--surface);
}

/* The login card's dark header band reads as a heavy slab against a flat page;
   an inset strip with a rule under it carries the same "this is the title"
   signal without the weight. */
.login-head {
  background-color: var(--bg-inset) !important;
  color: var(--fg) !important;
  border-bottom: 1px solid var(--border);
  padding-block: 1rem !important;
}

.login-head h3 {
  font-size: 1.05rem;
  font-family: var(--font-mono);
  font-weight: 600;
  letter-spacing: 0.02em;
}

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 0;
}

.card-header {
  background: var(--bg-inset);
  border-bottom: 1px solid var(--border);
  font-family: var(--font-mono);
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--fg-muted);
}

.list-group-item {
  background: var(--surface);
  border-color: var(--border);
  color: var(--fg);
}

.page-header h1,
.tab-content h3 {
  font-size: 1.35rem;
}

/* ---------------------------------------------------------------------------
   Account sidebar

   The avatar is a black-filled PNG from upstream's static tree, which we do not
   override — it turns into a solid blob on a dark page. Swapped for the
   FontAwesome glyph that is already loaded, drawn in the muted foreground so it
   sits back the way the rest of the chrome does.
   --------------------------------------------------------------------------- */

.profile-tab-nav {
  background: var(--bg-inset);
}

.img-circle img {
  display: none;
}

.img-circle::before {
  content: '\f007';
  font-family: 'Font Awesome 6 Free';
  font-weight: 900;
  font-size: 2.25rem;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 4.5rem;
  height: 4.5rem;
  color: var(--fg-faint);
  border: 1px solid var(--border-strong);
}

.profile-tab-nav h4 {
  font-family: var(--font-mono);
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--fg);
  word-break: break-all;
}

.nav-pills a.nav-link {
  font-family: var(--font-mono);
  font-size: 0.8rem;
  letter-spacing: 0.02em;
  color: var(--fg-muted);
  border-bottom: 1px solid var(--border);
  border-left: 2px solid transparent;
  border-radius: 0;
  padding: 0.7rem 1rem;
}

.nav-pills a.nav-link:hover {
  color: var(--accent);
  background: var(--surface);
}

/* Marked by an accent edge rather than a filled pill: the sidebar stays quiet
   and the current tab still reads at a glance. */
.nav-pills .nav-link.active,
.nav-pills .show > .nav-link {
  background-color: var(--surface);
  color: var(--fg);
  border-left-color: var(--accent);
}

/* ---------------------------------------------------------------------------
   Forms
   --------------------------------------------------------------------------- */

.form-label {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--fg-muted);
  margin-bottom: 0.35rem;
}

.form-control {
  background-color: var(--bg-inset);
  color: var(--fg);
  border: 1px solid var(--border-strong);
  border-radius: 0;
  font-family: var(--font-mono);
  font-size: 0.9rem;
}

/* iOS Safari zooms the page in when a focused input is under 16px, and the
   large login inputs are the ones a phone actually meets. */
@media (pointer: coarse) {
  .form-control,
  .form-control-lg {
    font-size: 16px;
  }
}

.form-control:focus {
  background-color: var(--surface);
  color: var(--fg);
  border-color: var(--accent);
  box-shadow: none;
}

.form-control::placeholder {
  color: var(--fg-faint);
}

.form-control:disabled,
.form-control[readonly] {
  background-color: var(--bg);
  color: var(--fg-muted);
  border-style: dashed;
}

/* Chrome paints its own pale yellow over autofilled inputs, which is unreadable
   on the dark palette. An inset shadow the size of the field is the only way to
   repaint it. */
.form-control:-webkit-autofill,
.form-control:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--fg);
  box-shadow: 0 0 0 100px var(--bg-inset) inset;
}

.form-text {
  color: var(--fg-muted);
  font-size: 0.8rem;
}

.form-control-plaintext {
  color: var(--fg);
}

/* ---------------------------------------------------------------------------
   Buttons
   --------------------------------------------------------------------------- */

.btn {
  border-radius: 0;
  font-family: var(--font-mono);
  font-size: 0.8rem;
  letter-spacing: 0.02em;
  padding: 0.5rem 0.9rem;
}

.btn-lg {
  font-size: 0.9rem;
  padding: 0.65rem 1.1rem;
}

.btn-sm {
  font-size: 0.72rem;
  padding: 0.35rem 0.6rem;
}

.btn:focus,
.btn:active:focus {
  box-shadow: none;
}

.btn-primary {
  background-color: var(--accent);
  border-color: var(--accent);
  color: var(--accent-fg);
}

.btn-primary:hover,
.btn-primary:focus,
.btn-primary:active {
  background-color: var(--fg);
  border-color: var(--fg);
  color: var(--bg);
}

.btn-secondary {
  background-color: transparent;
  border-color: var(--border-strong);
  color: var(--fg-muted);
}

.btn-secondary:hover,
.btn-secondary:focus,
.btn-secondary:active {
  background-color: var(--bg-inset);
  border-color: var(--fg-muted);
  color: var(--fg);
}

/* "Enabled" states (MFA on, token active) — outlined, not filled: they report
   a state rather than asking to be pressed. */
.btn-success {
  background-color: transparent;
  border-color: var(--ok);
  color: var(--ok);
}

.btn-success:hover,
.btn-success:focus,
.btn-success:active {
  background-color: var(--ok);
  border-color: var(--ok);
  color: var(--bg);
}

.btn-outline-danger {
  border-color: var(--danger);
  color: var(--danger);
}

.btn-outline-danger:hover,
.btn-outline-danger:focus,
.btn-outline-danger:active {
  background-color: var(--danger);
  border-color: var(--danger);
  color: var(--bg);
}

.btn-link {
  color: var(--accent);
  font-family: inherit;
}

/* Bootstrap 5.1 draws the dismiss "×" as a black SVG data-URI and has no
   light variant (that arrived in 5.3), so it has to be inverted by filter. */
@media (prefers-color-scheme: dark) {
  .btn-close {
    filter: invert(1) grayscale(100%) brightness(200%);
  }
}

:root[data-theme='light'] .btn-close {
  filter: none;
}

:root[data-theme='dark'] .btn-close {
  filter: invert(1) grayscale(100%) brightness(200%);
}

/* ---------------------------------------------------------------------------
   Alerts and badges

   Bootstrap's alerts are pastel fills that have no dark equivalent. These are
   surface panels with a coloured left edge instead — one rule that works on
   both palettes.
   --------------------------------------------------------------------------- */

.alert {
  background-color: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--border-strong);
  border-radius: 0;
  color: var(--fg);
  font-size: 0.9rem;
}

.alert-danger {
  border-left-color: var(--danger);
}

.alert-warning {
  border-left-color: var(--warn);
}

.alert-success {
  border-left-color: var(--ok);
}

/* Group memberships, rendered as .badge.bg-dark pills. */
.badge {
  border-radius: 0;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.02em;
}

.badge.bg-dark {
  background-color: var(--bg-inset) !important;
  color: var(--fg-muted);
  border: 1px solid var(--border);
}

.badge.bg-success {
  background-color: transparent !important;
  color: var(--ok);
  border: 1px solid var(--ok);
}

/* ---------------------------------------------------------------------------
   Modals

   Bootstrap's own (SSH key / OTP token dialogs) and SweetAlert2's confirm
   prompts. The confirm/cancel button colours in the SweetAlert calls are set
   inline by mokey's templates and are left alone — only the surrounding chrome
   is retinted.
   --------------------------------------------------------------------------- */

.modal-content,
.swal2-popup {
  background: var(--surface);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 0;
}

/* Inset, like .card-header: a dialog's title and action bars are chrome, and
   on the dark palette a plain surface leaves them indistinguishable from the
   body they frame. */
.modal-header,
.modal-footer {
  background: var(--bg-inset);
  border-color: var(--border);
}

.modal-title,
.swal2-title {
  font-size: 1.05rem;
  color: var(--fg);
}

.swal2-html-container {
  color: var(--fg-muted);
}

.swal2-styled {
  border-radius: 0 !important;
  font-family: var(--font-mono);
  font-size: 0.8rem;
}

.swal2-styled.swal2-cancel {
  background-color: transparent !important;
  border: 1px solid var(--border-strong) !important;
  color: var(--fg-muted) !important;
}

/* ---------------------------------------------------------------------------
   Footer
   --------------------------------------------------------------------------- */

footer.d-flex {
  border-top-color: var(--border) !important;
  font-family: var(--font-mono);
  font-size: 0.75rem;
}

footer.d-flex a {
  color: var(--fg-muted);
  text-decoration: none;
}

footer.d-flex a:hover {
  color: var(--accent);
}

/* ---------------------------------------------------------------------------
   OTP enrolment

   The QR code and its instruction image are black-on-transparent PNGs from
   upstream's static tree. A white plate keeps both scannable and visible
   whatever the page is doing behind them.
   --------------------------------------------------------------------------- */

img[alt='QRCode'],
img[alt='Scan QR Code'] {
  background: #ffffff;
  padding: 0.75rem;
  border: 1px solid var(--border);
}
