/* Transcribed from docs/design/wms_admin_prototip.html, which is canonical.
   Colour means state here exactly as it does on the phone: teal settled, sand
   in progress, apricot waiting, coral wrong, gold brand only.

   No build step, no framework, no external request. An instance sits on a
   client's network and the panel has to render the same with the internet
   unplugged.

   Motion is confirmation, not decoration, and all of it is switched off under
   prefers-reduced-motion at the bottom of this file. */

:root {
  --ink: #264653;
  --ink-soft: #4A6572;
  --teal: #2A9D8F;
  --teal-dark: #1F7A70;
  --sand: #D9B04C;
  --apricot: #E08F4F;
  --coral: #D65A3C;
  --gold: #F7BB2C;
  --bg: #F4F3EF;
  --card: #FFFFFF;
  --line: #DEDCD4;
  /* The scrollbar thumb: `--line` darkened until it reads as a control rather
     than as another border. It is not a state colour and never becomes one --
     a teal scrollbar would say "settled" about a piece of furniture. */
  --scroll: #C6C3B9;

  --radius: 12px;
  --radius-sm: 9px;
  /* The prototype is flat: 1px borders and nothing else. An earlier pass added
     shadows and it read softer and more "web app" than an industrial tool is
     meant to. Kept only where something genuinely floats above the page, which
     is a toast and a hovering button. */
  --shadow-1: none;
  --shadow-2: 0 4px 14px rgba(38, 70, 83, .10);
  --fast: 120ms;
  --normal: 200ms;
  --ease: cubic-bezier(.2, .7, .3, 1);

  /* ---- The landing page's row, and the one scale the page follows.

     `.events` set these numbers first and they were right, so they became the
     panel's row rather than one table's private taste. "Se lucreaza acum" used
     the default table instead -- 14px type, 13/18 cells, a 48px row directly
     above the 34px one -- so two panels stacked on one page disagreed about how
     tall a line of a list is. Both are sized from here now, and so is the
     status strip above them.

     **Fluid, and that is what the tokens are for rather than a flourish.** Each
     `clamp()` lands on the measured value at a 1440px window and grows from
     there, so a wider screen gets proportionally more without anybody editing a
     number. The floor is what stays readable on a laptop; the ceiling stops a
     4K panel rendering a spreadsheet at poster size.

     **`--row-h` is the row's whole pitch**, and that is what makes a scrollbox
     cap honest at every width: `calc(8 * var(--row-h))` is eight rows by
     construction. The old `--max:272px` was eight rows only at the one font
     size somebody measured it at -- and the note above it says so, having
     already been wrong once when the padding changed underneath it. A cell
     claims `--row-h` and centres its own text, so no line-height has to be
     guessed and no font metric can drift the count.

     The cell asks for the whole pitch and not `--row-h - 1px`: tables here are
     `border-collapse: collapse`, so the 1px rule is drawn inside the row rather
     than added to it. Subtracting it left eight rows measuring 264px against a
     272px window, which is eight rows and eight pixels of a ninth -- checked in
     a browser at 1440px, which is the only way this particular pixel is ever
     going to be right. */
  --row-h: clamp(29px, .556vw + 26px, 39px);
  --row-font: clamp(12.5px, .24vw + 10.05px, 15px);
  --row-pad-x: clamp(12px, .28vw + 10.97px, 20px);
  /* **A two-line row's whole pitch**, the way `--row-h` is a one-line row's,
     and on the same fluid curve: 1.3 is the ratio the hand-built two-line row
     measured (4px + 4px of padding, a 14px name and a 12px sub-line at 1.35)
     against a 34px single row at 1440. Without it a window capped in `--row-h`
     over a table of two-line rows promised eight rows and showed six, and two
     panels asking for different counts of the wrong unit came out unequal.
     `--row-font-sm` is the quiet second line, fluid for the same reason: a
     bigger screen should get a bigger row, not the same row with more air. */
  --row-h2: calc(var(--row-h) * 1.3);
  --row-font-sm: clamp(10.5px, .2vw + 8.5px, 13px);
  --row-label: clamp(10px, .14vw + 9.48px, 12px);
}

/* Lets a transition run towards `auto`, which is what makes an unfolding
   message animate instead of snapping. Scoped to nothing else because nothing
   else transitions to a keyword; harmless where unsupported. */
:root { interpolate-size: allow-keywords; }

* { margin: 0; padding: 0; box-sizing: border-box; }

body {
  background: var(--bg);
  color: var(--ink);
  /* A system stack rather than a web font: it renders identically offline and
     costs no request. */
  font-family: "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  font-size: 15px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

.mono, code { font-family: "Cascadia Mono", Consolas, "SF Mono", monospace; font-variant-ligatures: none; }

/* ------------------------------------------------------- navigation bar

   Two pixels across the top while a boosted page is on its way. Teal, because
   this is the panel's "in hand" colour, and it is the one piece of motion here
   that reports rather than decorates.

   It never reaches 100% on its own: the width it creeps to is a guess about a
   response that has not arrived, and a bar that filled and then waited would be
   lying. Completion is the only honest jump to the end. */
#progress {
  position: fixed;
  top: 0;
  left: 0;
  height: 2px;
  width: 0;
  z-index: 100;
  background: var(--teal);
  opacity: 0;
  pointer-events: none;
}
#progress.on {
  opacity: 1;
  width: 80%;
  /* Slow and decelerating: most of the distance in the first moments, then
     barely moving, so a fast answer looks finished and a slow one still looks
     alive. */
  transition: width 8s cubic-bezier(.05, .7, .1, 1), opacity var(--fast) var(--ease);
}
#progress.done {
  opacity: 0;
  width: 100%;
  transition: width var(--fast) var(--ease), opacity var(--normal) var(--ease);
}

/* ----------------------------------------------------------- scrollbars

   The panel windows several lists now -- the landing page's two, the article
   table, the flows -- so scrollbars stopped being an occasional accident and
   became part of the furniture. A default Windows scrollbar next to a flat,
   1px-bordered design is the loudest thing on the screen.

   Both syntaxes, because they are not interchangeable: Firefox takes
   `scrollbar-width`/`scrollbar-color` and ignores the pseudo-elements, Chrome
   and Edge do the opposite. Neither is a fallback for the other.

   The thumb is drawn inside a transparent border with `background-clip`, so it
   is a thin pill with room around it on any background -- the page's sand, a
   white panel, or the dark sidebar -- rather than a bar wedged against an edge
   that only looks right on one of them.

   Quiet until it is wanted (2026-09-08, asked for on Articole, where a long
   table draws both bars): a 4px pill in the border colour at rest, so it
   marks that the list goes on without competing with it, and the thumb
   colour while the pointer is over the thing that scrolls. Hover on the
   thumb itself goes one step darker, so it reads as something to grab. The
   Firefox pair changes on the same hover, so both engines tell one story. */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
}
*:hover { scrollbar-color: var(--scroll) transparent; }
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--line);
  border: 2px solid transparent;
  background-clip: padding-box;
  border-radius: 8px;
}
*:hover::-webkit-scrollbar-thumb { background: var(--scroll); background-clip: padding-box; }
::-webkit-scrollbar-thumb:hover { background: var(--ink-soft); background-clip: padding-box; }
/* Where a vertical and a horizontal bar meet. Left as the page rather than as
   the browser's grey square. */
::-webkit-scrollbar-corner { background: transparent; }

/* The sidebar is ink, so the same thumb would disappear into it. */
.side { scrollbar-color: rgba(255, 255, 255, .28) transparent; }
.side::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, .28); }
.side::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, .45); }

a { color: var(--teal-dark); }

:focus-visible {
  outline: 2px solid var(--teal);
  outline-offset: 2px;
  border-radius: 4px;
}

.app { display: flex; min-height: 100vh; }

/* ---------------------------------------------------------------- sidebar */

.side {
  width: 230px;
  flex-shrink: 0;
  background: var(--ink);
  color: #fff;
  padding: 22px 0;
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  height: 100vh;
  transition: width var(--normal) var(--ease);
}
.side .brand {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 22px 20px;
  font-size: 19px;
  font-weight: 700;
  letter-spacing: -.2px;
}
.side .brand .mark { display: flex; position: relative; }
.side .brand img { display: block; }

/* The collapse control. Quiet by default and only fully lit on hover: it is
   used once a session, and a button competing with the brand beside it would
   be the loudest thing in a sidebar whose job is the nine links below. */
