/* ============================================================
   Domain Panel — page-specific style
   ------------------------------------------------------------
   Builds on tokens.css + components.css. The older class names
   (.row, .dest, .prop …) remain because the pages' JS uses
   them; they are now just thin layers on top of the design system.
   ============================================================ */

.wrap { max-width: var(--max-width); margin: 0 auto; padding: var(--step-6) var(--step-5) var(--step-7); }

header { display: flex; align-items: baseline; gap: var(--step-3); margin-bottom: var(--step-5); flex-wrap: wrap; }
h1 { font-size: var(--text-2xl); font-weight: var(--weight-bold); letter-spacing: -.02em; margin: 0; }
.sub { color: var(--text-faint); font-size: var(--text-sm); }
.actions { margin-left: auto; display: flex; gap: var(--step-2); align-items: center; }
.progress { font-size: var(--text-sm); color: var(--text-muted); }
/* The height is reserved even when the row is empty, and the text is clipped instead of
   wrapping. Anything else makes the page jump while a sync counts down. */
.status-row {
  display: flex; align-items: center; gap: var(--step-4);
  box-sizing: border-box; height: 38px; padding: 0 var(--step-4);
  margin: calc(var(--step-5) * -1 + var(--step-2)) 0 var(--step-4);
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  overflow: hidden; white-space: nowrap;
}
/* The bar is always there, at the same height, so nothing below it moves.
   With nothing running it says so instead of standing empty. */
.status-row:not(:has(.progress:not(:empty)))::before {
  content: "Inget pågår just nu"; font-size: var(--text-xs); color: var(--text-faint);
}
/* Inside the bar the texts are plain items, not pills of their own. */
.status-row .progress:not(:empty) { background: none; border: none; padding: 0; }
.status-row #background:not(:empty) ~ #progress:not(:empty) {
  border-left: 1px solid var(--border); padding-left: var(--step-4);
}
.status-row .progress { overflow: hidden; text-overflow: ellipsis; }
.status-row #progress { flex-shrink: 0; }

button {
  font: var(--weight-normal) var(--text-md)/1 var(--font);
  padding: var(--step-2) var(--step-3);
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface); color: var(--text); cursor: pointer;
  transition: border-color var(--fast), color var(--fast), background var(--fast);
}
button:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
button:disabled { opacity: .5; cursor: default; }
button.primary { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: var(--weight-medium); }
button.primary:hover:not(:disabled) { background: var(--accent-dark); border-color: var(--accent-dark); color: #fff; }
.btn-xs { padding: var(--step-1) var(--step-2); font-size: var(--text-sm); }

/* --- key figures --- */
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--step-3); margin-bottom: var(--step-4); }
.stat { background: var(--surface); border: 1px solid var(--border); border-top: 3px solid var(--border-strong); border-radius: var(--radius-lg); padding: var(--step-3) var(--step-4); }
.stat b { display: block; font-size: var(--text-3xl); font-weight: var(--weight-bold); letter-spacing: -.02em; line-height: 1.15; font-variant-numeric: tabular-nums; }
.stat span { color: var(--text-muted); font-size: var(--text-2xs); text-transform: uppercase; letter-spacing: var(--label-spacing); }
.stat.alert { border-top-color: var(--error-text); }
.stat.alert b { color: var(--error-text); }
.stat.warn { border-top-color: var(--warn-text); }
.stat.warn b { color: var(--warn-text); }

/* --- tabs --- */
/* The tabs used to be underlined text — they looked like links, not like a control
   you switch views with. Now a cohesive button row: the whole row is one surface, and the
   selected tab is a raised card in it. Same pattern in admin. */
.tabs {
  display: flex; gap: 2px; margin-bottom: var(--step-5); flex-wrap: wrap;
  background: var(--surface-muted); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: 3px;
}
.tab {
  padding: var(--step-2) var(--step-4); font-size: var(--text-md);
  color: var(--text-muted); cursor: pointer; border-radius: var(--radius);
  transition: color var(--fast), background var(--fast), box-shadow var(--fast);
  user-select: none; display: flex; align-items: center; gap: var(--step-2);
  font-weight: var(--weight-medium); white-space: nowrap;
}
.tab:hover { color: var(--text); background: var(--surface-hover); }
.tab.on {
  color: var(--text); background: var(--surface);
  box-shadow: var(--shadow-sm); font-weight: var(--weight-medium);
}
.tab .pill { background: var(--accent); color: #fff; border-radius: var(--radius-pill); padding: 1px var(--step-2); font-size: var(--text-2xs); margin-left: 0; }
.tab .pill:empty { display: none; }

/* --- search row --- */
.toolbar { display: flex; gap: var(--step-2); margin-bottom: var(--step-3); align-items: center; flex-wrap: wrap; }
.search { flex: 1; min-width: 220px; background: var(--surface); border: 1px solid var(--border);
          border-radius: var(--radius); padding: var(--step-2) var(--step-3); font: var(--weight-normal) var(--text-md) var(--font); color: var(--text); }
.search:focus { outline: none; border-color: var(--accent); }
.chip { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-pill);
        padding: var(--step-1) var(--step-3); font-size: var(--text-sm); color: var(--text-muted); cursor: pointer; white-space: nowrap;
        transition: all var(--fast); user-select: none; }
.chip:hover { border-color: var(--border-strong); color: var(--text); }
.chip.on { background: var(--accent-surface); border-color: var(--accent-border); color: var(--accent); font-weight: var(--weight-medium); }

/* --- domain list --- */
.split { display: grid; grid-template-columns: 1fr var(--sidebar); gap: var(--step-4); align-items: start; }
@media (max-width: 900px) { .split { grid-template-columns: 1fr; } }
.list { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); overflow: hidden; }
/* The columns come from --columns, which is set per row by columns.js.
   The fallback value only applies if the variable is missing — without it, extra
   columns on newly created rows ended up ON TOP of each other, and whatever was clickable
   then lay under other content. */
.list-hdr, .list-row { display: grid; grid-template-columns: var(--columns, 12px 1.3fr 1fr 1fr);
             gap: var(--step-3); padding: var(--step-3) var(--step-4); align-items: center; }
/* Same thing one level down: a grid child is by default at least as wide
   as its content. Without this, minmax(0, …) on the column does not help. */
.list-hdr > *, .list-row > * { min-width: 0; }
.list-hdr { background: var(--surface); border-bottom: 2px solid var(--border);
       /* Headings should be possible to READ: text-xs, not 2xs, and --text-muted.
          Uppercase with tracking marks them as the header row, the way the
          section labels are marked. */
       font-size: var(--text-xs); color: var(--text-muted);
       text-transform: uppercase; letter-spacing: var(--label-spacing); font-weight: var(--weight-medium);
       letter-spacing: .01em; font-weight: var(--weight-medium); }
.list-row { border-bottom: 1px solid var(--border); cursor: pointer; transition: background var(--fast); }
.list-row:last-child { border-bottom: none; }
.list-row:hover { background: var(--surface-muted); }
.list-row.sel { background: var(--accent-surface); }
.dot { width: 8px; height: 8px; border-radius: 50%; justify-self: center; background: var(--gray-400); }
.dot.ok { background: var(--ok-text); } .dot.warn { background: #e8890c; }
.dot.bad { background: var(--error-text); } .dot.idle, .dot.unknown { background: var(--gray-400); }
.k { font-size: var(--text-md); font-weight: var(--weight-medium); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v { font-size: var(--text-xs); color: var(--text-faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cell.empty .k { color: var(--text-faint); font-weight: var(--weight-normal); font-style: italic; }
.note { grid-column: 2 / -1; font-size: var(--text-sm); margin-top: 2px; }
.note.bad { color: var(--error-text); } .note.warn { color: var(--warn-text); }

/* --- side panel --- */
.panel { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--step-4); position: sticky; top: var(--step-4); }
.panel h2 { font-size: var(--text-xl); font-weight: var(--weight-bold); margin: 0; }
.panel .host { color: var(--text-faint); font-size: var(--text-sm); margin-bottom: var(--step-3); }
.banner { border-radius: var(--radius); padding: var(--step-3); font-size: var(--text-sm); margin-bottom: var(--step-3);
          line-height: var(--row-height-tight); border: 1px solid transparent; }
.banner.bad { background: var(--error-surface); border-color: var(--error-border); border-left-width: 3px; color: var(--error-text); }
.banner.warn { background: var(--warn-surface); border-color: var(--warn-border); color: var(--warn-text); }
.banner.ok { background: var(--ok-surface); border-color: var(--ok-border); color: var(--ok-text); }
.lbl { font-size: var(--text-2xs); color: var(--text-muted); text-transform: uppercase;
       letter-spacing: var(--label-spacing); font-weight: var(--weight-medium); margin: var(--step-4) 0 var(--step-2); }
.meta { font-size: var(--text-sm); color: var(--text-muted); display: flex; justify-content: space-between; gap: var(--step-3); padding: 3px 0; }
.meta span:last-child { color: var(--text); text-align: right; }
.ns { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--text-muted); }
.connect { margin-top: var(--step-3); padding-top: var(--step-3); border-top: 1px solid var(--border); }
.empty { padding: var(--step-7) var(--step-5); text-align: center; color: var(--text-faint); line-height: var(--row-height-airy); }
.error-box { background: var(--error-surface); border: 1px solid var(--error-border); border-left-width: 3px; color: var(--error-text);
           border-radius: var(--radius); padding: var(--step-3); font-size: var(--text-sm); margin-bottom: var(--step-3); line-height: var(--row-height); }

/* --- destinations and proposals --- */
.dest { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--step-4); margin-bottom: var(--step-3); }
.dest-top { display: flex; align-items: center; gap: var(--step-2); margin-bottom: var(--step-1); flex-wrap: wrap; }
.dest-top b { font-size: var(--text-lg); font-weight: var(--weight-bold); }
.dest-top .count { color: var(--text-faint); font-size: var(--text-sm); }
.dest .target { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--text-muted); margin-bottom: var(--step-2); }
.dest .doms { font-size: var(--text-sm); color: var(--text-faint); line-height: var(--row-height-airy); }
.unnamed { border-color: var(--warn-border); background: var(--warn-surface); }
.name-form { display: flex; gap: var(--step-2); margin-top: var(--step-3); }
.name-form input { flex: 1; border: 1px solid var(--border); border-radius: var(--radius); padding: var(--step-2) var(--step-3);
                  font: var(--weight-normal) var(--text-md) var(--font); background: var(--surface); color: var(--text); }

