/* MTPReg.
 *
 * One file, no @import: there is no bundler, and an import would cost a round trip for a
 * stylesheet this size. Ordered tokens -> base -> layout -> forms -> search -> combobox ->
 * results -> detail -> utilities, so a rule is findable by what it is for.
 */

/* --- tokens ------------------------------------------------------------------------- */

:root {
  /* Tells the browser to render its own controls -- checkboxes, radios, select menus,
     scrollbars -- in the matching theme. Without it a dark page gets light checkboxes,
     and this screen is mostly form controls. */
  color-scheme: light dark;

  --bg: #ffffff;
  --surface: #f6f7f9;
  --field-bg: #ffffff;
  --stripe: #fafbfc;
  --fg: #1b1d20;
  --muted: #5f6672;
  --line: #dfe3e8;
  --line-strong: #c3cad3;
  --accent: #12507a;
  --accent-soft: #eaf1f7;
  --focus: #12507a;
  --warn-bg: #fdf3e3;
  --warn-fg: #7a4b06;
  --hist-bg: #eeeff1;
  --hist-fg: #4a5059;
  /* Text on an accent-filled button. */
  --on-accent: #ffffff;
  /* Ticket states: approved, completed, refused. `--bad-*` also colours the one button
     that cannot be undone. */
  --ok-bg: #e3f0e3;
  --ok-fg: #245c28;
  --done-bg: #efe7f7;
  --done-fg: #5b2d8e;
  --bad-bg: #f6e2e2;
  --bad-fg: #7a2b2b;
  --danger-line: #d8b4b4;
  --danger-line-strong: #b06a6a;
  --radius: 6px;
  --radius-lg: 10px;
  --shadow: 0 6px 20px rgba(0, 0, 0, .12);
}

/* The theme follows the computer until the header's toggle says otherwise. The choice is a
   cookie the server renders as `data-theme` on <html> (see `web.theme_of`), so the first
   paint is already right and no script has to run before it.

   CSS cannot say "the computer is dark *or* dark was chosen" in one selector, so the dark
   values appear twice: once under the media query, for anybody who has not chosen light,
   and once for `data-theme="dark"`. The two blocks must stay identical -- a test in
   tests/test_web.py compares them -- and every colour that differs between the themes is
   a token here, never a rule of its own inside a media query, so these two are the only
   places a dark value is written.

   `color-scheme` is set with them, so checkboxes, selects and scrollbars follow the chosen
   theme rather than the computer's. */
:root[data-theme="light"] { color-scheme: light; }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --bg: #14171a;
    --surface: #1b1f24;
    --field-bg: #1f242a;
    --stripe: #181c20;
    --fg: #e8eaed;
    --muted: #9aa3af;
    --line: #2c3238;
    --line-strong: #3c444d;
    --accent: #7db6e0;
    --accent-soft: #1b2a36;
    --focus: #7db6e0;
    --warn-bg: #3a2f18;
    --warn-fg: #e8c583;
    --hist-bg: #23272c;
    --hist-fg: #aab2bd;
    --on-accent: #10161c;
    --ok-bg: #1e3020;
    --ok-fg: #a5d3a8;
    --done-bg: #2a2036;
    --done-fg: #cdb4ee;
    --bad-bg: #33201f;
    --bad-fg: #e0a3a0;
    --danger-line: #5a3a38;
    --danger-line-strong: #8a5350;
    --shadow: 0 6px 20px rgba(0, 0, 0, .5);
  }
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: #14171a;
  --surface: #1b1f24;
  --field-bg: #1f242a;
  --stripe: #181c20;
  --fg: #e8eaed;
  --muted: #9aa3af;
  --line: #2c3238;
  --line-strong: #3c444d;
  --accent: #7db6e0;
  --accent-soft: #1b2a36;
  --focus: #7db6e0;
  --warn-bg: #3a2f18;
  --warn-fg: #e8c583;
  --hist-bg: #23272c;
  --hist-fg: #aab2bd;
  --on-accent: #10161c;
  --ok-bg: #1e3020;
  --ok-fg: #a5d3a8;
  --done-bg: #2a2036;
  --done-fg: #cdb4ee;
  --bad-bg: #33201f;
  --bad-fg: #e0a3a0;
  --danger-line: #5a3a38;
  --danger-line-strong: #8a5350;
  --shadow: 0 6px 20px rgba(0, 0, 0, .5);
}

/* --- base --------------------------------------------------------------------------- */

* { box-sizing: border-box; }

/* The room for a scrollbar is always kept, so a short page and a long one centre the same
   way and the header does not shift sideways between them. Overlay scrollbars take no
   room, and this changes nothing there. */
html { scrollbar-gutter: stable; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font: 15px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;
}

a { color: var(--accent); }

/* One focus treatment for the whole page, rather than each control inventing its own. The
   old search input swapped its border colour on focus, which shifted the layout. */
:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: 3px;
}

/* --- layout ------------------------------------------------------------------------- */

/* The bar's line runs edge to edge; what sits on it is inset to the page's own width, so
   the mark on the left and the account menu on the right share their edges with the panel
   below rather than sitting in the window's corners. `--page` is main's full max-width,
   on every page: a narrow page (`main.narrow`, 62rem) used to narrow the header with it,
   and the header then jumped between widths as the reader moved from a list to an item.
   The side gutter is the same 1.25rem as main's, so the two line up inside it too. */
header.site {
  --page: 88rem;
  border-bottom: 1px solid var(--line);
  padding: .85rem max(1.25rem, calc((100% - var(--page)) / 2 + 1.25rem));
  display: flex;
  align-items: baseline;
  gap: 1rem;
  flex-wrap: wrap;
}
header.site h1 { font-size: 1.05rem; margin: 0; letter-spacing: -.01em; }
header.site h1 a { text-decoration: none; color: inherit; }
/* The theme toggle, a row in the account menu: a moon while light is showing -- press for
   dark -- and a sun while dark is, then its name, then a switch that says whether it is on.
   Both icons are in the markup and the theme decides which is seen, so it is right even
   when only the computer chose. The switch follows `aria-pressed`, which app.js keeps true
   to the theme showing. Compact mode's toggle, the row under it, is drawn the same way with
   one icon of its own. */
.accountpanel .themetoggle, .accountpanel .densitytoggle {
  display: flex; align-items: center; gap: .55rem; width: 100%;
  padding: .45rem .8rem; border: 0; border-top: 1px solid var(--line); border-radius: 0;
  background: none; color: var(--fg); font: inherit; text-align: left; cursor: pointer;
}
.accountpanel .themetoggle:hover, .accountpanel .densitytoggle:hover { background: var(--accent-soft); }
.accountpanel .themetoggle:focus-visible,
.accountpanel .densitytoggle:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }
.accountpanel .themetoggle svg,
.accountpanel .densitytoggle svg { width: 1rem; height: 1rem; color: var(--muted); flex-shrink: 0; }
.accountpanel .themetoggle-label, .accountpanel .densitytoggle-label { flex-grow: 1; }
.accountpanel .themetoggle .switch, .accountpanel .densitytoggle .switch {
  position: relative; flex-shrink: 0; width: 1.9rem; height: 1.05rem;
  border: 1px solid var(--line); border-radius: 999px; background: var(--field-bg);
}
.accountpanel .themetoggle .switch::after, .accountpanel .densitytoggle .switch::after {
  content: ""; position: absolute; top: .12rem; left: .12rem;
  width: .7rem; height: .7rem; border-radius: 50%; background: var(--muted);
}
.accountpanel .themetoggle[aria-pressed="true"] .switch,
.accountpanel .densitytoggle[aria-pressed="true"] .switch {
  background: var(--accent); border-color: var(--accent);
}
.accountpanel .themetoggle[aria-pressed="true"] .switch::after,
.accountpanel .densitytoggle[aria-pressed="true"] .switch::after {
  left: auto; right: .12rem; background: var(--bg);
}
.themetoggle .sun { display: none; }
:root[data-theme="dark"] .themetoggle .sun { display: block; }
:root[data-theme="dark"] .themetoggle .moon { display: none; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .themetoggle .sun { display: block; }
  :root:not([data-theme="light"]) .themetoggle .moon { display: none; }
}

header.site .mainnav { display: flex; gap: .9rem; font-size: .875rem; }
header.site .mainnav a { text-decoration: none; color: var(--muted); }
header.site .mainnav a:hover { color: var(--fg); text-decoration: underline; }
header.site .mainnav a[aria-current="page"] { color: var(--fg); font-weight: 600; }
/* The current page's link is bold, which would widen it and push everything after it along.
   Each label carries an invisible bold copy of itself (`data-label`) that sets its width,
   so it takes the same room at either weight. */
header.site .mainnav > a,
header.site .navmenu summary .navlabel { display: inline-grid; }
header.site .mainnav > a::after,
header.site .navmenu summary .navlabel::after {
  content: attr(data-label); font-weight: 600;
  height: 0; overflow: hidden; visibility: hidden;
}
/* Administration: a heading in the bar, and its pages in a list under it. */
header.site .navmenu { position: relative; }
header.site .navmenu summary {
  list-style: none; cursor: pointer; color: var(--muted);
  display: inline-flex; align-items: center; gap: .25rem;
}
header.site .navmenu summary::-webkit-details-marker { display: none; }
header.site .navmenu summary::after { content: "▾"; font-size: .75em; }
header.site .navmenu summary:hover,
header.site .navmenu[open] summary { color: var(--fg); }
header.site .navmenu summary.current { color: var(--fg); font-weight: 600; }
header.site .navmenu-list {
  position: absolute; top: calc(100% + .4rem); left: 0; z-index: 60;
  min-width: 12rem; margin: 0; padding: .3rem 0; list-style: none;
  background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow);
}
header.site .navmenu-list a { display: block; padding: .4rem .8rem; color: var(--fg); white-space: nowrap; }
header.site .navmenu-list a:hover { background: var(--accent-soft); text-decoration: none; }
header.site .navmenu-list .newtab { color: var(--muted); font-size: .8em; margin-left: .2rem; }
/* Narrow: a menu further along the bar -- Hjälp, after Administration -- would open off the
   right edge. The lists hang from the start of the nav row instead, and wrap rather than
   outgrow the screen. */
@media (max-width: 40rem) {
  header.site .mainnav { position: relative; }
  header.site .navmenu { position: static; }
  header.site .navmenu-list { max-width: calc(100vw - 2.5rem); }
  header.site .navmenu-list a { white-space: normal; }
}

/* The account menu is the last thing on the bar, pushed to the right end, and its list
   hangs from the right edge. It holds the viewer's own settings, language and theme
   among them, so nothing else needs a place beside it. */
header.site .navmenu.account { margin-left: auto; font-size: .875rem; }
/* Batchåtgärder on: a pill before the account menu, which it pushes along instead. A
   flex box centring a drawn dot on the text, and centred itself in the bar -- as a flex
   item its baseline would be the dot's bottom edge, and the bar aligns on baselines. */
header.site .batchbadge {
  margin-left: auto; align-self: center;
  display: inline-flex; align-items: center; gap: .4rem;
  padding: .1rem .65rem; border-radius: 999px; line-height: 1.4;
  font-size: .8125rem; font-weight: 600; text-decoration: none; white-space: nowrap;
  background: var(--bad-bg); color: var(--bad-fg); border: 1px solid var(--danger-line-strong);
}
header.site .batchbadge:hover, header.site .batchbadge:focus-visible { text-decoration: underline; }
header.site .batchbadge .dot {
  display: block; flex: none; width: .5rem; height: .5rem; border-radius: 50%;
  background: currentColor;
}
header.site .batchbadge + .navmenu.account { margin-left: 0; }
/* The name sits on the bar's baseline with the menus beside it, and the badge is centred
   on the name. Not a flex box like the other summaries: an inline-flex box's baseline is
   its first item's, here the badge, a picture, whose baseline is its bottom edge -- which
   lifted the name, or, centred, dropped it. As inline content the name keeps the line's
   baseline, and the badge is lowered by half its height (the `m` badge, 1.75rem) less the
   height of the name's middle above the baseline -- `vertical-align: middle` aims at half
   an x-height instead, which left a logo visibly low beside capitals. */
header.site .navmenu.account summary { color: var(--fg); display: inline-block; }
header.site .navmenu.account summary .groupbadge { vertical-align: calc(.36em - .875rem); }
header.site .navmenu.account summary::after { margin-left: .25rem; }
header.site .navmenu.account .navmenu-list { left: auto; right: 0; min-width: 14rem; }
.accountpanel .who {
  margin: 0; padding: .4rem .8rem .5rem; font-size: .8rem; color: var(--muted);
  border-bottom: 1px solid var(--line); display: flex; flex-direction: column; gap: .1rem;
}
.accountpanel .who .email { word-break: break-all; }
.accountpanel form { margin: 0; }
.accountpanel form button.linkish {
  display: block; width: 100%; text-align: left; padding: .4rem .8rem;
  font-size: inherit; color: var(--fg); border-radius: 0;
}
.accountpanel form button.linkish:hover { background: var(--accent-soft); text-decoration: none; }
/* The page language: a labelled row of pills, drawn like the act-as control below it. They
   are links rather than radios, since choosing one loads the page in that language. */
.accountpanel .langpick {
  display: flex; flex-direction: column; gap: .35rem; padding: .5rem .8rem .6rem;
  border-top: 1px solid var(--line);
}
.accountpanel .langpick-label {
  font-size: .7rem; text-transform: uppercase; letter-spacing: .04em; color: var(--muted);
}
.accountpanel .langs {
  display: inline-flex; align-self: flex-start;
  border: 1px solid var(--line); border-radius: 999px; overflow: hidden;
}
header.site .accountpanel .langs a,
.accountpanel .langs .current {
  display: block; padding: .15rem .6rem; font-size: .8125rem; white-space: nowrap;
}
header.site .accountpanel .langs a { color: var(--muted); text-decoration: none; }
header.site .accountpanel .langs a:hover { color: var(--fg); background: var(--accent-soft); }
header.site .accountpanel .langs a:focus-visible { outline-offset: -2px; }
.accountpanel .langs .current {
  background: var(--accent-soft); color: var(--accent); font-weight: 600;
}
/* Narrow: the account menu is near the middle of a wrapped header, and a list hung from
   its right edge runs off the left of the screen. Hang it from the header itself,
   across the width, the way the nav menus hang from their row. */
@media (max-width: 40rem) {
  header.site { position: relative; }
  header.site .navmenu.account { position: static; }
  header.site .navmenu.account .navmenu-list {
    left: 1rem; right: 1rem; top: calc(100% + .25rem); min-width: 0;
  }
}

/* The sign-in pages: one card in the middle, under a header with nothing to navigate. */
header.site.slim .langs { margin-left: auto; font-size: .8rem; }
header.site.slim .langs a { margin-left: .5rem; }
header.site.slim .langs .current {
  font-weight: 600; text-decoration: none; color: var(--muted); margin-left: .5rem;
}
main.authpage { max-width: 26rem; padding-top: 3rem; }
.authcard {
  border: 1px solid var(--line); border-radius: var(--radius); padding: 1.75rem 1.75rem 1.5rem;
  background: var(--bg); box-shadow: var(--shadow);
}
.authcard .brandmark { width: 5rem; height: 5rem; display: block; }
.authcard h2 { margin: .75rem 0 .25rem; font-size: 1.3rem; }
.authcard .lead { margin: 0 0 1.25rem; color: var(--muted); font-size: .9rem; }
.authcard .actions { margin-top: 1.25rem; display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; }
.authcard .actions .primary { min-width: 9rem; }

/* Eight columns need room. The detail page asks for `narrow` instead: prose wants a
   measure, a table does not. The wide one is also the header's (`--page` above); change
   one and change the other. A narrow page is centred under the full-width header. */
main { max-width: 88rem; margin: 0 auto; padding: 1.25rem 1.25rem 4rem; }
main.narrow { max-width: 62rem; }
/* The ticket pages' column: `narrow`, with the form and its boxes filling it rather than
   stopping at the 46rem they keep elsewhere. */
main.column { max-width: 62rem; }
main.column :is(.ticketform, .kindpick, fieldset.block, .targets, .attachments,
                .revpick, .revchanges, .revsummary, .notice) { max-width: none; }

/* --- forms -------------------------------------------------------------------------- */

/* Form controls do not inherit the page font: every browser substitutes its own, which is
   what made the PMFU row read as a different typeface from the pills beside it. The whole
   page is form controls, so this is one rule rather than one per control -- and
   `font-family`, not `font: inherit`, which would also flatten the sizes set below. */
input, select, button, textarea { font-family: inherit; }

/* One treatment for every field label. The advanced boxes used to take only the first half
   of this and the filter groups both halves, so PMFU-poäng and Version read as a heading
   while Benämning beside them read as body text. */
.field > label,
.flabel {
  display: block;
  font-size: .75rem;
  color: var(--muted);
  margin-bottom: .2rem;
  text-transform: uppercase;
  letter-spacing: .04em;
  font-weight: 600;
}

.field input[type=text],
.field input[type=number] {
  width: 100%;
  padding: .4rem .55rem;
  font-size: .875rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--field-bg);
  color: var(--fg);
}
/* `input.bound`, not `.bound`: an attribute selector counts as a class, so
   `.field input[type=number]` above is (0,2,1) and a bare `.field .bound` (0,2,0) loses to
   it -- which silently makes the PMFU range full width and stacks it vertically. */
.field input.bound { width: 5rem; }
/* The date range: two date boxes sharing the row, the Skapad/Ändrad boxes under them.
   `min-width: 0` lets the pair shrink inside a 16rem grid column rather than overflow it. */
.field input[type=date] {
  flex: 1 1 0; min-width: 0;
  padding: .35rem .45rem; font-size: .875rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--field-bg); color: var(--fg);
}
.daterow { display: flex; align-items: center; gap: .4rem; }
.datefield .checks { margin-top: .4rem; }
.field select {
  padding: .35rem .4rem; font-size: .8125rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--field-bg); color: var(--fg);
}

.opts {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: .35rem .6rem; margin-top: .4rem; font-size: .8125rem;
}
.opts select { flex: 0 1 auto; min-width: 0; }

.checks { display: flex; flex-wrap: wrap; gap: .45rem .9rem; font-size: .8125rem; }
.checks label { display: inline-flex; align-items: center; gap: .3rem; cursor: pointer; }

/* Segmented control. The radios are moved off-screen rather than hidden, so they stay
   focusable: this is a real keyboard-navigable radio group that looks like pills, and it
   works with no JavaScript. */
.seg {
  display: inline-flex; border: 1px solid var(--line); border-radius: 999px;
  overflow: hidden;
}
.seg label { cursor: pointer; }
.seg input { position: absolute; opacity: 0; width: 0; height: 0; }
.seg span {
  display: block; padding: .15rem .6rem; font-size: .8125rem; color: var(--muted);
  /* A pill is one label. Broken across two lines it reads as two options. */
  white-space: nowrap;
}
.seg input:checked + span {
  background: var(--accent-soft); color: var(--accent); font-weight: 600;
}
.seg input:focus-visible + span { outline: 2px solid var(--focus); outline-offset: -2px; }
/* Four options do not fit the filter panel's narrow column in one frame, and a frame that
   wraps reads as a broken pill: each option becomes its own pill instead, as the ticket
   filters do on a phone. */
.seg.loose { flex-wrap: wrap; gap: .3rem; border: 0; border-radius: 0; overflow: visible; }
.seg.loose span { border: 1px solid var(--line); border-radius: 999px; }

/* --- search form -------------------------------------------------------------------- */