.sidetoggle {
  margin-left: auto;
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  padding: 0;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: rgba(255, 255, 255, .45);
  cursor: pointer;
  transition: background var(--fast) var(--ease), color var(--fast) var(--ease),
              transform var(--normal) var(--ease);
}
.sidetoggle:hover { background: rgba(255, 255, 255, .1); color: #fff; }
.sidetoggle svg { width: 18px; height: 18px; }
.side .brand b { color: var(--gold); font-weight: 800; }
.side .firm {
  margin: 0 16px 18px;
  padding: 11px 13px;
  border-radius: 10px;
  background: rgba(255, 255, 255, .09);
  font-size: 12.5px;
  color: rgba(255, 255, 255, .6);
  letter-spacing: .3px;
}
.side .firm b { display: block; color: #fff; font-size: 15px; margin-top: 2px; letter-spacing: 0; }

.nav a {
  display: flex;
  align-items: center;
  gap: 11px;
  position: relative;
  padding: 11px 22px;
  font-size: 14.5px;
  color: rgba(255, 255, 255, .72);
  text-decoration: none;
  border-left: 3px solid transparent;
  transition: background var(--fast) var(--ease), color var(--fast) var(--ease),
              border-color var(--fast) var(--ease), padding-left var(--fast) var(--ease);
}
/* Drawn with `currentColor`, sized here. The icon fades up together with the
   word beside it on hover, rather than staying lit next to dim text. */
.nav a svg, .side .me a svg { width: 19px; height: 19px; flex-shrink: 0; }
.nav a:hover { background: rgba(255, 255, 255, .06); color: #fff; padding-left: 25px; }
.nav a.on {
  background: rgba(255, 255, 255, .08);
  color: #fff;
  border-left-color: var(--gold);
  font-weight: 600;
}

.side .me {
  margin-top: auto;
  padding: 16px 22px 0;
  font-size: 12.5px;
  color: rgba(255, 255, 255, .5);
  border-top: 1px solid rgba(255, 255, 255, .1);
}
.side .me b { display: block; color: rgba(255, 255, 255, .8); }
/* One per line: two links reading as one sentence is how somebody clicks
   "Ieși din panou" while aiming for "Schimbă parola". */
.side .me a {
  display: flex;
  align-items: center;
  gap: 10px;
  position: relative;
  margin-top: 7px;
  color: rgba(255, 255, 255, .5);
  transition: color var(--fast) var(--ease);
}
.side .me a:hover { color: #fff; }

/* ------------------------------------------------------- the collapsed rail

   Wide enough for a 19px icon with a real touch target around it, narrow
   enough that collapsing is visibly worth doing.

   The labels are clipped rather than removed. `display:none` would take nine
   named links out of the accessibility tree and leave a screen reader reading
   nine anonymous ones, which is a worse menu than the one we started with. */
.side-collapsed .side { width: 64px; }

.side-collapsed .side .lbl {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
/* The firm badge is a paragraph, not a label, and there is no honest way to
   fit it in 64px. It survives as the logo's tooltip instead. */
.side-collapsed .side .firm { display: none; }

/* Logo above, control below. Side by side they do not fit, and shrinking
   either to make them fit is how a brand ends up looking like an afterthought. */
.side-collapsed .side .brand {
  flex-direction: column;
  gap: 12px;
  padding: 0 0 18px;
}
.side-collapsed .sidetoggle { margin-left: 0; transform: rotate(180deg); }

.side-collapsed .nav a,
.side-collapsed .side .me a { justify-content: center; gap: 0; padding-left: 0; padding-right: 0; }
/* The 3px active rail would push a centred icon off centre by half of it, so
   the current section is carried by a gold icon and the lit background. */
.side-collapsed .nav a { border-left-width: 0; }
.side-collapsed .nav a.on { color: var(--gold); }
.side-collapsed .nav a:hover { padding-left: 0; }
.side-collapsed .side .me { padding-left: 0; padding-right: 0; text-align: center; }

/* The label, back on hover. Rendered from `data-label`, so there is one source
   for the word and the tooltip cannot drift from the link it describes. */
.side-collapsed .side [data-label]::after {
  content: attr(data-label);
  position: absolute;
  left: calc(100% + 12px);
  top: 50%;
  transform: translateY(-50%);
  z-index: 30;
  padding: 6px 11px;
  border-radius: 8px;
  background: var(--ink);
  border: 1px solid rgba(255, 255, 255, .2);
  box-shadow: var(--shadow-2);
  color: #fff;
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--fast) var(--ease);
}
.side-collapsed .side [data-label]:hover::after,
.side-collapsed .side [data-label]:focus-visible::after { opacity: 1; }

/* ------------------------------------------------------------------- main */

/* On a 27-inch monitor the old 1100px cap left roughly a thousand pixels of
   nothing down the right-hand side, all of it on one side because the column
   is left-aligned against a sidebar. Two changes, and they do different jobs:

   1. A wider cap, so the tables that have somewhere to put the width actually
      get it. Not removed altogether -- a line of Romanian prose 2400px wide is
      unreadable, and the hint paragraphs under these tables are prose.
   2. Centred once there is more room than the cap, so whatever is left over is
      split either side instead of piling up on the right. That is the part
      that reads as deliberate rather than as a page that stopped early. */
.main {
  flex: 1;
  padding: 26px 30px 44px;
  max-width: 1560px;
  margin-inline: auto;
  min-width: 0;
}
/* Articole (the one `fillpage`) takes the same width and margins as every
   other page, on purpose. On 2026-09-08 it was tried wider (a 1760px cap,
   22px sides) and then with a tenth of the window free on either side; the
   maintainer settled on the distance Utilizatori keeps from the sidebar,
   which is this block unchanged. Recorded so the next "make it wider" starts
   from the two looks already rejected. */
/* **The whole scale came down one notch on 2026-09-03.** Page title, buttons,
   panel heads, table rows, form fields, gaps: each about 10-15% smaller, all
   at once. Reviewed page by page, the panel read as "thrown together" not
   because any one element was wrong but because every element was sized for a
   page that holds three things, on pages that hold thirty. The proportions
   are unchanged; the ruler is. Every value below was measured against the
   1440px window the fluid row scale is calibrated for. */
.pagehead { display: flex; align-items: center; margin-bottom: 14px; gap: 14px; }
.pagehead h1 { font-size: 20px; font-weight: 800; }
.pagehead .actions { margin-left: auto; display: flex; gap: 8px; }

.btn {
  height: 36px;
  padding: 0 14px;
  border-radius: var(--radius-sm);
  border: none;
  font: inherit;
  font-size: 13.5px;
  font-weight: 700;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  text-decoration: none;
  transition: transform var(--fast) var(--ease), box-shadow var(--fast) var(--ease),
              background var(--fast) var(--ease), filter var(--fast) var(--ease);
}
.btn.primary { background: var(--teal); color: #fff; box-shadow: var(--shadow-1); }
.btn.ghost { background: #fff; border: 1.5px solid var(--ink); color: var(--ink); }
.btn.danger { background: var(--coral); color: #fff; box-shadow: var(--shadow-1); }
.btn:hover { filter: brightness(1.05); box-shadow: var(--shadow-2); }
.btn.ghost:hover { background: rgba(38, 70, 83, .04); filter: none; }
/* Press feedback: the button moves under the finger rather than only changing
   colour, which is the difference between "it registered" and "did it?". */
.btn:active { transform: translateY(1px); box-shadow: var(--shadow-1); }
.btn:disabled { opacity: .45; cursor: not-allowed; transform: none; box-shadow: none; }

/* ---------------------------------------------------------- status strip */

/* **Sized down and made fluid on 2026-09-01**, with the rows below it and for
   the same reason: these four cards are the first thing on Tablou de bord and they
   were the largest thing on it, so a page whose job is a glance opened with its
   least urgent block shouting. They keep every proportion they had -- rail,
   three tiers, the same order -- at about two thirds of the weight, and they
   grow with the window instead of staying still while it changes. */
.statusstrip {
  display: flex;
  gap: clamp(8px, .7vw, 12px);
  margin-bottom: clamp(14px, 1.2vw, 22px);
  flex-wrap: wrap;
}
.stat {
  /* The basis shrinks with the window too, or four cards drop to two rows on a
     laptop at exactly the width where the page can least afford the height. */
  flex: 1 1 clamp(150px, 13vw, 200px);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: clamp(7px, .55vw, 12px) clamp(11px, 1.1vw, 17px)
           clamp(7px, .55vw, 12px) clamp(13px, 1.35vw, 21px);
  position: relative;
  box-shadow: var(--shadow-1);
  transition: box-shadow var(--normal) var(--ease);
}
.stat:hover { border-color: #CFCCC2; }
.stat::before {
  content: "";
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 4px;
  border-radius: var(--radius) 0 0 var(--radius);
  background: var(--rail, var(--teal));
  /* The strip refreshes itself, so a card going from healthy to degraded
     changes colour rather than jumping to it. */
  transition: background var(--normal) var(--ease);
}
/* Not configured yet, which is not the same as down. A grey rail rather than a
   state colour, quieter text, and nothing that moves: sand here would read as
   "in progress" and send someone looking for a problem that does not exist. */
.stat.muted { --rail: var(--line); }
.stat.muted .v { color: var(--ink-soft); font-weight: 700; }

.stat .l {
  font-size: clamp(9.5px, .18vw + 7.9px, 12px);
  font-weight: 600;
  letter-spacing: .9px;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.stat .v {
  font-size: clamp(13px, .3vw + 9.68px, 17px);
  font-weight: 800;
  margin-top: clamp(2px, .25vw, 5px);
  letter-spacing: -.2px;
}
/* A value that is an address rather than a number. At `.v`'s size and weight a
   URL either overflows its card or pushes the whole strip onto a second row,
   so it is smaller and allowed to break -- it is read once, at install, and
   never scanned across a row of cards. */
.stat .v.addr { font-size: clamp(11px, .2vw + 9.12px, 14px); letter-spacing: 0; word-break: break-all; }
/* On its own line, not trailing the number.
   Inline, a detail long enough to wrap breaks around the value and the card
   grows a ragged second line: "0.0.0 GITLAB_TOKEN or / GITLAB_PROJECT_ID is
   empty" reads as one broken sentence rather than a number and its
   explanation. Three tiers instead, always in the same places, so four cards
   can be scanned down the page rather than read across it. */
.stat .v small {
  display: block;
  margin-left: 0;
  margin-top: clamp(2px, .2vw, 3px);
  font-size: clamp(10px, .16vw + 8.7px, 12.5px);
  font-weight: 500;
  color: var(--ink-soft);
  letter-spacing: 0;
  line-height: 1.4;
}

/* ---------------------------------------------------------------- panels */

.grid { display: grid; grid-template-columns: minmax(0, 1fr) 330px; gap: 14px; align-items: start; }
/* The right column carries fixed-width panels -- counts, buttons, notices --
   so past a point the extra width belongs to it as much as to the list beside
   it, and a 330px strip against a 1200px list reads as an afterthought. */
/* **The list is always exactly as tall as the form beside it**, whatever it
   holds: one account or forty, the two cards line up and the list scrolls
   inside its own height.
   
   Getting there took three attempts, and the trick is the last line here. A
   grid row is as tall as its tallest item's *content*, so stretching alone gave
   the height of whichever panel happened to be longer -- a long list dragged
   the form down, and a single account floated in a card sized to the form.
   Taking the list out of flow with `position: absolute` means it contributes no
   height at all, so the row is the form's height and the list fills it. */
.grid.pair { align-items: stretch; }
.grid.pair > .panel { min-height: 0; }
.grid.pair > .panel:first-child { position: relative; }
/* `fills`, not `grow`: `.grow` is already the class the article class picker
   puts on its rows, and it is `display: grid` with `align-items: center`. The
   list inherited it and sat centred in the card with a band of white above and
   below -- a name collision, not a layout problem, and it cost two rounds to
   see because the symptom looked exactly like bad flexbox. */
.grid.pair > .panel > .scrollbox.fills {
  position: absolute;
  inset: 0;
  max-height: none;
  overflow-y: auto;
}

@media (min-width: 1500px) {
  .grid { grid-template-columns: minmax(0, 1fr) 400px; gap: 16px; }
}

/* Istoric (2026-09-08): Listări and Jurnal de securitate side by side under the
   documents, so three open panels of five rows fit one screen. Their message
   and detail cells are allowed to wrap here, where the page-wide tables keep
   them on one line: in half a page a long JSON detail would otherwise put a
   sideways scrollbar under a five-row window. */
/* **A list of two-line rows states its own pitch.** În lucru's two lists and
   the users list are all a name over a quieter second line, and their windows
   are capped in rows -- `--row-h2` -- so a cap of eight rows is eight rows at
   every window width. Without the height here the pitch is whatever the text
   happens to make it, the cap is in the wrong unit, and two lists asking for
   different counts of it come out unequal, which is exactly what În lucru
   looked like before 2026-09-08.

   A class rather than a page id, because three lists need it: written against
   `#activitate` it was true on one page and quietly false on Utilizatori,
   whose window is stated in the same units. */
table.rows-2l { font-size: var(--row-font); }
.rows-2l td { height: var(--row-h2); padding: 0 var(--row-pad-x); }
.rows-2l th { padding: 8px var(--row-pad-x); }
.rows-2l td .un { font-size: var(--row-font-sm); }

.journal-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; align-items: start; min-width: 0; }
.journal-pair > .panel { min-width: 0; }
/* The two boxes side by side are the same height whatever they hold
   (2026-09-08): Listări with three rows sat a third as tall as the security
   journal beside it, and two panels meant to read as a pair looked like two
   panels. The floor is the ceiling, the same `--max` the template sets, so
   the window holds five rows of air on a quiet day and scrolls on a busy one. */
.journal-pair .scrollbox { min-height: var(--max); }
.journal-pair .events td.msg,
.journal-pair .events td.detail { white-space: normal; overflow-wrap: anywhere; }
@media (max-width: 1100px) { .journal-pair { grid-template-columns: 1fr; } }

.stack { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
.panel {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  /* Hidden used to be both axes, for the rounded corners. On a narrow window
     that quietly cut the last column off every table, which is the column with
     the buttons in it: the actions were rendered, in the markup, reachable by
     nobody. Scrolling sideways keeps the corners and keeps the controls. */
  overflow-x: auto;
  overflow-y: hidden;
  box-shadow: var(--shadow-1);
  min-width: 0;
}
.panel .head {
  padding: 10px 16px;
  border-bottom: 1px solid var(--line);
  display: flex;
  align-items: center;
  gap: 10px;
}
.panel .head h2 { font-size: 14px; font-weight: 700; }
.panel .head .add { margin-left: auto; font-size: 13px; font-weight: 700; color: var(--teal-dark); }
.panel .body { padding: 16px; }

table { width: 100%; border-collapse: collapse; font-size: 14px; }
th {
  text-align: left;
  padding: 8px 16px;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: .8px;
  text-transform: uppercase;
  color: var(--ink-soft);
  border-bottom: 1px solid var(--line);
}
td { padding: 9px 16px; border-bottom: 1px solid var(--line); vertical-align: middle; }
tr:last-child td { border-bottom: none; }
tbody tr, table tr { transition: background var(--fast) var(--ease); }
table tr:hover td { background: rgba(42, 157, 143, .04); }
td .nm { font-weight: 700; }
td .un { font-size: 12.5px; color: var(--ink-soft); margin-top: 1px; }
/* Numbers line up under each other so a column can be scanned rather than read. */
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }

/* The name column carries the identity of the row, so it is never the one that
   wraps when the table is tight. */
td.name { min-width: 170px; }
td.name .nm { white-space: nowrap; }
/* A list to scan down, not to read.

   The two-line name cell -- a name over a code -- is right where it is used,
   but the default row built from it is 68px, and on Articole and Utilizatori
   somebody is looking for one line rather than reading the page. At that
   height the eye travels further between two codes than it does across one.
   Tighter padding and tighter leading, both lines kept, and a row is 50px.

   A class rather than a change to `td`, because it is a decision about a kind
   of table and not about the panel: Imprimante and Etichete are short lists of
   one-line rows where nothing is gained by squeezing them. */
/* 5px rather than 7: on this page a row already carries a name over a username
   and a column of badges, so the padding was adding to something tall rather
   than to a single line. */
/* 4px since 2026-09-03, with the rest of the scale: a two-line row is 44px. */
.rows-tight td { padding: 4px 16px; }
.rows-tight td.name .nm { line-height: 1.35; }
.rows-tight td .un { font-size: 12px; line-height: 1.35; margin-top: 0; }
/* The exception inside the exception. An editor opens as a full-width row in
   the same table, and it is a form rather than a line of a list -- 7px around
   it would put the first label against the row above. */
.rows-tight td.editrow { padding: 12px 16px; }
td.mono.old { color: var(--coral); font-weight: 700; }
td.acts { white-space: nowrap; text-align: right; }
td.acts form { display: inline; }
/* -------------------------------------------------------------- filters

   One row of controls that wraps, rather than a grid with fixed columns. The
   pickers are built from the data, so how many there are and how wide their
   longest entry is are both facts about the firm rather than about this
   stylesheet; a fixed grid would leave a gap on one firm and clip on another.

   **One storey, since 2026-09-03.** Until then every control carried a label
   above it, and the bar was two tiers and 90px tall -- the tallest thing on a
   page whose point is the list underneath. The argument for the labels was
   that a placeholder disappears the moment somebody types. It still does, and
   the answer is now in the controls themselves rather than above them: what a
   search box holds is self-evidently what is being searched for, and a picker
   names its dimension in its own "all" entry ("Toate clasele", never a bare
   "Toate"), so a chosen value reads as a class without a heading to say so.
   The accessible name stays on `aria-label`, so nothing the screen reader had
   is lost. */
.filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  /* The controls start where the table below them starts. Centred, the search
     box sat over the middle columns while the first thing it filters -- the
     Denumire column -- began at the left edge, so the bar and the list it
     narrows did not line up on anything. */
  justify-content: flex-start;
  gap: 8px;
  padding: 9px 16px;
  border-bottom: 1px solid var(--line);
  background: #FBFAF7;
}
.filters .f { display: flex; align-items: center; gap: 8px; min-width: 0; }
/* Sized, not stretched. Two controls set to fill a 1500px bar would put the
   search box and the class picker at opposite ends of the screen, which is the
   opposite of a pair. They keep a readable width and sit together at the left
   instead, where the list they narrow begins. */
.filters .f.wide { flex: 0 1 340px; }
.filters .f.wide input { width: 100%; max-width: none; }
.filters .f.narrow select { min-width: 120px; }
/* The action slot beside the pair. On Articole it holds only the submit
   button that scripting hides, so the rule below removes it; Renunță, which
   sat here from 2026-09-03, is at the far end of the bar again since
   2026-09-08 at the maintainer's instruction. The 09-03 argument is kept
   with `.filters .tail`, where the button now sits. */
.filters .f.act { gap: 8px; }
/* A bar with nothing visible in its action slot -- only the submit button
   that scripting hides -- has no slot to draw. Without this an empty flex item
   still took a gap. */
.filters .f.act:has(> .apply:only-child) { display: none; }
/* What sits at the far end: the count, the in-flight word, and Renunță.
   Renunță was moved *away* from here on 2026-09-03 because on a wide panel
   a metre of nothing separated the controls from the one button that undoes
   them; it came back on 2026-09-08 at the maintainer's instruction, and the
   older reason is kept so the next reader does not re-derive it. The
   page-size picker left for the bar under the list the same day. */
.filters .tail { margin-left: auto; display: flex; align-items: center; gap: 10px; min-width: 0; }
.filters .count { font-size: 12.5px; color: var(--ink-soft); white-space: nowrap; }
.filters input,
.filters select {
  height: 34px;
  padding: 0 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: #fff;
  color: var(--ink);
  font: inherit;
  font-size: 13.5px;
  /* Long producer names must not stretch the bar into one control per row. */
  max-width: 260px;
}
.filters .tail select { width: auto; }
.filters select { cursor: pointer; }
/* Nothing to choose. Visibly inert rather than merely empty, because an empty
   dropdown that still opens invites somebody to keep clicking it. */
.filters select:disabled {
  cursor: not-allowed;
  color: var(--ink-soft);
  background: #F4F3EF;
}
.filters input:focus-visible,
.filters select:focus-visible { border-color: var(--teal); }

/* Said once, under the bar, rather than marked on each control: what somebody
   needs to know after narrowing is that the count below is not the catalogue. */
.filterhint { padding: 12px 18px 0; }

/* Hidden whenever scripting is on, which is the normal case: the filters have
   already applied by the time anybody could reach this button. The <noscript>
   block in base.html brings it back. */
.filters .apply { display: none; }

/* Shown only while a filter request is in flight. htmx puts `htmx-request` on
   the indicator for exactly that window.

   It says "se filtrează", not a spinner: on a local instance the round trip is
   a few milliseconds and the word never appears at all, which is the honest
   outcome. It exists for the firm whose panel is a WireGuard hop away, where
   silence between keystroke and result reads as nothing having happened. */
.busy {
  font-size: 12.5px;
  color: var(--ink-soft);
  opacity: 0;
  transition: opacity var(--fast) var(--ease);
}
.busy.htmx-request { opacity: 1; }

/* A panel that opens. `<details>` carries the state itself, so there is no
   class to toggle and nothing to re-apply after a swap. */
.panel.subunit { overflow: visible; }
.panel.subunit > summary {
  cursor: pointer;
  list-style: none;
  user-select: none;
}
/* Both spellings: WebKit and Blink use the pseudo-element, Firefox honours
   `list-style: none` above. Without removing it the row carries two markers,
   the browser's and ours. */
.panel.subunit > summary::-webkit-details-marker { display: none; }
.panel.subunit > summary::marker { content: ""; }
.panel.subunit > summary:hover { background: rgba(42, 157, 143, .04); }
/* The chevron is ours, drawn in CSS rather than as another inline SVG: it has
   one job, it has to rotate, and a border triangle rotates without a file. */
.panel.subunit > summary::after {
  content: "";
  width: 8px;
  height: 8px;
  margin-left: 4px;
  border-right: 2px solid var(--ink-soft);
  border-bottom: 2px solid var(--ink-soft);
  transform: rotate(-45deg);
  transition: transform var(--fast) var(--ease);
}
.panel.subunit[open] > summary::after { transform: rotate(45deg); }
/* **The panel unfolds rather than appearing.** The chevron already turned
   smoothly while the content it points at snapped, which reads as two controls
   disagreeing about whether anything is happening.

   Same mechanism as the folded messages in the journal, and deliberately the
   same duration: one vocabulary for "something is opening", whether it is a
   sentence or a table. A touch longer than the default because this box is a
   whole panel and the eye has further to follow.

   Where `::details-content` is unsupported the panel still opens, instantly,
   which is what it did before -- nothing depends on the animation. */
.panel.subunit::details-content {
  block-size: 0;
  overflow: hidden;
  opacity: 0;
  transition:
    block-size 260ms var(--ease),
    opacity 200ms var(--ease),
    content-visibility 260ms allow-discrete;
}
.panel.subunit[open]::details-content { block-size: auto; opacity: 1; }
/* Closed, the summary is the whole panel, so its border would sit under a
   rounded corner it no longer has anything to separate from. */
.panel.subunit:not([open]) > summary { border-bottom: none; }
/* The tally sits at the right, before the chevron: the name says which site,
   this says whether it is worth opening. */
.panel.subunit > summary .sumstate {
  margin-left: auto;
  font-size: 13px;
  color: var(--ink-soft);
  white-space: nowrap;
}
/* `.head` already puts the "add" link there; with a tally present the code
   should not also claim the space. */
.panel.subunit > summary .un { margin-left: 0; }

/* A subunit as a row of one card rather than a card of its own (Fluxuri,
   2026-09-03). Same `<details>`, same summary, same chevron; only the
   geometry changes: no border or radius of its own, a rule between rows,
   and the summary is one `--row-h` line with the panel head's padding. The
   `.panel.subunit` rules above stay for the journal, which still folds whole
   panels. */
.panel > .subunit.row { border-bottom: 1px solid var(--line); }
.panel > .subunit.row:last-child { border-bottom: none; }
.subunit.row > summary {
  cursor: pointer;
  list-style: none;
  user-select: none;
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: var(--row-h);
  padding: 0 16px;
  border-bottom: none;
}
.subunit.row > summary::-webkit-details-marker { display: none; }
.subunit.row > summary::marker { content: ""; }
.subunit.row > summary:hover { background: rgba(42, 157, 143, .04); }
.subunit.row > summary h2 { font-size: 14px; font-weight: 700; }
.subunit.row > summary .un { margin-left: 0; }
.subunit.row > summary .sumstate { margin-left: auto; font-size: 13px; color: var(--ink-soft); white-space: nowrap; }
.subunit.row > summary::after {
  content: "";
  width: 8px;
  height: 8px;
  margin-left: 4px;
  border-right: 2px solid var(--ink-soft);
  border-bottom: 2px solid var(--ink-soft);
  transform: rotate(-45deg);
  transition: transform var(--fast) var(--ease);
}
.subunit.row[open] > summary::after { transform: rotate(45deg); }
/* Open, the row's table sits on a tinted ground so the rows of the table are
   not mistaken for more subunits. */
.subunit.row[open] > summary { border-bottom: 1px solid var(--line); }
.subunit.row > .fluxtable { background: #FBFAF7; }

/* The list as links (Fluxuri, 2026-09-08). The subunit row stopped folding
   its forms and became a contents line leading to the subunit's own page,
   so the same geometry as the summary above, on an anchor: no marker, the
   same chevron pointing on rather than down, the row's hover, and no
   underline on a line that is a row rather than a sentence. */
.subunit.row.link {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: var(--row-h);
  padding: 0 16px;
  color: inherit;
  text-decoration: none;
}
.subunit.row.link:hover { background: rgba(42, 157, 143, .04); }
.subunit.row.link h2 { font-size: 14px; font-weight: 700; }
.subunit.row.link .un { margin-left: 0; }
.subunit.row.link .sumstate { margin-left: auto; font-size: 13px; color: var(--ink-soft); white-space: nowrap; }
.subunit.row.link::after {
  content: "";
  width: 8px;
  height: 8px;
  margin-left: 4px;
  border-right: 2px solid var(--ink-soft);
  border-bottom: 2px solid var(--ink-soft);
  transform: rotate(-45deg);
}
/* On the subunit's page the table is the panel's body, not a fold's: the
   same tinted ground, and the folded note under it on the panel's padding. */
#flux-subunit > .fluxtable { background: transparent; }
#flux-subunit > .fluxnote { padding: 10px 16px 12px; border-top: 1px solid var(--line); }

/* --------------------------------------------------------------- fluxuri

   A flow as a compact card, two lines (2026-09-08, the third shape that
   day). The grid row was cramped; the annotated section that replaced it
   took 220px per flow and a page of scrolling for five, which the maintainer
   called horrible. So: line one is the name and state at the left and the
   two pickers with Salvează at the right; line two is the switches, the
   over-receipt sentence and the accounting fold, inline. Everything wraps,
   so on a phone the pickers stack full-width under the name. The `<form>`
   is still the card, still swapped back on its own after a save. */
.fluxtable { padding: 0 16px 10px; display: flex; flex-direction: column; }
.fr {
  padding: 10px 0 9px;
  border-bottom: 1px solid var(--line);
  transition: background var(--fast) var(--ease);
}
.fr:last-child { border-bottom: none; }
/* The confirmation after a save: a tint that fades. It reports the thing
   that just happened; a card that stayed green would read as a state. */
.fr.saved { background: rgba(42, 157, 143, .07); margin: 0 -16px; padding-inline: 16px; }

/* Line one. The name has a floor so the pickers start on one vertical line
   down the five flows; past it the pickers take the rest. */
.fc-main { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 18px; }
.fc-id { flex: 0 1 220px; min-width: 180px; display: flex; flex-direction: column; gap: 2px; }
.fc-id .nm { font-size: 14px; font-weight: 700; }
.fc-id .c-state { font-size: 12.5px; color: var(--ink-soft); white-space: nowrap; }
.fc-fields { flex: 1 1 420px; display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; min-width: 0; }
/* A field is its label beside its control, on one line, not over it: a
   label above costs a line per field, and there are two fields per flow. */
.fc-field { display: flex; align-items: center; gap: 8px; min-width: 0; flex: 1 1 200px; max-width: 300px; font-size: 12.5px; color: var(--ink-soft); }
.fc-field > span { white-space: nowrap; }
/* The same size for both pickers, asked for 2026-09-08: the carnet used to
   take twice the room, and a picker stretched across half the panel read as
   more important than the one beside it. */
.fc-field.wide { flex: 1 1 200px; }
.fc-field .fc-na { font-size: 13px; }
.fc-act { flex: 0 0 auto; margin-left: auto; }

.fr select,
.fr input[type="text"] {
  width: 100%;
  min-width: 0;
  height: 30px;
  padding: 0 8px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: #fff;
  color: var(--ink);
  font: inherit;
  font-size: 13px;
}
.fr select { cursor: pointer; }
.fr select:disabled { cursor: not-allowed; color: var(--ink-soft); background: #F4F3EF; }
.fr select:focus-visible,
.fr input[type="text"]:focus-visible { border-color: var(--teal); outline: none; }

/* Line two: the switches and the sentences, inline, each with its
   explanation folded beside it. The fold's summary carries no lead of its
   own here, so it is just the word "Detalii" after the switch. */
.fc-notes { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 22px; margin-top: 6px; }
.fc-notes .note { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 8px; min-width: 0; font-size: 12.5px; }
.fc-notes .note .un { font-size: 12.5px; }
.fc-notes .fold { margin: 0; }
.fc-notes .fold > summary { font-size: 12.5px; }
.fc-notes .fold[open] { flex-basis: 100%; max-width: 620px; }
.fr label.switch { display: flex; align-items: baseline; gap: 6px; font-size: 12.5px; color: var(--ink); cursor: pointer; }
.fr label.switch input { accent-color: var(--teal); }

/* The write-override fold, closed because empty is both the norm and the
   recommended answer; open, it takes the whole second line. */
.fr details.adv { min-width: 0; }
.fr details.adv[open] { flex-basis: 100%; }
.fr details.adv > summary {
  cursor: pointer;
  font-size: 12.5px;
  color: var(--ink-soft);
  display: flex;
  align-items: center;
  gap: 6px;
  list-style: none;
}
.fr details.adv > summary::-webkit-details-marker { display: none; }
.fr details.adv > summary::before {
  content: "";
  width: 6px;
  height: 6px;
  border-right: 2px solid var(--ink-soft);
  border-bottom: 2px solid var(--ink-soft);
  transform: rotate(-45deg);
  transition: transform var(--fast) var(--ease);
}
.fr details.adv[open] > summary::before { transform: rotate(45deg); }
.fr details.adv > summary .dot { margin: 0; }
.fr details.adv > .hint { margin: 6px 0 8px; max-width: 70ch; }
.fr details.adv .fc-fields { margin-bottom: 4px; }
.fr details.adv .hint.warn { color: var(--coral); margin-top: 6px; }

.savedtag {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 13.5px;
  font-weight: 700;
  color: var(--teal-dark);
}
.savedtag svg { width: 16px; height: 16px; }

/* A row action, not a page action. Full-height buttons in a row of twelve make
   the table twice as tall as the information in it. */
.btn.sm { height: 30px; padding: 0 11px; font-size: 13px; }
/* Buttons carrying an icon; sized here so every one of them matches. */
.btn svg { width: 17px; height: 17px; }

/* The sync indicator, in the strip. One line replaces the other rather than
   appearing under it, so the card does not change height mid-request.

   Written against `.stat .v small` rather than on the class alone: that rule
   sets `display: block` and is the more specific of the two, so a bare
   `.busy-inline { display: none }` lost to it and both lines showed at once. */
.stat .v small.busy-inline { display: none; }
.stat .v small.busy-inline.htmx-request { display: block; }
.stat .v small.busy-inline.htmx-request + small.idle { display: none; }

/* ------------------------------------------------- the gestiuni picker

   Two questions per row, so two controls: whether the person sees this
   warehouse, and whether it is the one their app opens on. They sit at
   opposite ends of the row rather than side by side in the middle -- the tick
   is what you scan down the left edge, the default is a single choice for the
   whole list, and putting them in adjacent columns made every row read as two
   identical circles with no clue which was which.

   The code is a chip, the same one the users table shows a granted gestiune
   with, so the thing you tick here and the thing you read there look alike. */
.gpick {
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: #fff;
  max-height: 268px;
  overflow-y: auto;
}
.gpick .notice { border-radius: 0; margin: 0; }
.gpick .hint { padding: 12px 14px; margin: 0; }
/* Teal, so a tick belongs to the same system as every other "settled" mark in
   the panel rather than to the operating system's blue. */
.gpick input { accent-color: var(--teal); }

.ghead, .grow {
  display: grid;
  grid-template-columns: 22px minmax(0, 1fr) 74px;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
}
.ghead {
  position: sticky;
  top: 0;
  z-index: 3;
  background: #FBFAF7;
  border-bottom: 1px solid var(--line);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .6px;
  text-transform: uppercase;
  color: var(--ink-soft);
}
/* The two headings sit over the controls they name: the first spans the tick
   and the label, the second sits above the radio at the right edge. */
.ghead span:first-child { grid-column: 1 / 3; }
.ghead span:last-child { text-align: center; }
/* Un antet de grup care nu se pliază: aceleași litere, dar întinse pe toată
   lățimea, cu numărul la dreapta. Folosit pe pagina unui utilizator, unde
   nimic nu se mai pliază (2026-09-08). */
.ghead.flat { grid-template-columns: minmax(0, 1fr) auto; }
.ghead.flat span:first-child { grid-column: auto; }
.ghead.flat span:last-child { text-align: right; text-transform: none; letter-spacing: 0; }
.ghead.flat .linkish { font-size: 11px; margin-left: 8px; }
/* Rândurile matricei sunt rândurile pickerului: aceeași înălțime, aceeași
   linie între ele, același padding. Patru panouri cu patru feluri de rând se
   citesc ca patru unelte lipite; cu unul singur, ca o pagină. */
.mxpick .mxrow {
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr) minmax(0, 1fr);
  gap: 10px;
  padding: 3px 11px;
  align-items: center;
  border-bottom: 1px solid var(--line);
}
.mxpick .ghead.flat.mxrow { padding: 8px 11px; }
/* Antetul matricei numește trei coloane, deci toate trei arată la fel: regula
   de mai sus scoate majusculele de pe ultima celulă fiindcă acolo stă de
   obicei un număr, iar aici stă un nume de coloană. */
.mxpick .ghead.flat.mxrow span:last-child { text-align: left; text-transform: uppercase; letter-spacing: .6px; }
.mxpick .mxrow:last-of-type { border-bottom: none; }
.mxpick .mxflow { font-size: var(--row-font); }
.mxpick select { width: 100%; min-width: 0; font-size: 12.5px; padding-top: 3px; padding-bottom: 3px; }

/* The column heads name what is inside a group, so with every group shut they
   head nothing. `:has` rather than a class, for the same reason the ticked-row
   rule uses it: the browser already knows, and a script would have to be told
   again on every swap. */
/* Antetul de coloane al pickerului dispare cât timp toate grupurile sunt
   strânse: numește coloanele unor rânduri pe care nu le vede nimeni.
   `.flat` e altceva -- e titlul unui grup care nu se pliază deloc, deci nu
   are când să fie de prisos. */
.gpick:not(:has(.pgroup[open])) .ghead:not(.flat) { display: none; }

.grow {
  cursor: pointer;
  border-bottom: 1px solid var(--line);
  transition: background var(--fast) var(--ease);
}
.grow:last-child { border-bottom: none; }
.grow:hover { background: rgba(42, 157, 143, .05); }
/* Ticked rows read as a set at a glance rather than as a column of marks to
   count. `:has` rather than a class toggled by script: the browser already
   knows, and a script would have to be told again on every swap. */
.grow:has(input[type="checkbox"]:checked) { background: rgba(42, 157, 143, .08); }
.grow:has(input[type="checkbox"]:checked) .gcode {
  background: rgba(42, 157, 143, .16);
  color: var(--teal-dark);
}
/* Choosing a default for a warehouse nobody may see decides nothing. Dimmed
   rather than disabled: ticking the box is what makes it meaningful, and a
   control that vanishes and returns is harder to follow than one that fades. */
.grow:not(:has(input[type="checkbox"]:checked)) input[type="radio"] { opacity: .3; }

.grow input { justify-self: center; width: 17px; height: 17px; cursor: pointer; margin: 0; }
.gname { display: flex; align-items: center; gap: 9px; min-width: 0; }
.gcode {
  flex-shrink: 0;
  padding: 3px 8px;
  border-radius: 7px;
  background: #EEF0EC;
  color: var(--ink-soft);
  font-family: "Cascadia Mono", Consolas, "SF Mono", monospace;
  font-size: 12px;
  font-weight: 600;
}
.gname .un { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.editrow { background: #FBFAF7; }

/* ------------------------------------------------------- editing a user

   Two columns of equal width and equal height. Equal width is asked for by the
   grid; equal height falls out of stretching the columns and letting each box
   fill its own -- so the Drepturi box grows to meet the Gestiuni box rather
   than sitting as a short card beside a tall one. */
.useredit { display: flex; flex-direction: column; gap: 14px; }
/* Bara de identitate: cine e omul acesta pentru sistem.
   Eticheta stă **lângă** control, nu deasupra lui. Deasupra, două cuvinte
   costau un rând întreg fiecare, iar editorul se deschidea cu vreo 40 de
   pixeli de aer înainte de prima decizie. */
.ue-top {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px 16px;
  /* Un rând, nu un panou: exact cât are nevoie un select ca să respire. Cerut
     pe 2026-09-04, după ce bara era cel mai înalt lucru din editor. */
  padding: 4px 10px;
  background: rgba(38, 70, 83, .03);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}
.ue-top .btn { margin-left: auto; }

/* Eticheta și controlul, pe aceeași linie de bază, oricât de înalt e câmpul
   vecin. */
.ue-field { display: flex; align-items: center; gap: 9px; min-width: 0; }
.ue-field > label {
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-soft);
  white-space: nowrap;
}
.ue-field select { min-width: 150px; }

/* Marca de agent, lângă rol. Mai lată decât rolul fiindcă o opțiune poartă
   marca și numele, iar un nume tăiat la jumătate nu se poate alege cu
   încredere. `min-width: 0` ca să se strângă pe ecran îngust în loc să împingă
   butonul Salvează afară din rând. */
.ue-agent { min-width: 0; }
.ue-agent select { min-width: 230px; max-width: 320px; }

/* Explicația mărcii, sub selectul ei și la lățimea lui. Pe toată lățimea
   editorului era o propoziție care împingea în jos tot ce urma. */


/* Two columns of equal width, each as tall as what is in it.

   They used to be stretched to equal height, so the shorter box grew to meet
   the taller one rather than sitting as a short card beside a tall one. That
   was right while both boxes were always fully open and roughly the same size.
   Now they fold, and both start shut: stretching would have the editor reserve
   a screen of height for two boxes that are three lines each, which is the
   whole thing this was asked to stop. `start` on the grid, and no `flex: 1` on
   the box, so a closed group costs exactly one summary. */
.ue-cols {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px 18px;
  align-items: start;
}

/* Cele cinci cutii au aceeași formă: o etichetă și o cutie care se pliază.
   Erau două clase pentru același lucru, `.ue-col` și `.fgroup`, iar diferența
   dintre ele nu însemna nimic pe ecran. */
.ue-box { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.ue-box > label {
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-soft);
}
.ue-box .hint { margin: 2px 0 0; }

/* Matricea, singura care are trei coloane înăuntru, ține rândul întreg. */
.ue-wide { grid-column: 1 / -1; }

/* Explicația unei cutii stă **înăuntrul** ei, sub bifele pe care le explică:
   se citește când schimbi ceva, nu de fiecare dată când deschizi editorul.
   Cinci propoziții pe toată lățimea făceau rândul un ecran întreg. */
.gnote {
  padding: 8px 14px 9px;
  margin: 0;
  border-top: 1px solid var(--line);
  background: rgba(38, 70, 83, .02);
}

/* --- Densitatea formularului unui utilizator -----------------------------
   Cerut pe 2026-09-04, când editorul stătea într-un rând de tabel și fiecare
   pixel pe verticală era luat din listă. Strâns pe formular, nu global:
   aceleași clase sunt folosite și în foaia de adăugare, unde nu sunt cinci
   cutii una sub alta. Rândurile strânse rămân -- o listă de bifat se citește
   mai bine deasă -- dar plafoanele de 300px au plecat odată cu rândul
   (2026-09-08): pe o pagină ele puneau o bară de derulare înăuntrul alteia,
   iar ce vede un om trebuia citit prin fereastra unei cutii. */
.useredit .grow, .useredit .ghead { padding: 3px 11px; }
.useredit .grow input { width: 15px; height: 15px; }
.useredit .gcode { padding: 1px 7px; }
.useredit .pgroup > summary { padding: 5px 12px; }
.useredit .mxrow { padding: 4px 11px; }
.useredit .ue-top select,
.useredit .mxrow select { padding-top: 3px; padding-bottom: 3px; }

/* Nelimitat pe pagină, mărginit în foaia de adăugare -- acolo cutia stă
   într-un dialog care nu poate crește oricât. Un depozit cu treizeci de
   gestiuni face pagina mai lungă, ceea ce e răspunsul corect: pagina
   derulează, cutia nu. */
.sheet .gpick { max-height: 300px; }
/* **Plafon legat de ecran, nu de firmă** (2026-09-08, după ce s-a pus
   întrebarea care trebuia: „ia în calcul că sunt 20 de subunități, multe
   clase de articole, multe gestiuni").

   O cutie nelimitată face pagina cât nomenclatorul clientului: la două
   gestiuni arată bine, la douăzeci de subunități are un metru. Cu plafonul
   legat de înălțimea ferestrei, pagina are aceeași înălțime la orice firmă,
   iar ce nu încape se derulează în cutia lui -- ceea ce e altceva decât
   ferestruica de 268px de dinainte: aici sunt patruzeci și ceva de procente
   din ecran, adică douăzeci și ceva de rânduri.

   Drepturile nu au plafon: sunt șaptesprezece și rămân șaptesprezece, iar o
   listă care nu crește cu firma nu are ce mărgini. */
.grid.user .gpick { max-height: min(44vh, 420px); }
.grid.user .permpick:not(.classpick) { max-height: none; }

/* --- Pagina unui om (2026-09-08, refăcută în aceeași zi) -----------------

   Prima variantă a mutat editorul din rând pe o pagină și s-a oprit acolo:
   patru panouri identice ca greutate, un tabel de fapte sub două selecturi,
   și niciun loc unde ochiul să se oprească întâi. Maintainerul: „arată foarte
   rău".

   Refăcută pe tiparul obișnuit al unei pagini de setări pentru un cont, care
   are patru piese și fiecare rezolvă altceva (uxpatterns.dev, plus felul în
   care GitHub și Linear își desenează paginile de cont):

   1. **un antet de identitate** -- cine e omul, în cel mai mare font de pe
      pagină, cu starea și cifrele lui alături, ca să nu fie nevoie să
      deschizi nimic ca să știi ce are;
   2. **secțiuni cu o propoziție sub titlu**, care spune ce decide secțiunea.
      Fără ea toate arată la fel de importante, ceea ce era jumătate din
      problemă;
   3. **o bară de salvare lipită de marginea de jos**, fiindcă formularul e mai
      lung decât ecranul și un buton la capăt e un buton la care se ajunge cu
      derulare;
   4. **o zonă separată pentru ce nu se ia înapoi** -- parola și dezactivarea
      -- cu șină coral, despărțită de restul. Sunt acte cu alt fel de urmări
      decât o bifă. */
/* **Cele două coloane se termină la aceeași înălțime**, măsurat pe firma
   reală (2026-09-08): 657px într-o parte și 1025 în cealaltă, fiindcă fiecare
   cutie lungă avea același plafon fix și panourile din jurul lor nu aveau
   aceeași înălțime. Un plafon fix nu poate ieși simetric decât din întâmplare.

   Așa că întind rândul grilei și las lista lungă din fiecare coloană să ia cât
   a mai rămas: coloanele sunt egale prin construcție, la orice firmă, iar ce
   nu încape se derulează în cutia lui. Înălțimea rândului o dă coloana cu cel
   mai mult conținut care nu se întinde -- drepturile, care sunt șaptesprezece
   și rămân șaptesprezece. */
.grid.user {
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: stretch;
}
/* **Plafon pe panou, nu pe grilă.** Sub el panoul e cât conținutul lui, deci o
   firmă cu trei gestiuni nu capătă cutii pe jumătate goale; peste el se
   oprește și lista derulează pe dinăuntru, deci una cu douăzeci de subunități
   și cincizeci de clase nu face pagina cât nomenclatorul ei. Pus pe grilă,
   plafonul nu mărginea nimic: rândurile treceau pur și simplu peste el.
   Legat de fereastră, cu un prag jos ca panourile să nu se strivească pe un
   laptop și unul sus ca să nu se întindă absurd pe un ecran mare. */
.grid.user > .panel { max-height: clamp(300px, 40vh, 520px); }
/* Panourile sunt copii direcți ai grilei, deci rândul le face egale singur.
   Puse în două coloane, fiecare coloană își aduna panourile și nimic nu se
   alinia cu nimic. */
.grid.user > .panel { min-height: 0; display: flex; flex-direction: column; }
.grid.user .panel.grows > .body { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.grid.user > .panel > .body { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.grow-box { flex: 1 1 auto; min-height: 120px; display: flex; flex-direction: column; }
.grow-box > .gpick { flex: 1 1 auto; min-height: 0; max-height: none; }
@media (max-width: 1100px) { .grid.user { grid-template-columns: minmax(0, 1fr); } }

/* Antetul: inițialele, numele, apoi cifrele. Inițialele și nu o poză, fiindcă
   nu avem poze de oameni și nu vrem: un cerc cu două litere spune „acesta e un
   om anume" fără să ceară nimănui să încarce ceva. */
.userhead {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  padding: 10px 14px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  margin-bottom: 14px;
}
.userhead .ini {
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: rgba(42, 157, 143, .12);
  color: var(--teal-dark);
  font-size: 15px;
  font-weight: 800;
  letter-spacing: .5px;
}
.userhead .who { min-width: 0; }
.userhead .who h2 { font-size: 17px; font-weight: 800; letter-spacing: -.2px; }
.userhead .who .un { margin-top: 3px; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
/* Rolul și agentul, în antet. Erau un panou, ceea ce le dădea greutatea a
   șaizeci de gestiuni; sunt două selecturi și atât. */
.headfields { margin-left: auto; display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.hf { display: flex; align-items: center; gap: 8px; min-width: 0; }
.hf label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .7px;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.hf select { padding-top: 4px; padding-bottom: 4px; font-size: 13px; }
.hf:first-child select { min-width: 130px; }
.hf:last-child select { min-width: 210px; max-width: 300px; }
@media (max-width: 900px) { .headfields { margin-left: 0; width: 100%; } }
/* Avertismentul de sub antet, la lățimea paginii. */
.headnote { margin: -6px 0 12px; }
/* Cifrele, la capătul din dreapta: câte gestiuni, câte drepturi, câte clase.
   Ce are omul, fără să deschizi nimic -- exact ce pierdeai când totul era
   pliat într-un rând de tabel. */
.userhead .tally { margin: 0 0 0 auto; display: flex; gap: clamp(14px, 2vw, 30px); }
.userhead .tally div { text-align: right; }
.userhead .tally .n { font-size: 15px; font-weight: 800; }
.userhead .tally .l {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .8px;
  text-transform: uppercase;
  color: var(--ink-soft);
}
@media (max-width: 700px) { .userhead .tally { margin-left: 0; width: 100%; } }

/* O propoziție sub titlul secțiunii. Panoul își ține antetul pe un rând, deci
   asta stă în corp, deasupra controalelor, la lățimea lor. */
.blurb {
  margin: 0 0 9px;
  font-size: 12px;
  line-height: 1.45;
  color: var(--ink-soft);
}

/* --- Densitatea paginii (2026-09-08, a treia oară în aceeași zi) ----------
   Cerut de maintainer: „scrolez mai mult și folosim elemente prea mari".
   Pagina spunea lucrurile corecte, la o scară de pagină de prezentare: antet
   de 52px, panouri cu 16px de aer pe fiecare parte, șaptesprezece drepturi
   una sub alta. Aici nu se citește un articol, se bifează o listă, iar o
   listă se citește mai bine deasă.

   Strâns pe pagina asta, nu global: aceleași clase desenează foaia de
   adăugare a unui utilizator, unde nu sunt cinci cutii una sub alta. */
.grid.user .panel .head { padding: 7px 14px; }
.grid.user .panel .head h2 { font-size: 13px; }
.grid.user .panel .body { padding: 12px 14px; }
.grid.user .stack { gap: 12px; }
.grid.user { gap: 12px; }

/* Bifele pe două coloane pe o fereastră largă. Fiecare e o linie scurtă --
   exact forma care se citește bine în două coloane și irosește jumătate de
   pagină într-una. Sub 1400px revin pe o coloană, unde altfel s-ar rupe
   etichetele lungi. */
.picks { display: grid; grid-template-columns: 1fr 1fr; }
.picks > .grow:nth-child(odd) { border-right: 1px solid var(--line); }
@media (max-width: 1400px) {
  .picks { grid-template-columns: 1fr; }
  .picks > .grow:nth-child(odd) { border-right: none; }
}

/* Bara de salvare. Lipită de marginea de jos a ferestrei cât timp formularul e
   pe ecran, deci butonul e la îndemână oriunde ai fi în pagină; fundalul
   opac și linia de sus o despart de conținutul care trece pe sub ea. */
.savebar {
  position: sticky;
  bottom: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  gap: 14px;
  margin-top: 12px;
  padding: 9px 14px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-2);
}
.savebar .hint { margin: 0; }
.savebar .btn { margin-left: auto; }

/* Ce nu se ia înapoi, despărțit de restul. Șina coral e singurul roșu de pe
   pagină, deci ce e periculos se vede dintr-o privire, iar bifele de deasupra
   rămân bife. */
.danger-zone { margin-top: 16px; border-color: #EFCBC1; }
/* Cele două acțiuni alături, fiecare cu explicația ei sub ea. */
.dz-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 0 24px; padding: 12px 14px; }
@media (max-width: 900px) { .dz-cols { grid-template-columns: 1fr; } }
.danger-zone > .head { border-bottom-color: #EFCBC1; }
.danger-zone > .head h2 { color: var(--coral); }
.dz-act { padding: 0; display: flex; flex-direction: column; }
.dz-cols > .dz-act + .dz-act { border-left: 1px solid var(--line); padding-left: 24px; }
@media (max-width: 900px) {
  .dz-cols > .dz-act + .dz-act {
    border-left: none;
    padding-left: 0;
    margin-top: 14px;
    padding-top: 14px;
    border-top: 1px solid var(--line);
  }
}
.dz-act .hint { margin: 6px 0 0; }
/* Aceeași formă pe amândouă jumătățile: etichetă, control, propoziție. Rândul
   de control are o înălțime, deci butoanele celor două acțiuni stau pe aceeași
   linie chiar dacă una are un câmp și cealaltă nu. */
.dzlabel {
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: .8px;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin-bottom: 7px;
}
.dz-form { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; min-height: 38px; }
/* Mărginit, nu întins: o parolă temporară e scurtă, iar un câmp cât toată
   pagina spune că se așteaptă un paragraf. */
.dz-form input[type="text"] { flex: 0 1 260px; min-width: 0; }
.dz-form .btn { flex: 0 0 auto; }
.dzstate { margin-right: auto; }
.dz-act .hint { margin: 9px 0 0; }

/* Eticheta unei cutii dinăuntrul unui panou (Clase statistice sub clasele de
   articole). */
.ue-sub {
  margin: 14px 0 6px;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: .8px;
  text-transform: uppercase;
  color: var(--ink-soft);
}

/* The rights box is the gestiuni box with one column instead of three: same
   border, same rows, same ticks. */
.permpick .ghead { grid-template-columns: minmax(0, 1fr); }
.permpick .grow { grid-template-columns: 22px minmax(0, 1fr); }

/* The rights, in two groups that fold.

   Eleven tick boxes in one flat list is a list somebody scrolls rather than
   reads, and the two halves answer different questions: what this person is
   offered on the phone, and what they may do with it.

   A `<details>`, so it folds with no script at all -- this panel has to work
   with JavaScript disabled, and a group that only opens once a script has
   loaded is a group somebody cannot open. The summary carries the count, so a
   closed group still says whether there is anything inside it. */
.pgroup > summary {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 9px 14px;
  border-bottom: 1px solid var(--line);
  background: rgba(38, 70, 83, .03);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-soft);
  cursor: pointer;
  list-style: none;
  transition: background var(--fast) var(--ease);
}
.pgroup > summary:hover { background: rgba(38, 70, 83, .07); }
.pgroup > summary::-webkit-details-marker { display: none; }

/* The marker, drawn rather than inherited: the browser default sits outside the
   padding and reads as a bullet rather than as a control. */
.pgroup > summary::before {
  content: "";
  width: 0;
  height: 0;
  border-left: 5px solid currentColor;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  transition: transform var(--fast) var(--ease);
}
.pgroup[open] > summary::before { transform: rotate(90deg); }

.pgroup > summary .un {
  margin-left: auto;
  letter-spacing: 0;
  text-transform: none;
  font-weight: 500;
}
/* The subunit's code, beside its name. Not `.un`, which is pushed to the far
   end of the summary by the rule above -- the code belongs to the name it
   follows, and the count is the only thing that goes to the right edge. */
.pgroup > summary .scode {
  letter-spacing: 0;
  text-transform: none;
  font-weight: 500;
  font-size: 11.5px;
  color: var(--ink-soft);
}
/* The last group has no border of its own; the box already has one. */
.pgroup:last-child > summary { border-bottom: 1px solid var(--line); }
.pgroup:last-child .grow:last-child { border-bottom: none; }
.editrow .inline { align-items: flex-end; }
.editrow .hint { margin-top: 10px; }
/* Alpine sets x-cloak until it has booted, so a row never flashes open on load. */
[x-cloak] { display: none !important; }

.tag {
  display: inline-block;
  padding: 4px 9px;
  border-radius: 7px;
  font-size: 12px;
  font-weight: 600;
  background: #EEF0EC;
  color: var(--ink-soft);
  margin: 1px 3px 1px 0;
}
.tag.default { background: rgba(42, 157, 143, .12); color: var(--teal-dark); }

.role { font-size: 13px; font-weight: 700; }
.role.sef_depozit { color: var(--teal-dark); }
.role.operator { color: var(--ink-soft); }
.role.admin { color: var(--ink); }
/* The panel's own roles. Darkest is the one that can hand out access, so the
   scale reads as reach rather than as three unrelated labels. */
.role.administrator { color: var(--ink); }
.role.supervizor { color: var(--teal-dark); }

.dot { display: inline-block; width: 9px; height: 9px; border-radius: 50%; margin-right: 8px; }
.dot.teal { background: var(--teal); }
.dot.sand { background: var(--sand); }
.dot.coral { background: var(--coral); }
/* Apricot is waiting-on-a-person, and an unconfirmed print job is exactly
   that: nothing is known to be broken, nothing is known to be fine, and
   somebody has to walk over and look. */
.dot.apricot { background: var(--apricot); }

.rowact {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-soft);
  background: none;
  border: none;
  cursor: pointer;
  font-family: inherit;
  padding: 4px 8px;
  border-radius: 6px;
  transition: background var(--fast) var(--ease), color var(--fast) var(--ease);
}
.rowact:hover { background: rgba(38, 70, 83, .06); color: var(--ink); }
.rowact.danger { color: var(--coral); }
.rowact.danger:hover { background: rgba(214, 90, 60, .10); }

/* A square button carrying only an icon. The plus that opens a sheet and the
   cross that closes it are the same control, so they are one class: 34px, the
   same hover wash as a row action, and never a colour of its own. Colour in
   this panel means state, and "add a user" is not a state. */
.iconbtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: none;
  border-radius: 8px;
  background: none;
  color: var(--ink-soft);
  cursor: pointer;
  transition: background var(--fast) var(--ease), color var(--fast) var(--ease);
}
.iconbtn svg { width: 19px; height: 19px; }
.iconbtn:hover { background: rgba(38, 70, 83, .06); color: var(--ink); }
.iconbtn:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }
/* In a panel head it goes to the far end, past the count. The negative block
   margin keeps its 34px hit area without growing the head: every other panel
   head in the panel is 50px, and one that is 62px because it has a button in
   it makes the page look assembled rather than laid out. */
.panel .head .iconbtn { margin-left: auto; margin-block: -6px; }

/* -------------------------------------------------------------- the sheet

   A form that is too wide for a column and too rare for a page of its own.
   Adding a user happens once a month; the list is what somebody opens this
   page for, so the form waits over the top of it rather than taking a third of
   the width permanently.

   A native `<dialog>`, which is the whole reason this is short: the backdrop,
   Escape, the focus trap and returning focus to the button that opened it are
   the browser's, not ours. `padding: 0` because the UA gives a dialog its own,
   and the three bands below carry theirs. */
.sheet {
  /* Centred by hand, and it has to be.

     A modal `<dialog>` is centred by the UA's own `margin: auto` against
     `inset: 0` -- and `* { margin: 0 }` at the top of this file wipes exactly
     that, so the sheet collapsed into the top-left corner of the viewport.
     Nothing in the markup said so, which is why it looked like a broken
     dialog rather than a reset doing its job. Position, inset and margin are
     written out here rather than left to the UA, so the next reset cannot
     take it away again. */
  position: fixed;
  inset: 0;
  margin: auto;
  width: min(720px, calc(100vw - 32px));
  max-height: calc(100vh - 32px);
  padding: 0;
  border: none;
  border-radius: var(--radius);
  background: var(--card);
  color: var(--ink);
  box-shadow: var(--shadow-2);
  /* So the footer band's corners are clipped by the sheet's radius. */
  overflow: hidden;
}
/* On `[open]` and not on `.sheet`, because author styles beat the UA sheet
   whatever the specificity: a bare `display: flex` here would override
   `dialog:not([open]) { display: none }` and leave the form sitting open on
   the page. Column, so the body between the two fixed bands is what shrinks
   when the viewport is short. */
.sheet[open] { display: flex; flex-direction: column; }
/* The form is the flex item, not the two bands inside it: the body and the
   footer are its children, so making only the dialog a column left the form
   free to be as tall as its contents and pushed the footer clean out of the
   sheet on a short screen. Direct child, so the little `method="dialog"` form
   holding the close cross is not caught by this. */
.sheet > form { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
.sheet-head, .sheet-foot { flex: 0 0 auto; }
/* Ink rather than black. The panel's own colour, dimmed, so the page behind
   reads as set aside rather than switched off. */
.sheet::backdrop { background: rgba(38, 70, 83, .45); }
.sheet-head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 14px 14px 18px;
  border-bottom: 1px solid var(--line);
}
.sheet-head h2 { font-size: 15px; font-weight: 700; }
.sheet-head form { margin-left: auto; }
/* The fields scroll, the head and the footer do not: on a short screen the
   gestiuni picker would otherwise push Adaugă off the bottom of the sheet,
   which is the one control somebody came here to reach. `min-height: 0` because
   a flex item refuses to shrink below its content without it, which is how the
   footer gets pushed off anyway on a laptop screen. */
.sheet-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 18px; }
.sheet-foot {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding: 14px 18px;
  border-top: 1px solid var(--line);
  background: #FBFAF7;
}
/* Four one-line answers, two at a time. The gestiuni picker sits below rather
   than in a column, because it is a list with two controls per row and the
   whole reason this form left the sidebar. */
.sheet-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 0 16px; }
@media (max-width: 620px) { .sheet-cols { grid-template-columns: 1fr; } }

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

label { display: block; font-size: 12.5px; font-weight: 600; color: var(--ink-soft); margin-bottom: 4px; }
input[type=text], input[type=password], input[type=search],
input[type=number], input[type=email], input[type=url], select {
  width: 100%;
  height: 36px;
  padding: 0 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  font: inherit;
  background: #fff;
  color: var(--ink);
  transition: border-color var(--fast) var(--ease), box-shadow var(--fast) var(--ease);
}
/* **Number fields were rendering as raw browser boxes**, because the rule above
   listed every input type in the panel except this one. Two of them are on
   screen -- the printer port and the document release timeout -- and both broke
   the column of full-width fields around them with a 70px box.

   The spinners go with it. They appear on hover, take a click each, and are
   the wrong instrument for a port number or a count of minutes: the value is
   typed, not nudged. Firefox needs the property, WebKit needs the pseudo. */
input[type=number] { -moz-appearance: textfield; appearance: textfield; }
input[type=number]::-webkit-outer-spin-button,
input[type=number]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

input:hover, select:hover { border-color: #C9C6BC; }
input:focus, select:focus {
  outline: none;
  border-color: var(--teal);
  box-shadow: 0 0 0 3px rgba(42, 157, 143, .15);
}
.field { margin-bottom: 12px; }
.hint { font-size: 12px; color: var(--ink-soft); margin-top: 4px; line-height: 1.4; }

/* A hint that is a warning, not a note. Coral on the text alone: the design
   system carries error state in the words and the rail, never in a tinted
   alert box. */
.hint.warn { color: var(--coral); font-weight: 600; }

/* A folded explanation: one sentence and a "Detalii" that opens the rest in
   place. See `_fold.html` for why. The summary is the hint's own size and
   colour so the lead sentence reads as a hint that happens to continue; only
   the word at its end says there is more. */
.fold { margin-top: 4px; }
.fold > summary { cursor: pointer; list-style: none; user-select: none; margin-top: 0; }
.fold > summary::-webkit-details-marker { display: none; }
.fold > summary::marker { content: ""; }
.fold .more { color: var(--teal-dark); font-weight: 600; white-space: nowrap; }
.fold > summary:hover .more { text-decoration: underline; }
.fold .more .hide { display: none; }
.fold[open] .more .show { display: none; }
.fold[open] .more .hide { display: inline; }
.fold > .hint { margin-top: 4px; }
.fold > .hint p { margin: 0 0 6px; }
.fold > .hint p:last-child { margin-bottom: 0; }
/* Inside a notice the lead keeps the notice's own size, not a hint's. */
.notice .fold > summary { font-size: inherit; color: inherit; }
.notice .fold > .hint { font-size: inherit; color: inherit; }

/* "Toate" / "Niciuna" above a group of tick boxes. Kept visually quiet: they
   are a shortcut for a choice somebody could make by hand, not the primary
   action of the form, and styling them as buttons would compete with Salvează. */
.pickall {
  display: flex;
  gap: 14px;
  margin: 2px 0 6px;
  padding: 0 8px;
}
.linkish {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-size: 12.5px;
  color: var(--teal-dark);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.linkish:hover { color: var(--ink); }

/* A child class, indented so the tree the symbol encodes is visible in the
   picker rather than only in the data. */
.gchild { padding-left: 26px; }

/* ------------------------------------------------- the two class pickers

   Denser than the gestiuni and the rights beside them, and deliberately only
   these two. A firm has a handful of warehouses and eleven rights; it can have
   dozens of article classes, and at the row height the others use a real
   nomenclature turns the editor into a scroll.

   Scoped with `.classpick` rather than by loosening `.grow` globally: shrinking
   every picker to suit the longest list would make the short ones look starved,
   and the gestiuni box carries a radio column these do not. */
.classpick .grow {
  padding: 3px 10px;
  font-size: 12.5px;
}
.classpick .grow input { width: 15px; height: 15px; }
.classpick .gname { gap: 6px; }

/* The code sits on the same line as the name, not under it.

   It used to be a sibling of `.gname`, so it was a third child of a two-column
   grid and wrapped onto its own row -- which read as a label stuck beneath the
   name rather than as part of it. Now both are one cell.

   No pill either. The chip that suits a warehouse symbol is too much furniture
   for a four-letter class code repeated down forty rows: it drew the eye to the
   code rather than to the name somebody is actually reading. Plain text, muted,
   with a thin separator. */
.gsep {
  color: var(--line-strong, #C9CFC7);
  flex-shrink: 0;
}
.gsym {
  flex-shrink: 0;
  font-family: "Cascadia Mono", Consolas, "SF Mono", monospace;
  font-size: 11.5px;
  color: var(--ink-soft);
}
.classpick .grow:has(input[type="checkbox"]:checked) .gsym { color: var(--teal-dark); }
.classpick .pgroup > summary { padding-top: 7px; padding-bottom: 7px; }

/* The gestiune / tip contabil matrix, on the user editor.

   Three rows of "flow, warehouse, account", and the flow column is fixed so the
   three names line up under each other: the question somebody is answering is
   "does this person write anywhere unusual", and a ragged left edge makes that
   three separate questions. The two selects share what is left equally, because
   neither is the more important one. */
.mxrow {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr) minmax(0, 1fr);
  gap: 8px;
  align-items: center;
  padding: 8px 14px;
  border-bottom: 1px solid var(--line);
}
.mxrow:last-child { border-bottom: none; }
.mxflow { font-size: 12.5px; color: var(--ink); }
.mxrow select { width: 100%; min-width: 0; font-size: 12.5px; }
/* Which column is which. Two selects side by side both reading "Ca pana acum"
   are indistinguishable without it, and the `aria-label` on each one answers
   the question for a screen reader and for nobody else. Same treatment as the
   filter labels, so it reads as a column heading rather than as a row. */
.mxhead {
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: .6px;
  text-transform: uppercase;
  color: var(--ink-soft);
  padding-top: 10px;
  padding-bottom: 6px;
}
/* Narrow enough that three columns would be three slivers. Stacked, with the
   flow name reading as the heading of its own pair. */
@media (max-width: 900px) {
  .mxrow { grid-template-columns: minmax(0, 1fr); gap: 6px; }
  .mxflow { font-weight: 600; }
  /* Three headings stacked above three stacked fields name nothing. Hidden
     rather than reflowed: at this width each select sits under the flow it
     belongs to, and a heading row would only add a column that is not there. */
  .mxhead { display: none; }
}

.inline { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.inline select { width: auto; min-width: 130px; }
.inline input[type=text] { width: auto; flex: 1; min-width: 120px; }

/* ----------------------------------------------------------------- login */

/* The one screen in the panel that is not a page of the panel: no sidebar, no
   navigation, nothing to do but sign in.

   Split rather than a card floating on a colour. A lone box centred on an empty
   background gives the eye nothing to land on and reads as unfinished, which is
   what it was. The left half is the only place in this product where the brand
   is allowed to take up room; the right half is one column of form, so the
   first thing in focus is the first thing to fill in. */

.loginpage {
  min-height: 100vh;
  display: grid;
  grid-template-columns: 1.05fr 1fr;
  background: var(--bg);
}

.loginaside {
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: 46px 50px;
  color: #fff;
  background:
    radial-gradient(115% 85% at 8% 0%, #315A6B 0%, rgba(49, 90, 107, 0) 62%),
    linear-gradient(168deg, #22404C 0%, #1A333D 100%);
}

/* A faint measuring grid, faded out towards the bottom right so it never
   competes with the words on top of it. Drawn in CSS rather than shipped as an
   image: one less file on an instance that has to render offline. */
.loginaside::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(rgba(255, 255, 255, .05) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, .05) 1px, transparent 1px);
  background-size: 44px 44px;
  -webkit-mask-image: radial-gradient(85% 75% at 22% 18%, #000 0%, transparent 78%);
  mask-image: radial-gradient(85% 75% at 22% 18%, #000 0%, transparent 78%);
}

/* A slow teal glow drifting behind the grid. The one place in this product
   where motion is allowed to be atmosphere rather than confirmation: nothing is
   happening on a login screen, nobody is waiting on a result, and a door can
   afford to look alive. Transform and opacity only, so it costs a compositor
   layer and no layout. */
.loginaside::after {
  content: "";
  position: absolute;
  top: -20%;
  left: -14%;
  width: 72%;
  aspect-ratio: 1;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(42, 157, 143, .30) 0%, rgba(42, 157, 143, 0) 68%);
  filter: blur(26px);
  animation: login-drift 24s var(--ease) infinite alternate;
  pointer-events: none;
}

@keyframes login-drift {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to { transform: translate3d(18%, 26%, 0) scale(1.2); }
}

/* A scanner passing over the grid, once every nine seconds. This product is
   about reading codes off boxes, and the login screen is the only surface with
   room to say so without words. Faint on purpose: at full strength it would be
   a screensaver. */
.loginaside .sweep {
  position: absolute;
  left: 0;
  right: 0;
  top: -190px;
  height: 190px;
  /* Bright core with a soft falloff either side, so it reads as a beam rather
     than a band. The first pass was half this strength and was invisible
     against the gradient: measured on screen, not guessed at in the file. */
  background: linear-gradient(
    180deg,
    rgba(42, 157, 143, 0) 0%,
    rgba(42, 157, 143, .16) 42%,
    rgba(132, 224, 210, .42) 52%,
    rgba(42, 157, 143, .10) 62%,
    rgba(42, 157, 143, 0) 100%
  );
  animation: login-sweep 7s cubic-bezier(.5, 0, .5, 1) infinite;
  pointer-events: none;
}

/* Down in four seconds, then three seconds of nothing. The pause is the point:
   a beam that never stops is a screensaver, and one that arrives every few
   seconds is a scanner passing over the shelf. */
@keyframes login-sweep {
  0% { transform: translateY(0); opacity: 0; }
  8% { opacity: 1; }
  46% { opacity: 1; }
  57% { transform: translateY(calc(100vh + 190px)); opacity: 0; }
  100% { transform: translateY(calc(100vh + 190px)); opacity: 0; }
}

/* Above both decorative layers, which are painted by pseudo-elements and would
   otherwise sit on top of the words. */
.loginaside > * { position: relative; z-index: 1; }
.loginaside::before, .loginaside::after, .loginaside .sweep { z-index: 0; }

/* The page arrives once, not a piece at a time.

   This used to stagger seven elements down the left half on delays from 60ms to
   560ms, each fading over 600ms, so the front door was not visually settled
   until about **1.16 seconds** after a page the server had already produced in
   six milliseconds. Read as "the page is still loading", which is exactly what
   it was not: nothing was outstanding, the words were simply being withheld one
   after another on a timer.

   It also broke this design system's own rule -- motion is confirmation, not
   decoration -- on the one screen where a first impression is the whole job.
   An entrance that makes somebody wait to read is decoration wearing the
   clothes of feedback.

   One short fade for both halves together. Long enough not to snap, short
   enough that nothing appears after anything else. */
@keyframes login-rise {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}

.loginaside,
.loginbox {
  animation: login-rise 200ms var(--ease) backwards;
}

/* The brand, at the only size it is allowed to be large. The mark carries this
   half of the screen on its own now that the line under it is gone, so the
   wordmark grows with it: a 96px logo beside 20px type reads as a logo somebody
   forgot to shrink. Sized here as well as in the markup, so a narrow window
   scales the image instead of squeezing it out of square. */
/* **The mark shrank by a third and the words grew by half, 2026-09-01.** The
   logo was carrying the half-screen on its own and the sentences beside it read
   as captions; the balance is now the other way round, which is what the half
   is for -- somebody is meant to read it, not admire it.

   `--logo` is declared here rather than repeated, because three things are
   derived from it: the image, and the halo's centre and diameter. Those were
   three hand-computed constants (96 = half of 192, 440, -220) and the comment
   below them explained the arithmetic -- which is exactly the shape that goes
   stale the first time somebody changes one number and not the other three. */
.loginmark {
  --logo: clamp(96px, 9.3vw, 168px);
  display: flex;
  align-items: center;
  gap: clamp(12px, 1.1vw, 20px);
  font-size: clamp(30px, 1vw + 29.1px, 52px);
  font-weight: 700;
  letter-spacing: -.45px;
}
.loginmark img { width: var(--logo); height: var(--logo); flex: none; }
.loginmark b { color: var(--gold); font-weight: 800; }

/* A slow halo behind the mark, gold because the mark is. It is the only thing
   in the top corner of an otherwise empty half, and without it the corner reads
   as a screenshot: nothing on this screen ever moves on its own except the
   sweep, and the sweep is away for three seconds out of every seven. Breathing,
   not blinking -- five and a half seconds one way, so it is noticed only if it
   is looked at. Opacity and transform, so it costs no layout.

   Scoped to the aside: the compact mark sits over a white form, where a glow
   would read as a smudge. */
.loginaside .loginmark::before {
  content: "";
  position: absolute;
  /* Centred on the 192px logo box by arithmetic rather than by percentages: the
     mark is a flex row as wide as the column, so a 50% of anything here is 50%
     of the row and lands the glow somewhere under the words. Half of 192, and
     the circle grew with it -- a 220px halo behind a 192px mark is a rim, not a
     glow, which is the one thing this element is for. */
  top: calc(var(--logo) / 2);
  left: calc(var(--logo) / 2);
  width: calc(var(--logo) * 2.29);
  height: calc(var(--logo) * 2.29);
  margin: calc(var(--logo) * -1.145) 0 0 calc(var(--logo) * -1.145);
  border-radius: 50%;
  background: radial-gradient(circle, rgba(247, 187, 44, .16) 0%, rgba(247, 187, 44, 0) 62%);
  animation: mark-breathe 5.5s var(--ease) infinite alternate;
  pointer-events: none;
  z-index: -1;
}

@keyframes mark-breathe {
  from { opacity: .45; transform: scale(.88); }
  to { opacity: 1; transform: scale(1.14); }
}

/* The narrow-window copy of the mark sits above a form rather than beside a
   headline, so it stays a step smaller: at full size it would outweigh the
   thing it introduces. */
/* **Keyed to a narrow window, because that is the only place it is ever seen.**
   This copy of the mark replaces the aside below 860px, so a `vw` scale tuned
   for a desktop lands on its own floor every single time it renders -- 2.5vw of
   860px is 21px. The numbers here are read at 760-860px and nowhere else. */
.loginbox .compactmark {
  --logo: clamp(32px, 4.2vw, 40px);
  font-size: clamp(28px, 3.6vw, 34.5px);
  gap: clamp(10px, 1.2vw, 16px);
}

/* The middle block sits a little above the optical centre. With the three
   blocks spaced apart, the mark and the footer pin the ends and the words land
   dead centre, which reads as low: the eye puts the centre of a tall column
   slightly above the middle. The margin below is free space taken out of the
   lower gap, and space-between splits what is left evenly, so the block rises
   by half of it. In vh rather than pixels, so a short window does not push the
   words into the footer. */
.loginlead { margin-bottom: 12vh; }

.loginlead h1 {
  /* Brought down on 2026-09-01, after the +50% pass overshot: 45px against a
     20px list read as a banner over a caption. 38px keeps the heading clearly
     first without the three lines under it becoming small print. */
  font-size: clamp(28px, .87vw + 25.47px, 46px);
  line-height: 1.22;
  font-weight: 800;
  letter-spacing: -.5px;
  max-width: 15ch;
}
/* `.loginlead p` lived here until 2026-09-01 and went with the paragraph it
   styled -- deleted rather than left behind, because a rule whose markup no
   longer exists is one the next person spends time understanding before finding
   out it does nothing. `git log` has it if the sentence ever comes back. */

/* What this panel is for, in three lines. Real sections of it, not slogans:
   somebody who has never seen the product should be able to tell from here
   whether they are in the right place.

   These sit directly under the heading now that the paragraph is gone, so the
   top margin is the whole gap between the two rather than half of it. */
.loginpoints {
  margin-top: clamp(18px, 1.8vw, 34px);
  display: flex;
  flex-direction: column;
  gap: clamp(8px, .76vw, 15px);
}
.loginpoints span {
  display: flex;
  align-items: center;
  gap: clamp(9px, .76vw, 15px);
  font-size: clamp(15px, .47vw + 13.48px, 24px);
  color: rgba(255, 255, 255, .78);
}
/* The three dots tick over in sequence, top to bottom, the way somebody reads
   down a list -- which is what this product does all day. Four fifths of the
   cycle is stillness: a dot that pulses without stopping is a warning light,
   and nothing here is wrong. Transform and opacity only. */
.loginpoints i {
  flex: none;
  width: clamp(6px, .62vw, 10px);
  height: clamp(6px, .62vw, 10px);
  border-radius: 50%;
  background: var(--teal);
  animation: point-tick 4.2s var(--ease) infinite;
}
.loginpoints span:nth-child(2) i { animation-delay: .5s; }
.loginpoints span:nth-child(3) i { animation-delay: 1s; }

@keyframes point-tick {
  0%, 22%, 100% { transform: scale(1); opacity: .6; }
  8% { transform: scale(1.6); opacity: 1; }
}

.loginaside footer { font-size: clamp(14px, .43vw + 12.56px, 22px); color: rgba(255, 255, 255, .42); }

.loginform {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 40px 30px;
}
.loginbox { width: 100%; max-width: 346px; }

/* Shown only when the brand half is not, so a narrow window still says which
   product it is asking for a password to. */
.loginbox .compactmark { display: none; margin-bottom: 26px; }

.loginfirm {
  display: inline-block;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--teal-dark);
  background: rgba(42, 157, 143, .10);
  padding: 4px 10px;
  border-radius: 6px;
}
.loginbox h2 {
  margin-top: 14px;
  font-size: 23px;
  font-weight: 800;
  letter-spacing: -.35px;
}
.loginbox .sub { margin-top: 6px; font-size: 14px; color: var(--ink-soft); }
.loginbox form { margin-top: 26px; }
.loginbox input { height: 46px; }
.loginbox .field { margin-bottom: 16px; }
.loginbox .btn { height: 46px; margin-top: 6px; }

/* The form answers being used. The label above the field in focus turns teal,
   which is this system's word for settled, and the button lifts a millimetre
   under the pointer. Small on purpose: this is confirmation, not atmosphere --
   the atmosphere is all on the other half of the screen. */
.loginbox label { transition: color var(--fast) var(--ease); }
.loginbox .field:focus-within label { color: var(--teal-dark); }
.loginbox .btn.primary:hover { transform: translateY(-1px); }
.loginbox .btn.primary:active { transform: translateY(1px); }

.loginnote {
  margin-top: 26px;
  padding-top: 18px;
  border-top: 1px solid var(--line);
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--ink-soft);
}

/* A sentence, not a tinted box. The same rule the phone follows: the error is
   carried by the words and by coral on the rail, never by a coloured panel that
   makes an ordinary typo look like a system fault. */
.loginerror {
  border-left: 3px solid var(--coral);
  padding: 3px 0 3px 12px;
  margin-bottom: 18px;
  font-size: 13.5px;
  line-height: 1.45;
  color: var(--ink);
}

.btn.wide { width: 100%; justify-content: center; }

/* One column below the width where two would squeeze the form. The brand half
   is the part that goes: it is the half nobody came here for. */
@media (max-width: 860px) {
  .loginpage { grid-template-columns: 1fr; }
  .loginaside { display: none; }
  .loginbox .compactmark { display: flex; }
}

/* ---------------------------------------------------------- article photo */

/* Fixed height so the panel does not jump between an article that has a photo
   and one that does not: this page is used by somebody working down a list. */
.photo {
  height: 210px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--bg);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.photo img { max-width: 100%; max-height: 100%; object-fit: contain; }
/* Muted, because most articles will never have one and that is expected. */
.empty-photo { color: var(--ink-soft); font-size: 13.5px; }

input[type=file] {
  width: 100%;
  font: inherit;
  font-size: 13.5px;
  color: var(--ink-soft);
}

/* ---------------------------------------------------------- configurare

   A list of settings, one row each (2026-09-03). Three tracks: what the
   setting is and one sentence about it; the control; Salvează. The first
   track is the widest because it is where the folded explanation opens,
   and the last hugs its button so the buttons line up down the card. */
.setrow {
  display: grid;
  grid-template-columns: minmax(260px, 1.3fr) minmax(220px, 1fr) auto;
  gap: 6px 20px;
  align-items: center;
  padding: 10px 16px;
  border-bottom: 1px solid var(--line);
}
.setrow:last-child { border-bottom: none; }
.setrow .sk { min-width: 0; }
.setrow .sk > label { margin: 0; font-size: 13.5px; font-weight: 600; color: var(--ink); }
.setrow .sk .hint { margin-top: 2px; }
.setrow .sv input, .setrow .sv select { max-width: 380px; }
/* A number and its unit on one line: "30  zile în urmă". */
.setrow .sv.num { display: flex; align-items: center; gap: 10px; }
.setrow .sv.num input { width: 110px; }
.setrow .sv.num .un { font-size: 12.5px; color: var(--ink-soft); white-space: nowrap; }
.setrow .sv .switch {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  font-size: 13.5px;
  font-weight: 500;
  color: var(--ink);
  cursor: pointer;
}
.setrow .sv .switch input { accent-color: var(--teal); width: auto; height: auto; }
.setrow .sa { justify-self: end; }

/* The connection: three fields and a button on one line, labels above. */
.wmerow {
  display: grid;
  grid-template-columns: minmax(240px, 2fr) minmax(150px, 1fr) minmax(150px, 1fr) auto;
  gap: 10px 12px;
  align-items: end;
}
.wmerow label { font-size: 12px; margin-bottom: 3px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wmetest { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: 10px; }
.wmetest .hint { margin: 0; }

/* --------------------------------------------------------------- notices */

/* A placeholder says so. A page that looks finished and is not is worse than
   an empty one, because nobody asks about it until it matters. */
.notice {
  border-left: 4px solid var(--rail, var(--sand));
  background: var(--card);
  border-radius: 0 var(--radius) var(--radius) 0;
  padding: 11px 14px;
  font-size: 13.5px;
}
.notice b { display: block; margin-bottom: 2px; }
/* Not coral: coral says something is wrong, and an unconfirmed job is not
   wrong, it is unknown. Apricot is the colour this design system already
   uses for work waiting on a human. */
.notice.waiting { --rail: var(--apricot); }

/* A rendered template, shown as the ZPL it is. Scrolls on its own rather
   than widening the page, since a label is wider than a column. */
.preview {
  /* Tall enough to show a whole small template without scrolling, capped so a
     long one does not decide the height of the row it sits in. */
  max-height: 420px;
  overflow: auto;
  margin-top: 12px;
  padding: 14px;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--ink-soft);
  overflow-x: auto;
  white-space: pre;
}

/* Spans both columns of .grid, for content that is neither list nor form. */
.panel.wide { grid-column: 1 / -1; }
/* Two halves of one job rather than a main column and a sidebar. Used where
   neither side is subordinate to the other: writing a label and seeing what it
   renders to are the same task looked at from two ends. */
.grid.even { grid-template-columns: 1fr 1fr; }
@media (max-width: 980px) { .grid.even { grid-template-columns: 1fr; } }

textarea {
  width: 100%;
  resize: vertical;
  padding: 9px 11px;
  border: 1px solid var(--line);
  border-radius: 9px;
  font-size: 13px;
  line-height: 1.55;
  color: var(--ink);
  background: var(--card);
}
textarea:focus { outline: 2px solid var(--teal); outline-offset: -1px; }

/* An empty state is a state, not a hole. Icon, one sentence, one action. */
.empty {
  text-align: center;
  padding: 40px 24px;
  color: var(--ink-soft);
}
.empty svg { width: 46px; height: 46px; margin-bottom: 12px; opacity: .55; }
.empty b { display: block; color: var(--ink); font-size: 15.5px; margin-bottom: 4px; }
.empty p { font-size: 13.5px; max-width: 380px; margin: 0 auto; }
.empty .btn { margin-top: 16px; }

/* ---------------------------------------------------------------- toasts */

/* The container is in the page whether or not it holds a message, so that a
   partial update can swap a toast into it. Empty it has no size, but it is
   fixed and above everything, so it declines clicks explicitly rather than
   relying on that: a box that swallows the bottom-right corner of a page is
   the kind of fault nobody attributes to a toast. */
.toasts {
  position: fixed;
  right: 20px;
  bottom: 20px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  z-index: 40;
  pointer-events: none;
}
.toast {
  pointer-events: auto;
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--card);
  border: 1px solid var(--line);
  border-left: 4px solid var(--rail, var(--teal));
  border-radius: var(--radius);
  box-shadow: var(--shadow-2);
  padding: 13px 16px;
  font-size: 14px;
  font-weight: 600;
  min-width: 260px;
  max-width: 380px;
}
.toast .x {
  margin-left: auto;
  border: none;
  background: none;
  cursor: pointer;
  color: var(--ink-soft);
  font-size: 16px;
  line-height: 1;
  padding: 2px 4px;
  border-radius: 4px;
}
.toast .x:hover { background: rgba(38, 70, 83, .07); }

/* ------------------------------------------------------- htmx transitions */

/* A swap the admin asked for fades out and back in, so it reads as the same
   page changing rather than as a reload.

   A poll is different. Fading out on a timer would make the panel blink every
   twelve seconds even when nothing changed, which is worse than no animation
   at all: motion that carries no information still costs attention. So polled
   fragments never fade out; they only settle in. */
.htmx-swapping:not([data-poll]) { opacity: 0; transition: opacity var(--fast) var(--ease); }
.htmx-added { animation: rise var(--normal) var(--ease); }
[data-poll].htmx-settling { animation: settle 180ms var(--ease); }
.htmx-settling tr:not([data-poll] tr) { animation: rise var(--normal) var(--ease); }

@keyframes rise {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: none; }
}

@keyframes settle {
  from { opacity: .6; }
  to { opacity: 1; }
}

@keyframes toast-in {
  from { opacity: 0; transform: translateY(10px) scale(.98); }
  to { opacity: 1; transform: none; }
}
.toast { animation: toast-in var(--normal) var(--ease); }

/* A request the admin started dims what it is about to replace, so a slow
   answer looks like waiting. A poll must never do this: nobody asked. */
.htmx-request.panel:not([data-poll]) { opacity: .55; transition: opacity var(--fast) var(--ease); }

/* -------------------------------------------------------------- skeleton */

/* Shaped like the rows that are coming, so the layout does not jump when they
   land. A spinner says "wait" and nothing else. */
.skeleton-row { display: flex; align-items: center; gap: 14px; padding: 13px 18px; border-bottom: 1px solid var(--line); }
.skeleton-bar { height: 13px; border-radius: 4px; background: linear-gradient(90deg, #ECEAE4 25%, #F5F3EE 50%, #ECEAE4 75%); background-size: 200% 100%; animation: shimmer 1.2s infinite linear; }
.skeleton-bar.wide { width: 42%; }
.skeleton-bar.mid { width: 22%; }
.skeleton-bar.thin { height: 10px; width: 15%; }

@keyframes shimmer {
  from { background-position: 200% 0; }
  to { background-position: -200% 0; }
}

/* ------------------------------------------------------------------- qr */

.qr { width: 210px; height: 210px; margin: 0 auto 14px; padding: 10px; background: #fff; border: 1px solid var(--line); border-radius: 10px; }
.qr svg { width: 100%; height: 100%; display: block; }
.qrmeta { text-align: center; font-size: 12.5px; color: var(--ink-soft); margin-bottom: 14px; }
.qrmeta b { color: var(--ink); }

/* A list longer than the page should grow for.
   
   The landing page used to get taller with every session opened and every
   login recorded, so the same screen was one panel on a quiet morning and a
   scroll by the afternoon. A fixed window turns "how much has happened" into
   something the scrollbar says, and leaves the page the same height either way
   -- which is the whole reason the two columns line up at all.

   The height comes in as `--max` from the template, beside the row count it is
   derived from, because a magic number here would drift from the list it is
   supposed to fit the moment either one changes. */
.scrollbox {
  max-height: var(--max, 320px);
  overflow-y: auto;
  /* Reaching the end of this list must not then scroll the page behind it. */
  overscroll-behavior: contain;
}
/* The header has to survive the scroll, or a column of bare numbers halfway
   down the list belongs to nothing. */
.scrollbox table th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--card);
}

/* **The window as the cap.** Articole since 2026-09-03: with a page-size
   picker, a fixed ten-row card made "100 pe pagină" show ten rows and a
   scrollbar, which is a control that changes nothing anybody sees. So the
   page column becomes the height of the window and stops scrolling, and the
   list takes whatever is left under the filter bar and scrolls inside that.
   Every level between the column and the list is a flex child with
   `min-height: 0`, because a flex item's default minimum is its content and
   a table of a hundred rows would otherwise push the column past the window
   exactly as before. The sticky header rule above still applies. */
.main.fillpage { height: 100vh; display: flex; flex-direction: column; overflow: hidden; }
.fillpage > .stack { flex: 1 1 auto; min-height: 0; }
/* `0 1 auto`, not `1 1 auto`, from the card down: the card may shrink to fit
   the window but never grows past its rows, so two matches make a short card
   with the pager right under them rather than a window-tall card with the
   pager pinned to its floor. */
.panel.fill { display: flex; flex-direction: column; flex: 0 1 auto; min-height: 0; overflow-x: hidden; }
.panel.fill > form { flex: 0 0 auto; }
.panel.fill > .results { display: flex; flex-direction: column; flex: 0 1 auto; min-height: 0; }
.scrollbox.fill { flex: 0 1 auto; max-height: none; min-height: 0; overflow: auto; }

/* Turning pages: a bar under the list, in the filter bar's vocabulary --
   the range at the left where the count reads, the buttons at the right. */
.pager {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 16px;
  border-top: 1px solid var(--line);
  background: #FBFAF7;
  flex: 0 0 auto;
}
.pager .count { font-size: 12.5px; color: var(--ink-soft); white-space: nowrap; }
.pager .tail { margin-left: auto; display: flex; align-items: center; gap: 10px; }
/* The page-size picker, first thing in the tail since 2026-09-08. The same
   height as the `sm` buttons beside it, so the row reads as one control strip
   rather than a select that happens to sit near two buttons. */
.pager select {
  height: 30px;
  padding: 0 8px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: #fff;
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}
.pager select:focus-visible { border-color: var(--teal); outline: none; }
/* The button that has nowhere to go stays, greyed, so the bar keeps its
   shape from page to page instead of the buttons shifting under the cursor. */
.pager .btn[aria-disabled] { opacity: .45; pointer-events: none; }

/* The same row, on a table that is not the journal.

   `.events` cannot simply be worn by "Se lucreaza acum": it carries column
   rules that belong to the journal and mean something else here -- `.dt` is
   right-aligned there, `.msg` claims the leftover width, `.ev` is bolded. This
   borrows the row and leaves the columns alone, which is the only part the two
   tables actually have to agree about. */
table.compact { font-size: var(--row-font); }
table.compact th {
  height: var(--row-h);
  padding: 0 var(--row-pad-x);
  font-size: var(--row-label);
}
table.compact td { height: var(--row-h); padding: 0 var(--row-pad-x); }
/* One line per cell, for the reason `.events` gives below: a cell that wraps is
   a taller row, which undoes the tightening it was wrapped by. The panel
   scrolls sideways if it has to; the row stays one line. */
table.compact td, table.compact th { white-space: nowrap; }
/* The editor that opens under a row is a form, not a line of the list: it
   takes its own height and may wrap. Same exception `.rows-tight` makes. */
table.compact td.editrow { height: auto; padding: 12px var(--row-pad-x); white-space: normal; }

/* The article list (2026-09-08, twice in one day). A 100%-wide table with
   automatic layout shares its slack between the columns in proportion to
   their content, so Denumire, the longest, took nearly all of it and Cod
   intern sat half a screen away from the name it belongs to. The first fix
   let the last column swallow the slack instead, which pulled every column
   against the left edge and left the right third of the panel empty -- the
   same unevenness, mirrored. So the layout is fixed and the shares are
   stated: the name gets a third, the five columns after it split the rest
   evenly, and the unit, which is two or three letters, gets what it needs.
   Automatic layout ignores a cell's max-width, which is why this is
   `table-layout: fixed` and not a cap; the cost is that a cell longer than
   its share is cut with an ellipsis rather than pushing its neighbours, and
   the name's full text is in the link's title for that case. Under 900px
   the shares would crush the codes, so the box scrolls sideways instead. */
table.catalog { table-layout: fixed; min-width: 900px; }
/* The row is Utilizatori's row (2026-09-08, "the sizes look so good there"):
   44px tall, 14px text, a 34px header with the 11.5px label, 16px sides.
   Stated through the variables `compact` already reads, so the two lists are
   one number apart rather than two rule sets. `compact`'s own scale, the
   viewport clamps above, stays for the landing page. */
table.catalog {
  --row-h: 44px;
  --row-font: 14px;
  --row-pad-x: 16px;
  --row-label: 11.5px;
}
table.catalog th { height: 34px; }
table.catalog th:nth-child(1) { width: 30%; }
table.catalog th:nth-child(2),
table.catalog th:nth-child(3),
table.catalog th:nth-child(4),
table.catalog th:nth-child(5) { width: 14%; }
/* 7%, not 5%: at 5% the three-letter unit was cut on an ordinary screen. */
table.catalog th:nth-child(6) { width: 7%; }
table.catalog td { overflow: hidden; text-overflow: ellipsis; }

.events { font-size: var(--row-font); }
/* **Half the old vertical padding and a third less horizontal.** These rows are
   read forty at a time, down a column, looking for one of them -- 10px top and
   bottom made a screenful hold six documents where it now holds ten, and 18px a
   side spread five short columns across the width of a panel so the eye had to
   travel between a document and its state.

   Reported on 2026-09-01: "linile prea inalte" and "coloanele prea rasfirate".
   They were both the same number.

   **Now stated as a row height rather than as padding**, and the swap is what
   lets the size scale. 6px above and below a 21px line happened to make a 34px
   row; asking for a 34px row and letting the cell centre its own text makes the
   same row without depending on the font's line box, which is exactly the
   coupling that made the last measurement wrong. The pair is identical at a
   1440px window and stays eight-rows-tall at every other width. */
.events td { height: var(--row-h); padding: 0 var(--row-pad-x); }
/* **One line per cell, so a row is one line tall.** Squeezing the columns made
   the short ones wrap -- "Iesiri catre subunitati" onto three lines, "esuat · 4
   incercari" onto two -- and a wrapped cell is a taller row, which is the
   opposite of what tightening them was for. The two cells that hold real
   sentences opt back out below. */
.events td, .events th { white-space: nowrap; }
/* The message column takes whatever width is left, so every column before it
   shrinks to its own content instead of sharing the slack equally. That is what
   gathers the table: nothing here is narrower, the emptiness simply stops being
   distributed. */
/* Left-aligned, but held off the time beside it. Right-aligning it put a gap in
   the middle of the row between the timestamp and the state, which are the two
   things a reader pairs; sitting flush against the time made them read as one
   field. The indent is the separation, and it costs no width because the
   column had slack anyway. */
.events .state { text-align: left; padding-left: 42px; }
/* **The slack is shared, not given away.** Sending all of it to one column
   pinned every other column against the left edge and left a third of the panel
   empty -- tidy in the markup, cramped on the screen. Without a `width` here the
   browser distributes the leftover width across all of them, so the table fills
   the panel and the columns keep the air between them that makes a row
   scannable. The tightening that was asked for lives in the padding, which is
   inside each cell, not in where the columns sit. */
.events .msg { min-width: 12ch; }
/* The security journal's detail column (2026-09-08): one line, cut with an
   ellipsis, and a link to a sheet with the whole of it. `max-width: 0` with
   `width: 100%` is the table trick that makes automatic layout size the cell
   by the slack rather than by its content, which is what lets the text be
   cut at all; the clip is on an inner span so the ellipsis never eats the
   link after it. The 7ch is the link's own room. */
.events td.detail { max-width: 0; width: 100%; }
.events td.detail .clip {
  display: inline-block;
  max-width: calc(100% - 7ch);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: bottom;
}
.events td.detail .more {
  margin-left: 8px;
  color: var(--teal-dark);
  font-size: 12.5px;
  font-weight: 600;
  text-decoration: none;
}
.events td.detail .more:hover { text-decoration: underline; }
.audit-sheet { width: min(640px, calc(100vw - 32px)); }
.audit-meta { margin: 0 0 12px; }
.audit-line { font-size: 13px; line-height: 1.5; white-space: pre-wrap; overflow-wrap: anywhere; }
.events .msg details { display: inline-block; }
.events .msg summary {
  cursor: pointer;
  color: var(--coral);
  font-size: 12.5px;
  font-weight: 600;
  /* Our own marker below, so the browser's default one goes. */
  list-style: none;
}
.events .msg details[open] summary { margin-bottom: 3px; }
/* **The message unfolds instead of appearing.** A `<details>` snaps: the row
   doubles in height between one frame and the next, and because the rows around
   it jump at the same moment the eye loses the line it was reading.
   
   `::details-content` is what browsers give us to animate that box, and `auto`
   only becomes an animatable value with `interpolate-size` -- without it there
   is no height to interpolate towards and the transition silently does nothing.
   `content-visibility` needs `allow-discrete` or the content pops in at the
   start of the open and out at the start of the close, which looks worse than
   no animation.
   
   Where this is unsupported the summary still opens, instantly, which is
   exactly today's behaviour: nothing is lost, it simply does not glide. That is
   why it is worth doing in CSS rather than reaching for script. */
.events .msg details::details-content {
  block-size: 0;
  overflow: hidden;
  opacity: 0;
  transition:
    block-size var(--normal) var(--ease),
    opacity var(--normal) var(--ease),
    content-visibility var(--normal) allow-discrete;
}
.events .msg details[open]::details-content { block-size: auto; opacity: 1; }
/* The marker turns rather than swapping glyph, so the control itself says the
   panel is opening while it opens. */
.events .msg summary::marker { content: none; }
.events .msg summary::before {
  content: "▸";
  display: inline-block;
  margin-right: 5px;
  transition: transform var(--fast) var(--ease);
}
.events .msg details[open] summary::before { transform: rotate(90deg); }
.events .msg details span {
  color: var(--ink-soft);
  font-size: 12.5px;
  /* The one place a sentence belongs, so it wraps rather than widening the
     table past the panel. */
  white-space: normal;
  display: block;
  max-width: 60ch;
}
/* A folded message that keeps the full width, for the one case that needs it:
   a stack trace from a phone does not fit beside five other columns. */
/* The unfolded message is a row under a row, sized by what it holds. A minimum
   height here would put a third of a line of air under every short one. */
.events .msgrow td { height: auto; padding-top: 0; }
.events .msgrow summary { cursor: pointer; color: var(--ink-soft); font-size: 12.5px; }
.events .ev { font-weight: 600; }
/* The source document's own number, under our NIR. Lighter and lighter-weight
   than the line above it, because the two are a pair and ours is the one this
   column is named for -- but the office searches WinMentor by theirs, so it has
   to be readable rather than merely present. */
.events .ev .sub {
  display: block;
  margin-top: 2px;
  font-weight: 400;
  font-size: 12.5px;
  color: var(--ink-soft);
}
/* Who did it. Quieter than what was done, because the action is what somebody
   is scanning the list for; the name is what they check once they find it. */
.events .who { color: var(--ink-soft); font-size: 12.5px; white-space: nowrap; }
.events .dt { color: var(--ink-soft); font-size: 12.5px; white-space: nowrap; text-align: right; }
/* The recorded detail of an audit row.
   It used to borrow `.dt`, which is right-aligned and nowrap, so a row with two
   keys in it pushed the whole table sideways and the column read from the wrong
   edge. It is the widest thing on the row and the last thing anybody looks at:
   left-aligned like the text it is, allowed to wrap, and given the leftover
   width rather than taking it. */
.events .detail {
  color: var(--ink-soft);
  font-size: 12.5px;
  text-align: left;
  white-space: normal;
  /* Takes the slack rather than competing for it, so the columns to its left
     stay one line each. Without this the actor column wraps "user demo" onto
     two, which makes a row twice as tall to say the same thing. */
  width: 100%;
  word-break: break-word;
}
.events .actor { white-space: nowrap; }

/* The instance's own counts, as a sentence under the page. A table of five
   zeroes is five rows saying nothing; a line says the same and stops being the
   first thing the eye lands on. Quiet on purpose: none of these numbers is a
   problem, and anything that is one is named above. */
.tally {
  margin-top: 18px;
  color: var(--ink-soft);
  font-size: 12.5px;
}

/* The problems read as a row when there is room for one, and stack when there
   is not. Full width, so three of them do not become three lines of a narrow
   column. */
.todolist { display: flex; flex-wrap: wrap; gap: 12px; }
.todolist .notice { flex: 1 1 280px; margin: 0; }
.todolist .notice .add { display: inline-block; margin-top: 6px; }

/* A button that says it is working, for the one action on this panel that
   takes seconds. The two labels swap rather than the text being rewritten by
   script: htmx puts `.htmx-request` on the element it disabled, so the whole
   state is one class and no JavaScript of ours.

   The icon keeps spinning meanwhile -- it is a sync arrow, and a still one on a
   button that says "se sincronizează" is a button contradicting itself. */
.btn .busy { display: none; }
.btn.htmx-request .idle { display: none; }
.btn.htmx-request .busy { display: inline; }
.btn.htmx-request { opacity: .75; cursor: progress; }
.btn.htmx-request svg { animation: spin 900ms linear infinite; }

@keyframes spin { to { transform: rotate(360deg); } }

@media (max-width: 980px) {
  .grid { grid-template-columns: 1fr; }
  .app { flex-direction: column; }
  .main { padding: 20px 16px 40px; }

  /* The rail becomes a bar, not a stack.

     Turning a 230px column into a full-width block used to make the menu 657px
     tall on a 900px window: the nine links stayed nine full-width rows, so the
     page itself started below the fold and every visit began by scrolling past
     a menu the person had already read. A sidebar that is merely wide is not a
     sidebar, it is a wall.

     Two rows instead, laid out by grid so the markup does not change: the
     brand, the firm and the account on the first, the nine links on the second
     as a horizontal strip that scrolls sideways when they do not fit. About
     100px total, and the page starts where the page starts.

     Sticky rather than static, for the same reason the column is sticky on a
     wide screen: navigation that scrolls away is navigation somebody has to go
     looking for. */
  .app .side {
    width: 100%;
    height: auto;
    position: sticky;
    top: 0;
    z-index: 30;
    padding: 0;
    display: grid;
    grid-template-columns: auto 1fr auto;
    grid-template-areas:
      "brand firm me"
      "nav   nav  nav";
    align-items: center;
    column-gap: 14px;
  }

  .app .side .brand {
    grid-area: brand;
    flex-direction: row;
    gap: 10px;
    padding: 11px 0 11px 16px;
    font-size: 17px;
  }

  /* The firm badge stops being a card and becomes a line: on one row beside
     the brand there is no room for a label above a value. */
  .app .side .firm {
    grid-area: firm;
    display: inline-flex;
    align-items: baseline;
    gap: 7px;
    justify-self: start;
    margin: 0;
    padding: 5px 10px;
    font-size: 12px;
  }
  .app .side .firm b { display: inline; margin-top: 0; font-size: 13.5px; }

  .app .side .me {
    grid-area: me;
    display: flex;
    align-items: center;
    gap: 14px;
    margin-top: 0;
    padding: 0 16px 0 0;
    border-top: none;
    text-align: left;
  }
  .app .side .me b { display: inline; }
  .app .side .me a { margin-top: 0; justify-content: flex-start; gap: 8px; }

  /* Sideways rather than wrapped: a strip that scrolls keeps the bar one row
     tall whatever the window does, and wrapping would put the bar back to
     guessing its own height. */
  .app .nav {
    grid-area: nav;
    display: flex;
    overflow-x: auto;
    border-top: 1px solid rgba(255, 255, 255, .1);
    scrollbar-width: thin;
  }
  /* The current section moves from a left rail to an underline, because in a
     row the left edge of a link is no longer the edge of the menu. */
  .app .nav a {
    flex: none;
    white-space: nowrap;
    padding: 11px 14px;
    border-left-width: 0;
    border-bottom: 3px solid transparent;
    justify-content: flex-start;
    gap: 11px;
  }
  .app .nav a:hover { padding-left: 14px; }
  .app .nav a.on { border-bottom-color: var(--gold); }

  /* The strip's own scrollbar, in the bar's colours rather than the operating
     system's: a light grey trough across a dark navy bar reads as damage. */
  .app .nav { scrollbar-color: rgba(255, 255, 255, .28) transparent; }
  .app .nav::-webkit-scrollbar { height: 6px; }
  .app .nav::-webkit-scrollbar-track { background: transparent; }
  .app .nav::-webkit-scrollbar-thumb {
    background: rgba(255, 255, 255, .28);
    border-radius: 3px;
  }

  /* The labels come back whatever the rail's saved state was: there is no
     collapsed rail here to hide them for, and the tooltip that stood in for
     them has nothing to hover over on a touch screen. */
  .app .side .lbl {
    position: static;
    width: auto;
    height: auto;
    margin: 0;
    overflow: visible;
    clip-path: none;
  }
  .app .side [data-label]::after { content: none; }

  /* Below this width the sidebar is already a band across the top rather than
     a column beside the page, so there is no width to reclaim and the control
     would promise something it cannot do.

     The preference is ignored here, never cleared: somebody who collapsed the
     rail on a wide monitor should find it collapsed when they go back to one,
     not silently reset by having once opened the panel on a laptop. */
  /* Stacked, and the buttons stop being pushed to a right edge that is no
     longer beside anything. */
  .filters .f.act { width: 100%; }
  .filters .f { flex: 1 1 100%; }
  .filters .tail { margin-left: 0; width: 100%; }

  /* The sidebar is a band across the top here, so there is no window height
     to fill under it; the page scrolls as any page does, and the list is
     capped like the other lists rather than by the window. */
  .main.fillpage { height: auto; display: block; overflow: visible; }
  .scrollbox.fill { max-height: 70vh; }

  /* One setting per row still, but the row stacks: name, control, button. */
  .setrow { grid-template-columns: 1fr; }
  .setrow .sa { justify-self: start; }
  .wmerow { grid-template-columns: 1fr; }

  /* A flow's section stacks its description over its card; the rule lives
     with the section's own block, at 900px. Nothing to add here since the
     row stopped being a row (2026-09-08). */
  .filters input, .filters select { max-width: none; width: 100%; }

  .sidetoggle { display: none; }
}

/* Everything above is confirmation rather than decoration, so removing it
   costs nothing but the confirmation. Someone who asked the operating system
   for less motion has usually asked for a reason. */
/* Narrower still, and the first row runs out of room: the brand, the firm and
   an account called something longer than "admin" stop fitting on one line and
   push the page sideways. The account keeps its two icons and loses its words.

   Clipped rather than removed, the same rule the collapsed rail follows: a
   screen reader still reads "Schimbă parola" and "Ieși din panou" on links
   that a sighted person now recognises by their icon. `display: none` would
   leave two unlabelled links, which is a worse menu than a narrow one. */
@media (max-width: 560px) {
  .app .side { column-gap: 10px; }
  .app .side .brand { padding-left: 12px; font-size: 16px; }
  .app .side .me { gap: 14px; padding-right: 12px; }
  .app .side .me a { gap: 0; }
  .app .side .me .lbl {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
  .nav a:hover { padding-left: 22px; }
  .btn:active { transform: none; }
  .loginbox .btn.primary:hover { transform: none; }
  /* Arriving on a page is motion somebody asked us not to make. The blanket
     rule above already collapses the duration; the delays would still hold
     each block back, so they go too. */
  .main > * { animation-delay: 0ms !important; }
  /* The sync icon stops turning; the label still says it is working. */
  .btn.htmx-request svg { animation: none; }
  /* The bar still reports, it just stops sliding: it appears, and it goes. */
  #progress.on { width: 80%; }
}

/* ------------------------------------------------------- arriving on a page */

/* **The one thing the panel had no motion for.** Rows already rise when htmx
   swaps them, toasts arrive, the login mark breathes -- but clicking through
   the rail snapped, which made a fast panel feel abrupt rather than quick.
   Reusing `rise` rather than inventing a second vocabulary: this is the same
   movement the swapped rows make, so navigating and updating look like one
   system.
   
   Short and staggered barely: 200ms with 40ms between blocks. Longer reads as
   waiting for a page that has already arrived, which is worse than no motion
   at all on a screen somebody opens forty times a shift.

   `backwards` so the first frame is the animation's own, not a flash of the
   final position. `[data-poll]` is excluded on purpose -- the status strip and
   the journal replace themselves every few seconds, and they already settle
   through htmx; animating them again would make the page twitch on a timer. */
.main > *:not([data-poll]) { animation: rise var(--normal) var(--ease) backwards; }
.main > *:nth-child(2) { animation-delay: 40ms; }
.main > *:nth-child(3) { animation-delay: 80ms; }
.main > *:nth-child(4) { animation-delay: 120ms; }

/* ------------------------------------------------------------ chips filter */

/* One row of filter buttons above a list. Links, not buttons: without
   scripting they navigate and the page renders filtered, which is the same
   answer arrived at the slow way.

   Quiet until chosen. A row of six filled buttons would compete with the rows
   underneath, and this page already spends its colour on state: the dot on a
   chip is the same dot its rows carry, so "eșuat" is recognisable before it is
   read. */
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 8px 16px;
  border-bottom: 1px solid var(--line);
}
.chips .chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 26px;
  padding: 0 10px;
  border: 1px solid var(--line);
  border-radius: 13px;
  background: #fff;
  color: var(--ink);
  font-size: 12.5px;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
}
.chips .chip:hover { background: rgba(38, 70, 83, .04); }
/* The chosen one is filled, so which question the list is answering is legible
   from across a desk rather than by comparing six borders. */
.chips .chip.on {
  background: var(--ink);
  border-color: var(--ink);
  color: #fff;
}
/* A dot on a dark chip needs its own ring, or coral on ink reads as a smudge. */
.chips .chip.on .dot { box-shadow: 0 0 0 1.5px rgba(255, 255, 255, .55); }

/* A folded panel's chips sit under the summary, so the top border would double
   up with the summary's own. */
.panel.subunit > .chips { border-top: none; }

/* ---------------------------------------------------------------- statistici */

/* The landing page's numbers (2026-09-08). The cards reuse `.statusstrip` and
   `.stat` above, so a count and a health check read as the same kind of thing.
   What is new is the bar chart, drawn from fourteen counts in CSS alone: the
   panel renders with the internet unplugged, and a charting library for one
   row of bars is a dependency for a picture. */
.statshead { display: flex; align-items: center; gap: 14px; margin: 4px 0 12px; }
.statshead h2 { font-size: 14px; font-weight: 700; }
.statshead .chips { margin-left: auto; }

.line-chart { display: block; width: 100%; height: auto; overflow: visible; }
.line-chart .floor { stroke: var(--line); stroke-width: 1; }
.line-chart .area { fill: var(--teal); opacity: .12; }
.line-chart .trace {
  fill: none;
  stroke: var(--teal);
  stroke-width: 2.5;
  stroke-linejoin: round;
  stroke-linecap: round;
}
.line-chart .pt { cursor: pointer; outline: none; }
.line-chart .hit { fill: transparent; }
.line-chart .guide { stroke: var(--ink); stroke-width: 1; stroke-dasharray: 3 3; opacity: 0; }
.line-chart .dot { fill: var(--card); stroke: var(--teal); stroke-width: 2; }
/* Today is the point somebody is watching, so it is the one filled in. */
.line-chart .today .dot { fill: var(--teal); }
.line-chart .val {
  font-size: 13px;
  font-weight: 800;
  fill: var(--ink);
  text-anchor: middle;
  opacity: 0;
  transition: opacity var(--fast, .12s) var(--ease, ease);
}
.line-chart .day { font-size: 11px; fill: var(--ink-soft); text-anchor: middle; }
/* Hover for a mouse, focus for a finger: a tapped point takes focus on a
   touch screen, so the same two rules answer both without a script. */
.line-chart .pt:hover .val, .line-chart .pt:focus .val,
.line-chart .pt:hover .guide, .line-chart .pt:focus .guide { opacity: 1; }
.line-chart .pt:hover .dot, .line-chart .pt:focus .dot { r: 5.5; }

.stats-tables { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-top: 14px; }
@media (max-width: 900px) { .stats-tables { grid-template-columns: 1fr; } }
.stats-tables .panel .head .un { margin-left: auto; }