.prop { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--step-4); margin-bottom: var(--step-3); }
.prop.done { opacity: .6; }
.prop-head { display: flex; align-items: center; gap: var(--step-2); margin-bottom: var(--step-1); flex-wrap: wrap; }
.prop-head b { font-size: var(--text-md); font-weight: var(--weight-bold); }
.who { background: var(--idle-surface); color: var(--idle-text); border-radius: var(--radius-sm); padding: 2px var(--step-2); font-size: var(--text-xs); font-weight: var(--weight-medium); }
.who.done { background: var(--ok-surface); color: var(--ok-text); }
.who.no { background: var(--error-surface); color: var(--error-text); }
.prop .when { color: var(--text-faint); font-size: var(--text-xs); margin-left: auto; }
.prop .prop-summary { color: var(--text-muted); font-size: var(--text-sm); margin-bottom: var(--step-1); }
.prop .review-group:first-of-type { margin-top: var(--step-2); }
.prop .acts { margin-top: var(--step-4); }
.prop p { color: var(--text-muted); font-size: var(--text-sm); margin: 0 0 var(--step-3); }
.diff { background: var(--surface-muted); border: 1px solid var(--border); border-radius: var(--radius);
        padding: var(--step-2) var(--step-3); font-family: var(--font-mono); font-size: var(--text-xs);
        line-height: var(--row-height-airy); margin-bottom: var(--step-3); white-space: pre-wrap; overflow-x: auto; }
.acts { display: flex; gap: var(--step-2); flex-wrap: wrap; }

/* --- domain page --- */
.records { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); overflow: hidden; margin-bottom: var(--step-4); }
table.dns { width: 100%; border-collapse: collapse; }
table.dns th { text-align: left; font-size: var(--text-2xs); color: var(--text-muted); text-transform: uppercase;
               letter-spacing: var(--label-spacing); padding: var(--step-3) var(--step-4);
               background: var(--surface-muted); border-bottom: 1px solid var(--border); font-weight: var(--weight-medium); }
table.dns td { padding: var(--step-2) var(--step-4); border-bottom: 1px solid var(--border); font-size: var(--text-md); vertical-align: middle; }
table.dns tr:last-child td { border-bottom: none; }
table.dns tr.protected td { color: var(--text-faint); background: var(--surface-muted); }
table.dns .mono { font-family: var(--font-mono); font-size: var(--text-xs); }
table.dns input { width: 100%; border: 1px solid var(--border); border-radius: var(--radius-sm);
                  padding: var(--step-1) var(--step-2); font: var(--weight-normal) var(--text-sm) var(--font); background: var(--surface); color: var(--text); }
table.dns input:focus { outline: none; border-color: var(--accent); }
table.dns input:disabled { border-color: transparent; background: transparent; color: var(--text-faint); }
table.dns td.narrow { width: 1%; white-space: nowrap; }
.type { display: inline-block; background: var(--idle-surface); color: var(--idle-text); border-radius: var(--radius-sm);
       padding: 2px var(--step-2); font-size: var(--text-xs); font-weight: var(--weight-medium); }
.row-changed { background: var(--warn-surface); }
.add-form { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--step-4); margin-bottom: var(--step-4); }
.add-form-row { display: grid; grid-template-columns: 1.2fr .8fr 2.5fr .7fr auto; gap: var(--step-2); align-items: end; }
/* Connection forms: one even column per credential, labels and help texts aligned at the top. */
.connection-fields { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: var(--step-3); align-items: start; margin-top: var(--step-3); }
.connection-fields .field { margin: 0; }
@media (max-width: 760px) { .add-form-row { grid-template-columns: 1fr 1fr; } }
/* The caption above a field. A DIRECT child only: a label deeper inside is a
   control of its own — a tick box with its text — and making that uppercase
   and block put the box above its own label instead of beside it. */
.field > label,
.field > span {
  display: block; font-size: var(--text-2xs); color: var(--text-muted); text-transform: uppercase;
  letter-spacing: var(--label-spacing); margin-bottom: var(--step-1); font-weight: var(--weight-medium);
}
/* The fields themselves are styled by the primitive in components.css. */
.pending-changes { position: sticky; bottom: 0; background: var(--surface); border: 1px solid var(--accent);
             border-radius: var(--radius-lg); padding: var(--step-3) var(--step-4); margin-top: var(--step-3);
             display: flex; align-items: center; gap: var(--step-3); box-shadow: var(--shadow-lg); flex-wrap: wrap; }
.pending-changes b { font-size: var(--text-md); }
.pending-changes .txt { flex: 1; font-size: var(--text-sm); color: var(--text-muted); }
.image { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg);
        padding: var(--step-3) var(--step-4); margin-bottom: var(--step-2); display: flex; align-items: center; gap: var(--step-3); flex-wrap: wrap; }
.image .time { font-size: var(--text-md); font-weight: var(--weight-medium); }
.image .reason { font-size: var(--text-xs); background: var(--idle-surface); color: var(--idle-text); border-radius: var(--radius-sm); padding: 2px var(--step-2); }
.image .reason.before { background: var(--warn-surface); color: var(--warn-text); }
.image .reason.after { background: var(--ok-surface); color: var(--ok-text); }
.image .count { font-size: var(--text-sm); color: var(--text-faint); }
.image .right { margin-left: auto; display: flex; gap: var(--step-2); align-items: center; }

/* --- login --- */
body.login-page { display: flex; align-items: center; justify-content: center; min-height: 100vh; padding: var(--step-5); }
.login-box { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-xl);
              padding: var(--step-6) var(--step-6); width: 100%; max-width: 380px; box-shadow: var(--shadow); }
.login-box h1 { font-size: var(--text-xl); margin-bottom: 3px; }
.login-sub { color: var(--text-faint); font-size: var(--text-sm); margin: 0 0 var(--step-5); }
.login-box .field { margin-bottom: var(--step-3); }
/* The selectors are narrow on purpose. ".login-box button { width: 100% }" made
   the eye button inside the password field 100 % wide, and ".login-box input"
   hit the checkbox. A broad selector on a login page hits everything added
   later. */
.login-box .field > input,
.login-box .password-field > input {
  /* Only what differs from the primitive: bigger text, because this is the
     one form somebody fills in before they are sure the panel works. */
  font-size: var(--text-lg);
}
.login-box > form > button[type="submit"] {
  width: 100%; padding: var(--step-3); font-size: var(--text-lg); margin-top: var(--step-2);
}
.help { font-size: var(--text-xs); color: var(--text-faint); margin-top: var(--step-1); line-height: var(--row-height); }
.signout { display: flex; align-items: center; gap: var(--step-2); font-size: var(--text-sm); color: var(--text-faint); }

/* The GitHub list had its own row rules before all lists shared table. Now
   the .hdr/.row-rules above and the mobile rules below apply to it too. */

