
:root {
  --bg-scheme: hsl(220 15% 10%);
  --card-scheme: hsl(220 15% 14%);
  --border-scheme: hsl(220 12% 22%);
  --text-scheme: hsl(220 15% 88%);
  --muted-scheme: hsl(220 10% 62%);
  --accent-fallback: hsl(210 90% 60%);
  --positive-fallback: hsl(140 60% 45%);
  --negative-fallback: hsl(0 70% 55%);
  /* theme overrides arrive as *-hsl vars injected by the renderer;
     absent, the scheme defaults above apply */
  --bg: var(--bg-override, var(--bg-scheme));
  --card-solid: var(--card-override, var(--card-scheme));
  --card: color-mix(in srgb, var(--card-solid) var(--card-opacity, 100%), transparent);
  --border: var(--border-override, var(--border-scheme));
  --text: var(--text-override, var(--text-scheme));
  --muted: var(--muted-override, var(--muted-scheme));
  --accent: var(--accent-override, var(--accent-fallback));
  --positive: var(--positive-override, var(--positive-fallback));
  --negative: var(--negative-override, var(--negative-fallback));
  --radius: 10px;
  --radius-sm: 6px;
  --font: ui-sans-serif, system-ui, -apple-system, sans-serif;
  --font-size: 15px;
}
@media (prefers-color-scheme: light) {
  :root {
    --bg-scheme: hsl(220 25% 96%);
    --card-scheme: hsl(0 0% 100%);
    --border-scheme: hsl(220 15% 86%);
    --text-scheme: hsl(220 25% 16%);
    --muted-scheme: hsl(220 10% 42%);
    /* light surfaces need darker brand/status colors to clear 4.5:1
       (accent 60% on white is 2.97) - theme overrides still win */
    --accent-fallback: hsl(210 90% 44%);
    --positive-fallback: hsl(140 60% 32%);
    --negative-fallback: hsl(0 70% 44%);
  }
}
* { box-sizing: border-box; }
html { font-size: calc(var(--font-size, 15px) * 16 / 15); }
body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: var(--font-size)/1.45 var(--font);
}
header {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  padding: 1rem 1.25rem 0.25rem;
}
header h1 { font-size: 1.05rem; margin: 0; letter-spacing: 0.02em; display: flex; align-items: center; gap: 0.45rem; }
header h1 a.brand { color: inherit; text-decoration: none; display: flex; align-items: center; gap: 0.45rem; }
header h1 a.brand:hover { color: var(--accent); }
header h1 img.logo { height: 1.5em; width: auto; border-radius: 4px; }
header .updated { color: var(--muted); font-size: 0.8rem; }
nav.pages { display: flex; gap: 0.3rem; }
/* every tab is a same-sized chip - inactive gets a quiet border so the
   active pill doesn't read as misaligned against borderless neighbors */
nav.pages a {
  color: var(--muted); text-decoration: none; font-size: 0.85rem;
  padding: 0.15rem 0.6rem; border-radius: var(--radius-sm);
  border: 1px solid var(--border);
}
nav.pages a:hover { color: var(--text); border-color: var(--muted); }
nav.pages a.active { color: var(--accent); background: var(--card); border-color: var(--accent); }
header .updated a { color: var(--muted); }
a.fs-btn {
  display: inline-block; text-decoration: none; font-size: 1.05rem; line-height: 1;
  color: var(--text); border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 0.25rem 0.45rem; margin-left: 0.35rem; vertical-align: middle;
  transition: color 0.12s, border-color 0.12s;
}
a.fs-btn:hover { color: var(--accent); border-color: var(--accent); }
/* global row: brand + view links, identical on every surface. Baseline
   alignment keeps the brand text and link text on one optical line
   despite different font sizes; the logo image centers within the h1's
   own flex row, unaffected. */