.searchbar { display: flex; gap: .5rem; flex-wrap: wrap; }
.searchbar input[type=search] {
  flex: 1 1 24rem;
  padding: .6rem .8rem;
  font-size: 1.0625rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--field-bg);
  color: var(--fg);
}
.searchbar button {
  padding: .6rem 1.1rem; font-size: .9375rem; font-weight: 600;
  border: 1px solid var(--accent); border-radius: var(--radius);
  background: var(--accent); color: var(--on-accent); cursor: pointer;
}

/* The panel sits on one line while closed, so the page is quiet at rest, and goes full
   width when opened. */
.toolbar {
  flex-basis: 100%;
  display: flex; flex-wrap: wrap; align-items: flex-start;
  gap: .4rem .75rem; margin-top: .5rem;
}
.panel { flex: 0 0 auto; }
.panel[open] {
  flex-basis: 100%; min-width: 0;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
}
.panel > summary {
  cursor: pointer; list-style: none;
  font-size: .8125rem; font-weight: 600; color: var(--accent);
  padding: .3rem .6rem; border-radius: 999px;
  display: inline-flex; align-items: center; gap: .35rem;
}
.panel > summary::-webkit-details-marker { display: none; }
.panel > summary::before {
  content: "\25B8"; display: inline-block; transition: transform .12s ease;
}
.panel[open] > summary::before { transform: rotate(90deg); }
.panel-body { padding: .1rem 1rem 1rem; }
/* The boxes on the left, the filters framed on the right. `minmax(0, 1fr)` on the first
   column, not `1fr`: a grid track's default minimum is its content, and the box grid inside
   it would otherwise refuse to shrink below its own widest field and push the filters off.
   The filter column is capped rather than fluid -- checkbox rows do not get more readable
   with more width, and the boxes are what deserve the room. */
.panelgrid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(15rem, 19rem);
  gap: 1.5rem;
  align-items: start;
}
.filterbox {
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: .85rem 1rem 1rem;
  background: var(--bg);
}
@media (max-width: 62rem) {
  /* Two columns stop being two columns before the filters get unusable, not after. */
  .panelgrid { grid-template-columns: minmax(0, 1fr); gap: 1.1rem; }
  /* Stacked, the frame is full width, and one tall stripe of controls down its left edge
     wastes all of it. The groups flow into columns instead -- which needs `.fgroup` to
     stop being `display: contents`, or a label and its own controls land in different
     columns. */
  .filterbox .filtergrid {
    grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
    gap: .7rem 1.5rem;
    align-items: start;
  }
  .filterbox .filtergrid .fgroup { display: block; }
  /* Every column there is, not `span 2`: on a phone only one fits, and spanning two
     made a second one, pushing the panel past the screen's edge. */
  .filterbox .filtergrid .wide { grid-column: 1 / -1; }
  .filterbox .filtergrid .checks,
  .filterbox .filtergrid .tagbody { margin-bottom: 0; }
}
.pip {
  min-width: 1.15em; padding: 0 .35em; border-radius: 999px;
  background: var(--accent-soft); color: var(--accent);
  font-size: .7rem; text-align: center; line-height: 1.45;
}
.toolbar .clear { font-size: .8125rem; align-self: center; }

.advgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
  gap: 1rem 1.5rem;
  align-items: start;
}
.field-wide { grid-column: 1 / -1; }
/* The grain checkbox has no <label> above it the way every other field does, so it
   supplies its own top margin to sit on the panel's baseline. */
.partsmode .checks { margin-top: .15rem; }
.partsmode .hint { margin-top: .25rem; }
/* A heading over cells that are empty at this grain -- greyed, so the blank column reads
   as "not applicable here" rather than as missing data. */
.results th.blankcol { color: var(--muted); font-weight: 500; }

/* One column, always, now that it lives in a column of its own. It used to put the label
   beside its controls and stack only on a narrow screen; inside a 19rem frame that pair
   leaves the checkboxes nothing, so the label goes above them at every width. */
.filtergrid {
  display: grid;
  grid-template-columns: 1fr;
  gap: .15rem;
  align-items: baseline;
}
.filtergrid .fgroup { display: contents; }
.filtergrid .flabel { margin: 0; }
.filtergrid .checks, .filtergrid .tagbody { margin-bottom: .5rem; }
/* The last group's own spacing is the frame's bottom padding, so the box does not end in a
   band of empty. */
.filtergrid .fgroup:last-child .checks,
.filtergrid .fgroup:last-child .tagbody { margin-bottom: 0; }

.tagpicker .tagbody {
  display: flex; flex-wrap: wrap; align-items: center; gap: .4rem .5rem;
}
.chips { display: flex; flex-wrap: wrap; gap: .35rem; }
.chip {
  display: inline-flex; align-items: center; gap: .3rem;
  font-size: .8rem; padding: .1rem .55rem; border-radius: 999px;
  text-decoration: none; background: var(--accent-soft); color: var(--accent);
}
.chip.drop:hover { text-decoration: line-through; }
/* Internal: told apart by colour, where the tag is a chip -- the temporary-code badge's,
   since a violet of its own read as the completed ticket state beside it. The word is kept
   for screen readers (`tagchip` in _macros.html), since a colour says nothing to one. */
.chip.internal { background: var(--warn-bg); color: var(--warn-fg); }
/* "intern" beside an internal tag's name, wherever an administrator sees one. Smaller and
   quieter than the name it follows: it qualifies the tag rather than competing with it. */
.tagmark {
  font-size: .68rem; padding: 0 .3rem; border-radius: 3px; margin-left: .15rem;
  background: var(--hist-bg); color: var(--hist-fg); font-weight: 500;
  vertical-align: .05em; white-space: nowrap;
}
#tagsearch {
  padding: .25rem .5rem; font-size: .8125rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--field-bg); color: var(--fg); min-width: 13rem;
}
.tagsuggestions {
  display: flex; flex-wrap: wrap; gap: .3rem .4rem;
  flex-basis: 100%; max-height: 6rem; overflow-y: auto;
}
.tagsuggestions .hint { margin: 0; }

/* --- combobox ----------------------------------------------------------------------- */
/* The popup is absolutely positioned, so no ancestor may have overflow: hidden -- not
   .panel, not .advgrid. That is the classic way this design breaks.
   It is positioned against .combo-anchor, which wraps the input alone, rather than against
   the whole .combo field: the maker and product boxes carry scope and matching pills below
   their input, and positioning against the field drops the list beneath those instead of
   under the box being typed into. */

.combo-anchor { position: relative; }
.combo-list {
  position: absolute; z-index: 20; left: 0; right: 0; top: 100%;
  margin: .2rem 0 0; padding: .2rem; list-style: none;
  max-height: 16rem; overflow-y: auto;
  background: var(--field-bg); color: var(--fg);
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.combo-list li {
  padding: .3rem .45rem; border-radius: 4px; cursor: pointer;
  font-size: .8125rem; line-height: 1.4;
}
.combo-list li:hover,
.combo-list li[aria-selected="true"] {
  background: var(--accent-soft); color: var(--accent);
}
.combo-list .opt-value {
  font-family: ui-monospace, Consolas, monospace;
  color: var(--muted); margin-right: .2rem;
}
.combo-list li[aria-selected="true"] .opt-value { color: inherit; }
/* Which kind of thing a mixed list's row is -- code, term, manufacturer -- in the reference
   box, where "SE15175" and "SE15175-00559" would otherwise read alike. */
.combo-list .opt-kind {
  display: inline-block; min-width: 5.5rem; margin-right: .35rem;
  font-size: .7rem; text-transform: uppercase; letter-spacing: .04em; color: var(--muted);
}
.combo-list li[aria-selected="true"] .opt-kind { color: inherit; }

/* --- results ------------------------------------------------------------------------ */

.resultmeta {
  display: flex; align-items: baseline; gap: 1rem; flex-wrap: wrap; margin-top: 1.25rem;
}
.resultmeta p { margin: 0; }
.resultmeta .spacer { flex: 1 1 auto; }
.sizeform { display: flex; align-items: center; }
.sizeform label {
  font-size: .8125rem; color: var(--muted);
  display: flex; align-items: center; gap: .4rem;
}
.sizeform select {
  padding: .25rem .35rem; font-size: .8125rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--field-bg); color: var(--fg);
}

/* Tagging every hit, for an administrator: a quiet row between the count and the table,
   in the page-size form's small type, because it is a tool on the answer rather than the
   answer. Its buttons only lead to a page that asks, so they need no weight of their own. */
/* `flex-end`, not `center`: the box carries its label above it, and centring the buttons
   against label and box together left them hanging between the two. */
.bulkbar {
  display: flex; align-items: flex-end; flex-wrap: wrap; gap: .4rem .5rem;
  margin: .6rem 0 0; padding: .45rem .6rem; font-size: .8125rem;
  border: 1px dashed var(--line-strong); border-radius: var(--radius);
}
.bulkbar label { color: var(--muted); }
.bulkbar select {
  padding: .25rem .35rem; font-size: .8125rem; max-width: 16rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--field-bg); color: var(--fg);
}
/* The box's own height, so the three sit on one line under the label. */
.bulkbar button { font-size: .8125rem; padding: 0 .75rem; height: 2.05rem; }
/* Wide enough for a tag's name and its "avvecklad" beside it in the list under the box. */
.bulkbar .combo { width: min(18rem, 100%); margin: 0; }
.bulkbar .combo input[type=text] { height: 2.05rem; }

.bulkcard h2 { margin-top: 0; }
.bulkfacts { margin: 1rem 0; }
.bulksummary { font-size: 1rem; }
.bulkcard .bulkform { margin: 1.25rem 0 0; }
.bulklinks { display: flex; flex-wrap: wrap; gap: .5rem 1.5rem; margin: 1rem 0 0; }

.wrap { overflow-x: auto; }
table.results {
  width: 100%; border-collapse: separate; border-spacing: 0;
  margin-top: .6rem; font-size: .875rem;
}
table.results th {
  text-align: left; vertical-align: bottom;
  font-size: .7rem; text-transform: uppercase; letter-spacing: .04em;
  color: var(--muted); font-weight: 600; white-space: nowrap;
  padding: .4rem .55rem;
  position: sticky; top: 0; z-index: 1;
  background: var(--bg);
  border-bottom: 1px solid var(--line-strong);
}
table.results th a { color: inherit; text-decoration: none; }
table.results th a.sorted { color: var(--accent); }
table.results th a:hover { text-decoration: underline; }
/* A reserved-width marker on every header, so the row does not jump when the sort moves. */
table.results th a::after {
  content: "\2195"; opacity: .3; margin-left: .3em; font-size: .95em;
}
table.results th a.sorted::after { content: ""; margin-left: 0; }

table.results td {
  padding: .4rem .55rem; vertical-align: top;
  border-bottom: 1px solid var(--line);
}
table.results tbody tr:nth-child(even) td { background: var(--stripe); }
table.results tbody tr:hover td { background: var(--accent-soft); }

/* The code column stays put while the rest scrolls -- at eight columns you lose your place
   otherwise. It needs its own background for each row state, or the scrolled content shows
   through it. */
.c-code {
  position: sticky; left: 0;
  font-family: ui-monospace, "Cascadia Code", Consolas, monospace;
  white-space: nowrap;
  background: var(--bg);
}
tbody tr:nth-child(even) .c-code { background: var(--stripe); }
tbody tr:hover .c-code { background: var(--accent-soft); }
table.results thead .c-code { z-index: 2; }
/* Every column is kept at every width, and a narrow screen scrolls the table sideways in
   its `.wrap`. The ticket's number and the term's key stay put the way the code does. */
table.tickets tr > :first-child, table.termlist .c-key {
  position: sticky; left: 0; background: var(--bg);
}
table.tickets tbody tr:nth-child(even) > :first-child,
table.termlist tbody tr:nth-child(even) .c-key { background: var(--stripe); }
table.tickets tbody tr:hover > :first-child,
table.termlist tbody tr:hover .c-key { background: var(--accent-soft); }
table.tickets thead tr > :first-child, table.termlist thead .c-key { z-index: 2; }

/* Truncate the columns people scan; let the one that carries the meaning wrap. */
.c-maker, .c-model, .c-oem, .c-trade {
  max-width: 22ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.c-term { min-width: 16ch; }
.c-pmfu { text-align: right; font-variant-numeric: tabular-nums; width: 5ch; }
.c-std { width: 7ch; }
.c-gmdn, .c-emdn, .c-udi, .c-created, .c-modified, .c-status {
  white-space: nowrap; font-variant-numeric: tabular-nums;
}
/* The values in monospace, as codes are; the headings stay in the headings' face. */
td.c-gmdn, td.c-emdn, td.c-udi {
  font-family: ui-monospace, "Cascadia Code", Consolas, monospace; font-size: .8125rem;
}
/* A code's UDI-DIs one under the other: they do not rank, so none is put first by being
   alone on the line. */
td.c-udi span { display: block; }
td.c-status .badge { margin-left: 0; }
td.c-status .badge + .badge { margin-left: .25rem; }
td.c-tags { min-width: 12ch; }
td.c-tags .chip { margin: 0 .25rem .15rem 0; white-space: nowrap; }
.badge.active { background: var(--ok-bg); color: var(--ok-fg); }

/* The columns this viewer has not chosen, from the cookie (`data-hide` on the table, kept
   by the gear's panel). One rule per column that can be hidden; the code cannot. */
table.results[data-hide~="term"] .c-term,
table.results[data-hide~="gmdn"] .c-gmdn,
table.results[data-hide~="emdn"] .c-emdn,
table.results[data-hide~="maker"] .c-maker,
table.results[data-hide~="oem"] .c-oem,
table.results[data-hide~="model"] .c-model,
table.results[data-hide~="trade"] .c-trade,
table.results[data-hide~="udi"] .c-udi,
table.results[data-hide~="pmfu"] .c-pmfu,
table.results[data-hide~="version"] .c-std,
table.results[data-hide~="status"] .c-status,
table.results[data-hide~="tags"] .c-tags,
table.results[data-hide~="created"] .c-created,
table.results[data-hide~="modified"] .c-modified { display: none; }
/* While the Status column is showing it carries Ersatt and Makulerad, so the term's cell
   stops saying them twice. Tillfällig and Utan GMDN stay: they are about the term. */
table.results:not([data-hide~="status"]) .c-term .badge.state { display: none; }

/* The gear that chooses the columns, after the page-size select and drawn like it, so the
   two read as the list's settings. Its panel drops from its corner over the rows. */
/* The select and the two buttons share one explicit height, and the buttons are as wide
   as they are tall. Stretching them to the select's height and asking for `aspect-ratio`
   did not make them square: a stretched item's width is settled before its height is, so
   they came out narrow and upright. */
.listsettings { --listctl: 1.85rem; display: flex; align-items: center; gap: .5rem; }
.listsettings .sizeform select { height: var(--listctl); box-sizing: border-box; }
.colpick { position: relative; display: flex; }
/* Kept at the right edge even when the count line is long enough to push them onto a line
   of their own, so the panel, which opens from the gear's right corner, stays on screen. */
.resultmeta .listsettings { margin-left: auto; }
/* The containing block for anything absolute inside the table -- the screen-reader text on
   an internal tag's chip, far right in Etiketter -- so it scrolls with the table rather than
   widening the page. */
.resultsbox .wrap { position: relative; }
.gearbtn {
  display: inline-grid; place-items: center; padding: 0; flex: none;
  width: var(--listctl); height: var(--listctl); box-sizing: border-box;
  text-decoration: none;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--field-bg); color: var(--muted); cursor: pointer;
}
.gearbtn:hover, .gearbtn[aria-expanded="true"] {
  color: var(--accent); border-color: var(--line-strong);
}
.colpanel {
  position: absolute; right: 0; top: calc(100% + .35rem); z-index: 5;
  width: 18rem; max-width: calc(100vw - 2.5rem);
  padding: .65rem .8rem .6rem;
  background: var(--bg); border: 1px solid var(--line-strong);
  border-radius: var(--radius-lg); box-shadow: var(--shadow);
}
.colpanel h3 {
  margin: 0 0 .45rem; font-size: .7rem; font-weight: 600; color: var(--muted);
  text-transform: uppercase; letter-spacing: .04em;
}
.colgrid { display: grid; grid-template-columns: 1fr 1fr; gap: .3rem .75rem; }
.colgrid label {
  display: flex; align-items: center; gap: .35rem; min-width: 0;
  font-size: .8125rem; cursor: pointer;
}
.colgrid label.locked { color: var(--muted); cursor: default; }
.colfoot {
  display: flex; justify-content: space-between; align-items: center; gap: .75rem;
  margin-top: .6rem; padding-top: .45rem; border-top: 1px solid var(--line);
  font-size: .75rem; color: var(--muted);
}
.colfoot button {
  padding: 0; border: 0; background: none; font: inherit; color: var(--accent); cursor: pointer;
}
.colfoot button:hover { text-decoration: underline; }


.pager {
  display: flex; align-items: center; gap: .4rem;
  margin-top: 1.1rem; flex-wrap: wrap; font-size: .875rem;
}
.pager a, .pager .off {
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: .3rem .6rem; text-decoration: none; color: var(--fg); line-height: 1;
}
.pager a:hover { background: var(--accent-soft); border-color: var(--accent); }
.pager .off { color: var(--line-strong); }
.pager .pos { color: var(--muted); margin: 0 .5rem; }

.badge {
  display: inline-block; font-size: .7rem; padding: .05rem .4rem;
  border-radius: 4px; vertical-align: middle; margin-left: .35rem; white-space: nowrap;
  background: var(--hist-bg); color: var(--hist-fg);
}
.badge.superseded, .badge.retired { background: var(--hist-bg); color: var(--hist-fg); }
.badge.temporary, .badge.obsolete { background: var(--warn-bg); color: var(--warn-fg); }
/* A code with no GMDN term and none expected: not a warning, so not the temporary colour. */
.badge.nogmdn { background: var(--accent-soft); color: var(--accent); }
.badge.system { background: var(--accent-soft); color: var(--accent); }
/* A badge that is a link -- "GMDN ordered" and "Utan GMDN" in the result list, which
   filter to their kind. Plain until pointed at, so a list of them does not read as a list
   of links. */
a.badge { text-decoration: none; cursor: pointer; }
a.badge:hover, a.badge:focus-visible { text-decoration: underline; }
.badge.sourced { background: var(--accent-soft); color: var(--accent); }

/* --- code detail -------------------------------------------------------------------- */

.card {
  border: 1px solid var(--line); border-radius: var(--radius-lg);
  padding: 1rem 1.25rem; margin: 1rem 0;
}
.card h2 {
  font-size: .75rem; text-transform: uppercase; letter-spacing: .04em;
  color: var(--muted); margin: 0 0 .75rem;
}
dl.fields {
  display: grid; grid-template-columns: minmax(7rem, 11rem) minmax(0, 1fr);
  gap: .4rem 1rem; margin: 0;
}
dl.fields dt { color: var(--muted); font-size: .875rem; }
/* `minmax(0, …)` above and `anywhere` here, or an unbreakable value -- a long reference
   URL -- widens the column past its card instead of wrapping. */
dl.fields dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }
@media (max-width: 34rem) {
  dl.fields { grid-template-columns: 1fr; gap: 0; }
  dl.fields dd { margin-bottom: .6rem; }
}
.note {
  background: var(--warn-bg); color: var(--warn-fg);
  padding: .6rem .8rem; border-radius: var(--radius); font-size: .875rem; margin: .5rem 0;
}
code.codeval { font-family: ui-monospace, Consolas, monospace; }

/* The identity block and the pictures, side by side. The right column is a fixed width
   rather than a fraction: the shots below it are a 4-up strip, and a strip that changes
   width changes the size of every thumbnail with it. */