/* ------------------------------------------------------------
   The domain list on a small screen.
   Lives here and not in components.css because app.css is loaded
   last — otherwise the base rules above would win over these.
   ------------------------------------------------------------ */
@media (max-width: 720px) {
  .list .list-hdr { display: none; }

  .list .list-row {
    display: grid;
    grid-template-columns: 12px 1fr;
    row-gap: 3px;
    padding: var(--step-3);
  }
  .list .list-row > .dot { align-self: start; margin-top: 6px; }
  .list .list-row > .cell,
  .list .list-row > .note { grid-column: 2; }

  .list .list-row .k,
  .list .list-row .v { white-space: normal; overflow: visible; }
  .list .list-row .cell-domain .k { font-size: var(--text-lg); }

  /* Every cell except the domain name gets its column heading in front of it. The rule
     is based on data-label and therefore works for any column at all,
     even ones added later. */
  .list .list-row > .cell[data-label]:not(.cell-domain) {
    display: flex; gap: 5px; align-items: baseline; flex-wrap: wrap;
  }
  .list .list-row > .cell[data-label]:not(.cell-domain)::before {
    content: attr(data-label) " ·";
    color: var(--text-faint); font-size: var(--text-xs);
    text-transform: uppercase; letter-spacing: var(--label-spacing);
    flex-shrink: 0;
  }
  .list .list-row > .cell[data-label]:not(.cell-domain) .k {
    font-size: var(--text-sm); font-weight: var(--weight-normal); color: var(--text-muted);
  }
  .list .list-row > .cell[data-label]:not(.cell-domain) .v { flex-basis: 100%; }
  /* Empty cells (the padding at the far right) should not produce a lone label. */
  .list .list-row > .cell:not([data-label]) { display: none; }
}

/* --- change queue on the domain page --- */
tr.new-row { background: var(--ok-surface); }
tr.new-row td:first-child::before { content: "ny "; color: var(--ok-text); font-size: var(--text-xs); font-weight: var(--weight-medium); }
tr.removed-row { background: var(--error-surface); opacity: .75; }
tr.removed-row .mono { text-decoration: line-through; }
tr.changed-row { background: var(--warn-surface); }
/* A word next to the colour, so the state does not hang on colour alone. */
tr.changed-row td:first-child::before { content: "ändrad "; color: var(--warn-text); font-size: var(--text-xs); font-weight: var(--weight-medium); }
tr.removed-row td:first-child::before { content: "tas bort"; margin-right: .35em; color: var(--error-text); font-size: var(--text-xs); font-weight: var(--weight-medium); text-decoration: none; display: inline-block; }
/* Rendered on every editable row; the input handler only toggles the class. */
.undo-edit { display: none; }
tr.changed-row .undo-edit { display: inline-flex; }
.pending-changes .pending-counts { color: var(--text-muted); }

/* --- review tables: parsed paste and the summary before a proposal --- */
table.review-table { margin-top: var(--step-2); }
table.review-table th, table.review-table td { padding: var(--step-2) var(--step-3); vertical-align: top; }
table.review-table .review-value { word-break: break-all; }
table.review-table .review-note { color: var(--text-faint); font-size: var(--text-xs); margin-top: 2px; }
table.review-table .old-value { text-decoration: line-through; color: var(--text-faint); }
.type.type-guessed { background: var(--warn-surface); color: var(--warn-text); }
.review-group { margin-top: var(--step-4); }
.review-heading { font-weight: var(--weight-medium); display: flex; gap: var(--step-2); align-items: center; }
.review-count { font-size: var(--text-xs); border-radius: var(--radius-pill); padding: 0 var(--step-2); }
.review-group-removed .review-count { background: var(--error-surface); color: var(--error-text); }
.review-group-added .review-count { background: var(--ok-surface); color: var(--ok-text); }
.review-group-changed .review-count { background: var(--warn-surface); color: var(--warn-text); }
table.review-removed tbody tr { background: var(--error-surface); }
table.review-removed .review-value { text-decoration: line-through; }
table.review-added tbody tr { background: var(--ok-surface); }
table.review-changed tbody tr { background: var(--warn-surface); }

.problem { display: flex; gap: var(--step-3); align-items: flex-start; padding: var(--step-3) 0; border-bottom: 1px solid var(--border); }
.problem:last-child { border-bottom: none; }
.problem .icon { width: 22px; height: 22px; border-radius: var(--radius-pill); display: grid; place-items: center;
                 font-size: var(--text-sm); font-weight: var(--weight-bold); flex: none; }
.problem.error .icon { background: var(--error-surface); color: var(--error-text); }
/* The level names come from dns-rules.js: error | warning. */
.problem.warning .icon { background: var(--warn-surface); color: var(--warn-text); }
.problem .text { flex: 1; font-size: var(--text-md); line-height: var(--row-height); }
.problem .action { margin-top: var(--step-2); }

.source-info { position: relative; cursor: help; border-bottom: 1px dotted var(--border-strong); }
.source-info .description {
  position: absolute; left: 0; bottom: calc(100% + 6px); z-index: 30;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow-lg); padding: var(--step-3); width: 290px;
  font-size: var(--text-sm); line-height: var(--row-height); color: var(--text);
  opacity: 0; pointer-events: none; transition: opacity var(--fast); font-weight: var(--weight-normal);
}
.source-info:hover .description { opacity: 1; }
.source-info .description .row-info { display: flex; justify-content: space-between; gap: var(--step-3); padding: 2px 0; }
.source-info .description .row-info span:last-child { color: var(--text-muted); }

.target-label { margin-top: 3px; min-height: 14px; }
.target-label:empty { display: none; }

/* Key figures that are shortcuts to filters. */
.stat.clickable { cursor: pointer; transition: border-color var(--fast), background var(--fast); }
.stat.clickable:hover { border-color: var(--accent); background: var(--surface-muted); }
.stat.clickable:active { transform: translateY(1px); }
.stat.clickable:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The figure the list is filtered on: unmistakably chosen, not just hovered. */
.stat.on { border-color: var(--accent); background: var(--accent-surface); box-shadow: inset 0 0 0 1px var(--accent); }
.stat.on span { color: var(--accent); }
.stat.on span::after { content: " · visas"; text-transform: none; letter-spacing: 0; }

/* ------------------------------------------------------------
   The GitHub tab.
   Lives last in app.css, which is loaded last — otherwise
   the base rules for .row and .chip would win over these.
   ------------------------------------------------------------ */

.gh-languages { display: flex; flex-wrap: wrap; gap: var(--step-2); margin-bottom: var(--step-3); }
.gh-languages .chip b { font-weight: var(--weight-bold); opacity: .65; margin-left: 2px; }

.gh-tools {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--step-2);
  margin-bottom: var(--step-3);
}
.gh-tools input[type="search"], .gh-tools select {
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: var(--step-2) var(--step-3);
  font: var(--weight-normal) var(--text-md) var(--font);
  background: var(--surface); color: var(--text);
}
.gh-tools input[type="search"] { flex: 1; min-width: 140px; }
.gh-tools input[type="search"]:focus, .gh-tools select:focus {
  outline: none; border-color: var(--accent);
}
.gh-age {
  margin-left: auto; display: flex; align-items: center; gap: var(--step-2);
  font-size: var(--text-xs); color: var(--text-faint);
}

/* The activity bar in the list. Shows commits over the latest 30 days relative
   to the most active project. */
.gh-bar {
  display: block; height: 4px; width: 100%; max-width: 120px;
  background: var(--border); border-radius: var(--radius-pill); overflow: hidden; margin-top: 5px;
}
.gh-bar > span { display: block; height: 100%; background: var(--accent); border-radius: inherit; }

/* The weekly graph in the drawer. currentColor makes it follow the theme. */
.gh-graph { display: block; color: var(--accent); }
.gh-graph rect { fill: currentColor; }

@media (max-width: 720px) {
  /* The toolbar row is stacked: the search field gets a row of its own, the rest flows below. */
  .gh-tools input[type="search"] { flex-basis: 100%; }
  .gh-age { margin-left: 0; flex-basis: 100%; justify-content: space-between; }
  /* The bar gets the full width once the row has become single-column. */
  .gh-bar { max-width: none; }
}

/* ------------------------------------------------------------
   Home screen mode on iPhone and iPad.
   With viewport-fit=cover the page reaches into the corners of the screen — without
   compensation the heading ends up under the camera and buttons behind the
   home indicator. The rules also apply in regular Safari, where
   env() is zero and nothing changes.
   ------------------------------------------------------------ */

