/* terdut web UI. * * Mobile first: one column, a top bar and a bottom tab bar. From 900px the tab * bar becomes a sidebar and the queue shows list and detail side by side. * Colour is reserved for severity and status; everything else is neutral. */ :root { --bg: #f5f6f8; --surface: #ffffff; --surface-2: #eff1f4; --surface-hover: #f7f8fa; --border: #d9dde4; --border-strong: #c5cad3; --text: #16181d; --muted: #5b626e; --faint: #676d79; --accent: #2f5bd3; --accent-text: #ffffff; --accent-soft: #e8eefc; --crit: #c22d26; --crit-soft: #fdecea; --warn: #8f5500; --warn-soft: #fdf3e1; --info: #245fc7; --info-soft: #e9f0fd; --ok: #1a7445; --ok-soft: #e6f5ec; --snooze: #6050c8; --snooze-soft: #efedfb; /* Two hues that mean nothing on their own. The rota needs six colours to tell six people apart and the palette above only has four that are not already an alarm. */ --teal: #0c7180; --teal-soft: #e3f4f6; --pink: #a63b73; --pink-soft: #fbe8f2; --radius: 10px; --radius-sm: 6px; --shadow: 0 1px 2px rgb(16 24 40 / 6%), 0 1px 3px rgb(16 24 40 / 8%); --shadow-lg: 0 12px 32px rgb(16 24 40 / 18%); --font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; /* A ~4-size type scale, per issue #26's "use one type scale" ask. This file still has a dozen one-off font-size values below; migrating the low-risk, purely cosmetic ones (standalone titles with no dimensional or functional constraint) onto these tokens is a start, not the whole job — the rest (sizes tied to a fixed shape like the avatar circle, to a deliberately prominent display like a stat tile or the on-call name, or to a non-negotiable constraint like the 16px that stops iOS zooming into an input) stay as either their own pixel value or a documented exception, since guessing at those without seeing them render risks trading one inconsistency for a worse one. */ --fs-xs: 12px; --fs-sm: 13px; --fs-base: 14px; --fs-lg: 18px; --fs-xl: 21px; --topbar-h: 52px; /* The phone-width bottom tab bar's height. Unused above 900px: the desktop block overrides .nav/.view/.toast directly rather than reading this back down to 0. */ --tabbar-h: 58px; --safe-top: env(safe-area-inset-top, 0px); --safe-bottom: env(safe-area-inset-bottom, 0px); } /* Dark palette. Applied by the OS preference unless the user chose Light in Account (data-theme="light" on , set by js/theme.js), and always when they chose Dark. The two blocks must stay identical: CSS has no way to share a declaration list between a media query and an attribute selector. */ @media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) { --bg: #0f1115; --surface: #1a1e26; --surface-2: #232834; --surface-hover: #20252e; --border: #343b49; --border-strong: #444d5f; --text: #e7e9ed; --muted: #a0a7b3; --faint: #8a92a0; --accent: #6d8ff0; --accent-text: #0b0d12; --accent-soft: #1d2640; --crit: #ff6b61; --crit-soft: #3a1c1b; --warn: #f0b140; --warn-soft: #362a14; --info: #74a3ff; --info-soft: #1a2640; --ok: #4cc488; --ok-soft: #15301f; --snooze: #a89bff; --snooze-soft: #262245; --teal: #4fc2d4; --teal-soft: #0f2e33; --pink: #f07fb8; --pink-soft: #3a1c2d; --shadow: 0 1px 2px rgb(0 0 0 / 40%); --shadow-lg: 0 16px 40px rgb(0 0 0 / 55%); } } :root[data-theme="dark"] { --bg: #0f1115; --surface: #1a1e26; --surface-2: #232834; --surface-hover: #20252e; --border: #343b49; --border-strong: #444d5f; --text: #e7e9ed; --muted: #a0a7b3; --faint: #8a92a0; --accent: #6d8ff0; --accent-text: #0b0d12; --accent-soft: #1d2640; --crit: #ff6b61; --crit-soft: #3a1c1b; --warn: #f0b140; --warn-soft: #362a14; --info: #74a3ff; --info-soft: #1a2640; --ok: #4cc488; --ok-soft: #15301f; --snooze: #a89bff; --snooze-soft: #262245; --teal: #4fc2d4; --teal-soft: #0f2e33; --pink: #f07fb8; --pink-soft: #3a1c2d; --shadow: 0 1px 2px rgb(0 0 0 / 40%); --shadow-lg: 0 16px 40px rgb(0 0 0 / 55%); } :root[data-theme="light"] { color-scheme: light; } :root[data-theme="dark"] { color-scheme: dark; } *, *::before, *::after { box-sizing: border-box; } [hidden] { display: none !important; } html { -webkit-text-size-adjust: 100%; } body { margin: 0; background: var(--bg); color: var(--text); font: 15px/1.45 var(--font); font-variant-numeric: tabular-nums; -webkit-font-smoothing: antialiased; -webkit-tap-highlight-color: transparent; } a { color: inherit; text-decoration: none; } button, input, textarea, select { font: inherit; color: inherit; } code { font-family: var(--mono); font-size: 0.9em; } h1, h2, h3 { margin: 0; line-height: 1.25; } :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } /* ---------- boot + login ---------- */ .boot { display: grid; place-items: center; min-height: 100dvh; } .spinner { width: 26px; height: 26px; border-radius: 50%; border: 3px solid var(--border); border-top-color: var(--accent); animation: spin 0.8s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } .login { min-height: 100dvh; display: grid; place-items: center; padding: calc(24px + var(--safe-top)) 16px calc(24px + var(--safe-bottom)); } .login-card { width: 100%; max-width: 360px; display: grid; gap: 14px; } .login-brand { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; } .login-brand h1 { font-size: 24px; letter-spacing: -0.01em; } .login-hint { color: var(--faint); font-size: 13px; margin: 4px 0 0; } .login-password { display: grid; gap: 14px; } /* "or" between the single sign-on button and the password form. */ .login-divider { display: flex; align-items: center; gap: 10px; margin: 0; color: var(--faint); font-size: 13px; } .login-divider::before, .login-divider::after { content: ''; flex: 1; height: 1px; background: var(--border); } label { display: grid; gap: 6px; } label > span { font-size: 13px; font-weight: 600; color: var(--muted); } input, textarea, select { width: 100%; min-height: 44px; padding: 10px 12px; background: var(--surface); border: 1px solid var(--border-strong); border-radius: var(--radius-sm); font-size: 16px; /* 16px stops iOS zooming into the field */ } textarea { min-height: 110px; resize: vertical; line-height: 1.45; } input:focus, textarea:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); } .form-error { margin: 0; padding: 10px 12px; background: var(--crit-soft); color: var(--crit); border-radius: var(--radius-sm); font-size: 14px; } /* ---------- buttons ---------- */ .btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; min-height: 44px; padding: 0 16px; border: 1px solid var(--border-strong); border-radius: var(--radius-sm); background: var(--surface); font-weight: 600; font-size: 15px; cursor: pointer; white-space: nowrap; transition: background 0.12s, border-color 0.12s, opacity 0.12s; } .btn:hover { background: var(--surface-hover); } .btn:disabled, .btn-sm:disabled { opacity: 0.55; cursor: default; } .btn-primary { background: var(--accent); border-color: var(--accent); color: var(--accent-text); } .btn-primary:hover { background: var(--accent); filter: brightness(1.06); } .btn-danger { background: var(--crit); border-color: var(--crit); color: #fff; } .btn-danger:hover { background: var(--crit); filter: brightness(1.06); } .btn-ghost { background: transparent; border-color: transparent; } .btn-ghost:hover { background: var(--surface-2); } .btn-block { width: 100%; } .btn-icon { width: 44px; padding: 0; } .btn-sm { min-height: 32px; padding: 0 8px; font-size: 13px; } .btn-sm svg { width: 16px; height: 16px; } .btn svg, .icon { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; } /* ---------- app frame ---------- */ .app { min-height: 100dvh; } .topbar { position: sticky; top: 0; z-index: 10; display: flex; align-items: center; justify-content: space-between; gap: 12px; height: calc(var(--topbar-h) + var(--safe-top)); padding: var(--safe-top) 16px 0; background: color-mix(in srgb, var(--bg) 88%, transparent); backdrop-filter: saturate(1.4) blur(12px); -webkit-backdrop-filter: saturate(1.4) blur(12px); border-bottom: 1px solid var(--border); } .topbar-left { display: flex; align-items: center; gap: 8px; min-width: 0; } .topbar-title { font-size: 18px; font-weight: 700; letter-spacing: -0.01em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; } .open-pill { display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px; border-radius: 999px; font-size: 13px; font-weight: 600; background: var(--surface-2); color: var(--muted); } .open-pill::before { content: ""; width: 8px; height: 8px; border-radius: 50%; background: var(--faint); } .open-pill.has-triggered { background: var(--crit-soft); color: var(--crit); } .open-pill.has-triggered::before { background: var(--crit); } .open-pill.all-acked::before { background: var(--warn); } /* "All clear" is a status, not an action: green with a check, no dot. */ .open-pill.all-clear { background: var(--ok-soft); color: var(--ok); } .open-pill.all-clear::before { content: none; } .open-pill .icon { width: 14px; height: 14px; } /* Bottom tab bar on phones (Queue, On-call, Alerts, Team, More); becomes the left sidebar from 900px, where the desktop block below redeclares display and shows every section flat, .nav-link-secondary included. */ .nav { display: flex; position: fixed; left: 0; right: 0; bottom: 0; z-index: 20; height: calc(var(--tabbar-h) + var(--safe-bottom)); padding-bottom: var(--safe-bottom); background: color-mix(in srgb, var(--surface) 92%, transparent); backdrop-filter: saturate(1.4) blur(12px); -webkit-backdrop-filter: saturate(1.4) blur(12px); border-top: 1px solid var(--border); } .nav-brand { display: none; } .nav-sep, .nav-avatar { display: none; } /* Hidden here (shown from 900px below): on the phone bar the team switcher lives in the topbar instead, as #team-selector-mobile. */ .nav-team-selector { display: none; } .nav-link { position: relative; /* flex: 1 spreads the tabs evenly across the bar's width; the desktop block below cancels it back to a natural-width row item. */ flex: 1 1 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; color: var(--muted); font-size: 12px; font-weight: 600; /* The More tab is a