header.global-nav { align-items: baseline; padding: 0.85rem 1.25rem 0.35rem; }
.global-nav nav.views { margin-left: auto; display: flex; gap: 0.15rem; }
.global-nav nav.views a {
  color: var(--muted); text-decoration: none; font-size: 0.85rem;
  padding: 0.15rem 0.6rem; border-radius: var(--radius-sm);
}
.global-nav nav.views a:hover { color: var(--text); }
.global-nav nav.views a.active { color: var(--accent); background: var(--card); }
/* secondary row: surface-specific tools (dashboard page tabs, kiosk) */
/* tabs are visible chips, so their LEFT EDGE aligns with the brand and
   content line (1.25rem) */
nav.subnav { display: flex; align-items: center; gap: 0.3rem; padding: 0 1.1rem 0.4rem 1.25rem; }
nav.subnav .fs-btn { margin-left: auto; }
header .logout { margin-left: auto; }
header .updated:last-child { margin-left: auto; }
/* mobile: controls stay visible on line one; page tabs become a
   full-width swipeable row beneath */
@media (max-width: 760px) {
  header { flex-wrap: wrap; row-gap: 0.35rem; }
  nav.pages {
    overflow-x: auto;
    scrollbar-width: none; -webkit-overflow-scrolling: touch;
  }
  nav.pages::-webkit-scrollbar { display: none; }
  nav.pages a { white-space: nowrap; padding: 0.3rem 0.7rem; min-height: 24px; }
  .global-nav nav.views a { padding: 0.3rem 0.7rem; }
  nav.subnav .fs-btn { flex: none; }
  header .updated { margin-left: auto; }
  /* touch targets: stacked feed links (title + comments) need >=24px
     boxes with breathing room between them */
  ul.feed li { padding: 0.5rem 0; }
  ul.feed a { display: inline-block; padding: 0.15rem 0; }
  /* clear daylight between the stacked title and comments targets -
     adjacent tap boxes 1-2px apart fail stricter spacing checks even
     when each is >=24px tall */
  ul.feed .meta { margin-top: 0.45rem; }
  .meta a.quiet { display: inline-block; padding: 0.4rem 0.3rem; margin: 0 -0.3rem -0.15rem; }
}
/* ---- input system: one base for text fields, shared by every page ---- */
input[type="text"], input[type="password"], input[type="search"], input[type="number"],
input:not([type]), select, textarea {
  background: var(--bg); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 0.35rem 0.55rem; font: inherit; font-size: 0.85rem;
  width: 100%; box-sizing: border-box;
  transition: border-color 0.12s;
}
input:hover, select:hover, textarea:hover { border-color: var(--muted); }
input:focus-visible, select:focus-visible, textarea:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 1px; border-color: var(--accent);
}
input.invalid { border-color: hsl(0 70% 55%); }
textarea { font-family: ui-monospace, monospace; font-size: 13px; }
label { display: block; margin: 0.55rem 0 0.15rem; font-size: 0.78rem; color: var(--muted); }

