<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light">
<title>Holger — log in</title>
<style>
:root {
color-scheme: light;
--bg: #f4f4f4;
--bg-rgb: 244 244 244;
--surface: oklch(1 0 0);
--raised: #e5e5e3;
--line: oklch(0.885 0.005 75);
--line-strong: oklch(0.775 0.006 75);
--ink: oklch(0.225 0.008 70);
--ink-dim: oklch(0.435 0.008 70);
--ink-mute: oklch(0.565 0.007 70);
--pass: oklch(0.52 0.15 155);
--bad: oklch(0.53 0.20 25);
--sans: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
--mono: ui-monospace, "JetBrains Mono", SFMono-Regular, Menlo, monospace;
--display: ui-serif, Georgia, "Iowan Old Style", "Palatino Linotype", Palatino, serif;
}
* { box-sizing: border-box; }
html, body { height: 100%; }
body {
margin: 0; display: flex; min-height: 100dvh;
background: var(--bg); color: var(--ink);
font: 14.5px/1.55 var(--sans); -webkit-font-smoothing: antialiased;
}
:root { --backdrop-image: url("/holger.webp"); }
.art {
flex: 0 0 50%;
background-color: var(--bg);
background-image:
linear-gradient(to right, rgb(var(--bg-rgb) / 0) calc(100% - 90px), rgb(var(--bg-rgb) / 1)),
var(--backdrop-image);
background-size: auto, contain;
background-position: center, center;
background-repeat: no-repeat, no-repeat;
}
.side {
flex: 1 1 50%; min-width: 0; min-height: 100dvh;
display: flex; flex-direction: column; gap: 1.6rem;
padding: clamp(1.25rem, 3.2vw, 2.75rem);
}
.entry { flex: 0 0 auto; max-width: 30rem; }
h1 {
margin: 0 0 .35rem;
font-family: var(--display);
font-size: 2.1rem; font-weight: 500; letter-spacing: -.012em;
}
.tagline {
margin: 0 0 1.25rem; font-family: var(--display); font-size: 1.05rem;
color: var(--ink-dim);
}
button {
width: 100%; padding: .62rem .8rem; border-radius: 6px;
border: 1px solid transparent; background: var(--raised); color: var(--ink);
font: inherit; font-size: .92rem; font-weight: 550; cursor: pointer;
transition: background .15s cubic-bezier(.16,1,.3,1), border-color .15s cubic-bezier(.16,1,.3,1);
}
button:hover:not(:disabled) { background: var(--raised); border-color: var(--ink-mute); }
button:disabled { opacity: .45; cursor: not-allowed; }
button.primary { background: var(--ink); border-color: var(--ink); color: var(--surface); }
button.primary:hover:not(:disabled) { background: oklch(0 0 0); border-color: oklch(0 0 0); }
button.primary.working { background: var(--surface); color: var(--ink); border-color: var(--ink); }
button.working:disabled { opacity: 1; cursor: progress; }
:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; border-radius: 5px; }
.msg { margin-top: .7rem; min-height: 1.2rem; font-size: .82rem; color: var(--ink-dim); }
.msg.err { color: var(--bad); font-family: var(--mono); overflow-wrap: anywhere; }
.msg.ok { color: var(--pass); }
.offer { margin: .9rem 0 0; color: var(--ink-dim); font-size: .86rem; }
.offer a { color: var(--ink); }
.offer code { font-family: var(--mono); font-size: .92em; }
details.reg { flex: 0 0 auto; margin-top: 1.4rem; max-width: 30rem; border-top: 1px solid var(--line); padding-top: 1.1rem; }
details.reg summary { cursor: pointer; font-size: .82rem; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-mute); }
details.reg summary::-webkit-details-marker { color: var(--ink-mute); }
.field { margin-top: .75rem; display: flex; flex-direction: column; gap: .3rem; }
[hidden] { display: none !important; }
.field label { font-size: .78rem; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-mute); }
.field input, .field select {
padding: .5rem .6rem; border-radius: 6px; border: 1px solid var(--line-strong);
background: var(--surface); color: var(--ink); font: inherit; font-size: .9rem;
}
.field input { font-family: var(--mono); }
.reg button { margin-top: 1rem; }
.by {
flex: 0 0 auto;
display: flex; align-items: center; flex-wrap: wrap; gap: .38rem;
margin: auto 0 0; font-family: var(--display); font-size: .95rem; color: var(--ink-dim);
}
.by strong { font-weight: 600; color: var(--ink); }
.by a { color: inherit; border-radius: 3px; }
@media (max-width: 720px) {
body { display: block; }
.art {
position: fixed; inset: 0; z-index: 0; pointer-events: none;
opacity: .16; filter: saturate(.9);
}
.side {
position: relative; z-index: 1; width: 100%;
height: auto; min-height: 100dvh;
padding: 1.25rem 1.1rem; gap: 1.2rem;
}
button { border-color: var(--line-strong); }
}
@media (prefers-reduced-motion: reduce) { * { transition: none !important; } }
</style>
</head>
<body>
<div class="art" aria-hidden="true"></div>
<div class="side">
<main class="entry">
<h1>Holger</h1>
<p class="tagline">Immutable artifact repository in pure Rust</p>
<button id="login" class="primary">Log in</button>
<div id="msg" class="msg" role="status" aria-live="polite"></div>
<p class="offer" id="invite-offer" hidden></p>
</main>
<details class="reg" id="reg">
<summary id="reg-summary">Register the first passkey</summary>
<div class="field" id="token-row">
<label for="token">Break-glass admin token</label>
<input id="token" type="password" autocomplete="off" spellcheck="false"
placeholder="the bootstrap bearer holger was installed with">
</div>
<div class="field" id="user-row">
<label for="user">User name</label>
<input id="user" type="text" autocomplete="username" spellcheck="false"
placeholder="lowercase, [a-z0-9._-]">
</div>
<div class="field">
<label for="label">Key label</label>
<input id="label" type="text" spellcheck="false" placeholder="yubikey">
</div>
<div class="field" id="role-row">
<label for="role">Role</label>
<select id="role">
<option value="viewer">viewer — read the console</option>
<option value="operator">operator — artifact work</option>
<option value="admin">admin — promotion, replication, passkeys</option>
</select>
</div>
<button id="register">Register this passkey</button>
</details>
<p class="by">
by
<a href="https://www.vetra.se" target="_blank" rel="noopener noreferrer"><strong>Vetra AB</strong></a>
&
<strong>Ignalina ApS</strong>
</p>
</div>
<script src="/login-helpers.js"></script>
<script src="/login.js"></script>
</body>
</html>