.hero {
  display: grid; grid-template-columns: minmax(0, 1fr) 21rem;
  gap: 1.25rem 2rem; align-items: start;
  padding-bottom: 1.25rem; border-bottom: 1px solid var(--line);
}
@media (max-width: 46rem) { .hero { grid-template-columns: minmax(0, 1fr); } }

.ident .codeline {
  display: flex; align-items: center; flex-wrap: wrap; gap: .5rem; margin-bottom: .55rem;
}
.ident .codeline .codeval { font-size: 1.35rem; font-weight: 600; letter-spacing: -.01em; }
.ident .codeline .badge { margin-left: 0; }
.ident h2.term {
  font-size: 1.6rem; line-height: 1.2; letter-spacing: -.015em;
  margin: 0 0 .35rem; text-wrap: balance;
}
.ident .product { font-size: 1rem; margin: 0 0 .1rem; }
.codetags { display: flex; flex-wrap: wrap; gap: .35rem; margin: .6rem 0 0; }
/* A slash, not a dash: a model name may itself contain a dash. */
.ident .product .sep { color: var(--line-strong); margin: 0 .4rem; }
.ident .trade { color: var(--muted); font-size: .9rem; margin: 0; }
.ident .keyline {
  display: flex; flex-wrap: wrap; gap: .3rem .9rem;
  margin: .9rem 0 0; font-size: .82rem; color: var(--muted);
}
.ident .keyline .codeval { color: var(--fg); }
.ident .keyline a.termkey .codeval { color: var(--accent); }

/* Images and documents share the column: to a reader they are both "the files on this
   code", and the heading is the same eyebrow the cards below use. */
.filehead {
  font-size: .75rem; text-transform: uppercase; letter-spacing: .04em;
  color: var(--muted); margin: 0 0 .5rem;
}
.files .filehead + .documents, .files .shotcap + .filehead { margin-top: 1rem; }

/* One grid, not a lead plus a nested strip: the first item spans the row. */
ul.shots {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(var(--cols, 4), 1fr); gap: .5rem;
}
ul.shots li { min-width: 0; }
ul.shots li:first-child { grid-column: 1 / -1; }
/* Two images are not a lead and a thumbnail, they are a pair. */
ul.shots.pair li:first-child { grid-column: auto; }
ul.shots.pair .shot { aspect-ratio: 4 / 3; }
.shot {
  display: block; position: relative; line-height: 0; overflow: hidden;
  aspect-ratio: 1 / 1; cursor: zoom-in;
  border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface);
}
ul.shots li:first-child .shot { aspect-ratio: 4 / 3; }
.shot:hover { border-color: var(--line-strong); }
.shot img { width: 100%; height: 100%; object-fit: contain; display: block; }
/* The fifth tile stands for itself and everything after it. */
.shot .more {
  position: absolute; inset: 0; display: grid; place-items: center;
  background: rgba(12, 18, 24, .62); color: #fff; font-size: .95rem; font-weight: 600;
  line-height: 1;
}
.shotcap { font-size: .78rem; color: var(--muted); margin: .3rem 0 0; }

/* Four cards of short fields. One to a row wasted most of the measure on each. */
.cardgrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 1rem; }
@media (max-width: 46rem) { .cardgrid { grid-template-columns: minmax(0, 1fr); } }

/* The viewer is deliberately one theme in both: a photograph reads best on a dark
   ground, and the chrome around it has to stay legible over whatever the photograph is.
   Its three colours are local custom properties rather than page tokens for exactly that
   reason -- they are not the page's colours and must not follow them. */
.viewer {
  --v-fg: #eef2f6;
  --v-dim: #9fb0bf;
  --v-line: rgba(238, 242, 246, .3);

  margin: 0; padding: 0; border: 0; background: transparent; color: var(--v-fg);
  /* The viewport less any scrollbar gutter, which 100vw would run under. */
  width: 100%; max-width: 100%; height: 100%; max-height: 100%;
}
/* The page behind the viewer kept its scrollbar, drawn down the right edge over the
   backdrop, and went on scrolling under the picture. Hiding it leaves the page where it
   was, since the gutter is kept on every page (see *base*). */
html:has(.viewer[open]) { overflow: hidden; }
/* Author styles beat the UA rule that hides a closed dialog whatever the specificity, so
   this must be [open] and not a bare .viewer. */
.viewer[open] { display: grid; grid-template-rows: auto minmax(0, 1fr); }
.viewer::backdrop { background: rgba(10, 14, 18, .92); }
/* The page's focus ring is a dark navy, which disappears on this ground. */
.viewer :focus-visible { outline-color: var(--v-fg); }

.vhead { display: flex; justify-content: flex-end; gap: .5rem; padding: .75rem 1rem; }
.vbtn.vrotate { display: inline-flex; align-items: center; gap: .35rem; }
.vrotate svg { width: .95rem; height: .95rem; display: block; }
/* Zooma ut, the level (which fits again) and Zooma in, as one control, set a little
   apart from Rotera. */
.vzoom { display: inline-flex; gap: .25rem; margin-right: .5rem; }
.vbtn.vicon { display: inline-grid; place-items: center; padding: .5rem .55rem; }
.vicon svg { width: .8125rem; height: .8125rem; display: block; }
.vbtn.vlevel {
  min-width: 4.25rem; font-variant-numeric: tabular-nums; text-align: center;
}
.vbtn:disabled { opacity: .45; cursor: default; }
.vbtn:disabled:hover { background: transparent; }
/* overflow clips a zoomed picture to the stage; touch-action hands a pinch and a drag
   to the viewer rather than to the browser's own zoom and scroll. */
.vstage { position: relative; display: grid; place-items: center; min-height: 0;
  padding: 0 3.25rem 1.5rem; overflow: hidden; touch-action: none; }
.vstage.zoomed img { cursor: grab; }
.vstage.dragging img { cursor: grabbing; }
.viewer figure {
  margin: 0; min-height: 0; max-height: 100%;
  display: grid; grid-template-rows: minmax(0, 1fr) auto; gap: .7rem; justify-items: center;
}
/* An author `display` beats the UA rule behind the hidden attribute whatever the
   specificity, so setting display above silently un-hides every figure. */
.viewer figure[hidden] { display: none; }
.viewer figure img {
  max-width: 100%; max-height: 100%; object-fit: contain; display: block;
  border-radius: var(--radius);
  user-select: none; -webkit-user-select: none; -webkit-user-drag: none;
}
/* Above a zoomed picture, which is transformed and so painted over what follows it. */
.viewer figcaption {
  display: flex; align-items: center; justify-content: center; flex-wrap: wrap;
  gap: .5rem 1rem; font-size: .875rem; position: relative; z-index: 1;
}
/* A ground behind it while a picture runs under it. A shadow, not padding: changing the
   caption's size would resize the picture under the zoom being worked out for it. */
.vstage.zoomed figcaption {
  border-radius: 999px; background: rgba(12, 18, 24, .75);
  box-shadow: 0 0 0 .35rem rgba(12, 18, 24, .75);
}
.vpos {
  font-family: ui-monospace, Consolas, monospace; color: var(--v-dim);
  font-variant-numeric: tabular-nums;
}
/* A ticket's picture is named by the file it came in as; a code's is not. */
.vname { color: var(--v-dim); overflow-wrap: anywhere; }
.vbtn {
  display: inline-block; font-size: .8125rem; line-height: 1; cursor: pointer;
  padding: .5rem .75rem; border-radius: 999px;
  border: 1px solid var(--v-line); background: transparent; color: var(--v-fg);
  text-decoration: none;
}
.vbtn:hover { background: rgba(238, 242, 246, .14); }
/* Previous and Next: a drawn chevron centred by grid, not a text glyph. The guillemets
   that were here sat low and off to one side of the circle, because where a glyph lands
   in its line box is the font's decision and differs between fonts and platforms. */
.vnav {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 2.5rem; height: 2.5rem; padding: 0; cursor: pointer;
  display: grid; place-items: center; border-radius: 50%;
  border: 1px solid var(--v-line); background: rgba(12, 18, 24, .6); color: var(--v-fg);
}
.vnav svg { width: 1.25rem; height: 1.25rem; display: block; }
.vnav:hover { background: rgba(12, 18, 24, .95); }
.vnav.prev { left: .5rem; }
.vnav.next { right: .5rem; }
@media (max-width: 34rem) {
  .vstage { padding: 0 .75rem 1.25rem; }
  .vnav { bottom: .4rem; top: auto; transform: none; }
}

ul.documents { list-style: none; margin: 0; padding: 0; }
ul.documents li { padding: .25rem 0; }
ul.documents .muted { font-size: .85rem; margin-left: .5rem; }

/* --- code viewer -------------------------------------------------------------------- */

/* The code page, opened over the search results. The page's own colours rather than the
   image viewer's dark ground: what it shows is the code page itself.

   A fixed size rather than one that follows its content, so stepping to a shorter code does
   not move Previous and Next out from under the pointer. The width is `main.narrow`'s, so the
   code lays out exactly as it does on its own page. `dvh`, not `vh`: on a phone `vh` counts
   the browser's toolbar and pushes the bottom of the frame off screen. */
.codeview {
  width: min(62rem, calc(100vw - 2rem)); max-width: 100vw;
  height: calc(100dvh - 3rem); max-height: 100dvh;
  padding: 0; overflow: hidden;
  border: 1px solid var(--line-strong); border-radius: var(--radius-lg);
  background: var(--bg); color: var(--fg); box-shadow: var(--shadow);
}
/* [open], not a bare .codeview -- the same trap noted at .viewer[open]. */
.codeview[open] { display: grid; grid-template-rows: minmax(0, 1fr); }
.codeview::backdrop { background: rgba(10, 14, 18, .5); }
/* Focusable only so the arrow keys keep reaching it after the focused link is swapped away.
   A ring round the whole frame would point at nothing. */
.codeview:focus-visible { outline: none; }

/* A header row over a scrolling body. The scroller is inside the swap, so every new code
   starts at its top without anything resetting it. */
.cvbody { display: grid; grid-template-rows: auto minmax(0, 1fr); min-height: 0; }
/* The loading marker, until an answer lands. Generated rather than markup, so there is
   nothing for the swap to leave behind. */
.cvbody:empty::before { content: "…"; color: var(--muted); padding: 1rem 1.25rem; }
.cvhead {
  display: flex; align-items: center; flex-wrap: wrap; gap: .5rem 1.25rem;
  /* The right padding is the close button's, which sits outside the swap. */
  padding: .6rem 5.5rem .6rem 1.25rem;
  border-bottom: 1px solid var(--line); background: var(--surface);
}
.cvhead .pager { margin: 0; }
.cvopen { font-size: .875rem; }
.cvclose { position: absolute; top: .55rem; right: .75rem; }
.cvscroll { overflow-y: auto; min-height: 0; padding: 1.25rem 1.25rem 2rem; }
.cvscroll > .notice { margin-top: 0; }

@media (max-width: 34rem) {
  .codeview { width: 100vw; height: 100dvh; border: 0; border-radius: 0; }
}

/* --- tag manager -------------------------------------------------------------------- */

/* The vocabulary, opened over the search page. The code viewer's frame: a fixed size, the
   page's own colours, and a header over a scrolling body. Narrower, because it is a list of
   short names and not a code page. */
.tagmanager {
  width: min(58rem, calc(100vw - 2rem)); max-width: 100vw;
  height: calc(100dvh - 3rem); max-height: 100dvh;
  padding: 0; overflow: hidden;
  border: 1px solid var(--line-strong); border-radius: var(--radius-lg);
  background: var(--bg); color: var(--fg); box-shadow: var(--shadow);
}
/* [open], not a bare .tagmanager -- the same trap noted at .viewer[open]. */
.tagmanager[open] { display: grid; grid-template-rows: minmax(0, 1fr); }
.tagmanager::backdrop { background: rgba(10, 14, 18, .5); }
.tmclose { position: absolute; top: .55rem; right: .75rem; z-index: 2; }
.tmbody { display: grid; min-height: 0; }
.tmbody:empty::before { content: "…"; color: var(--muted); padding: 1rem 1.25rem; }

.tagmanager-body { display: grid; grid-template-rows: auto minmax(0, 1fr); min-height: 0; }
/* The title and its two figures on the left, Ny etikett on the right -- the one thing an
   administrator comes to do that is not reading the list. */
.tmhead {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: .4rem 1rem;
  padding: .6rem 1.25rem; border-bottom: 1px solid var(--line); background: var(--surface);
}
/* Room for the close button, which sits outside the swap. */
.tagmanager .tmhead { padding-right: 5.5rem; }
.tmtitle { display: flex; flex-wrap: wrap; align-items: baseline; gap: .1rem .75rem; }
.tmhead h2 { margin: 0; font-size: 1.1rem; }
.tmsummary { font-size: .8125rem; color: var(--muted); font-variant-numeric: tabular-nums; }
.tmscroll { overflow-y: auto; min-height: 0; padding: .75rem 1.25rem 2rem; }
.tmscroll > .notice { margin-top: 0; }
.tmscroll > .tmintro { margin: 0 0 .9rem; }
/* On a page of its own it is a card rather than a frame, and the page scrolls. */
.tagpage .tagmanager-body {
  border: 1px solid var(--line); border-radius: var(--radius-lg); overflow: hidden;
}

/* The pills and the name box on one row, the box giving way to the next line when the
   pills need the width. A pill is a radio in a label: the circle is hidden, the label is
   what is drawn, and the checked one is filled with the accent. */
.tagfilter {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: .6rem 1rem; margin: 0 0 .35rem;
}
.tfpills {
  display: flex; flex-wrap: wrap; gap: .35rem; border: 0; padding: 0; margin: 0; min-width: 0;
}
.tfpill {
  position: relative; display: inline-flex; align-items: center; gap: .35rem;
  font-size: .875rem; line-height: 1.4; padding: .3rem .75rem; border-radius: 999px;
  border: 1px solid var(--line-strong); background: var(--field-bg); cursor: pointer;
}
.tfpill:hover { border-color: var(--accent); }
.tfpill input { position: absolute; opacity: 0; inset: 0; margin: 0; cursor: pointer; }
.tfpill:has(input:focus-visible) { outline: 2px solid var(--focus); outline-offset: 2px; }
.tfcount { color: var(--muted); font-variant-numeric: tabular-nums; }
.tfpill:has(input:checked) {
  background: var(--accent); border-color: var(--accent); color: var(--on-accent);
  font-weight: 600;
}
.tfpill:has(input:checked) .tfcount { color: var(--on-accent); }
.tfsearch {
  display: flex; align-items: center; gap: .4rem; flex: 0 1 15rem;
  padding: 0 .6rem; border: 1px solid var(--line-strong); border-radius: var(--radius);
  background: var(--field-bg); color: var(--muted);
}
.tfsearch:focus-within { outline: 2px solid var(--focus); outline-offset: 1px; }
.tfsearch input {
  flex: 1 1 auto; min-width: 0; border: 0; outline: none; background: transparent;
  padding: .4rem 0; font-size: .9375rem; color: var(--fg);
}
.tfmeta {
  display: flex; align-items: center; gap: .75rem; min-height: 1.5rem;
  margin: 0 0 .5rem; font-size: .8125rem; color: var(--muted);
}
.tfnone { margin: 1rem .5rem; }

.tagcreate {
  border: 1px solid var(--line); border-radius: var(--radius-lg);
  padding: .75rem 1rem; margin: 0 0 1.25rem;
}
.tcreatehead {
  display: flex; align-items: center; justify-content: space-between; margin: 0 0 .5rem;
}
.tagcreate h3 { margin: 0; font-size: .95rem; }
.tsimilar { margin: .75rem 0 0; max-width: none; }
.tsimilar ul { margin: .4rem 0 .6rem; }
.tcreaterow { display: flex; flex-wrap: wrap; align-items: flex-end; gap: .75rem 1rem; }
.tcreaterow .tfield { margin: 0; }
.tcreaterow .grow { flex: 1 1 14rem; }
.tcreaterow button { margin-bottom: .1rem; }
.tagcreate > .hint { margin-top: .5rem; }
/* The two visibilities side by side, under a label that reads like every other field's. */
fieldset.tvis { border: 0; padding: 0; min-width: 0; }
fieldset.tvis legend {
  padding: 0; font-weight: 600; font-size: .875rem; margin-bottom: .3rem;
}
fieldset.tvis label {
  display: inline-flex; align-items: center; gap: .3rem; margin-right: .9rem;
  font-size: .9375rem; padding: .35rem 0;
}

/* One row per tag, on one grid, so the columns line up without a table -- each row is a
   <details> summary, which a table row cannot be. */
.tagrow {
  display: grid; align-items: center; gap: .25rem .75rem;
  grid-template-columns: minmax(0, 1fr) 5.5rem 8.25rem 5rem 5.5rem 6rem 2.75rem;
  padding: .5rem .5rem;
}
.tagheads {
  font-size: .72rem; color: var(--muted); text-transform: uppercase; letter-spacing: .04em;
  font-weight: 600; border-bottom: 1px solid var(--line-strong); padding-bottom: .3rem;
}
/* Nobody but an administrator has the Ändra column, or the På ärenden one -- labels are
   internal -- so theirs is two columns shorter. */
.tagmanager-body:not(:has(details)) .tagrow {
  grid-template-columns: minmax(0, 1fr) 5.5rem 8.25rem 5rem 6rem;
}
.tagheads { align-items: end; }
.tagheads > span { line-height: 1.3; }
.tagheads a { color: inherit; text-decoration: none; }
.tagheads a:hover, .tagheads a:focus-visible { color: var(--fg); text-decoration: underline; }
.tagheads a.sorted { color: var(--fg); }
.taglist { list-style: none; margin: 0; padding: 0; }
.taglist > li { border-bottom: 1px solid var(--line); }
summary.tagrow { cursor: pointer; list-style: none; }
summary.tagrow::-webkit-details-marker { display: none; }
summary.tagrow:hover { background: var(--surface); }
.taglist details[open] > summary { background: var(--surface); }
.tname { font-weight: 500; overflow-wrap: anywhere; }
.tname .badge { margin-left: .3rem; }
.tagitem.retired .tname { color: var(--muted); font-weight: 400; }
.tedit { color: var(--accent); font-size: .8125rem; text-align: right; }
.taglist details[open] .tedit { color: var(--muted); }

.tlangs { display: flex; gap: .25rem; }
.tlang {
  font-size: .68rem; font-weight: 600; letter-spacing: .03em; line-height: 1.5;
  padding: 0 .3rem; border-radius: 3px; border: 1px solid var(--line-strong);
}
.tlang.own { background: var(--accent-soft); color: var(--accent); border-color: transparent; }
.tlang.missing { color: var(--muted); border-style: dashed; font-weight: 400; }
.tcount, .tlast, .ttickets { font-size: .875rem; font-variant-numeric: tabular-nums; }
.tlast { white-space: nowrap; }
.tvia { display: block; font-size: .72rem; color: var(--muted); }

.tagedit {
  padding: .75rem .5rem 1rem; border-top: 1px dashed var(--line);
  display: grid; gap: .75rem;
}
.tnames { display: grid; grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); gap: 0 1rem; }
.tageditform .tfield { margin-bottom: .75rem; }
.tageditform > .hint { margin: 0 0 .6rem; }
.tsave { display: flex; gap: .6rem; }
.tagactions {
  display: flex; flex-wrap: wrap; align-items: center; gap: .4rem 1.25rem; font-size: .875rem;
}
.tagactions form { margin: 0; }
.tagactions .hint { margin: 0; }
.tagedit > .hint { margin: 0; }
/* A system tag's rule and its Uppdatera, above the links. */
.tsystem { display: grid; gap: .5rem; }
.tsystem .hint { margin: 0; }
.tsystem form { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem 1rem; margin: 0; }
.trefresh { display: grid; gap: .5rem; }
.trefresh .notice { margin: 0; }
/* The tag's record, at the foot of its edit panel: when, what, who, a line each. */
.thistory { border-top: 1px solid var(--line); padding-top: .6rem; }
.thistory h4 {
  margin: 0 0 .35rem; font-size: .72rem; font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; color: var(--muted);
}
.thistory ol { list-style: none; margin: 0; padding: 0; display: grid; gap: .2rem; }
.thistory li {
  display: grid; grid-template-columns: 6rem minmax(0, 1fr) minmax(0, 11rem); gap: .75rem;
  font-size: .875rem;
}
.thistory time, .thistory .who { color: var(--muted); font-variant-numeric: tabular-nums; }