/* ---- button system: one base + variants, shared by every page ---- */
button {
  background: var(--card); color: var(--muted);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 0.3rem 0.75rem; cursor: pointer; font: inherit; font-size: 0.8rem;
  line-height: 1.3; transition: color 0.12s, background 0.12s, border-color 0.12s;
}
button:hover { color: var(--text); border-color: var(--muted); }
button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
button:disabled { opacity: 0.45; cursor: default; }
button.primary { background: var(--accent); border-color: var(--accent); color: var(--bg); }
button.primary:hover { filter: brightness(1.12); color: var(--bg); }
button.btn-danger { color: hsl(0 70% 62%); border-color: hsl(0 45% 38%); }
button.btn-danger:hover { background: hsl(0 70% 52%); border-color: hsl(0 70% 52%); color: #fff; }
button.btn-accent { color: var(--accent); border-color: var(--accent); }
button.btn-accent:hover { background: var(--accent); color: var(--bg); }
button.btn-ghost { background: none; border-color: transparent; padding: 0 0.25rem; }
button.btn-ghost:hover { color: var(--accent); border-color: transparent; }
section.widget form[action$="/revoke"] button {
  color: hsl(0 70% 62%); border-color: hsl(0 45% 38%);
}
section.widget form[action$="/revoke"] button:hover { background: hsl(0 70% 52%); border-color: hsl(0 70% 52%); color: #fff; }
main {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  padding: 1rem 1.25rem 2rem;
  width: 100%;
}
.row { display: grid; grid-template-columns: repeat(12, 1fr); gap: 1rem; }
/* fit-screen applies only while the multi-column grid is active - on
   stacked (mobile) layouts every card would be crushed into one viewport,
   so phones scroll normally instead */
@media (min-width: 761px) {
  /* fit-screen pages: viewport-height layout, rows share space, columns
     scroll internally instead of the page */
  body:has(> main.fit-screen) { height: 100dvh; overflow: hidden; display: flex; flex-direction: column; }
  main.fit-screen { flex: 1; min-height: 0; }
  /* fill rows (default): widgets stretch so all column bottoms in the row
   align — the updated-ago footnote stays pinned via margin-top:auto. Fit
   pages have their own flex sizing, so this applies to scrolling pages. */
main:not(.fit-screen) .row-fill .col > section.widget { flex: 1 1 auto; }
main.fit-screen .row { flex: 1 1 0; min-height: 0; grid-template-rows: minmax(0, 1fr); grid-auto-rows: minmax(0, 1fr); } /* inline flex-grow carries row height weights */
  main.fit-screen .row:has(> .row-title) { grid-template-rows: auto minmax(0, 1fr); }
  main.fit-screen .col { overflow: hidden; min-height: 0; }
  main.fit-screen .col > section.widget {
    flex: 1 1 0; min-height: 0; overflow-y: auto;
    scrollbar-width: thin; scrollbar-color: var(--border) transparent;
    scrollbar-gutter: stable;
  }
/* the card scrolls; its content must not be flex-shrunk to fit - that
   crushes pre boxes and lists into slivers */
main.fit-screen .col > section.widget > * { flex-shrink: 0; }
  /* title stays pinned while the card scrolls; negative margins + padding
     extend its background over the card's padding so content slides under
     it cleanly */
  main.fit-screen .col > section.widget > h2 {
    /* negative top matches the card padding: the title sticks flush with
       the card's border instead of at the content box, so no strip of
       scrolled content peeks above it */
    position: sticky; top: -0.85rem; z-index: 1; background: var(--card);
    margin: -0.85rem -1rem 0.6rem; padding: 0.85rem 1rem 0.35rem;
  }
}
.row-title { grid-column: 1 / -1; margin: 0.4rem 0 -0.4rem; font-size: 0.85rem;
  font-weight: 650; letter-spacing: 0.03em; }
.col-title { margin: 0; font-size: 0.7rem; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--muted); }
.col { display: flex; flex-direction: column; gap: 1rem; min-width: 0; grid-column: span 12; }
@media (min-width: 761px) {
  .col.span-2 { grid-column: span 2; }
  .col.span-3 { grid-column: span 3; }
  .col.span-4 { grid-column: span 4; }
  .col.span-6 { grid-column: span 6; }
  .col.span-8 { grid-column: span 8; }
  .col.span-9 { grid-column: span 9; }
  .col.span-10 { grid-column: span 10; }
  .col.span-12 { grid-column: span 12; }
}
section.widget {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.85rem 1rem;
  display: flex;
  flex-direction: column;
}
/* the trailing "updated N ago" footnote: anchored to the card's bottom
   (margin-top auto), pinned visible while fit-mode cards scroll (sticky),
   and fainter than regular meta text */
section.widget > span.meta:last-child {
  align-self: flex-end;
  margin: auto 0 0 auto;
  padding-top: 0.4rem;
  font-size: 0.68rem;
  /* no opacity: it composites text through to the background and sinks
     contrast below 4.5:1 (0.55 over white measured 2.27). The smaller
     size alone carries the "footnote" register. */
}
.page-desc { margin: 0; color: var(--muted); font-size: 0.85rem; }
.widget-desc { margin: -0.35rem 0 0.6rem; color: var(--muted); font-size: 0.78rem; }
section.widget > h2 {
  margin: 0 0 0.6rem;
  font-size: var(--title-size, 0.78rem);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--accent);
}
ul.feed { list-style: none; margin: 0; padding: 0; }
ul.feed li { padding: 0.35rem 0; border-top: 1px solid var(--border); }
ul.feed li:first-child { border-top: none; }
ul.feed a { color: var(--text); text-decoration: none; }
ul.feed a:hover { color: var(--accent); }
/* settings access sections (passkeys, MCP grants/tokens): roomy rows with
   the action button pinned right */