.wrap {
  padding-top: max(var(--step-6), env(safe-area-inset-top));
  padding-left: max(var(--step-5), env(safe-area-inset-left));
  padding-right: max(var(--step-5), env(safe-area-inset-right));
  padding-bottom: max(var(--step-7), env(safe-area-inset-bottom));
}

body.login-page { padding: max(var(--step-5), env(safe-area-inset-top))
                           max(var(--step-5), env(safe-area-inset-right))
                           max(var(--step-5), env(safe-area-inset-bottom))
                           max(var(--step-5), env(safe-area-inset-left)); }

/* The drawer slides up from below and would otherwise end right under the home indicator. */
.drawer { padding-bottom: env(safe-area-inset-bottom); }

/* The sticky "Apply changes" row is hit hardest:
   the button ends up exactly where the home indicator sits and cannot be hit. */
.pending-changes { margin-bottom: env(safe-area-inset-bottom); }

.notify-layer {
  right: max(var(--step-4), env(safe-area-inset-right));
  bottom: max(var(--step-4), env(safe-area-inset-bottom));
}

/* In home screen mode there is no address bar to pull down — some breathing room of its own
   at the top keeps the heading from sticking to the status bar. */
@media (display-mode: standalone) {
  .wrap { padding-top: max(var(--step-4), calc(env(safe-area-inset-top) + var(--step-2))); }
  /* The rubber-band effect at the end of scrolling otherwise shows a white strip
     that does not match the theme. */
  html { background: var(--surface-sunken); overscroll-behavior-y: none; }
}

/* The search field: icon inside, same height as the dropdown next to it. */
.gh-search {
  flex: 1; min-width: 160px; position: relative;
  display: flex; align-items: center;
}
.gh-search svg {
  position: absolute; left: var(--step-3); width: 15px; height: 15px;
  fill: none; stroke: var(--text-faint); stroke-width: 1.6; stroke-linecap: round;
  pointer-events: none;
}
.gh-search input {
  width: 100%; border: 1px solid var(--border); border-radius: var(--radius);
  padding: var(--step-2) var(--step-3) var(--step-2) calc(var(--step-3) * 2 + 15px);
  font: var(--weight-normal) var(--text-md) var(--font);
  background: var(--surface); color: var(--text);
}
.gh-search input:focus { outline: none; border-color: var(--accent); }
.gh-search input:focus + svg, .gh-search input:focus ~ svg { stroke: var(--accent); }
/* Safari's own clear button clashes with the field's rounding. */
.gh-search input::-webkit-search-cancel-button { -webkit-appearance: none; }

/* The dropdown should be as tall as the search field, not shrink. */
.gh-tools #gh-sort-by { min-width: 170px; }
@media (max-width: 720px) {
  .gh-tools #gh-sort-by { flex: 1; min-width: 0; }
}

/* ------------------------------------------------------------
   Sortable headings and the column picker.
   ------------------------------------------------------------ */

.hdr-cell {
  display: flex; align-items: center; gap: 4px;
  cursor: pointer; user-select: none; border-radius: var(--radius-sm);
  transition: color var(--fast);
}
.hdr-cell:hover { color: var(--text); }
.hdr-cell:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.hdr-cell.sorted { color: var(--accent); }
.hdr-cell .arrow { font-size: 10px; opacity: .9; }
/* The arrow takes up space even when empty, otherwise the heading jumps sideways. */
.hdr-cell .arrow::after { content: ''; display: inline-block; width: 0; }

.hdr-menu { position: relative; cursor: default; justify-content: flex-end; }
.hdr-menu:hover { color: inherit; }
.column-button {
  background: none; border: none; cursor: pointer; padding: 2px 6px;
  color: var(--text-faint); font-size: var(--text-lg); line-height: 1;
  border-radius: var(--radius-sm);
}
.column-button:hover { color: var(--text); background: var(--surface-hover); }

.column-menu {
  /* On body, placed from the button by JS — so no wrapper can clip it. */
  position: fixed; z-index: 70;
  width: 300px; max-height: 60vh; overflow-y: auto;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-lg);
  padding: var(--step-2); text-transform: none; letter-spacing: normal;
}
.column-menu-title {
  font-size: var(--text-2xs); color: var(--text-muted); text-transform: uppercase;
  letter-spacing: var(--label-spacing); padding: var(--step-2) var(--step-3) var(--step-1);
}
.column-menu-option {
  display: flex; gap: var(--step-3); align-items: flex-start;
  padding: var(--step-2) var(--step-3); border-radius: var(--radius);
  cursor: pointer; font-size: var(--text-sm); color: var(--text);
}
.column-menu-option:hover { background: var(--surface-muted); }
.column-menu-option.last { cursor: default; opacity: .7; }
.column-menu-option input { margin-top: 2px; flex-shrink: 0; accent-color: var(--accent); }
.column-menu-option b { font-weight: var(--weight-medium); display: block; }
.column-menu-option .help-text {
  display: block; font-size: var(--text-xs); color: var(--text-faint);
  line-height: var(--row-height); margin-top: 1px;
}
.column-menu [data-reset] { width: calc(100% - var(--step-4)); margin: var(--step-2); }

/* Status dots inside a cell (the email column). */
.cell .dot {
  display: inline-block; width: 7px; height: 7px; border-radius: 50%;
  margin-right: 6px; vertical-align: middle;
}
.dot-ok { background: var(--ok-text); }
.dot-warn { background: #e8890c; }
.dot-error { background: var(--error-text); }
.dot-off { background: var(--gray-400); }

/* Was called .empty at first, which clashed with the class for empty views and its
   padding: var(--step-7) — the row became three times too tall. */
.cell .k.missing { color: var(--text-faint); font-style: italic; font-weight: var(--weight-normal); }
.cell .severity { color: var(--error-text); font-weight: var(--weight-medium); }
.cell .warning { color: var(--warn-text); }

/* The sort row for narrow screens. Hidden on wide, where the headings do the job. */
.sort-row-mobile { display: none; }

@media (max-width: 720px) {
  .sort-row-mobile {
    display: flex; align-items: center; gap: var(--step-2);
    flex-wrap: nowrap; overflow-x: auto; -webkit-overflow-scrolling: touch;
    padding: var(--step-3); border-bottom: 1px solid var(--border);
    background: var(--surface-muted);
    /* Hidden scrollbar — the row should look like chips, not like a box. */
    scrollbar-width: none;
  }
  .sort-row-mobile::-webkit-scrollbar { display: none; }
  .sort-row-mobile .label {
    font-size: var(--text-2xs); color: var(--text-muted); text-transform: uppercase;
    letter-spacing: var(--label-spacing); flex-shrink: 0;
  }
  .sortchip {
    flex-shrink: 0; display: inline-flex; align-items: center; gap: 3px;
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-pill);
    padding: 4px var(--step-3); font-size: var(--text-xs); color: var(--text-muted);
    cursor: pointer; white-space: nowrap;
  }
  .sortchip.on { background: var(--accent-surface); border-color: var(--accent-border); color: var(--accent); }
  .sort-row-mobile .column-button { margin-left: auto; flex-shrink: 0; position: sticky; right: 0; }
}

/* --- screenshots ---
   The thumbnail in the list has a FIXED height. An image that gets to decide the row's height
   would make the list uneven, and 16:10 of 1280×800 cuts nothing important off. */
img.thumb { cursor: zoom-in; transition: border-color var(--fast); }
img.thumb:hover { border-color: var(--accent); }
.thumb { width: 80px; height: 50px; object-fit: cover; object-position: top center;
            border: 1px solid var(--border); border-radius: var(--radius-sm); display: block;
            background: var(--surface-muted); }
/* A newer attempt failed; the image shown is the previous one. */
.thumb-stale { outline: 2px solid var(--warn-text); outline-offset: -2px; }
.thumb-error, .thumb-empty {
  display: flex; align-items: center; justify-content: center; text-align: center;
  font-size: var(--text-2xs); color: var(--text-faint); line-height: 1.2; padding: 0 2px; }
.thumb-empty { border-style: dashed; }

/* In the drawer and on the domain page the image gets the full width — that is the point there. */
.shot { width: 100%; max-width: 560px; aspect-ratio: 16 / 10; object-fit: cover;
           object-position: top center; border: 1px solid var(--border);
           border-radius: var(--radius); background: var(--surface-muted); display: block; }
.shot-row { display: flex; align-items: center; gap: var(--step-3); flex-wrap: wrap;
               margin-bottom: var(--step-2); }
.shot-time { font-size: var(--text-xs); color: var(--text-faint); }

/* The button that is in progress. Red, never pink — it stops something. */
#btn-images.blocking { background: var(--error-text); border-color: var(--error-text); color: #fff; }
#btn-images.blocking:hover { background: var(--error-border); border-color: var(--error-border); }
.shot { cursor: zoom-in; }