/* Narrow: the name takes its own line and the figures sit beneath it. The heading row
   stays as a line of sort links, since there are no columns left for it to head; every
   figure also says what it is. */
@media (max-width: 40rem) {
  .tagheads .tlanghead, .tagheads > span:empty { display: none; }
  /* A wrapping line rather than a grid: four figures do not fit beside each other at
     every width, and a figure that wraps whole reads better than one squeezed. */
  .tagrow,
  .tagmanager-body:not(:has(details)) .tagrow {
    display: flex; flex-wrap: wrap; align-items: baseline; gap: .2rem .9rem;
  }
  .tagrow .tname { flex: 1 1 calc(100% - 5rem); }
  .tagrow .tedit { order: 1; }
  .tagrow .tlangs, .tagrow .tcount, .tagrow .ttickets, .tagrow .tlast { order: 2; }
  .tagrow .tlangs { align-self: center; }
  /* Its figure needs its word once the heading that said it has gone. */
  .ttickets .sr-only {
    position: static; width: auto; height: auto; margin: 0; clip: auto; overflow: visible;
    white-space: nowrap; color: var(--muted);
  }
  .tfsearch { flex: 1 1 100%; }
  .thistory li { grid-template-columns: 5.5rem minmax(0, 1fr); }
  .thistory .who { grid-column: 2; }
}
@media (max-width: 34rem) {
  .tagmanager { width: 100vw; height: 100dvh; border: 0; border-radius: 0; }
}

/* Redigera hjälp borrows the tag manager's form and row styles; only its row differs --
   name, address, where it opens -- and the address may be long, so it is the column that
   gives way. */
.helppage > h2 { margin: 0 0 .25rem; }
.helppage > .hint { margin: 0 0 1rem; }
.helppage .tagcreate .tfield { margin-bottom: .75rem; }
.helprow {
  display: grid; align-items: center; gap: .25rem .75rem; padding: .5rem;
  grid-template-columns: minmax(8rem, 1fr) minmax(0, 1.4fr) 8rem 3.5rem;
  cursor: pointer; list-style: none;
}
.helprow::-webkit-details-marker { display: none; }
.helprow:hover { background: var(--surface); }
.helpurl {
  font-size: .8125rem; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.helptarget { font-size: .8125rem; }
/* Extern sökning in Systeminställningar: the help-link row with the lookup as a UDI-DI shows
   it in front, where the help row has where a link opens at the end. */
.lookuprow { grid-template-columns: 4.5rem minmax(6rem, .8fr) minmax(0, 2fr) 3.5rem; }
.lookupicon { display: flex; }
#lookups > .hint { margin: 0 0 .75rem; }
.lookupcreate { margin-top: 1rem; }
.lookupcreate h4 { margin: 0 0 .5rem; font-size: .95rem; }
.lookupcreate .tfield, .lookuplist .tageditform .tfield { margin-bottom: .75rem; }
.lookupcreate input[type=file], .lookuplist input[type=file] { font-size: .875rem; }
@media (max-width: 40rem) {
  .helprow {
    grid-template-columns: 1fr auto;
    grid-template-areas: "name edit" "url url" "target target";
  }
  .helprow .tname { grid-area: name; }
  .helprow .helpurl { grid-area: url; }
  .helprow .helptarget { grid-area: target; }
  .helprow .tedit { grid-area: edit; }
  .lookuprow { grid-template-areas: "icon edit" "name name" "url url"; }
  .lookuprow .lookupicon { grid-area: icon; }
}

/* The button that opens it, beside the tag box. */
.tagmanage { white-space: nowrap; }

/* --- welcome page and service announcements ----------------------------------------- */

/* A service announcement is the warning colour wherever it is: in full on the welcome page,
   and as a band under the header everywhere else. Its edge is the text colour thinned, so
   the band needs no colour of its own in either theme. */
.announce {
  display: flex; align-items: flex-start; gap: .75rem;
  background: var(--warn-bg); color: var(--warn-fg);
  border: 1px solid color-mix(in srgb, var(--warn-fg) 35%, transparent);
}
.announce-icon { flex: none; width: 1.25rem; height: 1.25rem; margin-top: .1rem; }
.announce-kind {
  font-size: .72rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
}
.announce-when { font-size: .875rem; }
.announce a { color: inherit; }

.announce-bands { display: grid; }
.announce.band {
  border-width: 0 0 1px; border-radius: 0; align-items: center;
  padding: .55rem max(1.25rem, calc((100% - 88rem) / 2 + 1.25rem));
}
.announce-line {
  flex: 1; margin: 0; display: flex; flex-wrap: wrap; align-items: baseline; gap: .2rem .6rem;
}
.announce .dismiss {
  flex: none; border: 0; background: none; color: inherit; cursor: pointer;
  font-size: 1.35rem; line-height: 1; padding: .3rem .55rem; border-radius: var(--radius);
  min-width: 2.75rem; min-height: 2.75rem;
}
.announce .dismiss:hover { background: color-mix(in srgb, var(--warn-fg) 12%, transparent); }
.announce form { margin: 0; }

.announce.full {
  border-radius: var(--radius-lg); padding: 1rem 1.25rem; margin: 0 0 1rem;
}
.announce-text { min-width: 0; }
.announce-meta { margin: 0 0 .2rem; display: flex; flex-wrap: wrap; gap: .2rem .75rem; align-items: baseline; }
.announce.full h2 { margin: 0; font-size: 1.1rem; }
.announce-body p { margin: .4rem 0 0; line-height: 1.55; }

/* The welcome page: the introduction across the page, then three cards that fall into one
   column on a narrow screen. */
main.welcomepage { padding-top: 1.5rem; }
.welcome-hero {
  background: var(--accent-soft); border-radius: var(--radius-lg);
  padding: 2.5rem 1.5rem; margin: 0 0 1.5rem;
  display: flex; flex-direction: column; align-items: center; text-align: center;
}
.welcome-hero h2 { margin: 0 0 .75rem; font-size: 1.9rem; letter-spacing: -.015em; color: var(--accent); }
.welcome-intro { max-width: 46rem; line-height: 1.6; }
.welcome-intro p { margin: 0 0 .5rem; }
.welcome-search {
  display: flex; gap: .5rem; width: 100%; max-width: 44rem; margin: 1rem 0 .5rem;
}
/* Rounder than the search page's bar: on its own in the middle of the page it is the
   page's one invitation, and the mockup it was chosen from drew it so. */
.welcome-search input[type="search"] {
  flex: 1; min-width: 0; font-size: 1.05rem; padding: .7rem .9rem;
  border: 1px solid var(--line-strong); border-radius: .5rem;
  background: var(--field-bg); color: var(--fg);
}
.welcome-search button { font-size: 1.05rem; padding: .7rem 1.5rem; border-radius: .5rem; }
.welcome-advanced { margin: 0; font-size: .875rem; }
/* An unbroken line: the default skips descenders, which cut "sökning" and "søgning". */
.welcome-advanced a { text-decoration-skip-ink: none; text-underline-offset: .15em; }

.welcome-cards {
  display: grid; gap: 1.25rem; align-items: start;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
}
.welcome-card {
  border: 1px solid var(--line); border-radius: var(--radius-lg); padding: 1.1rem 1.25rem;
}
.welcome-card.mine { background: var(--surface); }
.welcome-card h2 { margin: 0 0 .75rem; font-size: 1.05rem; }
.cardlist { list-style: none; margin: 0; padding: 0; }
.cardlist li { display: grid; gap: .1rem; padding: .6rem 0; border-top: 1px solid var(--line); }
.cardlist li:first-child { border-top: 0; padding-top: 0; }
.cardlist a { font-weight: 600; }
.carddate { display: flex; align-items: center; gap: .3rem; font-size: .8125rem; color: var(--muted); }
.pin-icon { width: .9rem; height: .9rem; color: var(--accent); flex: none; vertical-align: -.1rem; }
.cardmore { margin: .75rem 0 0; font-size: .875rem; }
.cardmore + .cardmore { margin-top: .35rem; }
.welcome-card > .muted { margin: 0; }
.cardfoot { padding-top: .75rem; border-top: 1px solid var(--line); }
/* The tickets card's parts: stacked, each a small heading over its tiles, ruled apart. */
.cardparts > section { padding-block: .9rem; border-top: 1px solid var(--line); }
.cardparts > section:first-child { padding-top: 0; border-top: 0; }
.cardsub {
  margin: 0 0 .55rem; display: flex; flex-wrap: wrap; align-items: center; gap: .25rem .6rem;
  font-size: .75rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted);
}
.cardparts .cardmore { display: flex; flex-wrap: wrap; gap: .35rem 1rem; margin-top: .55rem; }

.tiles { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .6rem; }
.tile {
  display: flex; flex-direction: column; gap: .1rem; text-decoration: none; color: var(--fg);
  background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius);
  padding: .6rem .75rem;
}
.tile:hover { border-color: var(--accent); }
.tilecount { font-size: 1.6rem; font-weight: 700; font-variant-numeric: tabular-nums; line-height: 1.2; }
.tilelabel { font-size: .8125rem; color: var(--muted); }

/* /news: every post in full, in the order the cards list them. */
.newspost { padding: 1rem 0; border-top: 1px solid var(--line); }
.newspost h3 { margin: .2rem 0 .3rem; font-size: 1.1rem; }
.postmeta { margin: 0; display: flex; align-items: center; gap: .5rem; font-size: .8125rem; color: var(--muted); }
.postbody p { margin: .4rem 0 0; line-height: 1.55; }
.newspost:target { background: var(--accent-soft); border-radius: var(--radius); padding-inline: .75rem; }
.kindpill {
  font-size: .75rem; font-weight: 600; padding: .05rem .5rem; border-radius: 999px;
  background: var(--accent-soft); color: var(--accent);
}
.kindpill.info { background: var(--done-bg); color: var(--done-fg); }

/* Startsida och meddelanden borrows Redigera hjälp's page; its row is a kind or state, the
   title, and when. */
.announcerow {
  display: grid; align-items: center; gap: .25rem .75rem; padding: .5rem;
  grid-template-columns: 7.5rem minmax(0, 1fr) auto 3.5rem;
  cursor: pointer; list-style: none;
}
.announcerow > .badge, .announcerow > .kindpill { justify-self: start; }
.announcerow::-webkit-details-marker { display: none; }
.announcerow:hover { background: var(--surface); }
.announce-rowwhen { font-size: .8125rem; color: var(--muted); }
.badge.state-showing { background: var(--warn-bg); color: var(--warn-fg); }
.badge.state-upcoming { background: var(--accent-soft); color: var(--accent); }
.badge.state-ended { background: var(--hist-bg); color: var(--hist-fg); }
.announcepage section { margin: 0 0 1.5rem; }
.announcepage section > h3 { margin: 0 0 .4rem; font-size: 1rem; }
.announce-welcome { margin: 0 0 1.5rem; }
.announce-welcome > summary { cursor: pointer; }
.announce-welcome > summary h3 { display: inline; font-size: 1rem; }
.announce-welcome[open] > summary { margin-bottom: .5rem; }
.announce-welcome textarea, .announce-lang textarea { width: 100%; }
fieldset.announce-lang { border: 1px solid var(--line); border-radius: var(--radius); padding: .5rem .75rem .25rem; }
fieldset.announce-lang legend { font-weight: 600; font-size: .875rem; padding: 0 .3rem; }
fieldset.announce-lang .hint { margin: .15rem 0 .5rem; }
.announce-texts { gap: .75rem 1rem; margin-bottom: .75rem; }
.announce-window { display: flex; flex-wrap: wrap; gap: 0 1rem; align-items: flex-end; }
.announce-window .hint { flex-basis: 100%; margin-top: 0; }
@media (max-width: 40rem) {
  .announcerow {
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas: "kind name edit" "when when when";
  }
  .announcerow > .badge, .announcerow > .kindpill { grid-area: kind; }
  .announcerow .tname { grid-area: name; }
  .announcerow .announce-rowwhen { grid-area: when; }
  .announcerow .tedit { grid-area: edit; }
  .welcome-hero { padding: 1.75rem 1rem; }
  .welcome-hero h2 { font-size: 1.5rem; }
  .welcome-search { flex-direction: column; }
}

/* --- picture editor ----------------------------------------------------------------- */

/* A ticket's picture, being edited. The page's own colours, like the code viewer, because
   it is a form; the picture sits on the surface colour. A fixed frame rather than one that
   follows the picture, so turning a picture does not move the buttons out from under the
   pointer. */
.imageeditor {
  width: min(72rem, calc(100vw - 2rem)); max-width: 100vw;
  height: calc(100dvh - 3rem); max-height: 100dvh;
  padding: 0; overflow: hidden;
  border: 1px solid var(--line-strong); border-radius: var(--radius-lg);
  background: var(--bg); color: var(--fg); box-shadow: var(--shadow);
}
/* [open], not a bare .imageeditor -- the same trap noted at .viewer[open]. */
.imageeditor[open] { display: grid; grid-template-rows: minmax(0, 1fr); }
.imageeditor::backdrop { background: rgba(10, 14, 18, .5); }
.ieclose { position: absolute; top: .55rem; right: .75rem; z-index: 2; }
.iebody { display: grid; min-height: 0; }
/* The loading marker, as the code viewer has it. */
.iebody:empty::before { content: "…"; color: var(--muted); padding: 1rem 1.25rem; }
.ieproblem { margin: 3.5rem 1.25rem 1rem; }

/* The picture beside its tools, under one header. */
.ieform {
  display: grid; min-height: 0;
  grid-template-columns: minmax(0, 1fr) 18rem;
  grid-template-rows: auto minmax(0, 1fr);
  grid-template-areas: "head head" "stage tools";
}
.iehead {
  grid-area: head;
  display: flex; align-items: baseline; flex-wrap: wrap; gap: .25rem 1rem;
  /* The right padding is the close button's, which sits outside the swap. */
  padding: .6rem 5.5rem .6rem 1.25rem;
  border-bottom: 1px solid var(--line); background: var(--surface);
}
.iehead h3 { margin: 0; font-size: 1rem; }
/* Ångra and Gör om, at the header's far end -- before the room kept for Stäng. */
.iehistory { display: flex; gap: .35rem; margin-left: auto; }
.iehistory button { padding: .2rem .55rem; font-size: .8125rem; }
.iename { font-size: .875rem; overflow-wrap: anywhere; }
.iesize { font-size: .8125rem; color: var(--muted); font-variant-numeric: tabular-nums; }
.iestage {
  grid-area: stage; min-height: 0; overflow: auto;
  display: grid; place-items: center; padding: 1rem;
  background: var(--surface);
}
/* The frame is shrink-wrapped round the picture, so that what is drawn over it -- the
   crop, the selection -- can be placed in percentages of the picture itself, and the
   handles that straighten it at its corners. `.ieclip` clips what overflows the picture,
   which is why those handles are the frame's and not the picture's. `touch-action`, so a
   finger drags rather than scrolling the dialog.

   The marks are drawn over a photograph, not over the page, so their colours are the same
   in both themes -- local properties rather than tokens, as the image viewer's are. */
.ieframe {
  --ie-mark: #ffffff;
  --ie-mark-edge: rgba(0, 0, 0, .75);
  --ie-dim: rgba(10, 14, 18, .55);

  position: relative;
  /* Its shape from the server (--ratio, --w, --fit), not from the picture, so it keeps
     its size while the next preview loads. As wide as the stage allows, no wider than the
     preview is, and no taller than the frame leaves room for: the frame less its header
     and the stage's padding, a percentage needing a height the stage does not have. */
  aspect-ratio: var(--ratio);
  width: min(100%, var(--w), calc((100dvh - 9rem) * var(--fit)));
}
.iepicture {
  position: relative; width: 100%; height: 100%; line-height: 0;
  touch-action: none; cursor: crosshair;
}
/* The picture and the crop's dimming, clipped to the picture's box. The clipping used to
   be the picture's own, which clipped the selection's grips with it; they are now outside
   the selection, and a selection drawn from the picture's edges puts them outside the
   picture, on the stage, where they must stay visible. */
.ieclip { position: absolute; inset: 0; overflow: hidden; line-height: 0; }
/* The preview before this one, drawn beneath while this one loads; see the route. */
.iepicture img.ieunder { position: absolute; inset: 0; }
.iepicture img.iepreview { position: relative; }
/* A grid over the picture while Räta upp is on, to line an edge up against. `difference`,
   so a line shows on a light photograph and a dark one alike. */