.access { margin-bottom: 1.2rem; } /* settings main is block flow, not the dashboard's gapped flex */
.access ul.feed { margin-bottom: 0.8rem; }
.access ul.feed li { display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem 0.7rem; padding: 0.6rem 0; }
.access ul.feed li .meta { display: inline; margin: 0; flex: 1; min-width: 12rem; }
.access ul.feed li form { margin: 0; margin-left: auto; }
.access ul.feed li > button[disabled] { margin-left: auto; }
.access > p.meta { margin: 1.1rem 0 0.3rem; font-weight: 600; letter-spacing: 0.02em; }
.access h2 { margin-bottom: 0.4rem; }
.meta { display: block; color: var(--muted); font-size: 0.78rem; margin-top: 0.1rem; }
.meta a.quiet { color: var(--muted); text-decoration: none; }
.meta a.quiet:hover { color: var(--accent); }
.empty, .pending { color: var(--muted); font-size: 0.85rem; margin: 0; }
@keyframes spin { to { transform: rotate(360deg); } }
.pending:not(.error)::before, .btn-spinner {
  content: ""; display: inline-block; width: 0.8em; height: 0.8em;
  margin-right: 0.45em; vertical-align: -0.1em; border-radius: 50%;
  border: 2px solid var(--border); border-top-color: var(--accent);
  animation: spin 0.8s linear infinite;
}
.error { color: var(--negative); font-size: 0.8rem; margin: 0.4rem 0 0; }
.heartbeat .hb-row { display: flex; align-items: baseline; gap: 0.5rem; }
.heartbeat .dot { width: 10px; height: 10px; border-radius: 50%; align-self: center; }
.status-ok .dot { background: var(--positive); }
.status-late .dot, .status-running .dot { background: hsl(40 90% 55%); }
.status-fail .dot, .status-missed .dot { background: var(--negative); }
.status-waiting .dot { background: var(--muted); }
.heartbeat .hb-status { font-weight: 600; }
.heartbeat .hb-row .meta { display: inline; margin: 0; }
.hb-bars { display: flex; gap: 3px; margin-top: 0.55rem; }
.hb-bars .bar { width: 8px; height: 18px; border-radius: 2px; background: var(--border); }
.hb-bars .bar.ok { background: var(--positive); }
.hb-bars .bar.fail { background: var(--negative); }
.hb-bars .bar.timeout { background: hsl(0 40% 38%); }
.hb-bars .bar.open { background: hsl(40 90% 55%); }
ul.kv { list-style: none; margin: 0.4rem 0 0; padding: 0; }
ul.kv li { display: flex; justify-content: space-between; gap: 1rem; padding: 0.15rem 0; }
ul.kv .k { color: var(--muted); font-size: 0.85rem; }
ul.kv .v { font-variant-numeric: tabular-nums; }
.delta { font-size: 0.78rem; }
.delta.up { color: var(--positive); }
.delta.down { color: var(--negative); }
.mcp-text { margin: 0 0 0.5rem; font-size: 0.85rem; white-space: pre-wrap; overflow-wrap: anywhere; }
body > main:first-child { padding-top: 1rem; } /* kiosk: no header above */
/* JS fullscreen: hide the chrome but keep the page tabs so you can
   switch pages without leaving fullscreen (clicks soft-navigate) */
body.kiosk header h1, body.kiosk header .updated { display: none; }
body.kiosk header { padding-top: 0.5rem; }
.mcp-h { margin: 0.6rem 0 0.3rem; font-size: 0.9rem; font-weight: 700; }
.ui-tip { position: fixed; z-index: 60; background: var(--card); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 0.25rem 0.55rem; font-size: 0.75rem; pointer-events: none;
  box-shadow: 0 4px 14px rgb(0 0 0 / 0.35); max-width: 320px; overflow-wrap: anywhere; }