/* --- accent discipline ---
   The accent colour sat on tabs, domain names, target labels and buttons at the same time.
   When everything is blue, nothing is, and the eye cannot find what the
   actual action is. The domain name is the row's HEADING — the whole row is clickable
   anyway — so it gets the text colour and is only underlined when you point at it. */
.cell-domain a {
  color: var(--text);
  font-weight: var(--weight-medium);
  text-decoration: none;
}
.cell-domain a:hover { color: var(--accent); text-decoration: underline; }

/* The heading should read as a heading, not as yet another line of text. */
h1 { letter-spacing: -.021em; }
header { align-items: center; gap: var(--step-4); margin-bottom: var(--step-5); }
header .sub { font-size: var(--text-sm); }
.actions { gap: var(--step-2); }

/* The section labels were 11 px uppercase in grey — they disappeared entirely, and
   the page fell apart into a long run of identical boxes with no division. */
.lbl, .label {
  font-size: var(--text-xs);
  letter-spacing: .04em;
  color: var(--text-muted);
  margin-top: var(--step-6);
  margin-bottom: var(--step-2);
}

/* The key figures were the loudest element on the page even though they are seldom what
   you came for. The number gets to carry, the label is toned down. */
.stat b { font-size: var(--text-2xl); letter-spacing: -.02em; }
.stat span { font-size: var(--text-2xs); color: var(--text-faint); }


/* --- top row on a narrow screen ---
   The name, the logout and four buttons sat on ONE row. At 375 px they pushed
   15 px past the edge, so "Synka" was half clipped and the whole page could be
   dragged sideways. Now the row may wrap, and the logout is kept together. */
.signout { white-space: nowrap; }

@media (max-width: 720px) {
  header { flex-wrap: wrap; row-gap: var(--step-3); }
  h1 { font-size: var(--text-xl); }
  header .sub { display: none; }        /* says nothing you do not already know */
  .actions {
    margin-left: 0; width: 100%; flex-wrap: wrap;
    justify-content: flex-start; gap: var(--step-2);
  }
  .actions button, .actions a { flex: 0 1 auto; }

  /* The tab row scrolls sideways instead of wrapping onto three rows. */
  .tabs, .tabs {
    flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .tabs::-webkit-scrollbar, .tabs::-webkit-scrollbar { display: none; }
  .tab, .tab { flex: 0 0 auto; }
}

/* Status pills. Each process gets its own pill, so two at once read as two:
   the background sync with a quiet dot, work you started with a spinner. */
.progress:not(:empty) {
  display: inline-flex; align-items: center; gap: var(--step-2); max-width: 100%;
  padding: 3px var(--step-3); font-size: var(--text-xs);
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  color: var(--text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
#background:not(:empty)::before {
  content: ""; flex: none; width: 6px; height: 6px; border-radius: 50%;
  background: var(--text-faint);
}
#progress:not(:empty)::before {
  content: ""; flex: none; width: 10px; height: 10px; border-radius: 50%;
  border: 2px solid var(--border); border-top-color: var(--accent);
  animation: status-spin .8s linear infinite;
}
#progress:not(:empty) { color: var(--text); }
@keyframes status-spin { to { transform: rotate(360deg); } }

/* --- the cards describing a kind of token ---
   The heading says WHAT, the line below says WHAT IT IS FOR. It used to be
   large and accent blue — and thereby became the loudest element on the page even though
   it cannot be clicked. Blue means "press here", nothing else. */
.scope-card { gap: 0; }
.scope-head { display: flex; flex-direction: column; gap: 2px; }
.scope-head b { font-size: var(--text-lg); font-weight: var(--weight-bold); }
.scope-for {
  font-size: var(--text-sm); color: var(--text-muted);
  line-height: var(--row-height-tight);
}
.scope-text {
  margin: var(--step-3) 0 0; font-size: var(--text-sm);
  color: var(--text-faint); line-height: var(--row-height);
}
.scope-rights {
  display: flex; align-items: center; gap: var(--step-2);
  flex-wrap: wrap; margin-top: var(--step-3);
}
.scope-rights .label { margin: 0; }
/* The button is one of several equal choices, not the page's main action. Two filled
   blue buttons next to each other compete with each other and with everything else.
   Its placement at the bottom of the card comes from .card-grid in components.css. */

/* --- waiting while a screenshot is rendered ---
   The button is already locked, but a locked button that looks unchanged is a button you
   distrust — so you press again. The box instead shows that something is in progress, in
   the image's own shape, with a seconds counter so it is visible that it is alive. */
.shot-waiting {
  position: relative; width: 100%; max-width: 560px; aspect-ratio: 16 / 10;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface-muted); overflow: hidden;
  display: flex; align-items: center; justify-content: center;
}
.shot-shimmer {
  position: absolute; inset: 0;
  background: linear-gradient(100deg,
    transparent 20%, color-mix(in srgb, var(--accent) 12%, transparent) 50%, transparent 80%);
  background-size: 220% 100%;
  animation: skimra 1.6s ease-in-out infinite;
}
@keyframes skimra {
  from { background-position: 180% 0; }
  to   { background-position: -80% 0; }
}
.shot-waiting-text {
  position: relative; text-align: center; line-height: var(--row-height);
  padding: 0 var(--step-4);
}
.shot-waiting-text b { display: block; font-size: var(--text-md); }
.shot-waiting-text span { font-size: var(--text-sm); color: var(--text-faint); }

/* Motion is not always welcome. */
@media (prefers-reduced-motion: reduce) {
  .shot-shimmer { animation: none; background: none; }
  #progress:not(:empty)::before { animation: none; border-color: var(--accent); }
}

/* ============================================================
   The shell: sidebar + content
   ------------------------------------------------------------
   The tab row at the top scaled badly — six views fit, a seventh would have
   started to wrap. A vertical menu grows without the layout changing,
   and keeps the panel's identity visible on every page.
   ============================================================ */
.shell { display: grid; grid-template-columns: 232px minmax(0, 1fr); min-height: 100vh; }

.sidebar {
  display: flex; flex-direction: column; gap: var(--step-5);
  background: var(--sidebar-bg); border-right: 1px solid var(--sidebar-border);
  padding: var(--step-5) var(--step-4);
  position: sticky; top: 0; height: 100vh; overflow-y: auto;
}
.sidebar-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--step-2); }
/* min-width:0 is what makes the ellipsis below work. Without it this flex
   child refuses to shrink below its content, and the subtitle grows straight
   out through the sidebar's edge instead of being cut. */
.brand { text-decoration: none; display: block; min-width: 0; }
.brand-name {
  display: block; font-size: var(--text-lg); font-weight: var(--weight-bold);
  color: var(--sidebar-text); letter-spacing: -.02em;
}
/* One line, always: it is filled on every page, and it must never wrap, or the
   menu below it moves between pages. */
