@font-face { font-family:'DM Sans'; font-style:normal; font-weight:100 1000; font-display:swap; src:url('fonts/dm-sans-latin-ext.woff2') format('woff2'); unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family:'DM Sans'; font-style:normal; font-weight:100 1000; font-display:swap; src:url('fonts/dm-sans-latin.woff2') format('woff2'); unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family:'Space Grotesk'; font-style:normal; font-weight:300 700; font-display:swap; src:url('fonts/space-grotesk-latin-ext.woff2') format('woff2'); unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family:'Space Grotesk'; font-style:normal; font-weight:300 700; font-display:swap; src:url('fonts/space-grotesk-latin.woff2') format('woff2'); unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
/* The same palette the applications use, so arriving at the sign-in does not feel like leaving. */
:root { --ink:#172329; --muted:#718083; --paper:#f5f7f3; --line:#dde4df; --accent:#e3a25d; --teal:#3b7772; }
* { box-sizing:border-box; }
input { user-select:text; -webkit-user-select:text; }
[hidden] { display:none !important; }
html { -webkit-text-size-adjust:100%; text-size-adjust:100%; }
body { margin:0; min-height:100vh; background:radial-gradient(circle at 100% 0%, #e8efe8 0, transparent 35%), var(--paper); color:var(--ink); font-family:'DM Sans', sans-serif; }
button,input { font:inherit; }
button { cursor:pointer; }
h1,h2 { font-family:'Space Grotesk', sans-serif; margin:0; letter-spacing:0; }
h1 { font-size:clamp(2rem, 9vw, 3.4rem); max-width:520px; }
h2 { font-size:1.35rem; }
a { color:var(--teal); }
.kicker { color:var(--teal); font-size:.68rem; font-weight:700; letter-spacing:.14em; margin:0 0 7px; text-transform:uppercase; }
.muted { color:var(--muted); }

.shell { align-items:flex-start; display:flex; flex-direction:column; justify-content:center; min-height:100vh; padding:36px 0; width:min(100% - 32px, 680px); margin:0 auto; }
/* The icon draws its own rounded-square ground, so there is nothing to clip or colour here — this
   only reserves the space, which keeps the heading from jumping as the file arrives. */
.mark { display:block; height:64px; margin-bottom:26px; width:64px; }
.shell > .muted { margin:10px 0 28px; max-width:520px; line-height:1.5; }

form { display:flex; flex-direction:column; gap:9px; max-width:480px; width:100%; }
input[type=text],input[type=password] { background:#fff; border:1px solid var(--line); border-radius:5px; min-width:0; padding:14px; width:100%; }
input[type=text]:focus,input[type=password]:focus { border-color:var(--teal); outline:none; }
.actions { display:flex; gap:9px; margin-top:6px; }
.primary-button { background:var(--ink); border:0; border-radius:5px; color:var(--paper); flex:1; font-weight:700; min-height:48px; padding:0 18px; }
.ghost-button { background:transparent; border:1px solid var(--line); border-radius:5px; color:var(--ink); font-weight:500; min-height:48px; padding:0 18px; text-align:center; text-decoration:none; display:inline-flex; align-items:center; justify-content:center; }
.danger-button { background:#b85e53; }
.link-button { background:none; border:0; color:var(--teal); font-weight:700; padding:0; text-decoration:underline; }
.switch { color:var(--muted); font-size:.85rem; margin:16px 0 0; }
/* Spoken in the same voice as a field label, because it is one: the error belongs to the form, not
   to the page, and floating it at the top would separate it from what has to be retyped. */
.notice { background:#fbeee9; border:1px solid #e6c3b6; border-radius:5px; color:#8c4436; font-size:.88rem; line-height:1.45; margin:0 0 4px; padding:11px 13px; }
.consent { align-items:flex-start; color:var(--muted); display:flex; font-size:.85rem; gap:9px; line-height:1.4; margin:6px 0 2px; }
.consent input { margin-top:2px; }
/* Every field carries a label now, hidden because the placeholder already says the same thing on
   screen. It is not decoration: a placeholder is not an accessible name, and it is not what a
   password manager reads either — between the label, the id and the field name, there is nothing
   left for one to guess at. Positioned out of flow so the form's own gap does not count it. */
.sr-only { position:absolute; width:1px; height:1px; margin:-1px; padding:0; border:0; overflow:hidden; white-space:nowrap; clip-path:inset(50%); }

/* The shelf of apps, in either of the two shapes the toggle above it offers. Tiles are the default
   because there are a handful of apps and an icon is easier to aim at than a line of text; the list
   is what stays readable once there are more of them than fit across the page. Both are the same
   markup with the same icon — swapping them rebuilds nothing, which is why the choice can live on
   the device and be applied before the page is painted. */
.section-heading { align-items:center; display:flex; justify-content:space-between; margin:14px 0 10px; width:100%; }
.section-heading .count { color:var(--muted); font-size:.78rem; }
/* The same control the apps carry, down to the glyphs. Which button reads as pressed is derived from
   the layout class on <html> rather than from an .active class the way Tracktivity and Libri do it —
   they redraw their whole view in JavaScript anyway, whereas this page arrives already rendered and
   would otherwise show the wrong button lit for a frame. */
.layout-toggle { background:#fff; border:1px solid var(--line); border-radius:5px; display:flex; flex:none; overflow:hidden; }
.layout-button { background:transparent; border:0; color:var(--muted); font-size:.95rem; line-height:1; padding:8px 10px; }
.layout-button[data-layout=tiles],html.layout-list .layout-button[data-layout=list] { background:var(--teal); color:#fff; }
html.layout-list .layout-button[data-layout=tiles] { background:transparent; color:var(--muted); }

/* What the two shapes share. Everything that differs between them is written out under one of the
   two blocks below rather than as a modifier here, because the difference is not a detail: one is a
   home screen and the other is a directory. */
.apps { display:grid; gap:11px; list-style:none; margin:0; padding:0; width:100%; }
.app-card { background:#fff; border:1px solid var(--line); border-radius:8px; color:var(--ink); display:flex; text-decoration:none; }
.app-card:hover { border-color:var(--teal); }
.app-icon { border-radius:11px; display:block; flex:none; height:48px; width:48px; }
/* Nothing has been copied in for this one. An initial in the house colours reads as a deliberate
   placeholder rather than as a picture that failed to load. */
.app-monogram { align-items:center; background:var(--ink); color:var(--accent); display:flex; font-family:'Space Grotesk', sans-serif; font-size:1.4rem; font-weight:700; justify-content:center; }
.app-text { min-width:0; }
.app-text strong { display:block; font-family:'Space Grotesk', sans-serif; font-size:1.05rem; overflow-wrap:break-word; }
.app-text span { color:var(--muted); font-size:.8rem; }

/* Tiles: the icon over the name, centred, several across. `height:100%` keeps a row level when one
   name wraps and its neighbours do not. */
html:not(.layout-list) .apps { grid-template-columns:repeat(auto-fill, minmax(190px, 1fr)); }
html:not(.layout-list) .app-card { align-items:center; flex-direction:column; gap:11px; height:100%; padding:18px 14px; text-align:center; }
/* A phone leaves 328px of shelf, and at a 190px minimum track the grid quietly folds to one column —
   which is the list again wearing the other button's highlight. Three across is what makes this a
   home screen instead, and at that width the second line has nowhere to go, so it stands down in
   favour of the name. The list is untouched here: it is meant to be one column. */
@media (max-width:520px) {
  html:not(.layout-list) .apps { gap:9px; grid-template-columns:repeat(3, minmax(0, 1fr)); }
  html:not(.layout-list) .app-card { gap:8px; padding:13px 7px; }
  html:not(.layout-list) .app-icon { border-radius:10px; height:44px; width:44px; }
  html:not(.layout-list) .app-monogram { font-size:1.2rem; }
  html:not(.layout-list) .app-text strong { font-size:.82rem; line-height:1.25; }
  html:not(.layout-list) .app-text span { display:none; }
}

/* The list: one row each, icon at the left, and the line the tiles cannot always afford. */
html.layout-list .apps { gap:8px; grid-template-columns:1fr; }
html.layout-list .app-card { align-items:center; flex-direction:row; gap:14px; padding:12px 15px; }
html.layout-list .app-icon { border-radius:9px; height:38px; width:38px; }
html.layout-list .app-monogram { font-size:1.15rem; }
.section { border-top:1px solid var(--line); margin-top:34px; padding-top:22px; width:100%; }
.section h2 { margin-bottom:12px; }
.row { align-items:center; display:flex; flex-wrap:wrap; gap:9px; margin-top:14px; }
.legal { line-height:1.6; max-width:620px; }
.legal h2 { font-size:1.1rem; margin:26px 0 8px; }
.legal p,.legal li { color:var(--muted); font-size:.92rem; }

.build { color:var(--muted); font-size:.72rem; letter-spacing:.04em; margin:38px 0 0; }

/* The approval queue. Both answers sit next to each other rather than one of them behind a menu:
   refusing deletes an account, and hiding it makes it likelier to be pressed by accident, not less. */
.queue { display:grid; gap:9px; list-style:none; margin:14px 0 0; padding:0; width:100%; }
.queue li { align-items:center; background:#fff; border:1px solid var(--line); border-radius:8px; display:flex; flex-wrap:wrap; gap:12px; justify-content:space-between; padding:14px 16px; }
.queue strong { display:block; font-family:'Space Grotesk', sans-serif; font-size:1.05rem; }
.queue span { font-size:.8rem; }
.answer { align-items:center; display:flex; gap:8px; }
.answer form { max-width:none; width:auto; }
.answer button,.answer .ghost-button { flex:0 0 auto; font-size:.9rem; min-height:42px; padding:0 16px; }

/* The data browser. The only pages here wider than a form: a table of twelve columns squeezed into
   680px is a table nobody can read, so these shells take the window and the grid scrolls inside its
   own frame rather than dragging the whole page sideways on a phone. */
.shell-wide { width:min(100% - 32px, 1400px); justify-content:flex-start; }
.shell-wide h1 { max-width:none; }
.kicker a { color:inherit; text-decoration:none; }
.dbname { color:var(--muted); font-family:'DM Sans', sans-serif; font-size:.8rem; font-weight:400; margin-left:6px; }
code { background:#e8eee9; border-radius:3px; font-size:.86em; padding:1px 4px; }
.tables { display:grid; gap:8px; grid-template-columns:repeat(auto-fill, minmax(210px, 1fr)); list-style:none; margin:0; padding:0; }
.tables a { background:#fff; border:1px solid var(--line); border-radius:8px; color:var(--ink); display:block; padding:11px 14px; text-decoration:none; }
.tables a:hover { border-color:var(--teal); }
.tables strong { display:block; font-family:'Space Grotesk', sans-serif; overflow-wrap:anywhere; }
.tables span { color:var(--muted); font-size:.8rem; }
.grid-wrap { background:#fff; border:1px solid var(--line); border-radius:8px; max-width:100%; overflow:auto; width:100%; }
/* Tall enough to be the page, short enough that the header row stays in sight while it scrolls. */
.grid-wrap.tall { max-height:calc(100vh - 150px); }
.grid { border-collapse:collapse; font-size:.82rem; width:100%; }
.grid th,.grid td { border-bottom:1px solid var(--line); padding:7px 11px; text-align:left; vertical-align:top; }
.grid th { background:#f0f4f0; position:sticky; top:0; white-space:nowrap; z-index:1; }
.grid th a { color:var(--ink); text-decoration:none; }
.grid th a:hover { color:var(--teal); }
.grid th small { color:var(--muted); display:block; font-size:.68rem; font-weight:400; }
.grid th .arrow { color:var(--teal); font-size:.7rem; }
.grid th .key { color:var(--accent); }
/* One line a cell, cut short; the whole value is in the title for the cell that needs it. */
.grid td { max-width:32ch; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.grid tbody tr:hover td { background:#f6f9f6; }
.grid .num { font-variant-numeric:tabular-nums; text-align:right; }
.grid .when { font-variant-numeric:tabular-nums; }
.grid .null,.grid .null * { color:#b3bdbf; font-style:italic; }
.grid .redacted { color:var(--muted); letter-spacing:.1em; }
.grid .bool { color:var(--teal); }
.grid .mirror { color:var(--teal); text-align:center; }
.pager { align-items:center; display:flex; flex-wrap:wrap; gap:8px; margin:10px 0; }
.pager .ghost-button { font-size:.85rem; min-height:36px; padding:0 12px; }
.admin-bar { display:flex; flex-wrap:wrap; gap:8px; margin:-14px 0 22px; }
.admin-bar .ghost-button { background:#fff; font-size:.88rem; min-height:40px; padding:0 14px; }
/* A reset code is read aloud or copied once, so it gets the room to be read: wrapped at any
   character rather than overflowing on a phone, and large enough to tell an l from a 1. */
.reset-code { background:#fff; border:1px solid var(--line); border-radius:8px; margin:14px 0; padding:14px 16px; width:100%; }
.reset-code code { background:none; font-size:1.05rem; overflow-wrap:anywhere; padding:0; user-select:all; }