.ieframe.straightening .iepicture { cursor: grab; }
.ieframe.straightening .iepicture::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background-image:
    linear-gradient(to right, #fff 1px, transparent 1px),
    linear-gradient(to bottom, #fff 1px, transparent 1px);
  background-size: calc(100% / 6) calc(100% / 6);
  mix-blend-mode: difference; opacity: .6;
}
/* The handles that straighten it, centred on its corners, while Räta upp is on. Twice the
   size to a finger as they look. */
.ieturn {
  display: none;
  position: absolute; z-index: 1; width: 22px; height: 22px; padding: 0;
  transform: translate(-50%, -50%); touch-action: none; cursor: grab;
  border: 2px solid var(--accent); border-radius: 50%;
  background: var(--bg); color: var(--accent);
  font-size: 13px; line-height: 1;
}
.ieturn::before { content: "↻"; }
.ieturn::after { content: ""; position: absolute; inset: -9px; }
.ieframe.straightening .ieturn { display: block; }
.ieturn:hover { background: var(--accent-soft); }
.ieframe.turning, .ieframe.turning .iepicture, .ieframe.turning .ieturn { cursor: grabbing; }
/* Räta upp while it is on. */
#ie-straighten[aria-pressed=true] { border-color: var(--accent); background: var(--accent-soft); }
.ieturn[data-corner=nw] { left: 0; top: 0; }
.ieturn[data-corner=ne] { left: 100%; top: 0; }
.ieturn[data-corner=sw] { left: 0; top: 100%; }
.ieturn[data-corner=se] { left: 100%; top: 100%; }
/* The angle, while it is being turned. */
.iedegrees {
  position: absolute; z-index: 1; top: .6rem; left: 50%; transform: translateX(-50%);
  padding: .15rem .55rem; border-radius: 999px; pointer-events: none;
  background: rgba(10, 14, 18, .78); color: #fff;
  font-size: .8125rem; line-height: 1.4; font-variant-numeric: tabular-nums;
}
.iepicture:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
.iecrop, .ieband { position: absolute; }
/* What falls outside the crop is dimmed rather than cut away, so it can still be seen and
   selected. The shadow is what dims it; the picture's overflow is what stops it there. */
.iecrop {
  pointer-events: none;
  box-shadow: 0 0 0 100vmax var(--ie-dim); outline: 1px solid var(--ie-mark);
}
/* The selection takes the pointer: dragged by its middle it moves, by a corner it sizes. */
.ieband {
  cursor: move;
  outline: 2px dashed var(--ie-mark);
  box-shadow: 0 0 0 1px var(--ie-mark-edge), inset 0 0 0 1px var(--ie-mark-edge);
}
.ieband:focus-visible { outline-style: solid; outline-width: 3px; }
/* A grip at each corner and in the middle of each side, just outside the band and
   touching its outline, so nothing covers what was selected. `--ie-grip-out` is the
   grip's size plus the 2px outline; the stage's padding is more than that, so a grip
   pushed past the picture's edge still has room. The band's hit area and the grips'
   overlap at the edge, and the grip wins, being on top. */
.ieband { --ie-grip: 12px; --ie-grip-out: calc(-1 * var(--ie-grip) - 2px); }
.iegrip {
  position: absolute; width: var(--ie-grip); height: var(--ie-grip);
  background: var(--ie-mark); border: 1px solid var(--ie-mark-edge);
}
/* Twice the size to a finger as it looks. */
.iegrip::after { content: ""; position: absolute; inset: -9px; }
.iegrip[data-handle=nw] { left: var(--ie-grip-out); top: var(--ie-grip-out); cursor: nwse-resize; }
.iegrip[data-handle=ne] { right: var(--ie-grip-out); top: var(--ie-grip-out); cursor: nesw-resize; }
.iegrip[data-handle=sw] { left: var(--ie-grip-out); bottom: var(--ie-grip-out); cursor: nesw-resize; }
.iegrip[data-handle=se] { right: var(--ie-grip-out); bottom: var(--ie-grip-out); cursor: nwse-resize; }
.iegrip[data-handle=n] { left: calc(50% - var(--ie-grip) / 2); top: var(--ie-grip-out); cursor: ns-resize; }
.iegrip[data-handle=s] { left: calc(50% - var(--ie-grip) / 2); bottom: var(--ie-grip-out); cursor: ns-resize; }
.iegrip[data-handle=w] { left: var(--ie-grip-out); top: calc(50% - var(--ie-grip) / 2); cursor: ew-resize; }
.iegrip[data-handle=e] { right: var(--ie-grip-out); top: calc(50% - var(--ie-grip) / 2); cursor: ew-resize; }
.iepicture img {
  display: block; width: 100%; height: 100%;
  user-select: none; -webkit-user-select: none;
}
.ietools {
  grid-area: tools; min-height: 0; overflow-y: auto;
  display: flex; flex-direction: column; gap: 1.25rem;
  padding: 1rem 1.25rem; border-left: 1px solid var(--line);
}
.ietool { margin: 0; padding: 0; border: 0; min-width: 0; }
.ietool legend, .ietool h4 { margin: 0 0 .4rem; padding: 0; font-size: .875rem; font-weight: 600; }
.ietool .muted { margin: 0; font-size: .875rem; }
.ietool .hint { margin: 0 0 .5rem; }
.ietool .hint.ieafter { margin: .45rem 0 0; }
.iebuttons { display: flex; flex-wrap: wrap; gap: .4rem; }
.iewidth { display: grid; gap: .15rem; font-size: .8125rem; color: var(--muted); }
.iewidth input { max-width: 8rem; }
.ietool input[type=number] {
  width: 100%; padding: .35rem .5rem; font-size: .875rem; font-variant-numeric: tabular-nums;
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  background: var(--field-bg); color: var(--fg);
}
.ienotice { margin: .45rem 0 0; font-size: .8125rem; font-weight: 600; color: var(--warn-fg); }

/* Läs text's answer: a line saying it is reading while Tesseract works -- seconds, not
   milliseconds -- and then the box. Monospaced, so that O and 0, l and 1 can be told
   apart in a model designation, which is exactly where they get confused. */
.iereading { display: none; margin: 0; font-size: .8125rem; color: var(--muted); }
.iereading.htmx-request { display: block; }
#ie-text:empty { display: none; }
.ietext { display: grid; gap: .35rem; }
.ietext label { font-size: .875rem; font-weight: 600; }
.ietext textarea {
  width: 100%; padding: .45rem .55rem; resize: vertical;
  font-family: ui-monospace, Consolas, monospace; font-size: .875rem; line-height: 1.45;
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  background: var(--field-bg); color: var(--fg);
}
.ietext .hint { margin: 0; }
.iecopied { align-self: center; font-size: .8125rem; color: var(--muted); }
.iechanges { list-style: none; margin: 0 0 .25rem; padding: 0; font-size: .875rem; }
.iechanges li {
  display: flex; align-items: baseline; justify-content: space-between; gap: .5rem;
  padding: .15rem 0; border-bottom: 1px solid var(--line);
}
.iechanges button.linkish { text-decoration: none; font-size: 1rem; line-height: 1; }
/* Spara at the foot of the tools, where the eye ends up after using them -- and kept in
   sight there when the tools are longer than the frame, as they are once there is text
   read off the picture, rather than scrolled away beneath it. The negative offsets reach
   over the tools' own padding, which a sticky box keeps clear of, so nothing shows as it
   scrolls by underneath. */
.ieactions {
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem;
  position: sticky; bottom: -1rem;
  margin: auto -1.25rem 0; padding: .75rem 1.25rem 1rem;
  border-top: 1px solid var(--line); background: var(--bg);
}
.ieactions .hint { flex-basis: 100%; margin: 0; }

/* One column on a narrow screen: the header, the picture, then the tools under it,
   scrolling as one. Plain blocks rather than grid rows. The rows took their heights from
   the frame rather than from what was in them -- the picture and the tools may shrink to
   nothing beside each other -- and a tall picture spilled over the header and the tools.
   With nothing beside it, there is no height to centre the picture in either. */
@media (max-width: 46rem) {
  .imageeditor { width: 100vw; height: 100dvh; border: 0; border-radius: 0; }
  .ieform { display: block; overflow-y: auto; }
  /* The header stays put while the rest scrolls, so Stäng -- which sits over its right
     end, outside the swap -- never sits over the tools instead. */
  .iehead { position: sticky; top: 0; z-index: 1; }
  .iestage { display: block; overflow: visible; }
  .ieframe { margin: 0 auto; width: min(100%, var(--w), calc(60dvh * var(--fit))); }
  .ietools { overflow: visible; border-left: 0; border-top: 1px solid var(--line); }
  .ieactions { position: static; margin: 0; padding: 0; border-top: 0; }
}

/* --- tickets ------------------------------------------------------------------------ */

/* A page title with its badges and its one action on the same line. The heading carries
   the measure; everything after it is chrome that should not push it around. */
.pagehead {
  display: flex; align-items: baseline; gap: .6rem; flex-wrap: wrap;
  margin: 1.25rem 0 .5rem;
}
.pagehead h2 { margin: 0; font-size: 1.35rem; }
.pagehead .button, .pagehead a.button { margin-left: auto; }
/* The ticket list's Nytt ärende: a small pill right after the heading. */
.pagehead .button.newpill {
  margin-left: 0; align-self: center; padding: .15rem .65rem; border-radius: 999px;
  font-size: .8125rem; font-weight: 600; color: var(--accent);
}
.lede { color: var(--muted); margin: 0 0 1.25rem; max-width: 46rem; }

/* Buttons. The search page styles its own submit; these are the rest of the app's, and
   they share one shape so that "this does something" reads the same everywhere. */
button, .button {
  font-size: .875rem; padding: .4rem .8rem; line-height: 1.4;
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  background: var(--field-bg); color: var(--fg); cursor: pointer;
  text-decoration: none; display: inline-block;
}
button:hover, .button:hover { border-color: var(--accent); background: var(--accent-soft); }
button.primary, .button.primary {
  /* `--on-accent` rather than white: the accent is a light blue in the dark theme, and
     white text on it is unreadable. */
  background: var(--accent); border-color: var(--accent); color: var(--on-accent);
  font-weight: 600;
}
button.primary:hover, .button.primary:hover { filter: brightness(1.08); }
button[disabled] { opacity: .5; cursor: default; }

/* A button that acts like a link, for a destructive-but-trivial action sitting inside a
   list row. A real button, because it posts -- an <a> that changes state is a lie. */
button.linkish {
  border: 0; background: none; padding: .1rem .25rem; color: var(--accent);
  font-size: .8125rem; text-decoration: underline;
}
button.linkish:hover { background: none; color: var(--fg); }

/* --- the list --- */

.ticketfilters {
  display: flex; align-items: flex-end; gap: 1.25rem; flex-wrap: wrap;
  margin: 1rem 0; padding: .85rem 1rem;
  border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--surface);
}
/* The status boxes as tall as the tag box beside them, centred in that height: the row is
   aligned on its bottom edge, and a shorter group put its label lower than the box's. */
.ticketfilters .checks { min-height: 2.125rem; align-items: center; }
/* The pills likewise, in a box of that height: stretched themselves, they would no
   longer be pills. */
.ticketfilters .segbox { display: flex; align-items: center; min-height: 2.125rem; }
/* On a phone a group of four pills can be wider than the box -- Användare's roles are.
   Joined in one rounded frame they cannot wrap without looking like a broken pill, so
   there they come apart: each option its own pill, wrapping onto a second line when it
   must. */
@media (max-width: 34rem) {
  .ticketfilters .seg {
    flex-wrap: wrap; gap: .3rem; border: 0; border-radius: 0; overflow: visible;
  }
  .ticketfilters .seg span { border: 1px solid var(--line); border-radius: 999px; }
}
/* The tag box: wide enough for its own placeholder, and the same height as the boxes
   beside it. Left to the flex row it took the browser's default input width, which cut
   "Skriv namnet på en etikett…" short. */
.ticketfilters .combo { flex: 0 1 18rem; min-width: 16rem; margin: 0; }
.ticketfilters .combo input[type=text] { height: 2.125rem; }
/* A label on a ticket is a link to the list filtered on it. */
a.chip:hover { text-decoration: underline; }
/* The count and, when anything is narrowed, the way back -- beside each other, because the
   reset belongs to the answer it resets rather than to the controls above it. */
.listmeta { display: flex; align-items: baseline; gap: 1rem; flex-wrap: wrap; margin: 1rem 0; }
.listmeta .reset { font-size: .8125rem; }

table.tickets td.tid, table.tickets td.twhen, table.tickets td.c-kind { white-space: nowrap; }
table.tickets td.tid a { font-variant-numeric: tabular-nums; }
table.tickets td.ttitle { min-width: 14rem; }
/* The labels follow the title on its line, and go under it only when they do not fit:
   inline, so the line breaks between chips like it breaks between words. */
table.tickets td.ttitle .ticketlabels { display: inline; margin-left: .3rem; }
table.tickets td.ttitle .ticketlabels .chip { margin: .1rem .3rem .1rem 0; vertical-align: baseline; }
table.tickets td.twhen { color: var(--muted); font-size: .8125rem; }

/* Status, as a badge, in the same visual language as the code badges. Grouped by what the
   state means rather than one colour each: waiting, needs somebody, decided, done, refused.

   Approved and completed are apart on purpose. An approved new-code ticket still has work
   waiting -- the code does not exist until the implement form is sent -- so reading it as
   finished is the mistake the two colours prevent. Purple for done, as most trackers use. */
/* The kind beside it is the same size, so the two read as one row of facts. */
.badge.tstatus, .badge.tkind { font-size: .75rem; padding: .1rem .45rem; }
.badge.tstatus.draft { background: var(--hist-bg); color: var(--hist-fg); }
.badge.tstatus.submitted,
.badge.tstatus.in_review { background: var(--accent-soft); color: var(--accent); }
.badge.tstatus.needs_info { background: var(--warn-bg); color: var(--warn-fg); }
.badge.tstatus.approved { background: var(--ok-bg); color: var(--ok-fg); }
.badge.tstatus.completed { background: var(--done-bg); color: var(--done-fg); }
.badge.tstatus.rejected { background: var(--bad-bg); color: var(--bad-fg); }

table.tickets td.c-group .groupname { white-space: nowrap; }

/* --- choosing a kind --- */

.kindpick { display: grid; gap: .75rem; max-width: 46rem; }
/* A whole card is the button, so it undoes the button defaults it would otherwise inherit:
   full width, left-aligned text, the page's type size rather than a control's. */
.kindoption {
  display: block; width: 100%; text-align: left; font: inherit; line-height: 1.4;
  border: 1px solid var(--line); border-radius: var(--radius-lg);
  padding: .85rem 1rem; cursor: pointer; background: var(--bg); color: var(--fg);
}
.kindoption:hover { border-color: var(--accent); background: var(--accent-soft); }
.kindoption:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.kindname { display: block; font-weight: 600; }
.kindhelp { display: block; color: var(--muted); font-size: .8125rem; margin-top: .2rem; }

/* --- the ticket form --- */

.ticketform { max-width: 46rem; }
.tfield { margin: 0 0 1.15rem; display: block; }
.tfield > label, .tfield > span:first-child {
  display: block; font-weight: 600; font-size: .875rem; margin-bottom: .3rem;
}
.tfield input[type=text], .tfield input[type=url], .tfield input[type=search],
.tfield input[type=email], .tfield input[type=password],
.tfield textarea, .tfield select {
  width: 100%; padding: .45rem .6rem; font-size: .9375rem;
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  background: var(--field-bg); color: var(--fg);
}
.tfield textarea { resize: vertical; min-height: 5rem; }
/* The helper text is the point of the macro, so it must not read as an afterthought:
   close enough to the field to belong to it, quiet enough not to compete with the label. */
.tfield .hint { margin: .3rem 0 0; max-width: 44rem; }
/* A new password's length rule: a grey chip with a ring until the box holds enough, then
   the approved state's green with a tick, and the count beside it. The hint follows. */
.reqrow { display: flex; align-items: center; gap: .5rem; margin: .4rem 0 0; font-size: .8125rem; }
.reqrule {
  display: inline-flex; align-items: center; gap: .35rem; padding: .05rem .5rem;
  border-radius: 999px; background: var(--hist-bg); color: var(--hist-fg);
}
.reqrule svg { flex: none; fill: none; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
.reqrule .reqoff { stroke: currentColor; }
.reqrule .reqon { display: none; stroke: currentColor; stroke-width: 2.2; }
.reqrule.met { background: var(--ok-bg); color: var(--ok-fg); font-weight: 600; }
.reqrule.met .reqoff { display: none; }
.reqrule.met .reqon { display: block; }
.reqcount { color: var(--muted); font-variant-numeric: tabular-nums; }
.reqcount.met { color: var(--ok-fg); }
.reqrow + .hint { margin-top: .25rem; }
abbr.req { color: var(--warn-fg); text-decoration: none; font-weight: 700; }
/* A request field the code was made with differently; the title says what was asked. */
abbr.modified {
  color: var(--warn-fg); text-decoration: none; font-weight: 700;
  cursor: help; margin-left: .1rem;
}

.formactions {
  display: flex; align-items: center; gap: .6rem; flex-wrap: wrap;
  margin: 1.5rem 0 3rem; padding-top: 1rem; border-top: 1px solid var(--line);
}
.savedstate { font-size: .8125rem; margin-right: auto; }
.savedstate .ok { color: var(--muted); }
.savedstate .warn { color: var(--warn-fg); }
/* Two warnings -- a term and a link not saved -- read as two lines, not one long one. */
.savedstate .warn + .warn { display: block; }

/* Said out loud, in the flow of the page. There is no flash-message mechanism here and
   this is why there does not need to be one: every message is a fact about the thing on
   screen, rendered from the state the server just wrote. */
.notice {
  border: 1px solid var(--line-strong); border-left-width: 4px;
  border-radius: var(--radius); padding: .7rem 1rem; margin: 1rem 0;
  background: var(--surface); max-width: 46rem;
}
.notice p { margin: 0; }
.notice ul { margin: .4rem 0 0; padding-left: 1.2rem; }
.notice.error { border-left-color: var(--warn-fg); background: var(--warn-bg); color: var(--warn-fg); }
.notice.ok { border-left-color: var(--accent); background: var(--accent-soft); }

/* The term box is the search page's combobox, reused whole -- which also brings the search
   panel's field styling with it: a small uppercase label and a thinner input. Beside the
   macro-built fields that reads as a different kind of control, so it is brought into line
   here rather than by giving the macro a second appearance to keep in step. */
.ticketform .field { margin: 0 0 1.15rem; }
.ticketform .field > label {
  text-transform: none; letter-spacing: 0;
  font-size: .875rem; font-weight: 600; color: var(--fg); margin-bottom: .3rem;
}
.ticketform .field input[type=text] {
  padding: .45rem .6rem; font-size: .9375rem; border-color: var(--line-strong);
}
.ticketform .field .hint { margin: .3rem 0 0; max-width: 44rem; }

/* The implement form is long enough to want dividing: which term, and which product.
   A real fieldset and legend, so the grouping is in the document and not only in the
   paint -- a screen reader announces the legend with every field inside it. */
fieldset.block {
  border: 1px solid var(--line); border-radius: var(--radius-lg);
  padding: 1rem 1.25rem .25rem; margin: 0 0 1.5rem; max-width: 46rem;
}
fieldset.block legend {
  font-size: .75rem; text-transform: uppercase; letter-spacing: .04em;
  color: var(--muted); font-weight: 600; padding: 0 .4rem;
}

/* --- targets --- */

.targets {
  border: 1px solid var(--line); border-radius: var(--radius-lg);
  padding: 1rem 1.25rem; margin: 1.5rem 0; max-width: 46rem;
}
.targets h3 { margin: 0; font-size: .95rem; }
.targetlist { list-style: none; margin: .75rem 0; padding: 0; }
.targetlist li {
  display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
  padding: .35rem 0; border-bottom: 1px solid var(--line);
}
.targetlist.plain li { border-bottom: 0; padding: .2rem 0; }
/* The confirm under a replacement's or a re-key's preview carries the new term's name, so
   it needs the room a field has above it, not the hint's flush bottom. */
.ticketform.confirmform { margin-top: 1.15rem; }
/* What the key names, beside it: nobody recognises a device by its number. */
.tdetail { font-size: .875rem; }
.ttype {
  font-size: .7rem; text-transform: uppercase; letter-spacing: .04em;
  color: var(--muted); min-width: 5.5rem;
}
.targetadd { margin-top: 1rem; }
.targetrow { display: flex; gap: .5rem; align-items: stretch; flex-wrap: wrap; }
.targetrow select { flex: 0 0 auto; width: auto; }
.targetrow input { flex: 1 1 12rem; }
.targethits { list-style: none; margin: .5rem 0 0; padding: 0; }
.targethits li {
  display: flex; align-items: baseline; gap: .5rem; flex-wrap: wrap;
  padding: .3rem .4rem; border-radius: var(--radius);
}
.targethits li:nth-child(even) { background: var(--stripe); }
.hitterm { font-weight: 600; }

/* --- attachments --- */

.attachments {
  border: 1px solid var(--line); border-radius: var(--radius-lg);
  padding: 1rem 1.25rem; margin: 1.5rem 0; max-width: 46rem;
}
.attachments h3 { margin: 0; font-size: .95rem; }
.attachments .hint { margin: .3rem 0 .75rem; }

.filelist { list-style: none; display: flex; flex-wrap: wrap; gap: .75rem; margin: .75rem 0; padding: 0; }
.filelist li { display: flex; flex-direction: column; gap: .2rem; font-size: .8125rem; }
/* A fixed box, so a portrait photograph and a landscape one do not make the row jump.
   `object-fit: cover` crops rather than distorts -- a squashed label is unreadable. */
.filelist img {
  width: 7rem; height: 7rem; object-fit: cover;
  border: 1px solid var(--line); border-radius: var(--radius); display: block;
}
.filelist a.doc {
  display: flex; align-items: center; min-height: 3rem; max-width: 14rem;
  padding: .4rem .7rem; border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface); word-break: break-word;
}
.filelist.inline { margin: .4rem 0 0; }
.filekind { color: var(--muted); font-size: .75rem; }

.filelist.inline img { width: 5rem; height: 5rem; }
.filemeta { display: flex; align-items: baseline; gap: .4rem; }