.brand-sub {
  display: block; font-size: var(--text-2xs); color: var(--sidebar-muted); margin-top: 2px;
  min-height: calc(var(--text-2xs) * var(--row-height)); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.menu { display: flex; flex-direction: column; gap: var(--step-4); }
.menu-group { display: flex; flex-direction: column; gap: 2px; }
.menu-group-title {
  font-size: var(--text-2xs); text-transform: uppercase; letter-spacing: var(--label-spacing);
  color: var(--sidebar-heading); font-weight: var(--weight-medium);
  padding: 0 var(--step-3); margin-bottom: var(--step-1);
}
.sidebar-foot { margin-top: auto; display: flex; flex-direction: column; gap: 2px; }

.menu-item {
  display: flex; align-items: center; gap: var(--step-2);
  width: 100%; text-align: left; text-decoration: none;
  padding: var(--step-2) var(--step-3);
  font: var(--weight-medium) var(--text-md) var(--font);
  color: var(--sidebar-muted); background: none; border: 1px solid transparent;
  border-radius: var(--radius); cursor: pointer;
  transition: color var(--fast), background var(--fast);
}
.menu-item:hover { color: var(--sidebar-text); background: var(--sidebar-hover); }
/* The current view: a solid accent bar on the left edge, not a tinted pill. */
.menu-item.on { color: #fff; background: var(--sidebar-active); border-color: transparent; box-shadow: inset 3px 0 0 var(--accent); }
.menu-item:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.menu-item .pill {
  margin-left: auto; background: var(--accent); color: #fff;
  border-radius: var(--radius-pill); padding: 1px var(--step-2); font-size: var(--text-2xs);
}
.menu-item .pill:empty { display: none; }
.sidebar-foot .signout {
  padding: var(--step-2) var(--step-3); font-size: var(--text-xs); color: var(--sidebar-muted);
  border-top: 1px solid var(--sidebar-border); margin-top: var(--step-2); padding-top: var(--step-3);
}
.sidebar-foot .signout a { color: var(--sidebar-text); }
.sidebar .menu-button { color: var(--sidebar-text); background: transparent; border-color: var(--sidebar-border); }

.content { padding: var(--step-5) var(--step-6) var(--step-7); min-width: 0; max-width: 1360px; }
.content > header { margin-bottom: var(--step-5); padding-bottom: var(--step-3); border-bottom: 1px solid var(--border); }
.content > header h1 { font-size: var(--text-2xl); }

.menu-button { display: none; }
.sidebar-scrim { display: none; }

@media (max-width: 900px) {
  .shell { grid-template-columns: 1fr; }
  /* The menu sits off screen until you ask for it. A menu that takes half the
     phone is no menu. */
  .sidebar {
    position: fixed; inset: 0 auto 0 0; width: 272px; z-index: 60;
    transform: translateX(-100%); transition: transform .2s ease;
    box-shadow: var(--shadow-lg);
  }
  .sidebar.open { transform: none; }
  .sidebar-scrim {
    display: block; position: fixed; inset: 0; z-index: 55;
    background: rgba(0, 0, 0, .35);
  }
  .menu-button {
    display: inline-flex; align-items: center; justify-content: center;
    width: 38px; height: 38px; padding: 0; font-size: 18px;
    border: 1px solid var(--border); border-radius: var(--radius);
    background: var(--surface); color: var(--text); cursor: pointer;
  }
  .content { padding: var(--step-4) var(--step-4) var(--step-7); }
  .content > header h1 { font-size: var(--text-xl); }
}

/* The domain page's overview: the interpretation on the left, facts and image on the right.
   minmax(0, …) on both tracks — a bare fr has min-width:auto, and then
   a long TXT record blows up the column instead of wrapping. */
.overview {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
  gap: var(--step-5);
  align-items: start;
}
.overview > * { min-width: 0; }

@media (max-width: 900px) {
  .overview { grid-template-columns: minmax(0, 1fr); }
}

/* --- the domain page's status band ---
   Four answers before any tab: where it points, who controls DNS, whether
   e-mail is protected, what the site looks like. A cell is a door to the tab
   where you act on it. */
.status-band { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--step-3); margin-bottom: var(--step-4); }
.status-cell {
  background: var(--surface); border: 1px solid var(--border); border-top: 3px solid var(--border-strong);
  border-radius: var(--radius-lg); padding: var(--step-3) var(--step-4); min-width: 0; position: relative;
}
.status-cell[data-open-view] { cursor: pointer; transition: border-color var(--fast), background var(--fast); }
.status-cell[data-open-view]:hover { border-color: var(--accent); background: var(--surface-muted); }
.status-cell.tone-warn { border-top-color: var(--warn-text); }
.status-cell.tone-error { border-top-color: var(--error-text); }
.status-cell.tone-ok { border-top-color: var(--ok-text); }
.status-cell .l { font-size: var(--text-2xs); text-transform: uppercase; letter-spacing: var(--label-spacing); color: var(--text-muted); margin-bottom: var(--step-1); }
.status-cell .k { font-size: var(--text-md); font-weight: var(--weight-medium); display: flex; align-items: center; gap: var(--step-2); min-height: 22px; }
.status-cell .v { font-size: var(--text-xs); color: var(--text-faint); font-family: var(--font-mono); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.status-cell .status-thumb { display: block; width: 100%; height: 44px; object-fit: cover; object-position: top; border: 1px solid var(--border); margin-top: var(--step-1); }
@media (max-width: 720px) { .status-band { grid-template-columns: 1fr 1fr; } }

/* The Loopia call meter at the foot of the menu. */
.call-meter {
  padding: var(--step-2) var(--step-3); font-size: var(--text-xs); color: var(--sidebar-muted);
  cursor: default;
}
.call-meter-text { display: flex; justify-content: space-between; gap: var(--step-2); font-variant-numeric: tabular-nums; }
.call-meter-bar { height: 3px; margin-top: var(--step-1); background: var(--sidebar-border); border-radius: 2px; overflow: hidden; }
.call-meter-fill { height: 100%; width: 0; background: var(--sidebar-muted); transition: width var(--slow); }
.call-meter[data-tone="warn"] { color: #f0c36d; }
.call-meter[data-tone="warn"] .call-meter-fill { background: #f0c36d; }
.call-meter[data-tone="blocked"] { color: #f28b82; }
.call-meter[data-tone="blocked"] .call-meter-fill { background: #f28b82; }

/* --- invoices page: unpaid, earlier Loopia, Scaleway --- */
.lbl.invoice-section { margin-top: var(--step-6); margin-bottom: var(--step-2); }
.invoice-meta { display: flex; flex-wrap: wrap; gap: var(--step-3); align-items: center; margin-bottom: var(--step-3); color: var(--text-muted); }
.invoice-lines th.num, .invoice-lines td.num, .invoice-totals td.num { text-align: right; }
.invoice-totals { margin: var(--step-3) 0 0 auto; border-collapse: collapse; }
.invoice-totals td { padding: 2px 0 2px var(--step-5); }
.invoice-totals tr.sum td { font-weight: var(--weight-medium); border-top: 1px solid var(--border); padding-top: var(--step-2); }
.invoice-note { margin-top: var(--step-3); }
.invoice-actions { margin-top: var(--step-3); gap: var(--step-2); }

/* Printing an invoice: only the open dialog, as plain black on white. */
@media print {
  body:has(.invoice-detail) > *:not(.modal) { display: none !important; }
  body:has(.invoice-detail) .modal { position: static; transform: none; box-shadow: none; max-width: none; width: auto; opacity: 1; }
  body:has(.invoice-detail) .modal .button-row,
  body:has(.invoice-detail) .modal .modal-icon,
  body:has(.invoice-detail) .invoice-actions { display: none !important; }
}

/* --- pending proposals on the domain page --- */
.pending-proposals {
  background: var(--surface); border: 1px solid var(--accent-border, var(--border));
  border-radius: var(--radius); padding: var(--step-4); margin-bottom: var(--step-4);
}
.pending-proposal { border-top: 1px solid var(--border); padding: var(--step-3) 0; }
.pending-proposal:first-of-type { border-top: none; padding-top: var(--step-2); }
.pending-proposal summary { cursor: pointer; display: flex; flex-wrap: wrap; gap: var(--step-3); align-items: baseline; }
.pending-proposal-meta, .pending-proposal-counts { color: var(--text-muted); font-size: var(--text-sm); }
.pending-proposal .acts { margin-top: var(--step-3); }
/* A row a pending proposal changes: same colours as your own unsaved change,
   but tagged with the proposal and not editable. */
table.dns tr.pending-row td:first-child::before {
  content: "förslag #" attr(data-proposal); margin-right: .35em; text-decoration: none; display: inline-block;
  color: var(--accent); font-size: var(--text-xs); font-weight: var(--weight-medium);
}
tr.pending-row .pending-value { word-break: break-all; font-size: var(--text-xs); }
tr.pending-row .old-value { text-decoration: line-through; color: var(--text-faint); }

/* --- the DNS rules page --- */
.rule-group { margin-top: var(--step-5); }
.rule { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: var(--step-3) var(--step-4); margin-top: var(--step-2); }
.rule-head { display: flex; flex-wrap: wrap; gap: var(--step-2) var(--step-3); align-items: baseline; }
.rule-stage { margin-left: auto; color: var(--text-faint); font-size: var(--text-xs); }
.rule-body { display: grid; grid-template-columns: 110px 1fr; gap: 2px var(--step-3); margin: var(--step-2) 0 0; font-size: var(--text-sm); }
.rule-body dt { color: var(--text-muted); }
.rule-body dd { margin: 0; }
@media (max-width: 600px) { .rule-body { grid-template-columns: 1fr; } .rule-body dd { margin-bottom: var(--step-2); } }

/* --- applied changes reaching the public resolvers --- */
.propagation-block { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: var(--step-3) var(--step-4); margin-bottom: var(--step-4); }
.propagation-line { display: flex; flex-wrap: wrap; justify-content: space-between; gap: var(--step-2) var(--step-4); padding: var(--step-2) 0; border-top: 1px solid var(--border); font-size: var(--text-sm); }
.propagation-line:first-of-type { border-top: none; }
.prop-resolvers { display: flex; gap: var(--step-2); }
.prop-resolver { font-size: var(--text-xs); padding: 1px var(--step-2); border-radius: var(--radius); }
.prop-resolver.ok { background: var(--ok-surface); color: var(--ok-text); }
.prop-resolver.waiting { background: var(--surface-muted); color: var(--text-faint); }

/* --- Larm (uptime) --- */
.alerts-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--step-4); flex-wrap: wrap; margin-bottom: var(--step-3); }
.alerts-head .stats { flex: 1; }
.monitor { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: var(--step-3) var(--step-4); margin-bottom: var(--step-3); cursor: pointer; }
.monitor:hover { border-color: var(--border-strong); }
.monitor.paused { opacity: .65; }
.monitor-head { display: flex; align-items: center; gap: var(--step-3); flex-wrap: wrap; }
.monitor-name { font-size: var(--text-md); }
.monitor-url { color: var(--text-muted); font-size: var(--text-sm); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 40ch; }
.monitor-actions { margin-left: auto; display: flex; gap: var(--step-2); }
.monitor-state { font-size: var(--text-xs); font-weight: var(--weight-medium); padding: 2px var(--step-2); border-radius: var(--radius); }
.monitor-state.ok { background: var(--ok-surface); color: var(--ok-text); }
.monitor-state.bad { background: var(--error-surface); color: var(--error-text); }
.monitor-state.idle { background: var(--surface-muted); color: var(--text-faint); }
.monitor-figures { display: flex; flex-wrap: wrap; gap: var(--step-2) var(--step-4); margin: var(--step-2) 0; font-size: var(--text-sm); color: var(--text-muted); }
.monitor-figures b { color: var(--text); font-variant-numeric: tabular-nums; }
.monitor-error { color: var(--error-text); font-size: var(--text-sm); margin-top: var(--step-2); }
.uptime-bars { display: grid; grid-template-columns: repeat(48, 1fr); gap: 2px; height: 22px; }
.uptime-bars .bar { border-radius: 1px; }
.uptime-bars .bar.up { background: var(--ok-text); opacity: .75; }
.uptime-bars .bar.down { background: var(--error-text); }
.uptime-bars .bar.partly { background: var(--warn-text); }
.uptime-bars .bar.none { background: var(--surface-muted); }
.monitor-form { display: flex; flex-direction: column; gap: var(--step-3); }
.monitor-form label { display: flex; flex-direction: column; gap: var(--step-1); font-size: var(--text-sm); color: var(--text-muted); }
.monitor-form label.check { flex-direction: row; align-items: center; gap: var(--step-2); color: var(--text); }
.monitor-form .form-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--step-3); }
@media (max-width: 600px) { .monitor-form .form-row { grid-template-columns: 1fr; } .monitor-actions { margin-left: 0; } }

/* Something is waiting under this tab. A proposal lives under DNS-poster, but
   you land on Översikt — without this there is nothing on the page to say so. */
.tab-pending {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 17px;
  height: 17px;
  margin-left: var(--step-1);
  padding: 0 5px;
  border-radius: 9px;
  background: var(--warn-text);
  color: var(--text-inverse);
  font-size: var(--text-2xs);
  font-weight: var(--weight-bold);
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

/* ---------- moving the DNS ---------- */

/* Where it lives now, first. Moving TO Cloudflare and moving AWAY are
   opposite errands, and which one applies depends on this line. */
.move-now {
  display: flex;
  gap: var(--step-3);
  align-items: flex-start;
  padding: var(--step-3) var(--step-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface-muted);
  margin-bottom: var(--step-4);
}
.move-now .dot { margin-top: 6px; flex-shrink: 0; }

/* Stacked, not side by side. Two 340px columns made the record table inside
   Cloudflare's card unreadable, and pushed the buttons that carry the errand
   forward below the fold. */
.move-choices {
  display: flex;
  flex-direction: column;
  gap: var(--step-4);
}
.move-card {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--step-4);
  background: var(--surface);
  min-width: 0;
}
/* The one the domain is already on is a statement, not an offer. */
.move-card.is-current { border-color: var(--ok-border); background: var(--ok-surface); }
.move-card-head {
  display: flex;
  gap: var(--step-2);
  align-items: baseline;
  font-size: var(--text-lg);
  margin-bottom: var(--step-2);
}
.move-steps {
  margin: 0 0 var(--step-3);
  padding-left: var(--step-5);
  font-size: var(--text-sm);
  color: var(--text-muted);
}
.move-steps li { margin-bottom: 3px; }

/* ---------- recommendations ---------- */

/* Advice, not a problem with the page: at the bottom, quieter than the rest,
   and only present when something is actually missing. */
.advice { margin-top: var(--step-6); }
.advice-item {
  display: flex;
  gap: var(--step-3);
  align-items: flex-start;
  padding: var(--step-3) 0;
  border-bottom: 1px solid var(--border);
}
.advice-item:last-child { border-bottom: none; }
.advice-item .dot { margin-top: 7px; flex-shrink: 0; }
.advice-body { min-width: 0; flex: 1; }
.advice-title { font-size: var(--text-md); font-weight: var(--weight-medium); }
.advice-what { font-size: var(--text-sm); margin-top: 2px; }
.advice-why { font-size: var(--text-xs); color: var(--text-muted); margin-top: var(--step-1); }
.advice-note { font-size: var(--text-xs); color: var(--warn-text); margin-top: var(--step-1); }
.advice-record {
  display: flex;
  flex-wrap: wrap;
  gap: var(--step-2);
  align-items: center;
  margin-top: var(--step-2);
}
.advice-record code {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  background: var(--surface-muted);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 2px var(--step-2);
  overflow-wrap: anywhere;
}
.advice-manual {
  font-size: var(--text-xs);
  color: var(--text-faint);
  margin-top: var(--step-2);
}


/* ---------- moving DNS ---------- */

/* The primary way through: one button that runs every step. */
.move-go {
  margin: var(--step-3) 0;
  padding: var(--step-3);
  border-radius: var(--radius);
  background: var(--accent-surface, var(--surface-sunken));
  border: 1px solid var(--accent-border, var(--border));
}
.btn-lg { font-size: var(--text-md); padding: var(--step-2) var(--step-4); }

/* The step-by-step route, folded away. It is for the person who wants it,
   not the first thing anyone has to read. */
.move-manual > summary,
.move-records > summary,
.move-card > details > summary {
  cursor: pointer;
  user-select: none;
  color: var(--text-muted);
  font-size: var(--text-sm);
  padding: var(--step-2) 0;
}
.move-card > details > summary {
  color: var(--text);
  font-size: var(--text-lg);
  margin-bottom: 0;
}
.move-manual[open] > summary,
.move-records[open] > summary { margin-bottom: var(--step-2); }
.move-records { margin-top: var(--step-3); }

/* Where you are. Rendered by every stage of the move, so it is never
   necessary to scroll to find out what is done and what is next. */
.move-progress {
  display: flex;
  flex-wrap: wrap;
  gap: var(--step-2) var(--step-3);
  list-style: none;
  margin: 0 0 var(--step-3);
  padding: 0;
  font-size: var(--text-sm);
  color: var(--text-faint);
}
.move-progress li { display: flex; align-items: center; gap: var(--step-2); }
.move-progress .n {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.5em; height: 1.5em; border-radius: 50%;
  background: var(--surface-sunken); color: var(--text-faint);
  font-size: var(--text-xs); font-variant-numeric: tabular-nums;
}
.move-progress .is-done { color: var(--text-muted); }
.move-progress .is-done .n { background: var(--ok-surface); color: var(--ok-text, var(--text)); }
.move-progress .is-at { color: var(--text); font-weight: var(--weight-medium); }
.move-progress .is-at .n { background: var(--accent); color: #fff; }

/* What to do next, kept with the button that does it. */
.move-next {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--step-3);
  flex-wrap: wrap;
  margin-top: var(--step-3);
  padding: var(--step-3);
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--surface-sunken);
}
.move-next > div { flex: 1; min-width: 220px; }

/* ---------- the about page ---------- */

/* Prose, so it is centred and given a reading measure rather than the
   panel's full-width layout. */
body.about-page { background: var(--surface-sunken); }

.about {
  max-width: 62ch;
  margin: 0 auto;
  padding: var(--step-6) var(--step-4) var(--step-7);
}
/* display:block is not redundant: there is a global `header { display: flex }`
   for the panel's top row, and this is a <header> too — without it the logo
   and the name sat side by side instead of stacked. */
.about-head { display: block; text-align: center; margin-bottom: var(--step-6); }
.about-logo { display: block; width: 56px; height: 56px; margin: 0 auto var(--step-3); }
.about-head h1 { margin: 0; font-size: var(--text-xl); letter-spacing: -0.02em; }
.about-lead { margin: var(--step-1) 0 0; color: var(--text-muted); }
.about section { margin-bottom: var(--step-6); }
.about h2 {
  font-size: var(--text-md);
  margin: 0 0 var(--step-2);
  letter-spacing: -0.01em;
}
.about p { margin: 0 0 var(--step-3); line-height: var(--row-height); }
.about p:last-child { margin-bottom: 0; }
.about-never { margin: 0 0 var(--step-3); padding-left: var(--step-5); }
.about-never li { margin-bottom: var(--step-1); }
.about-services { display: flex; flex-wrap: wrap; gap: var(--step-2); margin-bottom: var(--step-3); }
.about-back { text-align: center; margin: var(--step-6) 0 0; }

@media (max-width: 34rem) {
}

/* ---------- a domain's activity log ---------- */

.log-list { display: flex; flex-direction: column; }
.log-row {
  display: grid;
  grid-template-columns: auto 1fr auto auto;
  align-items: baseline;
  gap: var(--step-2) var(--step-3);
  padding: var(--step-3);
  border-bottom: 1px solid var(--border);
}
.log-row:last-child { border-bottom: none; }
.log-main { min-width: 0; }
.log-what { font-weight: var(--weight-medium); }
.log-detail {
  font-size: var(--text-sm);
  color: var(--text-muted);
  overflow-wrap: anywhere;
  margin-top: 2px;
}
.log-who { font-size: var(--text-sm); color: var(--text-muted); white-space: nowrap; }
.log-when { font-size: var(--text-sm); color: var(--text-faint); white-space: nowrap; }

@media (max-width: 40rem) {
  .log-row { grid-template-columns: auto 1fr; }
  .log-who, .log-when { grid-column: 2; }
}

/* ---------- has it synced? ---------- */

.sync-status {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0 var(--step-2);
  padding: var(--step-2) var(--step-3);
  font-size: var(--text-2xs);
  color: var(--sidebar-muted);
}
.sync-status-text { color: var(--sidebar-text); }
.sync-status-detail { width: 100%; color: var(--sidebar-muted); }

/* ---------- notes on a domain ---------- */

.comment { padding: var(--step-3) 0; border-bottom: 1px solid var(--border); }
.comment:first-child { padding-top: 0; }
.comment-body { white-space: pre-wrap; overflow-wrap: anywhere; }
.comment-meta {
  display: flex;
  align-items: baseline;
  gap: var(--step-2);
  margin-top: var(--step-1);
  font-size: var(--text-sm);
  color: var(--text-faint);
}
/* Only on hover: removing someone's note should take a moment's intent. */
.comment-remove { margin-left: auto; opacity: 0; transition: opacity 120ms; }
.comment:hover .comment-remove,
.comment-remove:focus-visible { opacity: 1; }

.comment-form { display: flex; gap: var(--step-2); align-items: flex-end; margin-top: var(--step-3); }
.comment-form textarea {
  flex: 1;
  resize: vertical;
  font: inherit;
  padding: var(--step-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
}

/* ---------- tick boxes in a dialog ---------- */

.ask-choices {
  display: flex;
  flex-direction: column;
  gap: var(--step-2);
  margin: var(--step-3) 0 0;
  text-align: left;
}
/* Grid, not flex. As a flex row the label resolved to zero width even with
   flex:1 and min-width:0 — a grid column simply gets the space that is left,
   with no basis to negotiate. */
.ask-choice {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: start;
  gap: var(--step-2);
  cursor: pointer;
}
.ask-choice input { margin-top: 3px; }
.ask-choice > span { min-width: 0; }
.ask-choice-help {
  display: block;
  font-size: var(--text-sm);
  color: var(--text-muted);
}

/* A choice another choice already covers. Left visible rather than hidden:
   seeing that it is included explains the set, where removing it would just
   make options appear and disappear. */
.ask-choice.is-included { cursor: default; opacity: 0.55; }
.ask-choice-included {
  display: block;
  font-size: var(--text-xs);
  color: var(--text-faint);
  font-style: italic;
}

/* A second question in the same dialog: what a token may DO is one list,
   what it may reach is another, and they are not the same kind of choice. */
.ask-section { margin: var(--step-4) 0 0; text-align: left; }
.ask-section-help {
  display: block;
  font-size: var(--text-sm);
  color: var(--text-muted);
}
/* A domain list is as long as the account is. Scrolled rather than allowed
   to push the buttons off the screen. */
.ask-reach {
  max-height: 220px;
  overflow-y: auto;
  padding-right: var(--step-2);
}

/* Something required and left unanswered. Shown where the answer belongs,
   not as a message elsewhere: the dialog stays open and says which part. */
.ask-choices.is-missing,
.field.is-missing {
  outline: 1px solid var(--error-border);
  outline-offset: var(--step-2);
  border-radius: var(--radius);
}

/* What a token is for, under its name. A sentence, so it wraps rather than
   stretching the column it sits in. */
.token-note {
  margin-top: var(--step-1);
  max-width: 34ch;
  font-size: var(--text-sm);
  color: var(--text-muted);
  white-space: normal;
}

/* A heading with an action on the right. The button belongs beside what it
   acts on, not above the whole page. */
.lbl.list-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--step-3);
}