.ui-tip[hidden] { display: none; }
.mcp-quote { margin: 0 0 0.5rem; padding: 0.1rem 0 0.1rem 0.7rem; border-left: 3px solid var(--accent);
  color: var(--muted); font-size: 0.9rem; }
.mcp-ul { margin: 0 0 0.5rem; padding-left: 1.2rem; font-size: 0.85rem; }
.mcp-ul li { margin: 0.15rem 0; }
.mcp-pre { background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 0.55rem 0.7rem; font-family: ui-monospace, monospace; font-size: 12px;
  line-height: 1.5; overflow-x: auto; margin: 0 0 0.5rem;
  scrollbar-width: thin; scrollbar-color: var(--border) transparent; }
.mcp-pre::-webkit-scrollbar { height: 6px; }
.mcp-pre::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
.mcp-pre::-webkit-scrollbar-track { background: transparent; }
.site-footer { padding: 0.6rem 1.25rem 1rem; }
.site-footer a { color: var(--muted); font-size: 0.72rem; text-decoration: none; }
.site-footer a:hover { color: var(--accent); }
/* links inside markdown PROSE stay underlined - color alone isn't a
   sufficient cue among body text (WCAG 1.4.1); list-style links (feed,
   bookmarks) are exempt since everything there is a link */
.mcp-text a, .mcp-ul a, .mcp-h a {
  color: var(--accent);
  text-decoration: underline;
  text-decoration-color: color-mix(in srgb, var(--accent) 45%, transparent);
  text-underline-offset: 2px;
}
.mcp-text a:hover, .mcp-ul a:hover, .mcp-h a:hover { text-decoration-color: var(--accent); }
.mcp-text code, .mcp-ul code { background: var(--bg); border-radius: 4px; padding: 0 0.25rem;
  font-family: ui-monospace, monospace; font-size: 0.9em; }
.weather .now { display: flex; align-items: baseline; gap: 0.6rem; }
.weather .temp { font-size: 2rem; font-weight: 650; }
.weather .cond { font-size: 0.95rem; }
.weather .now .meta { display: inline; margin: 0; }
.weather ul.days { list-style: none; margin: 0.6rem 0 0; padding: 0; }
.weather ul.days li { display: flex; justify-content: space-between; padding: 0.15rem 0; }
.weather ul.days .meta { display: inline; margin: 0; }
section.widget iframe { border-radius: var(--radius-sm); background: var(--bg); }
.countdown .cd-remaining { display: block; font-size: 1.8rem; font-weight: 650; }
.countdown .meta { margin-top: 0.1rem; }
ul.bookmarks-list li { display: flex; align-items: center; gap: 0.45rem; }
ul.bookmarks-list .favicon { width: 16px; height: 16px; border-radius: 3px; flex: none; }
.search-form input[type="search"] { font-size: 1rem; padding: 0.5rem 0.7rem; }
ul.cal { list-style: none; margin: 0; padding: 0; }
ul.cal li { display: flex; gap: 0.6rem; padding: 0.3rem 0; border-top: 1px solid var(--border); align-items: baseline; }
ul.cal li:first-child { border-top: none; }
.cal-when { flex: none; width: 7.5em; color: var(--accent); font-size: 0.8rem; font-variant-numeric: tabular-nums; }
.cal-what { min-width: 0; overflow-wrap: anywhere; }
.cal-what .meta { display: block; margin-top: 0; }
.image-widget { width: 100%; display: block; border-radius: var(--radius-sm); }

.image-link { display: block; line-height: 0; }

/* Fit pages: the image expands to the card's remaining height and
   centers within it (object-fit letterboxes symmetrically). Overrides
   the card's flex-shrink:0 content rule and any fixed height - the
   card's share defines the size there. */
main.fit-screen .col > section.widget > .image-widget,
main.fit-screen .col > section.widget > .image-link {
  flex: 1 1 0;
  min-height: 0;
}
main.fit-screen .col > section.widget > .image-link { display: flex; }
main.fit-screen .col > section.widget .image-widget {
  height: 100% !important;
  width: 100%;
}