/* The two drop boxes: side by side while there is room for a thumbnail row in each, one
   above the other on a phone. A dashed edge is the conventional "drop here" signal. */
.dropzones {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: .9rem; margin: .75rem 0;
}
.dropzone {
  display: flex; flex-direction: column; gap: .5rem;
  border: 2px dashed var(--line-strong); border-radius: var(--radius-lg);
  padding: .75rem .85rem; background: var(--bg);
}
.dropzone.wanted { border-color: var(--warn-fg); }
.dropzone.dragging { border-color: var(--accent); border-style: solid; background: var(--accent-soft); }
.dropzone .filelist { margin: 0; }
.dzhead { display: flex; align-items: baseline; gap: .5rem; margin: 0; font-size: .875rem; }
.dzname { font-weight: 600; }
.dzmissing { color: var(--warn-fg); font-size: .8125rem; font-weight: 600; }
.dzform { margin: 0; display: flex; flex: 1 1 auto; }
/* The prompt fills an empty box, so a click anywhere in it opens the picker; once there
   are pictures it shrinks to a line under them. */
.dzpick {
  flex: 1 1 auto; display: flex; align-items: center; justify-content: center;
  position: relative; min-height: 7rem; padding: .5rem; text-align: center; cursor: pointer;
  border-radius: var(--radius); color: var(--muted); font-size: .875rem;
}
.dropzone.filled .dzpick { min-height: 2.5rem; justify-content: flex-start; }
.dzpick:hover { background: var(--surface); color: var(--fg); }
.dzpick:focus-within { outline: 2px solid var(--focus); outline-offset: 2px; }
/* Off-screen rather than hidden: it stays focusable, and a label click still reaches it. */
.dzinput { position: absolute; width: 1px; height: 1px; opacity: 0; overflow: hidden; }
.dzbusy { display: none; }
.dropzone.htmx-request .dzprompt { display: none; }
.dropzone.htmx-request .dzbusy { display: inline; }
.dochead { margin: 1rem 0 .25rem; font-size: .8125rem; color: var(--muted); }

/* --- the administrator's controls --- */

.actions .actionrow {
  display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; margin: .5rem 0;
}
.actions .actionrow label { font-size: .875rem; font-weight: 600; }
.actions .actionrow select {
  padding: .35rem .5rem; font-size: .875rem;
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  background: var(--field-bg); color: var(--fg);
}
.actions .actionrow .hint { margin: 0; }
/* Take it, or hand it on: two forms side by side in one row. */
.assignrow form { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; margin: 0; }
.actions .assignrow { column-gap: 1.5rem; }
.actions .hint { margin: 0 0 1rem; }
/* The ways to approve, side by side: each a button and its info pill, the row wrapping only
   when the card is too narrow for both. */
.actions .approverow { column-gap: 1.5rem; }
.approvegroup { display: inline-flex; align-items: center; gap: .4rem; margin: 0; }
.pillhead { display: flex; align-items: center; gap: .4rem; }

/* An info pill: a small "i" whose help opens beneath it on hover, on focus and on a tap.
   See the `infopill` macro for why this is not a `title=` tooltip. */
.infopill { position: relative; display: inline-flex; align-items: center; }
.infopill-i {
  width: 1.25rem; height: 1.25rem; padding: 0; border-radius: 999px;
  display: inline-flex; align-items: center; justify-content: center;
  font: italic 600 .75rem/1 Georgia, "Times New Roman", serif;
  color: var(--muted); background: var(--bg); border: 1px solid var(--line-strong);
  cursor: help;
}
.infopill-i:hover, .infopill.open .infopill-i, .infopill-i:focus-visible {
  color: var(--accent); border-color: var(--accent); background: var(--accent-soft);
}
.infopill-tip {
  display: none;
  position: absolute; top: calc(100% + .4rem); left: -.5rem; z-index: 40;
  width: max-content; max-width: min(24rem, 80vw);
  padding: .55rem .7rem; font-size: .8125rem; font-weight: 400; line-height: 1.45;
  text-transform: none; letter-spacing: normal; white-space: normal;
  color: var(--fg); background: var(--bg);
  border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow);
}
/* A pill with a word instead of the "i": GMDN, EMDN. */
.infopill-i.labelled {
  width: auto; padding: 0 .4rem; border-radius: 999px;
  font-family: inherit; font-style: normal; font-weight: 600; font-size: .625rem;
  line-height: 1; letter-spacing: .04em;
}
.infopill:hover .infopill-tip,
.infopill:focus-within .infopill-tip,
.infopill.open .infopill-tip { display: block; }
/* On a phone a pill can sit near the right edge, where a tip hung from it would run off
   the screen. There the tip hangs from the row instead, as wide as the row. */
@media (max-width: 40rem) {
  .actions .actionrow, .internaltags { position: relative; }
  .actions .infopill, .internaltags .infopill { position: static; }
  .actions .infopill-tip, .internaltags .infopill-tip { left: 0; right: 0; width: auto; max-width: none; }
}

/* Ticket mail beside a thread line's time: an envelope when it went out, a crossed one in
   the warning colour when some did not. Opens like an info pill. */
.mailmark { margin-left: .1rem; }
/* Sat on the text's baseline, as a letter would be, rather than centred: where the middle
   of a line falls depends on the font -- Segoe UI on Windows is not the font a Linux
   browser draws -- and the baseline does not. So the button is an inline box in the line's
   own font and size, and the envelope inside it is sized and lowered in em: drawn from
   5/24 to 19/24 of its height, at 1.2em lowered by 0.25em it spans the baseline to about
   0.7em, the height of the figures in the time beside it, whatever the font. */
.mailicon {
  display: inline-block; padding: 0 .15rem; border: 0; border-radius: 4px;
  font: inherit; line-height: inherit;
  background: none; color: var(--muted); cursor: help;
}
.mailicon svg { width: 1.2em; height: 1.2em; vertical-align: -.25em; }
.mailicon:hover, .mailmark.open .mailicon, .mailicon:focus-visible {
  color: var(--accent); background: var(--accent-soft);
}
.mailmark.unsent .mailicon { color: var(--warn-fg); }
.mailmark .mailline { display: block; }
.mailmark .mailline + .mailline { margin-top: .2rem; }
/* On a phone the icon sits near the right edge after a long name, so the tip hangs from
   the line instead, as wide as it, as the info pills in the cards do. */
@media (max-width: 40rem) {
  .entries .entrymeta { position: relative; }
  .entries .mailmark { position: static; }
  .entries .mailmark .infopill-tip { left: 0; right: 0; width: auto; max-width: none; }
}

/* "Godkänn och skapa koden" asks first, in a dialog of its own: it carries the regulation no
   request asks for, and the administrator's word that they have read the request. The tag
   manager's frame, sized to a short form. It sits inside the Handläggning card, whose
   heading and hint rules are put back here for a form. */
.confirmdialog {
  width: min(34rem, calc(100vw - 2rem)); max-width: 100vw;
  max-height: calc(100dvh - 2rem); overflow-y: auto;
  padding: 1.25rem 1.5rem;
  border: 1px solid var(--line-strong); border-radius: var(--radius-lg);
  background: var(--bg); color: var(--fg); box-shadow: var(--shadow);
}
.confirmdialog::backdrop { background: rgba(10, 14, 18, .5); }
.confirmdialog h2 {
  margin: 0 0 .5rem; font-size: 1.125rem; text-transform: none; letter-spacing: 0;
  color: var(--fg);
}
.confirmdialog .lede { margin: 0 0 1rem; }
.confirmdialog dl.fields { margin: 0 0 1.25rem; grid-template-columns: minmax(6rem, 8rem) minmax(0, 1fr); }
.confirmdialog .tfield .hint { margin: .3rem 0 0; }
.confirmdialog .checkline {
  display: flex; align-items: flex-start; gap: .5rem; cursor: pointer;
  font-size: .9375rem; font-weight: 600;
}
.confirmdialog .checkline input { margin: .3rem 0 0; flex: none; }
.confirmdialog .formactions { margin: 1.25rem 0 0; justify-content: flex-end; }
@media (max-width: 34rem) {
  .confirmdialog { padding: 1rem; }
  .confirmdialog dl.fields { grid-template-columns: 1fr; }
}

/* Rejection is the one action here that cannot be undone, so it is the one that does not
   look like the others. Colour and a border, not a red fill: it sits in a row of ordinary
   buttons and shouting would make the row harder to read, not safer. */
/* Throwing a draft away: its own small form, at the right-hand end of the page header --
   after Redigera where there is one, which already takes that place. */
.deletedraft { margin: 0; display: inline-flex; }
/* A ticket's heading: Redigera and Ta bort utkastet follow the badges, left-aligned, and as
   one group (`.headactions`), so a long title that wraps them takes both to the start of
   the next line together. Redigera comes first; the irreversible one is last. */
.tickethead .headactions { display: inline-flex; align-items: center; gap: .4rem; }
.tickethead .button, .tickethead a.button { margin-left: 0; }
.tickethead > .deletedraft { margin-left: .4rem; }
/* Back to the list on the left, stepping through it on the right; wraps under on a narrow
   page rather than squeezing. */
.ticketwalk {
  display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between;
  gap: .4rem 1rem; margin: 1rem 0;
}
.ticketwalk .walknav { display: inline-flex; align-items: baseline; gap: .9rem; }
.ticketwalk a { text-decoration: none; font-weight: 600; }
.ticketwalk a:hover { text-decoration: underline; }
.ticketwalk .walknav .off { color: var(--muted); }
.ticketwalk .walkpos { color: var(--muted); font-size: .875rem; font-variant-numeric: tabular-nums; }
button.danger { color: var(--bad-fg); border-color: var(--danger-line); }
button.danger:hover { background: var(--bad-bg); border-color: var(--danger-line-strong); }

/* The box sits above the thread it adds to, ruled off from the newest entry. */
.replybox { margin-bottom: .5rem; padding-bottom: 1rem; border-bottom: 1px solid var(--line); }
/* Visa fler, under the two newest entries. */
.threadtoggle { margin: 0; padding-top: .35rem; }
/* Ruled off from the entry above it, open or closed: closed, that entry is not the last
   child, and open, it is -- one rule either way, never two. */
.entries:has(+ .threadtoggle) > li:last-child { border-bottom: 1px solid var(--line); }
.threadtoggle button.linkish { padding-left: 0; font-size: .875rem; }
.replybox .tfield { margin-bottom: .6rem; }
.replyactions { display: flex; gap: .5rem; flex-wrap: wrap; }
/* An internal comment is the internal tag's amber wherever it appears: its button, the box
   on a closed ticket where it is all that can be written, and the entry in the thread. */
button.internal { color: var(--warn-fg); border-color: var(--warn-fg); background: var(--warn-bg); }
button.internal:hover { color: var(--warn-fg); border-color: var(--warn-fg); background: var(--bg); }
.replybox.internal textarea { border-color: var(--warn-fg); background: var(--warn-bg); }

/* The administrator's card sits below the read-only ones and is visibly a different kind
   of thing: everything above describes the code, this changes it. */
.admincard { border-color: var(--line-strong); background: var(--surface); }
.admincard h3 {
  margin: 2rem 0 .5rem; font-size: .75rem; text-transform: uppercase;
  letter-spacing: .04em; color: var(--muted);
  padding-top: 1.25rem; border-top: 1px solid var(--line);
}
.admincard .ticketform { max-width: 40rem; }
.admincard .formactions { margin-bottom: 0; }
/* The code page's card is a <details>: its heading is the summary, with the search
   panel's turning marker, and closed it is the heading alone. */
.codeedit > summary { cursor: pointer; list-style: none; }
.codeedit > summary::-webkit-details-marker { display: none; }
.codeedit > summary h2 { display: inline; margin: 0; }
.codeedit > summary::before {
  content: "\25B8"; display: inline-block; margin-right: .4rem; color: var(--accent);
  transition: transform .12s ease;
}
.codeedit[open] > summary::before { transform: rotate(90deg); }
.codeedit[open] > summary { margin-bottom: .75rem; }
.inlineform { display: inline; }

/* --- the thread --- */

/* Not scoped to `.thread`: the code detail page renders the same list in its history
   card, where an unstyled <ol> shows its numbers and reads as a ranking. */
.entries { list-style: none; margin: 0; padding: 0; }
.entries li { padding: .6rem 0; border-bottom: 1px solid var(--line); }
.entries li:last-child { border-bottom: 0; }
.codehistory { margin-top: .5rem; }
.legacyref { margin: .15rem 0 0; font-size: .8125rem; color: var(--muted); }
.entrymeta {
  display: flex; gap: .6rem; align-items: baseline;
  margin: 0 0 .2rem; font-size: .8125rem; color: var(--muted);
}
.entrymeta .who { font-weight: 600; color: var(--fg); }
/* An internal comment: the administrators' note to each other, which the requester never
   reads. A box in amber with a rule down its side and the word in its header, so it cannot
   be taken for something said to the requester -- the badge says it to a screen reader. */
.entries li.internal {
  margin: .35rem 0; padding: .6rem .8rem; border-bottom: 0;
  background: var(--warn-bg); border-left: 4px solid var(--warn-fg);
  border-radius: var(--radius);
}
/* On a phone the header does not hold the badge as well: it takes a line of its own rather
   than squeezing the name and the time into two lines each. The gap spaces it. */
.entries li.internal .entrymeta { flex-wrap: wrap; row-gap: .2rem; }
.badge.internal {
  background: var(--bg); color: var(--warn-fg); border: 1px solid var(--warn-fg);
  font-weight: 600; margin-left: 0;
}
/* A comment is somebody talking and an event is something that happened. They are not the
   same kind of thing, so they do not get the same weight. */
.entrybody { margin: 0; white-space: pre-wrap; }
/* A colleague an internal comment names -- and so mailed. */
.entrybody .mention { font-weight: 600; color: var(--accent); }
.entryevent { margin: 0; color: var(--muted); font-style: italic; }
/* What an answer changed in the request: a short list under its line in the history. */
.changedhead { margin: .35rem 0 0; font-size: .8125rem; color: var(--muted); }
.changedlist { margin: .1rem 0 0; padding-left: 1.1rem; font-size: .875rem; }
.changedlist li { margin: .1rem 0; overflow-wrap: anywhere; }
/* The question a ticket was sent back with, at the top of the form that answers it. */
.notice.asked { border-left-color: var(--warn-fg); }
.notice.asked .ticketbody { margin-top: .3rem; }
.ticketbody { white-space: pre-wrap; margin: .4rem 0 0; max-width: 46rem; }
/* The request card's two parts -- the code, and the rest -- each under its own heading, with
   a rule between, so the code's fields do not run on into the people and the comment. */
.request .reqpart + .reqpart { border-top: 1px solid var(--line); margin-top: 1rem; padding-top: .9rem; }
.request .reqpart h4 { margin: 0 0 .5rem; font-size: .9375rem; color: var(--fg); font-weight: 600; }
.request .reqpart h5 { margin: .9rem 0 .2rem; font-size: .8125rem; color: var(--muted); font-weight: 600; }
/* The request's files, top right of the part saying what the code is, as the code page has
   its pictures beside its name -- read-only here; they change on the request form.

   The card asks its own width rather than the window's. It shares its row with the targets
   card on a revision, and is the whole measure on a new code, so one window width can be
   two card widths. The column narrows with the card, and only on a phone do the files go
   under the fields, as the code page's do: stacked in a card beside another, one picture
   was the width of the card and the files were no longer in a corner. */
.card.request { container-type: inline-size; }
.request .reqpart.withfiles {
  display: grid; grid-template-columns: minmax(0, 1fr) clamp(10rem, 32%, 14rem);
  gap: 1rem 1.5rem; align-items: start;
}
@container (max-width: 30rem) {
  .request .reqpart.withfiles { grid-template-columns: minmax(0, 1fr); }
}
/* In a narrow card the label column gives the values back what the files take -- in the
   whole card, so the values of its two parts still line up. Not on a phone, where the
   labels sit above the values anyway. */
@container (min-width: 30rem) and (max-width: 44rem) {
  .request dl.fields { grid-template-columns: minmax(6rem, 8.5rem) minmax(0, 1fr); }
}
.request .reqfiles .filehead { margin: 0 0 .5rem; font-size: .75rem; font-weight: 600; }
.request .reqfiles ul.documents li { overflow-wrap: anywhere; }
.request .reqfiles ul.documents .muted { white-space: nowrap; }
.request .reqfiles .filehead + .documents { margin-top: 0; }
.request .reqfiles dialog + .filehead { margin-top: 1rem; }
.card h3 {
  font-size: .75rem; text-transform: uppercase; letter-spacing: .04em;
  color: var(--muted); margin: 0 0 .75rem;
}
.card h4 { font-size: .8125rem; margin: 1rem 0 .2rem; color: var(--muted); }

/* --- compact mode ------------------------------------------------------------------- */

/* For a laptop's screen -- 1366×768, 1280×800 -- where the ordinary spacing leaves room for
   a handful of rows. Chosen in the account menu; a cookie the server renders as
   `data-density="compact"` on <html> (see `web.density_of`), so the page arrives tight.

   Only room changes: padding, gaps, line height, the search boxes and the page headings.
   Text keeps its size, focus keeps its outline, and every hint stays where it is -- a
   field's hint is a requirement, not decoration. The one thing folded away is the search
   page's panel, which search.html draws closed here rather than open.

   Every rule is under the attribute, so with the setting off nothing below applies. Spacing
   only, never colour, so the dark theme needs nothing of its own here. */

/* The frame: header, page, line height. */
:root[data-density="compact"] body { line-height: 1.35; }
:root[data-density="compact"] header.site {
  padding-block: .4rem;
  padding-inline: max(1rem, calc((100% - var(--page)) / 2 + 1rem));
}
:root[data-density="compact"] main { padding: .6rem 1rem 2rem; }
:root[data-density="compact"] .announce.band {
  padding-block: .1rem;
  padding-inline: max(1rem, calc((100% - 88rem) / 2 + 1rem));
}
:root[data-density="compact"] .announce .dismiss { min-height: 2rem; padding-block: .1rem; }
:root[data-density="compact"] .pagehead { margin: 0 0 .4rem; }
:root[data-density="compact"] .ticketwalk { margin: .5rem 0; }
:root[data-density="compact"] .pagehead h2 { font-size: 1.15rem; }
:root[data-density="compact"] .lede { margin-bottom: .6rem; }
:root[data-density="compact"] .notice { padding: .4rem .7rem; margin: .5rem 0; }

/* The search page. */
:root[data-density="compact"] .searchbar input[type=search] { padding: .35rem .6rem; font-size: .9375rem; }
:root[data-density="compact"] .searchbar button { padding: .35rem .9rem; }
:root[data-density="compact"] .toolbar { margin-top: .25rem; }
:root[data-density="compact"] .panel > summary { padding-block: .15rem; }
:root[data-density="compact"] .panel-body { padding: 0 .7rem .6rem; }
:root[data-density="compact"] .panelgrid { gap: .9rem; }
:root[data-density="compact"] .advgrid { gap: .5rem 1rem; }
:root[data-density="compact"] .filterbox { padding: .5rem .7rem .6rem; }
:root[data-density="compact"] .panel-reset { margin-top: .5rem; }
:root[data-density="compact"] .resultmeta { margin-top: .5rem; }
:root[data-density="compact"] .bulkbar { margin-top: .35rem; padding: .3rem .5rem; }

/* Tables: every list in the app is `table.results`, so this is the rows on the search,
   ticket, user, term and translation pages at once. */
