/* MTPReg X.
 *
 * 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; }

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 ------------------------------------------------------------------------- */

header.site {
  border-bottom: 1px solid var(--line);
  padding: .85rem 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; }
header.site .tagline { color: var(--muted); font-size: .875rem; }
header.site .langs { font-size: .8rem; }

/* The theme toggle, last in the header. A round icon button: a moon while light is showing
   -- press for dark -- and a sun while dark is. Both icons are in the markup and the theme
   decides which is seen, so it is right even when only the computer chose. */
header.site .themetoggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 2rem; height: 2rem; padding: 0; align-self: center;
  border: 1px solid var(--line); border-radius: 50%;
  background: var(--field-bg); color: var(--muted);
}
header.site .themetoggle:hover { color: var(--fg); border-color: var(--accent); }
header.site .themetoggle:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
header.site .themetoggle svg { width: 1rem; height: 1rem; }
.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 .langs a { margin-left: .5rem; }
header.site .langs .current {
  font-weight: 600; text-decoration: none; color: var(--muted); margin-left: .5rem;
}

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; }
/* 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 and the language links travel together at the right end, so the
   `auto` margin belongs to the first of the pair. Two of them would split the free space
   and leave a gap between the two. Its list hangs from the right edge, since it is the
   last thing on the bar. */
header.site .navmenu.account { margin-left: auto; font-size: .875rem; }
header.site .navmenu.account summary { color: var(--fg); }
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; }
/* 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; }
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: 2.5rem; height: 2.5rem; color: var(--accent); 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. */
main { max-width: 88rem; margin: 0 auto; padding: 1.25rem 1.25rem 4rem; }
main.narrow { max-width: 62rem; }

/* --- 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; }
.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; }

/* --- 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%;
  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: 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; }
  .filterbox .filtergrid .wide { grid-column: span 2; }
  .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); }
thead .c-code { 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; }

/* Priority columns. OEM goes first because it is empty for every imported code. */
.narrownote { display: none; }
@media (max-width: 78rem) {
  .c-oem, .c-trade { display: none; }
  .narrownote { display: block; }
}
@media (max-width: 60rem) {
  .c-pmfu, .c-std { display: none; }
}

.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); }
.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); }

/* 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);
  width: 100vw; max-width: 100vw; height: 100vh; max-height: 100vh;
}
/* 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; padding: .75rem 1rem; }
.vstage { position: relative; display: grid; place-items: center; min-height: 0;
  padding: 0 3.25rem 1.5rem; }
.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);
}
.viewer figcaption {
  display: flex; align-items: center; justify-content: center; flex-wrap: wrap;
  gap: .5rem 1rem; font-size: .875rem;
}
.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); }
.vnav {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 2.5rem; height: 2.5rem; padding: 0; cursor: pointer;
  font-size: 1.5rem; line-height: 1; border-radius: 50%;
  border: 1px solid var(--v-line); background: rgba(12, 18, 24, .6); color: var(--v-fg);
}
.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(52rem, 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; }
.tmhead {
  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; }
.tmhead h2 { margin: 0; font-size: 1.1rem; }
.tmhead .hint { margin: .15rem 0 0; }
.tmscroll { overflow-y: auto; min-height: 0; padding: 1rem 1.25rem 2rem; }
.tmscroll > .notice { margin-top: 0; }
/* 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;
}

.tagstats { display: flex; flex-wrap: wrap; gap: .5rem; margin: 0 0 1.25rem; }
.tagstats > div {
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: .35rem .75rem; background: var(--surface); min-width: 7rem;
}
.tagstats dt { font-size: .75rem; color: var(--muted); }
.tagstats dd {
  margin: 0; font-size: 1.2rem; font-weight: 600; font-variant-numeric: tabular-nums;
}

.tagcreate {
  border: 1px solid var(--line); border-radius: var(--radius-lg);
  padding: .75rem 1rem; margin: 0 0 1.5rem;
}
.tagcreate h3 { margin: 0 0 .5rem; font-size: .95rem; }
.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) 6rem 9.5rem 6.5rem 3.5rem;
  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, so theirs is a column shorter. */
.tagmanager-body:not(:has(details)) .tagrow {
  grid-template-columns: minmax(0, 1fr) 6rem 9.5rem 6.5rem;
}
.tagheads > span { white-space: nowrap; }
.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 { font-size: .875rem; font-variant-numeric: tabular-nums; }
.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; }