/* ---------- domain groups ---------- */

.group-row {
  display: flex;
  align-items: flex-start;
  gap: var(--step-3);
  padding: var(--step-3);
  border-bottom: 1px solid var(--border);
}
.group-row:last-child { border-bottom: none; }
.group-main { flex: 1; min-width: 0; }
.group-name {
  display: flex;
  align-items: baseline;
  gap: var(--step-2);
  flex-wrap: wrap;
  font-weight: var(--weight-medium);
  font-size: var(--text-md);
}
.group-note { color: var(--text-muted); font-size: var(--text-sm); margin-top: 2px; }
.group-domains { display: flex; flex-wrap: wrap; gap: var(--step-1); margin-top: var(--step-2); }
.group-actions { display: flex; gap: var(--step-2); flex: none; }

/* The domain picker inside the dialog. Scrolls rather than growing: with
   seventy domains the buttons would otherwise end up past the fold, which is
   exactly the mistake the move page made. */
.group-picker {
  max-height: 260px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--step-2);
  margin-top: var(--step-2);
}
.group-pick {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  gap: var(--step-2);
  padding: 2px 0;
  cursor: pointer;
}
.group-pick > span { min-width: 0; overflow-wrap: anywhere; }

/* ---------- choosing what somebody may reach ---------- */