:root[data-density="compact"] table.results { margin-top: .25rem; }
:root[data-density="compact"] table.results th,
:root[data-density="compact"] table.results td { padding: .2rem .45rem; }
:root[data-density="compact"] table.changes th,
:root[data-density="compact"] table.changes td { padding: .2rem .45rem; }
:root[data-density="compact"] .pager { margin-top: .5rem; }
:root[data-density="compact"] .pager a,
:root[data-density="compact"] .pager .off { padding: .2rem .5rem; }

/* Filter strips: the ticket list's, and Användare's and Benämningar's, which borrow it. */
:root[data-density="compact"] .ticketfilters { margin: .4rem 0; padding: .4rem .7rem; gap: .5rem .9rem; }
:root[data-density="compact"] .listmeta { margin: .4rem 0; }

/* Cards, field lists and forms: the code, term, ticket, user and group pages. */
:root[data-density="compact"] .card { padding: .6rem .85rem; margin: .6rem 0; }
:root[data-density="compact"] .card h2,
:root[data-density="compact"] .card h3 { margin-bottom: .4rem; }
:root[data-density="compact"] .card h4 { margin-top: .6rem; }
:root[data-density="compact"] dl.fields { gap: .2rem 1rem; }
:root[data-density="compact"] .cardgrid { gap: 0 .75rem; }
:root[data-density="compact"] .hero { gap: .75rem 1.5rem; padding-bottom: .75rem; }
:root[data-density="compact"] .ident h2.term { font-size: 1.35rem; }
:root[data-density="compact"] .ident .keyline { margin-top: .5rem; }
:root[data-density="compact"] .tfield,
:root[data-density="compact"] .ticketform .field { margin-bottom: .6rem; }
:root[data-density="compact"] .tfield input[type=text],
:root[data-density="compact"] .tfield input[type=url],
:root[data-density="compact"] .tfield input[type=search],
:root[data-density="compact"] .tfield input[type=email],
:root[data-density="compact"] .tfield input[type=password],
:root[data-density="compact"] .tfield select,
:root[data-density="compact"] .ticketform .field input[type=text] { padding: .3rem .5rem; }
:root[data-density="compact"] fieldset.block { padding: .6rem .85rem .1rem; margin-bottom: .75rem; }
:root[data-density="compact"] .targets { padding: .6rem .85rem; margin: .75rem 0; }
:root[data-density="compact"] .formactions { margin: .75rem 0 1.5rem; padding-top: .6rem; }
:root[data-density="compact"] .request .reqpart + .reqpart { margin-top: .6rem; padding-top: .5rem; }
:root[data-density="compact"] .entries li { padding: .3rem 0; }
:root[data-density="compact"] .entries li.internal { padding: .35rem .6rem; }

/* The administrators' list pages: Etiketter and Redigera hjälp (one list), Grupper,
   Startsida och meddelanden, and Systeminformation's sections. */
:root[data-density="compact"] .tagcreate { padding: .45rem .7rem; margin-bottom: .6rem; }
:root[data-density="compact"] .tagrow { padding: .2rem .5rem; }
:root[data-density="compact"] .groupitem { padding: .25rem .6rem; }
:root[data-density="compact"] .announcerow { padding: .2rem .5rem; }
:root[data-density="compact"] .announcepage section,
:root[data-density="compact"] .announce-welcome { margin-bottom: .75rem; }
:root[data-density="compact"] .sysblock { margin-bottom: .75rem; padding-top: .5rem; }
:root[data-density="compact"] .sysblock h4 { margin-top: .6rem; }
:root[data-density="compact"] dl.facts dt,
:root[data-density="compact"] dl.facts dd { padding-block: .15rem; }

/* The welcome page: the same hero, intro and cards, nearer one another, so the cards come
   up into a laptop's first screen. Nothing on it is hidden. */
:root[data-density="compact"] main.welcomepage { padding-top: .6rem; }
:root[data-density="compact"] .announce.full { padding: .5rem .85rem; margin-bottom: .5rem; }
:root[data-density="compact"] .welcome-hero { padding: .9rem 1.25rem; margin-bottom: .75rem; }
:root[data-density="compact"] .welcome-hero h2 { font-size: 1.35rem; margin-bottom: .35rem; }
:root[data-density="compact"] .welcome-intro { line-height: 1.4; }
:root[data-density="compact"] .welcome-intro p { margin-bottom: .25rem; }
:root[data-density="compact"] .welcome-search { margin: .5rem 0 .25rem; }
:root[data-density="compact"] .welcome-search input[type="search"],
:root[data-density="compact"] .welcome-search button { font-size: .9375rem; padding-block: .4rem; }
:root[data-density="compact"] .welcome-search input[type="search"] { padding-inline: .7rem; }
:root[data-density="compact"] .welcome-cards { gap: .75rem; }
:root[data-density="compact"] .welcome-card { padding: .6rem .85rem; }
:root[data-density="compact"] .welcome-card h2 { margin-bottom: .4rem; }
:root[data-density="compact"] .cardlist li { padding: .3rem 0; }
:root[data-density="compact"] .cardlist li:first-child { padding-top: 0; }
:root[data-density="compact"] .cardmore { margin-top: .4rem; }
:root[data-density="compact"] .cardfoot { padding-top: .4rem; }
:root[data-density="compact"] .cardparts > section { padding-block: .5rem; }
:root[data-density="compact"] .cardparts > section:first-child { padding-top: 0; }
:root[data-density="compact"] .cardsub { margin-bottom: .35rem; }
:root[data-density="compact"] .tiles { gap: .4rem; }
:root[data-density="compact"] .tile { padding: .3rem .55rem; }
:root[data-density="compact"] .tilecount { font-size: 1.25rem; }
:root[data-density="compact"] .newspost { padding: .5rem 0; }

/* --- utilities ---------------------------------------------------------------------- */

.muted { color: var(--muted); }
.hint { color: var(--muted); font-size: .8125rem; margin: .5rem 0 0; }
.options { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; }
.options label { display: inline-flex; align-items: center; gap: .35rem; }

.spinner { visibility: hidden; color: var(--muted); font-size: .85rem; }
.htmx-request .spinner,
.htmx-request.spinner { visibility: visible; }

/* Alpine strips this once it boots. If it never does -- no JavaScript, a failed request --
   the attribute stays and the empty list stays hidden forever, which is exactly right. */
[x-cloak] { display: none !important; }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* A reset sits at the end of the panel it resets, quiet enough not to compete with the
   controls above it. */
.panel-reset { margin: .9rem 0 0; text-align: right; font-size: .8125rem; }

/* Tags not yet on a code, one button each: a click applies it. Smaller than a form button,
   because a row of them is a list to pick from rather than a set of actions. */
.tagchips { display: flex; flex-wrap: wrap; gap: .4rem; margin: .5rem 0 0; }
.tagchips button { font-size: .8125rem; padding: .2rem .6rem; border-radius: 999px; }

/* "New term, or unsure" sits right under the term box it switches, pulled up into that
   field's bottom margin so it reads as part of the same question. */
.termchoice .checkline {
  display: flex; align-items: center; gap: .45rem;
  margin: -.6rem 0 0; font-size: .875rem; cursor: pointer;
}
.termchoice .checkhint { margin: .2rem 0 1.4rem 1.45rem; }
.termchoice .checkline input { margin: 0; }
/* "Ändra EMDN": under the term's reference, so not pulled up into a box as the term
   choice is; its hint indents to the label as that one's does. */
.emdnchange .checkline {
  display: flex; align-items: center; gap: .45rem;
  margin: 0; font-size: .875rem; cursor: pointer;
}
.emdnchange .checkline input { margin: 0; }
.emdnchange .checkhint { margin: .2rem 0 1.1rem 1.45rem; }
/* Unticked, the hint is hidden with the box, so the checkbox keeps the field's gap itself. */
.emdnchange .checkline:has(input:not(:checked)) { margin-bottom: 1.1rem; }
/* The picked term's GMDN and EMDN, under a term box: reference, so quieter than a field.
   Pulled up into the box's own bottom margin, as the hints are, and gone entirely while the
   box names no term. */
.termref { margin: -.6rem 0 1.15rem; padding: .45rem .7rem; font-size: .8125rem;
  border-left: 3px solid var(--line); background: var(--accent-soft); border-radius: var(--radius); }
.termref:empty { display: none; }
/* With "Ändra benämning" ticked the term box is hidden, and the reference to the picked term
   stays, to compare the new details with: there is no box above it to tuck under. */
.termrefwrap.alone .termref { margin-top: 0; }
.termref .termref-head { margin: 0 0 .25rem; color: var(--muted); font-weight: 600; }
.termref dl.fields { grid-template-columns: 4rem 1fr; gap: .15rem .6rem; }
.termref dl.fields dt { font-size: .8125rem; }
.termref dl.fields dd { overflow-wrap: anywhere; }
@media (max-width: 34rem) {
  .termref dl.fields { grid-template-columns: 4rem 1fr; gap: .15rem .6rem; }
  .termref dl.fields dd { margin-bottom: 0; }
}
/* The requester's own words for an undecided term, under the box they are checked against. */
.termchoice .suggested { margin: -.4rem 0 1.1rem; font-size: .875rem; color: var(--muted); }
.termchoice .suggested strong { color: var(--fg); font-weight: 600; }

/* "Spara på koden" under each of the ticket's files on the implement form. */
.keepfiles .keep, .codefiles .keep {
  display: flex; align-items: center; gap: .3rem; font-size: .8125rem; cursor: pointer;
}
.keepfiles .keep input, .codefiles .keep input { margin: 0; }
/* The tags a code is made with, one checkbox each: the same "goes on the code" choice the
   files above make, as a plain list. */
.implementtags ul { list-style: none; margin: .5rem 0 .9rem; padding: 0; }
.implementtags li label {
  display: inline-flex; align-items: center; gap: .4rem; padding: .15rem 0; cursor: pointer;
}
.implementtags li input { margin: 0; }
#implement-tag-error:empty { display: none; }
.proposeform { margin: .5rem 0 0; }
/* A revision's tags: what the code has now, then what to add. A tag asked to come off is
   marked in the warning colours, and struck through where the request is shown. */
.codetagshead { margin: .8rem 0 .2rem; font-size: .8125rem; font-weight: 600; }
.tagmark.removing { background: var(--warn-bg); color: var(--warn-fg); }
.chip.removing { text-decoration: line-through; }

/* The administrators' internal tags on a ticket, at the end of the Handläggning card: the
   chips, each with its own remove, then a box and a button on one line under its label. */
.internaltags { margin: 1rem 0 0; padding-top: .9rem; border-top: 1px solid var(--line); }
.internaltags h4 { margin: 0 0 .2rem; font-size: .875rem; }
.internaltags > .hint { margin: 0 0 .6rem; }
.internalchips { list-style: none; margin: 0 0 .7rem; padding: 0; }
.chipx {
  border: 0; background: none; padding: 0 .1rem; margin: 0; color: inherit;
  font-size: .95rem; line-height: 1; cursor: pointer;
}
.tagaddrow { display: flex; align-items: flex-end; flex-wrap: wrap; gap: .4rem .5rem; }
.tagaddrow .combo { flex: 1 1 14rem; margin: 0; }
.tagaddrow button { height: 2.2rem; padding: 0 .8rem; }
.tagaddrow .combo input[type=text] { height: 2.2rem; }
/* A revision's say about the code's own files: its pictures as the attachment boxes show
   theirs, each with a box to take it off, then its documents one to a row, each with a name
   box -- a name is typed, and a row gives it the width to be read. */
.codefiles .codepictures { margin: .5rem 0 0; }
/* A picture's row is as wide as its thumbnail, so what an edit does wraps under it rather
   than pushing the next picture along. */
.codefiles .codepictures li { max-width: 11rem; }
.codefiles .codepictures li.edited img { border-color: var(--accent); }
.codefiles .editnote { margin: 0; color: var(--muted); }
/* A button in the row's column would stretch to its width and centre its words. */
.codefiles .codepictures li > button { align-self: flex-start; }
.codefiles .codedocs { list-style: none; margin: .9rem 0 0; padding: 0; }
.codefiles .codedocs li { padding: .7rem 0 .5rem; border-top: 1px solid var(--line); }
.codefiles .codedocs .tfield { margin-bottom: .45rem; }
.codefiles .codedocs .was { margin: -.2rem 0 .45rem; }
.codefiles .docopen { margin-left: .5rem; font-size: .8125rem; font-weight: 400; }

/* --- revisions --- */

/* The code a revision is about, above the picker that changes it. */
.revpick { max-width: 46rem; margin: 0 0 1.25rem; }
.revcode { display: flex; gap: .6rem; align-items: baseline; flex-wrap: wrap; margin: .5rem 0 1rem; }
.revcode .revemdn { color: var(--muted); font-size: .875rem; }

/* Before and after, one row per changed field. A table, because it is one: three columns
   read across, and a screen reader announces the field with each value. */
.revchanges {
  border: 1px solid var(--line); border-radius: var(--radius-lg);
  padding: 1rem 1.25rem; margin: 1.5rem 0; max-width: 46rem;
}
.revchanges h3 { margin: 0 0 .5rem; font-size: .95rem; }
table.changes { width: 100%; border-collapse: collapse; font-size: .875rem; }
table.changes th, table.changes td {
  text-align: left; vertical-align: top; padding: .4rem .5rem; border-bottom: 1px solid var(--line);
  overflow-wrap: anywhere;
}
table.changes thead th {
  font-size: .7rem; text-transform: uppercase; letter-spacing: .04em; color: var(--muted);
}
table.changes tbody th { font-weight: 600; white-space: nowrap; }
table.changes tr:last-child th, table.changes tr:last-child td { border-bottom: 0; }

/* "Now: ..." under a box on the approval form whose value is not the code's. */
.was { margin: -.8rem 0 1.15rem; font-size: .8125rem; color: var(--warn-fg); }

/* The approval summary: each change on a line, with whatever it sets off beneath it. */
.revsummary { border-top: 1px solid var(--line); margin-top: .5rem; padding-top: .75rem; max-width: 46rem; }
.revsummary h4 { margin: 0 0 .4rem; font-size: .875rem; }
ul.plan { margin: 0 0 .5rem; padding-left: 1.1rem; font-size: .875rem; }
ul.plan li { margin: .25rem 0; }
ul.plan .warn { color: var(--warn-fg); font-size: .8125rem; margin-left: .3rem; }
.revsummary p.warn { color: var(--warn-fg); font-size: .875rem; margin: .5rem 0 0; }
ul.plan .consequence { display: block; color: var(--warn-fg); font-size: .8125rem; }
/* Rename a shared manufacturer or model for every code, or replace this one alone: the
   choice under the change it belongs to, one option a line so a long label wraps by itself. */
fieldset.partscope { border: 0; padding: 0; margin: .3rem 0 .1rem; min-width: 0; }
fieldset.partscope legend { padding: 0; font-weight: 600; font-size: .8125rem; }
fieldset.partscope label {
  display: flex; align-items: baseline; gap: .4rem; padding: .2rem 0; font-size: .875rem;
}
/* Where what was done is not what was asked: an administrator's correction, on show. */
table.changes td.deviates { font-weight: 600; }

.revsummary .replacing {
  margin: 0 0 .5rem; padding: .5rem .75rem; border-left: 4px solid var(--warn-fg);
  background: var(--warn-bg); color: var(--warn-fg); font-size: .875rem;
}

/* The documents box stands alone under its heading, and is lower than the picture boxes:
   a document is a line in a list, not a thumbnail. */
.attachments > .dropzone { margin: .75rem 0 .5rem; }
.attachments > .dropzone .dzpick { min-height: 4.5rem; }

/* --- UDI-DIs --- */

/* A UDI-DI with its chip: the issuer and whether the value is well formed by that issuer's
   rule, green or red on the state tokens. The reason follows it in quieter text and wraps
   underneath when the line is narrow, so the value and its chip stay together. */
ul.udivalues { list-style: none; margin: 0; padding: 0; }
/* Centred rather than on the baseline, which would sink the 24 px lookup links. */
ul.udivalues li {
  display: flex; flex-wrap: wrap; align-items: center; gap: .15rem .45rem;
  margin: 0 0 .5rem;
}
ul.udivalues li:last-child { margin-bottom: 0; }
/* A HIBCC value with its secondary data runs to forty characters with no space in it; as a
   flex item it would otherwise refuse to shrink and run out of the card. */
ul.udivalues code { min-width: 0; overflow-wrap: anywhere; }
.udichip {
  display: inline-block; font-size: .75rem; line-height: 1.5; padding: 0 .5rem;
  border-radius: 999px; white-space: nowrap; font-weight: 500;
}
.udichip.ok { background: var(--ok-bg); color: var(--ok-fg); }
.udichip.bad { background: var(--bad-bg); color: var(--bad-fg); }
.udiproblem { font-size: .8125rem; color: var(--muted); }
/* UDI-DI-dubblett: a chip like the verdict, because it is another fact about the value,
   in the warning colours, because it is the one a save would refuse on. It is a link to the
   code holding the value, plain until pointed at, as `a.badge` is. The one chip long enough
   to widen a narrow cell -- a revision's change table on a phone -- so the one that wraps. */
a.udichip.dup {
  background: var(--warn-bg); color: var(--warn-fg); text-decoration: none;
  white-space: normal; max-width: 100%;
  /* A link, so 24 px tall like the lookups beside it (WCAG 2.2 target size). */
  display: inline-flex; flex-wrap: wrap; align-items: center; column-gap: .25em;
  min-height: 1.5rem; box-sizing: border-box; border-radius: .75rem;
}
/* Wrapped, it breaks at the dot, never inside the code. */
.udidupcode { white-space: nowrap; }
a.udichip.dup:hover, a.udichip.dup:focus-visible { text-decoration: underline; }

/* Extern sökning: a link per registry beside the chip. An icon in a bordered box, or the
   registry's name as a pill where it has none; either way 24 px tall, the WCAG 2.2 target
   size, and centred on the chip's line. The border is what says "a button to press" next to
   the borderless verdict, and the hover is the accent's, as every other link's. */
.udilookups { display: inline-flex; flex-wrap: wrap; align-items: center; gap: .3rem; vertical-align: middle; }
.udilookup {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 1.5rem; height: 1.5rem; box-sizing: border-box;
  border: 1px solid var(--line); border-radius: 6px; background: var(--bg);
  text-decoration: none; color: var(--accent);
}
/* Fitted rather than squared: an uploaded icon is drawn 18 px square, a shipped flag at its
   own proportions, 20 px wide. */
.udilookup img {
  display: block; width: auto; height: auto; max-width: 20px; max-height: 18px; border-radius: 2px;
}
.udilookup.text {
  padding: 0 .5rem; border-radius: 999px; border-color: var(--accent-soft);
  background: var(--accent-soft); font-size: .75rem; font-weight: 500; white-space: nowrap;
}
a.udilookup:hover, a.udilookup:focus-visible { border-color: var(--accent); background: var(--accent-soft); }
a.udilookup.text:hover, a.udilookup.text:focus-visible { text-decoration: underline; }

/* The boxes on a form, one a UDI-DI: the box and its × on a line, the chip under the box.
   Under rather than beside, so the box does not change width as the verdict changes. The
   × of the only row is hidden: there is nothing to take away, and emptying is typing. */
ul.udirows { list-style: none; margin: 0 0 .4rem; padding: 0; }
li.udirow {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center;
  column-gap: .35rem; margin: 0 0 .45rem;
}
li.udirow .udicheck {
  grid-column: 1 / -1; display: flex; flex-wrap: wrap; align-items: center;
  gap: .15rem .45rem; margin-top: .25rem;
}
li.udirow .udicheck:empty { display: none; }
ul.udirows > li.udirow:only-child .udidrop { visibility: hidden; }
button.udidrop {
  font-size: 1.25rem; line-height: 1; text-decoration: none; color: var(--muted);
  padding: .15rem .45rem;
}
button.udidrop:hover { color: var(--bad-fg); }
button.udiadd { font-size: .8125rem; padding: .25rem .65rem; }
button.udifix { font-size: .75rem; line-height: 1.5; padding: 0 .55rem; border-radius: 999px; }