/* Narrow: the name takes its own line and the figures sit beneath it. The heading row goes,
   since there are no columns left for it to head; every figure also says what it is. */
@media (max-width: 40rem) {
  .tagheads { display: none; }
  .tagrow,
  .tagmanager-body:not(:has(details)) .tagrow {
    grid-template-columns: auto auto 1fr auto;
    grid-template-areas: "name name name edit" "langs count last last";
  }
  .tagrow .tname { grid-area: name; }
  .tagrow .tlangs { grid-area: langs; }
  .tagrow .tcount { grid-area: count; }
  .tagrow .tlast { grid-area: last; }
  .tagrow .tedit { grid-area: edit; }
}
@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; }
@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; }
}

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

/* --- 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. The picture clips what overflows it, which
   is why the 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; overflow: hidden;
  touch-action: none; cursor: crosshair;
}
/* 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, inside the band: a frame that
   starts at the picture's edges has them there, where the picture's clipping would halve
   any that stood outside. */
.iegrip {
  position: absolute; width: 12px; height: 12px;
  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: 0; top: 0; cursor: nwse-resize; }
.iegrip[data-handle=ne] { right: 0; top: 0; cursor: nesw-resize; }
.iegrip[data-handle=sw] { left: 0; bottom: 0; cursor: nesw-resize; }
.iegrip[data-handle=se] { right: 0; bottom: 0; cursor: nwse-resize; }
.iegrip[data-handle=n] { left: calc(50% - 6px); top: 0; cursor: ns-resize; }
.iegrip[data-handle=s] { left: calc(50% - 6px); bottom: 0; cursor: ns-resize; }
.iegrip[data-handle=w] { left: 0; top: calc(50% - 6px); cursor: ew-resize; }
.iegrip[data-handle=e] { right: 0; top: calc(50% - 6px); 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; }
.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; }
/* 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 { white-space: nowrap; }
table.tickets td.tid a { font-variant-numeric: tabular-nums; }
table.tickets td.ttitle { min-width: 14rem; }
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. */
.badge.tstatus { 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); }

/* The same treatment the results table gets: narrow means fewer columns, not a table
   sliced down the middle by its scroll container. What survives to the end is the one
   thing this list is for -- which ticket, and what state it is in. */
@media (max-width: 62rem) {
  table.tickets .c-when { display: none; }
}
@media (max-width: 48rem) {
  table.tickets .c-who, table.tickets .c-kind { display: none; }
}

/* --- 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; }
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); }

/* 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; }
/* 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; }
}

/* "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; }
.pagehead .deletedraft { margin-left: auto; }
.pagehead .button + .deletedraft { margin-left: 0; }
button.danger { color: var(--bad-fg); border-color: var(--danger-line); }
button.danger:hover { background: var(--bad-bg); border-color: var(--danger-line-strong); }

.replybox { margin-top: 1.25rem; padding-top: 1rem; border-top: 1px solid var(--line); }
.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; }
.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; }
.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; }
/* 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); }

/* --- 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; }
/* 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; }
/* 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; }
ul.udivalues li {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .1rem .45rem;
  margin: 0 0 .2rem;
}
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); }

/* 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: baseline;
  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 {
  font-family: ui-monospace, "Cascadia Code", Consolas, monospace;
  white-space: nowrap; width: 1%;
}
table.translations .tkey { display: block; }
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; }
/* 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-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); }
}

/* --- 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); }
.userfilters .tquery { min-width: 14rem; }
table.userlist td.uname .handle { display: block; font-size: .8125rem; }
table.userlist td.uemail { overflow-wrap: anywhere; }
@media (max-width: 48rem) {
  table.userlist .c-when, table.userlist .c-roles { display: none; }
}
.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); }
.linkcard .thelink { margin: .5rem 0 0; overflow-wrap: anywhere; }
.linkcard code { font-size: .875rem; user-select: all; }

/* --- 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; }
@media (max-width: 60rem) { table.userlist .c-group { display: none; } }
.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; }
.actingbar {
  display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;
  padding: .45rem 1.25rem; font-size: .875rem;
  background: var(--warn-bg); color: var(--warn-fg); border-bottom: 1px solid var(--line);
}
.actingbar form { margin: 0 0 0 auto; }
.actingbar button {
  font: inherit; font-size: .8125rem; padding: .2rem .6rem; cursor: pointer;
  background: transparent; color: var(--warn-fg); border: 1px solid var(--warn-fg);
  border-radius: var(--radius);
}
.actingbar button:hover { background: var(--bg); }