.access-list { padding: 0; }
.access-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--step-3);
  flex-wrap: wrap;
  padding: var(--step-3);
  border-bottom: 1px solid var(--border);
}
.access-row:last-child { border-bottom: none; }
.access-what { min-width: 0; flex: 1; }
.access-sub {
  display: block;
  font-size: var(--text-sm);
  color: var(--text-faint);
  margin-top: 2px;
}
.access-levels { display: flex; gap: var(--step-1); flex-wrap: wrap; flex: none; }

/* Already reachable through something else. Dimmed rather than hidden: the
   row explains why it is already covered, where removing it would just make
   domains appear and disappear as groups are granted. */
.access-row.is-included { opacity: 0.6; }
.btn.is-off { opacity: 0.45; }

/* ---------- projects ---------- */

.project-card { margin-bottom: var(--step-3); }
.project-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--step-3);
  flex-wrap: wrap;
}
.project-name { font-size: var(--text-lg); font-weight: var(--weight-medium); }
.project-note { color: var(--text-muted); font-size: var(--text-sm); margin-top: 2px; }
.project-parts { margin-top: var(--step-3); display: flex; flex-direction: column; gap: var(--step-2); }
.project-part { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--step-2); }

/* What the panel thinks belongs. Kept visually apart from what somebody
   chose: a guess shown like a decision is how a panel stops being trusted. */
.project-suggestions {
  margin-top: var(--step-4);
  padding-top: var(--step-3);
  border-top: 1px dashed var(--border);
}
.project-suggestion {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--step-3);
  flex-wrap: wrap;
  padding: var(--step-2) 0;
}
.project-why {
  display: block;
  font-size: var(--text-sm);
  color: var(--text-faint);
  margin-top: 2px;
}

/* Something that is on screen but not yet stored. Dimmed rather than
   hidden: it was just typed in, and showing nothing is what made the button
   look like it had done nothing. */
.project-card:has(.project-saving) { opacity: 0.6; }
.project-saving {
  font-size: var(--text-sm);
  font-weight: var(--weight-normal);
  color: var(--text-faint);
  font-style: italic;
}
.form-lead {
  margin: 0 0 var(--step-4);
  color: var(--text-muted);
  font-size: var(--text-sm);
  line-height: var(--row-height);
  text-align: left;
}