/* Översättningar. The filter bar is the ticket list's, aligned on its top edge instead:
   the query box carries a hint underneath, and bottom-aligned the pills would sit level
   with the hint rather than with the box. */
.transfilters { align-items: flex-start; }
.transfilters .tquery { min-width: 16rem; }
.transfilters .tquery .hint { margin: .2rem 0 0; }
table.translations .c-keys { white-space: nowrap; width: 1%; }
/* The key in monospace, its badge in the page's own type. */
table.translations .tkey a { font-family: ui-monospace, "Cascadia Code", Consolas, monospace; }
table.translations .tkey { display: block; }
/* Active codes under the concept: a number, right-aligned; labelled only when stacked. */
table.translations .c-codes { width: 1%; text-align: right; font-variant-numeric: tabular-nums; }
table.translations .tcodes-label { display: none; }
table.translations .c-sv { min-width: 14ch; width: 40%; }
/* The concept's GMDN and EMDN, beside its Swedish name: what a translator reads from. */
.tref { display: inline-flex; gap: .3rem; margin-left: .4rem; vertical-align: middle; }
.tref .infopill-tip strong { display: block; }
.tref .tdef { display: block; margin-top: .35rem; color: var(--muted); }
table.translations .c-target { min-width: 18rem; }
.tform { display: flex; gap: .4rem; align-items: center; }
.tform input[type=text] {
  flex: 1 1 auto; min-width: 0;
  padding: .3rem .5rem; font-size: .875rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--field-bg); color: var(--fg);
}
.tform button { flex: none; }
.tsource { margin: .25rem 0 0; font-size: .8125rem; display: flex; flex-wrap: wrap; gap: .2rem .6rem; align-items: center; }
.tsource .badge { margin-left: 0; }
/* A name offered from a key the row does not show; nothing is stored until it is saved. */
.badge.suggested { background: var(--warn-bg); color: var(--warn-fg); }
/* The two translation pages, term names and EMDN names, as tabs: links that look like the
   segmented pills, the current one filled. */
.ttabs { display: flex; flex-wrap: wrap; gap: .3rem; margin: .2rem 0 .6rem; }
.ttabs a {
  padding: .2rem .75rem; border: 1px solid var(--line); border-radius: 999px;
  font-size: .875rem; color: var(--muted); text-decoration: none; white-space: nowrap;
}
.ttabs a:hover { color: var(--accent); border-color: var(--accent); }
.ttabs a[aria-current="page"] {
  background: var(--accent-soft); color: var(--accent); font-weight: 600;
  border-color: var(--accent-soft);
}
/* An EMDN code's category, under its English name. */
table.translations .tcat { display: block; margin-top: .2rem; font-size: .8125rem; color: var(--muted); }
.tprogress { display: flex; flex-wrap: wrap; align-items: center; gap: .3rem .8rem; margin: .5rem 0; }
.tprogress progress { width: min(16rem, 100%); accent-color: var(--accent); }
/* One column under 40rem: the keys above the names, the field the full width. */
@media (max-width: 40rem) {
  table.translations thead { display: none; }
  table.translations tr, table.translations td { display: block; }
  table.translations td { border-bottom: 0; padding: .2rem 0; }
  table.translations tbody tr:nth-child(n) td { background: none; }
  table.translations tr { border-bottom: 1px solid var(--line); padding: .3rem 0; }
  table.translations .c-keys, table.translations .c-sv { width: auto; }
  table.translations .tkey { display: inline; margin-right: .6rem; }
  table.translations .c-codes { width: auto; text-align: left; color: var(--muted); font-size: .8125rem; }
  table.translations .tcodes-label { display: inline; }
  table.translations .c-target { min-width: 0; }
}

/* --- the account page --------------------------------------------------------------- */

.accountgrid { align-items: start; }
.accountgrid .card { margin: 0 0 1rem; }
.accountpage .tfield .fixed { margin: 0; padding: .45rem 0; font-size: .9375rem; }
@media (max-width: 48rem) {
  .accountgrid { grid-template-columns: minmax(0, 1fr); }
}

/* --- terms ------------------------------------------------------------------------- */

/* The translation page's filter bar: aligned on its top edge, since the query box carries
   a hint underneath. */
.termfilters { align-items: flex-start; }
.termfilters .tquery { min-width: 16rem; }
.termfilters .tquery .hint { margin: .2rem 0 0; }
.termfilters select { height: 2.125rem; }
table.termlist .c-key { white-space: nowrap; width: 1%; }
table.termlist .c-name .badge { margin-left: .3rem; }
table.termlist .c-num { text-align: right; white-space: nowrap; }
table.termlist th.c-num a { justify-content: flex-end; }
table.termlist .c-tags .chip { margin: 0 .25rem .2rem 0; }
.termident { margin-bottom: 1rem; }
/* The three components side by side where they fit: they are one assessment. */
/* The three selects inside a form of fields, under one heading that reads like a field's
   label rather than as a frame of its own. */
.pmfuset { border: 0; margin: 0; padding: 0; min-width: 0; }
/* Out-ranks `fieldset.block legend`, the section heading this sits inside. */
fieldset.block fieldset.pmfuset > legend {
  padding: 0; margin-bottom: .2rem; font-weight: 600; font-size: .875rem;
  text-transform: none; letter-spacing: normal; color: var(--fg);
}
.pmfuset > .hint { margin: 0 0 .6rem; }
.pmfulive { margin: .5rem 0 0; }
.pmfufields { display: grid; gap: 0 1rem; grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 48rem) { .pmfufields { grid-template-columns: minmax(0, 1fr); } }
.termlink { margin: 1.25rem 0 0; }

/* --- user administration ------------------------------------------------------------ */

.badge.ustatus.active { background: var(--ok-bg); color: var(--ok-fg); border-color: var(--ok-fg); }
.badge.ustatus.invited { background: var(--warn-bg); color: var(--warn-fg); border-color: var(--warn-fg); }
.badge.ustatus.disabled { background: var(--hist-bg); color: var(--hist-fg); border-color: var(--hist-fg); }
/* Every control in the box one height, so the row lines up on its bottom edge: the
   search box is `type=search`, which the field rules for text boxes do not reach. */
.userfilters .tquery { min-width: 14rem; }
.userfilters input[type=search] {
  width: 100%; height: 2.125rem; padding: .4rem .55rem; font-size: .875rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--field-bg); color: var(--fg);
}
.userfilters select { height: 2.125rem; }
table.userlist td.uname .handle { display: block; font-size: .8125rem; }
table.userlist td.uemail { white-space: nowrap; }
.userpage .crumb { margin: 1rem 0 0; font-size: .875rem; }
.usergrid { align-items: start; }
.usergrid .card { margin: 0 0 1rem; }
@media (max-width: 48rem) {
  .usergrid { grid-template-columns: minmax(0, 1fr); }
}
.useractions { display: flex; flex-wrap: wrap; gap: .25rem 1rem; margin-top: 1rem; }
.useractions form { margin: 0; }
.useractions .danger { color: var(--bad-fg); }
.linkcard { border-color: var(--warn-fg); }
/* The link in a box that fills the row, Kopiera beside it. A box rather than text, so the
   whole of a long link is one click to select and the page never grows sideways. */
.linkcard .thelink { display: flex; gap: .5rem; margin: .5rem 0 0; }
.linkcard .thelink input {
  flex: 1 1 auto; min-width: 0; font-family: ui-monospace, Consolas, monospace; font-size: .8125rem;
}
.linkcard .thelink button { flex: none; }
.linkcard .copystatus { margin: .35rem 0 0; font-size: .8125rem; color: var(--muted); min-height: 1.2em; }

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

.groupbadge { display: inline-block; vertical-align: middle; border-radius: 50%; flex-shrink: 0; color: var(--accent); }
.groupbadge.s { width: 1.25rem; height: 1.25rem; }
.groupbadge.m { width: 1.75rem; height: 1.75rem; }
.groupbadge.l { width: 2.5rem; height: 2.5rem; }
.groupbadge.xl { width: 4.5rem; height: 4.5rem; }
img.groupbadge { object-fit: cover; background: var(--bg); }
svg.groupbadge text { font-family: system-ui, -apple-system, "Segoe UI", sans-serif; }
.groupname { display: inline-flex; align-items: center; gap: .4rem; }
header.site .navmenu.account summary .groupbadge { margin-right: .1rem; }
.accountpanel .grouprow {
  display: flex; align-items: center; margin: 0; padding: .5rem .8rem;
  border-bottom: 1px solid var(--line); color: var(--fg); text-decoration: none; font-size: .875rem;
}
.accountpanel a.grouprow:hover { background: var(--accent-soft); }
.groupspage .tagcreate .grouprow3 { display: grid; grid-template-columns: minmax(9rem, 12rem) minmax(0, 1fr) minmax(0, 1fr); gap: 0 1rem; }
@media (max-width: 48rem) { .groupspage .tagcreate .grouprow3 { grid-template-columns: minmax(0, 1fr); } }
ul.grouplist { list-style: none; margin: 1rem 0 0; padding: 0; border: 1px solid var(--line); border-radius: var(--radius); }
.groupitem { display: flex; align-items: center; gap: .8rem; padding: .55rem .8rem; border-bottom: 1px solid var(--line); }
.groupitem:last-child { border-bottom: 0; }
.groupitem .gkey { width: 2.2rem; color: var(--muted); font-size: .8125rem; font-variant-numeric: tabular-nums; }
.groupitem .gname { flex-grow: 1; min-width: 0; }
.groupitem .gsite, .groupitem .gcount { font-size: .8125rem; white-space: nowrap; }
.groupitem .gcount { width: 7rem; text-align: right; }
.groupitem.inactive .gname, .groupitem.inactive .groupbadge { opacity: .6; }
@media (max-width: 48rem) { .groupitem .gsite { display: none; } .groupitem .gcount { width: auto; } }
.grouppage .crumb { margin: 1rem 0 0; font-size: .875rem; }
.grouppage .pagehead h2 .groupname { gap: .6rem; }
.logorow { display: flex; align-items: flex-start; gap: 1rem; margin-bottom: 1rem; }
.logorow .hint { margin-top: 0; }
.logorow .gtitle { margin: 0; font-weight: 600; }
.logoform .tfield input[type=file] { font-size: .875rem; }
ul.memberlist { margin: 0; padding-left: 1.2rem; font-size: .9375rem; }
ul.memberlist li { margin: .15rem 0; }
.groupcard { margin-top: 0; }
table.userlist td.c-group .groupname { white-space: nowrap; }
.reqgroup { display: inline-flex; margin-left: .4rem; font-size: .8125rem; color: var(--muted); vertical-align: middle; }
.reqgroup .groupname { gap: .3rem; }

/* --- acting as a role --------------------------------------------------------------- */

.badge.acting { background: var(--warn-bg); color: var(--warn-fg); font-weight: 600; }
.accountpanel .actas {
  display: flex; flex-direction: column; gap: .35rem; padding: .5rem .8rem .6rem;
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
}
.accountpanel .actas-label {
  font-size: .7rem; text-transform: uppercase; letter-spacing: .04em; color: var(--muted);
}
.accountpanel .actas .seg label { white-space: nowrap; }

/* --- Systeminformation -------------------------------------------------------------- */

/* A page of figures: a section per subject, its cross-table first and the smaller lists in
   a grid of columns under it, so a wide screen is not one long column of short lines. */
.systoc {
  display: flex; flex-wrap: wrap; gap: .3rem 1rem; margin: 0 0 1.25rem;
  font-size: .875rem;
}
.sysblock { margin: 0 0 2rem; padding-top: 1rem; border-top: 1px solid var(--line); scroll-margin-top: 1rem; }
.sysblock h3 { margin: 0 0 .5rem; font-size: 1.1rem; }
.sysblock h4 { margin: 1rem 0 .4rem; font-size: .9375rem; }
.sysblock .hint { margin: 0 0 .5rem; }
.sysgrid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(min(22rem, 100%), 1fr));
  gap: 0 2.5rem; align-items: start;
}
/* Label and value on one line, the values lined up; the label wraps rather than the value. */
dl.facts {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 0;
  margin: 0; font-size: .875rem; max-width: 40rem;
}
dl.facts.wide { grid-template-columns: minmax(10rem, max-content) minmax(0, 1fr); max-width: none; }
dl.facts > div { display: contents; }
dl.facts dt, dl.facts dd { padding: .3rem 0; border-bottom: 1px solid var(--line); margin: 0; }
dl.facts dt { color: var(--muted); padding-right: 1rem; }
dl.facts dd { text-align: right; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
dl.facts.wide dd { text-align: left; }
dl.facts .warn dt, dl.facts .warn dd { color: var(--warn-fg); background: var(--warn-bg); }
dl.facts .warn dt { padding-left: .4rem; }
dl.facts .warn dd { padding-right: .4rem; }
dl.facts code, table.stats code { font-size: .8125rem; }

table.stats { width: auto; min-width: min(36rem, 100%); }
table.stats caption {
  caption-side: top; text-align: left; font-size: .875rem; color: var(--muted);
  padding: 0 0 .3rem;
}
/* A row's name is a name, not a column heading: none of the header's small capitals. */
table.stats th[scope=row] {
  text-align: left; font-weight: 500; white-space: nowrap; vertical-align: top;
  font-size: inherit; text-transform: none; letter-spacing: normal; color: var(--fg);
  position: sticky; top: auto; border-bottom: 1px solid var(--line);
}
table.stats.compact th[scope=row] { white-space: normal; }
table.stats .n { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
table.stats tr.total > * { font-weight: 600; border-top: 1px solid var(--line-strong); }
table.stats.compact { min-width: 0; width: 100%; }
table.stats .warncell { color: var(--warn-fg); background: var(--warn-bg); }
table.stats .nowrap { white-space: nowrap; }
/* The row's name stays put while a wide table scrolls, as the ticket number does. */
table.stats tr > :first-child { position: sticky; left: 0; background: var(--bg); }
table.stats tbody tr:nth-child(even) > :first-child { background: var(--stripe); }
table.stats tbody tr:hover > :first-child { background: var(--accent-soft); }
table.stats thead tr > :first-child { z-index: 2; }
table.stats.audit tr > :first-child { position: static; }

ol.toplist { margin: 0; padding-left: 1.6rem; font-size: .875rem; }
ol.toplist li { padding: .2rem 0; border-bottom: 1px solid var(--line); }
ol.toplist li .n { float: right; font-variant-numeric: tabular-nums; }
ol.toplist code { font-size: .8125rem; margin-right: .3rem; }
.sysblock ul.problems { max-height: 20rem; overflow: auto; }
.sysblock .wrap + .sysgrid { margin-top: .5rem; }

/* --- batch operations ---------------------------------------------------------------- */

/* Batchåtgärder: the warning, the switch and its second confirmation, the runs. The red
   is the one the app keeps for what cannot be undone in a step. */
.batchpage .card h3, .batchpage .card h4 { margin: 0 0 .5rem; font-size: 1.05rem; color: var(--fg); }
.settingsgroup { margin-top: 1.5rem; }
.settingsgroup > h3 { margin: 0 0 .75rem; font-size: 1.2rem; }
.batchpage .card p { margin: .4rem 0; }
.batchwarn { border-color: var(--danger-line); }
.batchpage .batchwarn h3, .batchpage .batchwarn h4 { color: var(--bad-fg); }
.batchfns { margin-bottom: 1.1rem; }
.batchfns dt { color: var(--fg); font-weight: 600; }
.batchfns dd { color: var(--muted); }
.batchstate {
  display: flex; flex-wrap: wrap; align-items: center; gap: .75rem 1rem;
  padding-top: .75rem; border-top: 1px solid var(--line);
}
.batchstate p { margin: 0; color: var(--muted); }
.batchstate > :last-child { margin-left: auto; }
.batchstate .batchon {
  flex: 1 1 100%; padding: .5rem .75rem; border-radius: var(--radius);
  background: var(--ok-bg); color: var(--ok-fg);
}
.batchconfirm {
  margin-top: .75rem; padding: 1rem 1.1rem; border-radius: var(--radius-lg);
  border: 2px solid var(--danger-line-strong); background: var(--bad-bg);
}
.batchconfirm .batchconfirm-title { margin: 0 0 .6rem; font-weight: 700; color: var(--bad-fg); }
.batchunderstood { display: flex; gap: .6rem; align-items: flex-start; cursor: pointer; }
.batchunderstood input { width: 1.1rem; height: 1.1rem; margin-top: .2rem; flex: none; }
.batchconfirm .formactions { margin: .9rem 0 0; }
button.dangerous {
  background: var(--bad-fg); border-color: var(--bad-fg); color: var(--bg); font-weight: 600;
}
button.dangerous:hover { filter: brightness(1.12); background: var(--bad-fg); border-color: var(--bad-fg); }
button.dangerous[disabled]:hover { filter: none; }
.batchruns td.nowrap { white-space: nowrap; }
.batchpage .muted { color: var(--muted); }

/* The batch bar above a list, while Batchåtgärder is on: red-edged, because its button
   rewrites rows, and a row's own box in the first column. The column is not one of the
   picker's: it shows while batch mode does, whatever the cookie hides. */
.batchbar {
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem 1.1rem;
  margin: .6rem 0 0; padding: .45rem .75rem; font-size: .875rem;
  border: 1px solid var(--danger-line); border-radius: var(--radius-lg);
  background: var(--bad-bg);
}
.batchbar-label { color: var(--bad-fg); }
.batchbar-all { display: inline-flex; align-items: center; gap: .45rem; cursor: pointer; }
.batchbar-all input { width: 1.05rem; height: 1.05rem; margin: 0; }
.batchbar-hint { color: var(--muted); }
.batchbar .batchgo {
  margin-left: auto; font-weight: 600;
  color: var(--bad-fg); border-color: var(--bad-fg); background: var(--bg);
}
.batchbar .batchgo:hover:not([disabled]) { background: var(--bad-bg); }
table.results th.c-pick, table.results td.c-pick { width: 2.2rem; padding-right: 0; }
table.results .c-pick input { width: 1.05rem; height: 1.05rem; margin: 0; vertical-align: middle; }
table.results tr:has(td.c-pick input:checked) td { background: var(--accent-soft); }
.batchcard .batchkeep { border-left-color: var(--warn-fg); background: var(--warn-bg); color: var(--warn-fg); }
.batchlist { color: var(--muted); font-size: .875rem; }
.batchlist code { color: var(--fg); white-space: nowrap; }
.mergeinto fieldset { border: 1px solid var(--line); border-radius: var(--radius-lg); padding: .6rem 1rem .8rem; margin: 1rem 0; }
.mergeinto legend { font-weight: 600; padding: 0 .3rem; }
.mergechoice { display: flex; align-items: center; flex-wrap: wrap; gap: .25rem .5rem; padding: .3rem 0; cursor: pointer; }
.mergechoice input { width: 1.05rem; height: 1.05rem; margin: 0; }
.mergechoice .mergename { font-weight: 600; }
.mergepart { border-top: 1px solid var(--line); margin-top: 1rem; padding-top: .75rem; }
.mergepart h3 { margin: 0 0 .35rem; font-size: 1rem; }
table.results.mergetable tbody tr.clash td { background: var(--bad-bg); color: var(--bad-fg); }
table.results.mergetable tr.clash a { color: inherit; }
/* The reason is a sentence: on a phone the table scrolls in its box rather than squeeze it. */
.mergetable td:last-child { min-width: 16rem; }
.mergetable code { white-space: nowrap; }
