/* App design system — one file, driven by the variables below.
   Numbers come from the FileMaker layouts (Pi "AAN", Omicron "TEM", 1368 px wide).
   Every screen is built from these classes; nothing here is table-specific.
   See docs/design.md for the reasoning and the FM style-name mapping. */

[hidden] { display: none !important; }   /* `hidden` wins over any display rule (the picker window, the subgroup rows) */

:root {
  /* grid */
  --canvas: 1368px;
  --margin: 16px;
  --col: 424px;
  --half-col: 208px;    /* half a column less half the gap: two of them and the gap make one column (Dominik, 2026-09-24) */
  --gutter: 32px;
  --bar: 40px;          /* top bar, buttons, rows: everything is 40 px tall */
  --row-gap: 4px;
  --code-box: 86px;     /* the code box on every screen (Dominik, 2026-09-13): FM's box; the longest code, 99.99.99, is 68 wide in Inter 16, centred with no padding */
  --mark: 20px;         /* the fold mark's slot on a line, between the number and the name (Dominik, 2026-09-25): the mark is 16, the slot 20; reserved on every row, paid for by the name column */
  --open-marks: 36px;   /* the room the marks stand in at a row's right end (2026-10-11): an opened long row keeps its
                           **first line** clear of them, so the row's own mark and number stay readable on the text */
  /* the screen's rows (Dominik, 2026-09-29; decisions.md → "The edit screen's rows"): a top band of 11, then 17 rows of
     44, ending on 755, then the bottom margin of 16 — the sides' own number, so the frame is one number all round. A row
     is a box of 40 with its 4 underneath, and the 4 belongs to the column, not to the row. Row n starts at 11 + (n−1)×44:
     row 1 the top bar (11), row 2 the first thing under it (55), row 9 the name (363), row 10 a title (407), row 17
     Delete (715). The band is 11 and not 5 because 705 is not a whole number of 44s and the remainder has to live in one
     place; on top it leaves the bottom margin at 16 and Delete exactly where it stands. */
  --top: 11px;          /* top bar starts at y 11 — row 1 */
  --list-top: 55px;     /* and everything under it at y 55 — row 2 */

  /* colour — hex names match the FM style names (5E, A9, E9, W, B) */
  --ink: #000000;       /* B  : record text */
  --btn-text: #5E5E5E;  /* 5E : button labels */
  --muted: #A9A9A9;     /* A9 : secondary text, placeholders */
  --line: #E9E9E9;      /* E9 : borders, selected/pressed state */
  --track: #F9F9F9;     /* F9 : the fill of a segmented bar (nav bar, phone switch), E9 frame and dividers (Dominik, 2026-09-07) */
  --field: #F9F9F9;     /* normal field fill, hover */
  --pink: #FFF9FB;      /* P — FM's "fill here": the tint of every editable box on an edit screen (Dominik, 2026-09-12) */
  --surface: #FFFFFF;   /* W  : white fills */
  --green: #34C759;     /* Apple system green (iOS), Dominik 2026-09-05: add on hover */
  --green-dark: #2A9F47;   /* add while pressed */
  --danger: #FF3B30;    /* Apple system red: delete on hover, error text */
  --highlight: #FFD60A; /* Apple system yellow: an element's bookmark, the print's highlight of a change (Dominik, 2026-09-17) */
  --option: #FF6B5B;    /* coral: an option element's ribbon (Dominik, 2026-09-17) */
  --ice: #A7DBF2;       /* ice blue: a frozen element's ribbon (Dominik, 2026-09-17) */
  --behind: #FF9F0A;    /* Apple system orange: behind Levels — the ribbon, the row's line and the band's count (Dominik, 2026-09-27) */
  --danger-dark: #C92A20;  /* delete while pressed */
  --band-open: var(--btn-text);    /* 5E: an opened element on the estimate's elements page, the dark grey band (Dominik, 2026-09-14) */
  --band-open-2: var(--muted);     /* A9: an opened calc line, the light grey band — for the CAL step */

  /* shape */
  --r-pill: 25px;       /* r25 */
  --r-card: 5px;        /* r5 */
  --border: 1px;

  /* type */
  --font: "Inter", -apple-system, "Helvetica Neue", Arial, sans-serif;  /* same as the website */
  --font-mark: "bc-civitas", "Inter", Arial, sans-serif;  /* KALKULATOR wordmark only */
  --fs: 16px;
  --fs-small: 12px;
  --inset: 16px;        /* L8/L56 in FM: horizontal text inset */
  --inset-icon: 56px;   /* text inset when a leading icon is present */
}

/* --- page --------------------------------------------------------------- */

* { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--surface);
  color: var(--ink);
  font: var(--fs)/1.25 var(--font);
}

a { color: inherit; }

.canvas {
  width: var(--canvas);
  height: 100vh;         /* definite height: lists scroll inside their column */
  margin: 0 auto;
  padding: var(--top) var(--margin) var(--margin);
  display: grid;
  grid-template-rows: auto 1fr;
  row-gap: 0;
}

/* three columns: 424 / 32 / 424 / 32 / 424 */
.columns {
  display: grid;
  grid-template-columns: repeat(3, var(--col));
  column-gap: var(--gutter);
  align-items: stretch;
  min-height: 0;
}

.topbar    { height: var(--bar); margin-bottom: calc(var(--list-top) - var(--top) - var(--bar)); }

/* a column is a stack of fixed-height pieces; only a list is flexible and scrolls */
.col { min-width: 0; min-height: 0; display: flex; flex-direction: column; gap: var(--row-gap); }
.col > * { flex: none; }
.col > .list { flex: 1 1 0; }

/* --- top bar pieces ------------------------------------------------------ */

.wordmark {
  font-family: var(--font-mark);
  font-weight: 700;
  font-size: var(--fs);
  letter-spacing: 0.04em;
  text-decoration: none;
  line-height: var(--bar);
}


/* quick find: pill with search icon left and clear button right */
.quickfind {
  position: relative;
  height: var(--bar);
  border: var(--border) solid var(--line);
  border-radius: var(--r-pill);
  background: var(--surface);
}

.quickfind input {
  width: 100%;
  height: 100%;
  border: 0;
  border-radius: var(--r-pill);
  background: transparent;
  padding: 0 var(--bar) 0 var(--inset-icon);
  font: inherit;
  color: var(--ink);
}

.quickfind input::placeholder { color: var(--line); }                       /* placeholder as light as the frame */
.quickfind input::-webkit-search-cancel-button { -webkit-appearance: none; }  /* the browser's own ×: hidden, we have ours */
.quickfind input:focus { outline: none; }
.quickfind:hover:not(:has(.finder summary:hover, .clear:hover)), .quickfind:focus-within { background: var(--field); }   /* hover and typing: F9 on the whole pill; on a small button only that button (Dominik, 2026-09-07) */
.quickfind input:placeholder-shown ~ .clear { display: none; }                /* × only when there is text */

.quickfind .icon, .quickfind .clear {
  position: absolute;
  top: 0;
  width: var(--bar);
  height: 100%;
  display: grid;
  place-items: center;
  color: var(--line);          /* icon in E9, like the frame */
  border: 0;
  background: transparent;
  padding: 0;
  font: inherit;
  text-decoration: none;
  cursor: pointer;
}

.quickfind .clear { color: var(--btn-text); border-radius: 50%; }   /* × is a button: 5E like every button */
.quickfind .clear:hover { background: var(--field); }                /* hover: a round F9 fill on the × only, same tint as the pill */
.quickfind .icon  { left: 0; pointer-events: none; }
/* the finder: the magnifying glass as a button, its choices drop out as pills (like + New) */
.quickfind .finder { position: absolute; left: 0; top: 0; width: var(--bar); height: 100%; z-index: 2; }
.quickfind .finder summary { list-style: none; display: grid; place-items: center; width: 100%; height: 100%; border-radius: 50%; color: var(--btn-text); cursor: pointer; }
.quickfind .finder summary::-webkit-details-marker { display: none; }
.quickfind .finder summary .icon { position: static; width: auto; height: auto; color: var(--btn-text); pointer-events: none; }   /* the glass in 5E: it is a button now */
.quickfind .finder summary svg { width: 20px; height: 20px; }
.quickfind .finder summary:hover { background: var(--field); }
.quickfind .finder[open] summary { background: var(--line); }
.quickfind .finder .menu { left: 0; right: auto; width: 280px; }
.quickfind .finder ~ input { padding-left: var(--inset-icon); }
.quickfind .icon svg { width: 20px; height: 20px; }
.quickfind .clear { right: 0; }

/* --- the bar: nav bar, action bar, phone column switch ------------------ */
/* Several buttons (segments) in one pill, Safari-tab style: a soft grey track, the active
   segment a white raised pill, thin dividers only between inactive segments. */

.segments {
  display: flex;
  height: var(--bar);
  padding: 0;                   /* segments fill the track's height; the active one covers its frame (Dominik, 2026-09-07) */
  column-gap: 4px;              /* segments never touch each other */
  border: var(--border) solid var(--line);   /* E9 frame like every other pill (Dominik, 2026-09-07) */
  border-radius: var(--r-pill);
  background: var(--track);                  /* F9 fill, segments transparent so they are F9 too */
}

.segments > * {
  position: relative;
  flex: 1 1 0;
  display: grid;
  place-items: center;
  height: 100%;
  border: 0;
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--btn-text);
  font: inherit;
  text-decoration: none;
  padding: 0 8px;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* divider: a thin line at the left of every segment except the first, hidden next to the active one */
.segments > * + *::before {
  content: "";
  position: absolute;
  left: 0;                      /* at the segment's edge: the segment clips its overflow, so a line in the gap was never seen */
  top: 25%;
  height: 50%;
  width: var(--border);
  background: var(--line);    /* E9 on the F9 track */
}
.segments > .is-active::before, .segments > .is-active + *::before,
.segments > :where(:not([aria-disabled="true"])):hover::before, .segments > :where(:not([aria-disabled="true"])):hover + *::before { display: none; }

.segments > :where(:not([aria-disabled="true"])):hover { background: rgba(255, 255, 255, 0.55); }
.segments > :where(:not([aria-disabled="true"])):active { background: rgba(255, 255, 255, 0.85); }
.segments > .is-active { background: var(--surface); border: 3px solid var(--line); padding: 0 5px; margin: -1px; height: calc(100% + 2px); }  /* active: white pill, full bar height, 3 px E9 frame over the track's frame; padding 5 + frame 3 = the others' 8, so every segment is equally wide (Dominik, 2026-09-18); the shadow smudged on the F9 track (Dominik, 2026-09-07) */
.segments > [aria-disabled="true"] { cursor: default; }   /* not built yet: looks like every other segment */

/* the branch bar (top middle): not a track, a plain white pill with the E9 frame and icon buttons
   inside; no active segment, no dividers; hover = F9 pill on the icon, like a row */
.branchbar { background: var(--surface); border: var(--border) solid var(--line); padding: 1px; }
.branchbar > * { color: var(--btn-text); }
.branchbar > *::before { display: none; }
.branchbar > :where(:not([aria-disabled="true"])):hover, .branchbar > :where(:not([aria-disabled="true"])):active { background: var(--field); }
.branchbar > .is-active { background: transparent; border: 0; padding: 0 8px; margin: 0; height: 100%; }   /* the branch bar has no active state */
.branchbar > .is-active:hover { background: var(--field); }
.branchbar > * { flex: 0 0 40px; }                   /* icon buttons: fixed 40 px, same position on every screen (icons only, no words — Dominik, 2026-09-10) */
.branchbar .initial { display: none; }                /* phone only: the who-segment shrinks to the user's initial */
.branchbar .who { flex: 1 1 auto; min-width: 60px; } /* the who-segment takes whatever is left */
.branchbar .who, .branchbar .who summary { display: flex; flex-direction: column; justify-content: center; align-items: flex-start; width: 100%; padding: 0 var(--inset); border-radius: var(--r-pill); }
.branchbar .who { padding: 0; }                       /* the inset sits on the menu button (summary) … */
.branchbar .who:not(:has(summary)) { padding: 0 var(--inset); }   /* … or on the segment itself where there is no menu (design sheet) */
.branchbar .who .line { display: block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--fs-small); line-height: 15px; color: var(--btn-text); }
.branchbar .who .line.sub { color: var(--muted); }
.branchbar .who .menu { left: 0; right: auto; width: 280px; }
.menu.v-pills form { display: contents; }
.menu.v-pills > form > .btn { width: 100%; justify-content: flex-start; }

.segments svg { width: 24px; height: 24px; }

/* the user menu: a small panel under the bar, no script (details/summary) */
/* the menu panel: one panel for every popup (user menu, "+ New"); a details/summary, no script */
.usermenu, .newmenu { position: relative; overflow: visible; }
.usermenu summary, .newmenu summary { list-style: none; height: 100%; cursor: pointer; }
.usermenu summary::-webkit-details-marker, .newmenu summary::-webkit-details-marker { display: none; }
.menu {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 3;
  width: var(--half-col); padding: 4px; display: flex; flex-direction: column; gap: var(--row-gap);   /* a menu dropped from a button or a disc is a dropdown, so it takes half a column (the pill norms, 2026-09-27) */
  background: var(--surface); border: var(--border) solid var(--line); border-radius: var(--r-card);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.10); white-space: nowrap; color: var(--ink);
}
.menu form { display: contents; }
/* the calendar (sample, Dominik 2026-09-08): our own month grid under a date field, in the menu panel's look.
   Days are 32 px discs in a 7-column grid; today = E9 disc, the chosen day = green disc with white figure,
   other months' days A9; the month header with thin chevrons; Today · Clear as small pills below. */
.cal { width: 280px; padding: 8px; gap: 4px; white-space: normal; }
.menu-demo .cal { left: 0; right: auto; }   /* the sample on the sheet opens under the field's left edge */
.cal-head { display: grid; grid-template-columns: 32px 1fr 32px; align-items: center; height: 32px; font-weight: 500; text-align: center; }
.cal-head button { width: 32px; height: 32px; border: 0; border-radius: 50%; background: transparent; color: var(--btn-text); display: grid; place-items: center; cursor: pointer; }
.cal-head button:hover { background: var(--field); }
.cal-head button.is-prev svg { transform: scaleX(-1); }
.cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.cal-grid .wd { height: 24px; line-height: 24px; text-align: center; font-size: var(--fs-small); color: var(--muted); }
.cal-grid .d { height: 32px; margin: 0 auto; width: 32px; border-radius: 50%; border: 0; background: transparent; font: inherit; font-variant-numeric: tabular-nums; color: var(--ink); display: grid; place-items: center; cursor: pointer; }
.cal-grid .d:hover { background: var(--field); }
.cal-grid .d.is-other { color: var(--muted); }
.cal-grid .d.is-today { background: var(--line); }
.cal-grid .d.is-picked { background: var(--green); color: var(--surface); }
.cal-foot { display: flex; gap: 8px; justify-content: flex-end; padding-top: 4px; }
.cal-foot .btn { height: 32px; min-width: 72px; font-size: var(--fs-small); }
/* the "+ New" menu (Dominik, 2026-09-06): pills, no panel; green frame, fills green on hover */
/* a menu of pills (Dominik, 2026-09-20): the pills are white and solid — nothing reads through the words — and they float on frosted glass
   with no frame, so what lies behind the gaps is a quiet blur and the menu is one thing, not stripes over a list */
.menu.v-pills { width: var(--half-col); padding: 4px; gap: 4px; border: 0; background: rgba(255, 255, 255, 0.55);
  -webkit-backdrop-filter: blur(18px) saturate(1.4); backdrop-filter: blur(18px) saturate(1.4); }
.menu.v-pills .btn { justify-content: flex-start; background: var(--surface); }
.menu.v-pills .btn-green:hover { background: var(--green); }                                                   /* the green pill fills green with white text, as everywhere (the frosted rule outranked it: label vanished) */
.menu.v-pills .btn-green:active { background: var(--green-dark); }
.menu.v-pills .btn[aria-disabled="true"] { color: var(--muted); border-color: var(--line); cursor: default; }   /* not built yet */

.menu-demo { position: relative; }
.menu-space { height: 180px; }   /* room under an opened demo menu on the sheet */
.menu button.row { width: 100%; font: inherit; text-align: left; }
.menu .row { padding-left: var(--inset); }

/* --- buttons ------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;                    /* icon slot at the left of the label, as in Rho */
  height: var(--bar);
  padding: 0 24px;
  border: var(--border) solid var(--line);
  border-radius: var(--r-pill);
  background: var(--surface);
  color: var(--btn-text);
  font: inherit;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}

.btn:where(:not([aria-disabled="true"]):not(:disabled)):hover  { background: var(--field); }
.btn:where(:not([aria-disabled="true"]):not(:disabled)):active, .btn.is-pressed { background: var(--line); color: var(--ink); }
.btn:disabled, .btn[aria-disabled="true"] { color: var(--muted); cursor: default; background: var(--surface); }
.btn svg { width: 20px; height: 20px; flex: none; }
.btn-block  { display: flex; width: 100%; }

/* the card buttons: everything you can do with the record, as separate pill buttons of equal width,
   together as wide as the column, 8 px apart. Pencil = edit screen, arrow = open the children list,
   plus = "+ New" popup, and the go-somewhere actions a screen has (docs, mail, site, call). */
.cardbtns { display: flex; gap: 8px; }
.cardbtns > * { flex: 1 1 0; min-width: var(--bar); height: var(--bar); display: grid; place-items: center; border: var(--border) solid var(--line); border-radius: var(--r-pill); background: var(--surface); color: var(--btn-text); cursor: pointer; text-decoration: none; }
.cardbtns > :where(:not([aria-disabled="true"])):hover { background: var(--field); }
.cardbtns > :where(:not([aria-disabled="true"])):active { background: var(--line); }                     /* pressed, like every button */
/* the add button: green on hover, green filled while pressed or open */
.cardbtns > .newmenu:hover { color: var(--surface); border-color: var(--green); background: var(--green); }
.cardbtns > .newmenu:active, .cardbtns > .newmenu[open] { color: var(--surface); border-color: var(--green-dark); background: var(--green-dark); }
/* the language button (2026-09-27, access.md L6): it drops a menu like "+ New" and looks like the preview switch, not
   like a green action — nothing is created by reading. The icon cannot say which language is on, so the button stays
   pressed while one is and its title names it */
.cardbtns > .langmenu:hover, .cardbtns > .langmenu[open] { color: var(--btn-text); border-color: var(--line); background: var(--field); }
.cardbtns > .langmenu[data-on] { background: var(--line); }
.cardbtns > [data-copied] { background: var(--line); }                              /* the analyze press has copied its line: the pressed look, held for a moment (app.js 23) */
.cardbtns > [data-copied="no"] { background: var(--surface); color: var(--danger); border-color: var(--danger); }   /* and red when the browser would not let it — the app's own red, as Delete wears it: the one thing he could not otherwise tell */
.cardbtns > [aria-disabled="true"] { color: var(--line); cursor: default; }
.cardbtns svg { width: 24px; height: 24px; }
.cardbtns .newmenu { display: block; padding: 0; }   /* block, not grid: the summary fills the whole pill and the icon centres */
.cardbtns .newmenu summary { display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; margin: 0; padding: 0; border-radius: var(--r-pill); cursor: pointer; line-height: 0; }
.cardbtns .menu { right: 0; }
/* corner buttons (Dominik, 2026-09-07): round 40 px, top right of the column, like the corner buttons of an iPhone screen */
.cardbtns.is-corner { justify-content: flex-end; }
.cardbtns.is-corner > * { flex: 0 0 var(--bar); width: var(--bar); border-radius: 50%; }
.cardbtns.is-corner .newmenu summary { border-radius: 50%; }
/* the picker (Dominik, 2026-09-07): column 3 = every row that can be linked, the linked ones ticked (green disc), a click
   toggles the link; its quick find and the × sit in the top bar where the nav bar was (.pickbar) */
.picker { display: flex; flex-direction: column; flex: 1 1 0; min-height: 0; }
.picker > .list { flex: 1 1 0; }
[inert] { opacity: 0.45; }                          /* modal: the blocked parts dim, the picker and Done stay live (Dominik, 2026-09-07) */
.pickbar { display: flex; gap: 8px; height: var(--bar); }
.pickbar.is-right { justify-content: flex-end; }                                                    /* Done alone at the right (edit screen) */
/* the close stands apart from the record's buttons (Dominik, 2026-09-20): its own nav, a gap and a hairline between them, so a × is never
   a mis-tap away from "add elements". Only where a screen has no second row for the record's buttons — the estimate's elements page;
   design.md holds the rule. The line is a drawing, not a button: no frame, no hand. */
/* the groups in the corner stand apart, the same 8 px the column head's three already keep (Dominik, 2026-09-27:
   "there is no space between buttons" — the rule was written for the column head and the corner never got it, which
   showed the day the language menu came to stand beside the sparkle, 2026-10-09). The hairline keeps its own 12. */
.corner-row { display: flex; align-items: center; justify-content: flex-end; gap: 8px; }
.corner-row > .sep { flex: none; width: 1px; min-width: 0; height: 24px; margin: 0 4px; background: var(--line); cursor: default; }
.cardbtns.is-corner.is-left { justify-content: flex-start; }                                        /* no screen uses it since 2026-09-20; kept for a left-hand corner if one comes back */
.screen-title { text-align: center; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }   /* the edit screen's title, centred in the middle bar; a long one is cut at its end with … */
/* the edit screen's top (Dominik, 2026-09-07): the record's face and name centred over the three columns, the middle column's width */
.hero-row { margin-bottom: calc(var(--list-top) - var(--top) - var(--bar)); }   /* the face in the middle, a section on either side when the form places one there (Dominik, 2026-09-11) */
.hero { width: var(--col); margin: 0 auto; }
.hero-row .col > .card { flex: none; overflow: visible; }   /* the face and name take their own height; only a list scrolls inside a column */
/* **The face block — rows 2 to 9** (Dominik, 2026-09-29; decisions.md → "The name row, and what grows into the face").
   55 to 403, 348 tall: the picture 304 (rows 2–8, seven rows exactly), its 4, then **one box of 40 holding the name and
   the grey line under it** — the name 23 on top, the line 17 under. The grey line **always holds its 17**, filled or
   empty, which is what makes the fields under it start at **407 on every screen** instead of at 399, 416 or 418
   depending on what the record happens to carry. That difference is what stood Delete 17 too high on AAN · CON · PRO.
   **A long name grows upwards.** Its foot stays at 386 and it takes as many lines as it needs, over the picture, stopping
   at the top of the block (row 2) — never into the buttons bar. Nothing below it moves: the grey line stays at 403 and
   row 10 still starts at 407. This replaces the two-line clamp of 2026-09-27, which existed to stop a sentence name
   pushing the fields out of the window; growing upwards solves that without spending a row.
   The block is **opaque white and sits over the picture**, which is neither scaled nor cropped, only covered (Dominik:
   "sit over and make sure it is on white background"). Scaling was offered and refused: a logo, a client's drawing and
   the project's map would all shrink for one long name, and white behind the name is what keeps it readable over
   whatever a contractor uploads. What a long name costs is the bottom of the picture. */
.card.hero { position: relative; height: 348px; }          /* rows 2–9: the picture 304, its 4, the name box 40 */
.card.hero .figure { height: 304px; }   /* the edit screen's face is 424 × 304, as FileMaker's edit layouts: the whole screen then fits the Air without scrolling (Dominik, 2026-09-11) */
.card.hero > .nameblock {
  position: absolute; left: 0; right: 0; bottom: 0;
  max-height: 100%; overflow: hidden;                      /* it may cover the whole picture and no more — the block's own height, never a second copy of 348 */
  background: var(--surface);                              /* opaque: the picture underneath never shows through the name */
  display: flex; flex-direction: column; justify-content: flex-end;   /* the foot is fixed, so every extra line is added on top */
}
.card.hero > .nameblock > .title { flex: none; min-height: 0; display: block; line-height: 23px; padding: 0 1px; text-align: center; }
.card.hero > .nameblock > .subtitle { flex: none; min-height: 0; height: 17px; display: block; line-height: 17px; padding: 0 1px; text-align: center; overflow: hidden; }
/* The mark that opens the old FileMaker text (description screens only) keeps its place under the grey line and rides inside the block, so
   the block is 68 there instead of 40 and still grows upwards from the same foot: the fields under it start at 407 on that screen too. */
.card.hero > .nameblock > .textmark { flex: none; }
/* the kind pills over the columns (STORE: labour · material · equipment · subcontract): the nav bar's own
   track, only as wide as its words and centred over the three columns. A screen that draws them says so on
   the canvas, so the lists keep the height they have on every other screen. */
.colhead > .kinds { width: 100%; }   /* STORE's kind pills fill the left column's title line (Dominik, 2026-09-10) */
.canvas.is-page { height: 100vh; grid-template-rows: auto auto minmax(0, 1fr); align-content: start; }   /* the edit screen has the list screens' fixed height: top bar, the face row, the columns to the bottom margin (Dominik, 2026-09-12) */
.canvas.is-page.is-faceless { grid-template-rows: auto minmax(0, 1fr); }   /* no face row at all (the estimate, Dominik 2026-09-29): two rows, not three, so the columns start at row 2 and the screen keeps its fixed height */
/* **The two rules of an edit screen** (Dominik, 2026-09-29; decisions.md → "The size of a screen, measured"). They are separate on purpose:
   one is about how full a screen is, the other about who owns a place.
   1 · **The screen** is built for a window of 1368 × 771 — his own working window on the Air, dock and browser bars included, measured that
       morning. **Nothing ends below 755**: the window less the app's own 16 bottom margin, the same 16 it keeps at the sides.
   2 · **A fixed item** stands at a **stated number**, measured from the top of the screen, and depends on nothing else on that screen.
       There is exactly one today: **Delete, at 715–755**, the middle column's own. A screen without a Delete leaves that space empty, and
       a new fixed item is allowed only once it is listed with its own number — no number, not on the screen.
   The numbers live here and in the guard that holds them (estimation/tests.py → ScreenFitsTests), and nowhere else.
   Named `--screen-line`, never `--line`: that one is the frame colour of every box in App, and a length in it makes
   `border: 1px solid 755px`, which draws no frame anywhere (learnt the hard way, 2026-09-29) */
.canvas.is-page { position: relative; --screen-line: 755px; --row-17: calc(var(--screen-line) - var(--bar)); }
/* Delete stands at a **stated number** (Dominik, 2026-09-29; decisions.md → "A pinned item stands at a stated number"): 715 measured from the
   top of the screen, placed against the screen itself, depending on nothing else on it — not the face, not the fields above it, not how tall a
   card happens to be. Its place was *computed* until today ("715 less where the columns start"), and where the columns start was written as one
   number that is only true of a face block carrying the code line under the name: the library had it, AAN · CON · PRO have not, and Delete
   stood 17 too high on every one of them. A computed place is not pinned — it only looks pinned until a screen differs.
   The middle column's own place, its width: x = the margin and one column and one gutter. Nothing above may reach 715, and that is the size
   rule's business, proven by the guard (estimation/tests.py → ScreenFitsTests), never by the layout holding Delete down.
   Desktop only: the phone has no fixed window and Delete stands under the form, where the page scrolls to it. */
@media (min-width: 901px) {
  .canvas.is-page main .col[data-col="middle"] > form[data-confirm] {
    position: absolute;
    top: var(--row-17);
    left: calc(var(--margin) + var(--col) + var(--gutter));
    width: var(--col);
    height: var(--bar);
  }
  /* *(gone 2026-10-09.)* **Ready for the user** was the left column's own fixed item from 2026-09-29 — row 17,
     715–755, one column to Delete's left — with its refusal floating above it, since row 17 has nothing under it. The
     switch left every screen with the three screens it stood on (part two): the mark is `manage.py ready`'s, and
     nothing in the app writes it. Delete above is the one fixed item again. */
}
.canvas.is-page .col > .edit { flex: none; overflow: visible; }

/* the edit screen's top bar: × · the title · Done — the title takes everything between the two buttons, so a long
   name (a STABU text row) is read whole (Dominik, 2026-09-12). Desktop only: the phone keeps its own top bar (below) */
@media (min-width: 901px) {
  /* **The screen's name stands in the middle of the screen** (Dominik, 2026-10-07: "gray text in nav middle column is not
     centered"). It was `auto 1fr auto`: the empty left column took no width, so the middle was everything left over beside
     the presses and its centre sat half their width too far left — and it **moved** with the number of presses a record
     carries. Two equal shares on either side of the name hold it in the middle whatever stands there, and a name too long
     for what is left is cut with its … as before. The right share **never gives up what its presses need**: they are
     `flex: 1 1 0` inside it, so a plain `1fr` lets them shrink to nothing and a 166-character name pushes the × off the
     screen — `max-content` is the floor that stops it, measured on STABU's longest name. */
  .canvas.is-page .topbar { grid-template-columns: minmax(0, 1fr) auto minmax(max-content, 1fr); column-gap: var(--gutter); }
}
.hero-row .col > .list { flex: 1 1 0; min-height: 0; overflow-y: auto; }   /* a list beside the face (an item's "used in", later) takes the face's height and scrolls inside (Dominik, 2026-09-12) */
.hero-row.is-tall-left .col:first-child { position: relative; }          /* a group's rows: nothing else stands in column 1, so the list runs from the top to the bottom margin, over the empty column below */
.hero-row.is-tall-left .col:first-child > .list { position: absolute; left: 0; right: 0; top: calc(var(--bar) + var(--row-gap)); height: calc(100vh - var(--list-top) - var(--bar) - var(--row-gap) - var(--margin)); }
/* On the screen the portal runs from row 3 to row 16 — 99 to 711, its title on row 2 and **row 17 left to the Ready switch** (Dominik,
   2026-09-29: "extend the used in portal till 16"). 612 is those fourteen rows: 14 × 44 − 4. Most of it stands empty on most records, which is
   the point — the list never moves and never decides where anything else goes. The phone has no row 17 and no fixed window, so it keeps the
   height it had. */
@media (min-width: 901px) {
  .hero-row.is-tall-left .col:first-child > .list { height: 612px; }
}
/* **What the record is wired to, rows 2–17** (Dominik, 2026-10-09: "make it full height"): on a template's sheet the
   right column holds one list — its STABU lines and its alternatives — and nothing else, so it runs the whole height
   beside the face and under it, over the empty column below, exactly as the description does on the left and the
   "used in" portal does on an edit screen. Its title stands on row 2 and the rows run 3 to 17: **656** is those
   fifteen rows, 15 × 44 − 4, and it ends on 755, the app's own line — 659 put it three pixels past it, measured on
   the screen. The phone has no grid of rows, so there the list keeps the height it had and stands after the face. */
.hero-row.is-tall-right .col:last-child { position: relative; }
@media (min-width: 901px) {
  .hero-row.is-tall-right .col:last-child > .list {
    position: absolute; left: 0; right: 0; top: calc(var(--bar) + var(--row-gap)); height: 656px;
  }
}
@media (max-width: 900px) {
  .hero-row.is-tall-right .col:last-child { order: 3; }
  .hero-row.is-tall-right .col:last-child > .list { max-height: 60vh; }
}

/* **The description read out** (Dominik, 2026-10-01): one white box in the left column, **rows 2–17** — 55 to 755, the full height beside the
   face and under it, over the empty column below exactly as the "used in" portal does. Inside it the questions stand one under the other,
   **the question grey and what was written under it in ink, both centred**; a long description scrolls inside the box, so nothing it holds can
   move anything else on the screen. 700 is those sixteen rows: 16 × 44 − 4. templates/parts/edit_screen.html, library/forms.py → ReadingForm */
.hero-row.is-tall-left .col:first-child > .readout {
  display: flex; flex-direction: column; justify-content: safe center;   /* the text in the middle of the field (Dominik, 2026-10-01); `safe` so a description taller than the box still starts at its top and scrolls */
  overflow-y: auto; padding: 12px 8px; text-align: center;
  background: var(--surface); border: var(--border) solid var(--line); border-radius: var(--r-card);
}
@media (min-width: 901px) {
  /* the window: the box stands in the column's own place, rows 2–17 — 700 is those sixteen rows, 16 × 44 − 4 */
  .hero-row.is-tall-left .col:first-child > .readout { position: absolute; left: 0; right: 0; top: 0; height: 700px; }
}
@media (max-width: 900px) {
  /* **The phone has no grid of rows**: the one column stacks, so the box stands in the flow and is as tall as its text —
     and it stands **under the face**, since a record is known by its picture and its name first (Dominik, 2026-09-29). */
  .hero-row.is-tall-left .col:first-child { order: 2; }
  .hero-row.is-tall-left .col:first-child > .readout { max-height: 60vh; }
}
/* **The note, rows 2–17** (Dominik, 2026-10-07: "the description on left can be full height"): a document's note is the
   one thing in its left column, so it runs the whole height — beside the face and under it, over the empty column below,
   exactly as the description read out does. Its title takes row 2 and the box the fifteen rows under it; 700 is the
   sixteen together, 16 × 44 − 4. library/forms.py → DocForm, templates/parts/edit_screen.html */
@media (min-width: 901px) {
  .hero-row.is-tall-left .col:first-child > .edit { position: absolute; left: 0; right: 0; top: 0; height: 700px; }
}
@media (max-width: 900px) {
  /* **The phone has no grid of rows**: the box is as tall as its words, up to most of the screen, and stands under the
     face — the shape the description read out already has there. Fifteen empty rows on a phone is a page of nothing. */
  .hero-row.is-tall-left .col:first-child > .edit .fld.is-readonly.is-answer { height: auto; }
  .hero-row.is-tall-left .col:first-child > .edit .fld.is-readonly.is-answer > .val { height: auto; min-height: var(--bar); max-height: 60vh; }
}
.readout-q { margin: 0; color: var(--muted); font-size: var(--fs-small); line-height: 17px; }
.readout-a { margin: 0 0 12px; color: var(--ink); line-height: 17px; white-space: pre-wrap; }
.readout > :last-child { margin-bottom: 0; }

.pickbar > .quickfind { flex: 1 1 0; min-width: 0; }
.pickbar > .btn { flex: none; min-width: 80px; }   /* Done: a text pill is at least 80 wide and grows with its text (Dominik, 2026-09-07) */

.btn-placeholder { color: var(--line); border-color: var(--line); cursor: default; }   /* Rho btn_placholder: not available here */
/* add and delete: hover fills the colour with white text, pressed goes darker */
.btn-green  { color: var(--green); border-color: var(--green); }
.btn-green:hover  { background: var(--green); color: var(--surface); }
.btn-green:active, .btn-green.is-pressed { background: var(--green-dark); border-color: var(--green-dark); color: var(--surface); }
.btn-danger { color: var(--danger); border-color: var(--danger); }
.btn-danger:hover { background: var(--danger); color: var(--surface); }
.btn-danger:active, .btn-danger.is-pressed { background: var(--danger-dark); border-color: var(--danger-dark); color: var(--surface); }

/* --- lists and rows ------------------------------------------------------ */
/* A list fills its column and scrolls inside itself, like an FM portal. */

.list {
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--row-gap);
  padding: 0;
  margin: 0;
  list-style: none;
}

/* **An alternative stands in grey** (Dominik, 2026-10-09): on a template's sheet the right column lists what the
   template brings — its STABU lines in ink — and under them the hand-picked alternatives, which it offers instead of
   them. One token, the same A9 every secondary line of the app uses; the row itself is unchanged, so the two kinds
   read as one list and not as two. `.hint` is the line that counts the alternatives whose records are gone. */
/* a row nothing leads from: no hand, no hover, nothing to press (Dominik, 2026-10-09) */
.row.is-read { cursor: default; }
.row.is-read:hover { background: var(--surface); border-color: var(--line); }

.list > li.is-faded .row, .list > li.is-faded .row * { color: var(--muted); }
.hint { padding: 0 var(--inset); color: var(--muted); font-size: var(--fs-small); }

/* **A note on a sheet** (Dominik, 2026-10-09): a line of advice to somebody pricing — "counted once per job" — not
   a value with a label. Grey and without the box's frame, so it reads as a sentence and not as a field, and it keeps
   the row's own height, so nothing below it moves. */
.fld.is-note { background: none; border-color: transparent; color: var(--muted); }

.list-title {
  height: var(--bar);
  display: flex;
  align-items: center;
  padding: 0 var(--inset);
  color: var(--muted);
  font-size: var(--fs-small);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.row {
  height: var(--bar);
  display: flex;
  align-items: center;
  gap: var(--inset);                       /* disc 40 + 16 = text inset 56, as in FM (L56) */
  padding: 0 var(--inset) 0 0;
  border: var(--border) solid transparent;     /* no frame; the selected row gets the E9 frame */
  border-radius: var(--r-pill);
  background: var(--surface);
  color: var(--ink);
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;     /* the row does not clip: the disc sits on its frame line; the label clips itself */
}

button.row { width: 100%; font: inherit; text-align: left; }   /* a row that posts (picker): the same row, as a button */
.row:where(:not([aria-disabled="true"])):hover { background: var(--field); }        /* also on the selected row */
.row:where(:not([aria-disabled="true"])):active { background: var(--line); }        /* pressed, like FM's pressed state */
.row.is-selected { border-color: var(--line); }   /* exactly one row per list: the row whose card is shown — the group
   repeated on top included, when its own card is open (Dominik, 2026-09-10). .is-head itself carries no mark */
.row.is-selected .disc { background: var(--line); color: var(--surface); border-color: var(--line); }
.row.is-selected .disc svg { color: var(--surface); }
.row.is-group .label { text-transform: uppercase; }   /* main groups in capitals */
.row.is-group .label .sub { text-transform: none; }   /* …but not what the second line says: "3 of 39 ready" is a count, not a name (2026-09-27) */
/* Rule (Dominik, 2026-09-10): every row in a list starts at the same left edge — an opened subgroup's items are never inset, nothing styles a row by depth. */
.row.is-subgroup { color: var(--muted); }             /* subgroup rows in A9; records black, regular */
/* **a subgroup's disc carries its number at 12** (Dominik, 2026-10-14: *"i dont like when there is no difrenc between
   group and subroup in middle column"*). A main group's number stays 16, so the step down is read on the disc as well
   as in the name — on every screen a subgroup row is drawn, not in the picker alone. */
.row.is-subgroup > .disc { font-size: var(--fs-small); }
.row[aria-disabled="true"] { color: var(--muted); cursor: default; }
/* column titles (library, Dominik 2026-09-10): the list title left, the card's corner buttons right, one 40 px line */
/* the groups of corner buttons stand apart as the buttons inside a group do (Dominik, 2026-09-27: "there is no space between buttons") —
   the language, the view switch and the record's own buttons were three flush blocks reading as one strip of five */
.colhead { display: flex; align-items: center; justify-content: flex-end; gap: 8px; height: var(--bar); }
.colhead .list-title { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.colhead .cardbtns { flex: none; }
.row .label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; line-height: 18px; }
/* a row that opens another screen (Dominik, 2026-09-18): the chevron (icons/open.html) at its right end while the mouse is on it — the middle
   columns, the finders, the Users screen's contractors; a row that only selects in place has none. Its place is kept, so the name does not
   move; drawn here, no weight per row. A touch screen has no hover: there it always shows */
.row.is-jump::after { content: ""; flex: none; width: 24px; height: 24px; margin-right: -8px; background: var(--btn-text); opacity: 0;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='155.1 -803.4 646.8 646.8'%3E%3Cpath d='M546.405 -480 357.405 -669 384 -695.595 599.595 -480 384 -264.405 357.405 -291Z'/%3E%3C/svg%3E") center / contain no-repeat; mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='155.1 -803.4 646.8 646.8'%3E%3Cpath d='M546.405 -480 357.405 -669 384 -695.595 599.595 -480 384 -264.405 357.405 -291Z'/%3E%3C/svg%3E") center / contain no-repeat; }
.row.is-jump:hover::after { opacity: 1; }
@media (hover: none) { .row.is-jump::after { opacity: 1; } }
/* the marks of a row (Dominik, 2026-09-12): 1 · eye · 9 at 12 px in A9, one slot each, at the right end of the row and under a card's title */
.marks { display: inline-flex; align-items: center; gap: 4px; flex: none; color: var(--btn-text); }   /* 5E, darker than A9, on every screen (Dominik, 2026-09-17) */
.marks svg { width: 12px; height: 12px; display: block; }
.marks > span { display: block; }
.fld > .marks { position: absolute; top: 2px; right: 8px; height: 14px; }   /* on a field: the top right corner, on the label's line */
/* what is left to do on a library row (Dominik, 2026-10-11): the `pending` mark on a record that is not ready for the user,
   and on a group the number under it still to do — at the right end, after the row's own marks, so a clean row means ready.
   A9 and not the marks' 5E: it is a number and a state of the work, not a flag of the record, and most of the library wears
   one today. The number is tabular, so a column of them does not dance as the list scrolls. */
.todo { display: inline-flex; align-items: center; gap: 4px; flex: none; margin-left: 4px; color: var(--muted); font-size: var(--fs-small); font-variant-numeric: tabular-nums; line-height: 1; }
.todo > span { display: block; }
/* The mark itself is **drawn here, not in the row** (the rule the Go-to chevron above follows): most of the library wears
   one today, and the `pending` drawing inlined 3,4 KB into every row — a shop-item picker of 200 rows weighed 790 KB
   against a budget of 180. One copy in the stylesheet, cached, and a row pays 60 bytes for the span that wears it.
   The drawing is `templates/icons/pending.html`'s own path, its numbers cut to two decimals (scripts/icons.py builds the
   file; this is the same figure, as the chevron's is above). */
.todo .pending { width: 12px; height: 12px; background: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-76.7 -1036.7 1113.4 1113.4'%3E%3Cpath d='M287.97 -426.43Q310.28 -426.43 325.93 -442.04Q341.57 -457.66 341.57 -479.97Q341.57 -502.28 325.96 -517.93Q310.34 -533.57 288.03 -533.57Q265.72 -533.57 250.07 -517.96Q234.43 -502.34 234.43 -480.03Q234.43 -457.72 250.04 -442.07Q265.66 -426.43 287.97 -426.43ZM479.97 -426.43Q502.28 -426.43 517.93 -442.04Q533.57 -457.66 533.57 -479.97Q533.57 -502.28 517.96 -517.93Q502.34 -533.57 480.03 -533.57Q457.72 -533.57 442.07 -517.96Q426.43 -502.34 426.43 -480.03Q426.43 -457.72 442.04 -442.07Q457.66 -426.43 479.97 -426.43ZM671.97 -426.43Q694.28 -426.43 709.93 -442.04Q725.57 -457.66 725.57 -479.97Q725.57 -502.28 709.96 -517.93Q694.34 -533.57 672.03 -533.57Q649.72 -533.57 634.07 -517.96Q618.43 -502.34 618.43 -480.03Q618.43 -457.72 634.04 -442.07Q649.66 -426.43 671.97 -426.43ZM480.32 -108.85Q403.72 -108.85 336.05 -137.84Q268.39 -166.82 217.61 -217.58Q166.84 -268.34 137.85 -335.95Q108.85 -403.55 108.85 -480.42Q108.85 -557.28 137.84 -624.45Q166.82 -691.61 217.58 -742.39Q268.34 -793.16 335.95 -822.15Q403.55 -851.15 480.42 -851.15Q557.28 -851.15 624.45 -822.16Q691.61 -793.18 742.39 -742.42Q793.16 -691.66 822.15 -624.29Q851.15 -556.92 851.15 -480.32Q851.15 -403.72 822.16 -336.05Q793.18 -268.39 742.42 -217.61Q691.66 -166.84 624.29 -137.85Q556.92 -108.85 480.32 -108.85ZM480 -168Q610 -168 701 -259Q792 -350 792 -480Q792 -610 701 -701Q610 -792 480 -792Q350 -792 259 -701Q168 -610 168 -480Q168 -350 259 -259Q350 -168 480 -168ZM480 -480Q480 -480 480 -480Q480 -480 480 -480Q480 -480 480 -480Q480 -480 480 -480Q480 -480 480 -480Q480 -480 480 -480Q480 -480 480 -480Q480 -480 480 -480Z'/%3E%3C/svg%3E") center / contain no-repeat; mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-76.7 -1036.7 1113.4 1113.4'%3E%3Cpath d='M287.97 -426.43Q310.28 -426.43 325.93 -442.04Q341.57 -457.66 341.57 -479.97Q341.57 -502.28 325.96 -517.93Q310.34 -533.57 288.03 -533.57Q265.72 -533.57 250.07 -517.96Q234.43 -502.34 234.43 -480.03Q234.43 -457.72 250.04 -442.07Q265.66 -426.43 287.97 -426.43ZM479.97 -426.43Q502.28 -426.43 517.93 -442.04Q533.57 -457.66 533.57 -479.97Q533.57 -502.28 517.96 -517.93Q502.34 -533.57 480.03 -533.57Q457.72 -533.57 442.07 -517.96Q426.43 -502.34 426.43 -480.03Q426.43 -457.72 442.04 -442.07Q457.66 -426.43 479.97 -426.43ZM671.97 -426.43Q694.28 -426.43 709.93 -442.04Q725.57 -457.66 725.57 -479.97Q725.57 -502.28 709.96 -517.93Q694.34 -533.57 672.03 -533.57Q649.72 -533.57 634.07 -517.96Q618.43 -502.34 618.43 -480.03Q618.43 -457.72 634.04 -442.07Q649.66 -426.43 671.97 -426.43ZM480.32 -108.85Q403.72 -108.85 336.05 -137.84Q268.39 -166.82 217.61 -217.58Q166.84 -268.34 137.85 -335.95Q108.85 -403.55 108.85 -480.42Q108.85 -557.28 137.84 -624.45Q166.82 -691.61 217.58 -742.39Q268.34 -793.16 335.95 -822.15Q403.55 -851.15 480.42 -851.15Q557.28 -851.15 624.45 -822.16Q691.61 -793.18 742.39 -742.42Q793.16 -691.66 822.15 -624.29Q851.15 -556.92 851.15 -480.32Q851.15 -403.72 822.16 -336.05Q793.18 -268.39 742.42 -217.61Q691.66 -166.84 624.29 -137.85Q556.92 -108.85 480.32 -108.85ZM480 -168Q610 -168 701 -259Q792 -350 792 -480Q792 -610 701 -701Q610 -792 480 -792Q350 -792 259 -701Q168 -610 168 -480Q168 -350 259 -259Q350 -168 480 -168ZM480 -480Q480 -480 480 -480Q480 -480 480 -480Q480 -480 480 -480Q480 -480 480 -480Q480 -480 480 -480Q480 -480 480 -480Q480 -480 480 -480Q480 -480 480 -480Z'/%3E%3C/svg%3E") center / contain no-repeat; }
.row.is-group .todo { text-transform: none; }   /* a main group's row is in capitals; a number is a number */
.row.is-selected .todo { color: var(--btn-text); }   /* on the one marked row: the same darker grey the selected row's frame has */
/* a calc line's marks in the top of its code cell (Dominik, 2026-09-17: "in code field there is space above text"), where a field's label stands: the name keeps
   its whole cell. Side by side from the left, the first mark first — no empty slot for a mark not set (Dominik, same day) — its drawing starting
   at 8 like a field's text: the box stands at 5, the figure has about 3 px of air inside its 16 px box */
.line > .fld.is-code > .marks { top: 1px; left: 5px; right: auto; height: 16px; }   /* 4 apart, as every row's marks; an element's and a library line's the same (Dominik, 2026-09-17; every line, 2026-10-10) */
.line > .fld.is-code > .marks svg { width: 16px; height: 16px; }
/* an element's bookmark (Dominik, 2026-09-17): the ribbon filled highlighter yellow with a thin 5E line, hanging from the code cell's top edge as a
   real bookmark — its figure's own air above it taken up; the print's yellow highlight of a change since the previous version */
.line.is-ele > .fld.is-code > .marks > [data-mark="bookmark_filled"] { color: var(--highlight); transform-origin: top; transform: translateY(-4.6px) scaleY(1.35); }   /* longer (Dominik, 2026-09-17): from the cell's top edge down to the other marks' foot — its figure 10,7 of its 16 px box, grown to about 14,4 */
.line.is-ele > .fld.is-code > .marks > [data-mark="bookmark_filled"].is-option { color: var(--option); }   /* the same ribbon for its status (Dominik, 2026-09-17) */
.line.is-ele > .fld.is-code > .marks > [data-mark="bookmark_filled"].is-frozen { color: var(--ice); }
.line.is-ele > .fld.is-code > .marks > [data-mark="bookmark_filled"] + [data-mark="bookmark_filled"] { margin-left: -11px; }   /* two ribbons close together (Dominik, 2026-09-17): each figure 7,5 of its 16 px box, 12,5 apart with the gap — brought to 1,5 */
.line.is-ele > .fld.is-code > .marks > [data-mark="bookmark_filled"] path { stroke: var(--btn-text); stroke-width: 40; paint-order: stroke; }
.line.is-cal > .fld.is-one-off { display: grid; place-items: center; color: var(--ink); }   /* a one-off line's qty cell holds the one-off mark, not the number — its qty is its norm (Dominik, 2026-09-17: "important and easy to miss") */
.line.is-cal > .fld.is-one-off > .val { display: none; }
.line.is-cal > .fld > .lbl { color: var(--btn-text); }
.line.is-ele:is(.is-option, .is-frozen) > .fld.is-cell.is-readonly > .val { color: var(--muted); }
.line.is-ele.is-disabled { opacity: 0.45; }   /* a disabled element, shown to bring it back (Dominik, 2026-09-17): dimmed, its name struck through */
.line.is-ele.is-disabled > .fld.is-name > :is(.val, .input) { text-decoration: line-through; }
.cardbtns > [data-show-disabled][aria-pressed="true"] { background: var(--line); }   /* the switch on: pressed */   /* an option element: priced, not counted in the totals — its amounts A9 (Dominik, 2026-09-17) */   /* "9 %" over Lab, Mat, OA (Dominik, 2026-09-17): 5E as the marks; none over an empty cell (the rule of every amount cell) */
.line.is-cal > .fld.is-one-off > svg { width: 24px; height: 24px; }   /* a 16 box, its figure about 11 — as tall as the code's digits (Dominik, 2026-09-17: "I want them 16 as text"); 1–17 px, above the code's line at 18 */
/* a resource's count in the top of its code cell, where a calc line has its marks (Dominik, 2026-09-17): how many resources of the same shop item the
   estimate holds, from 2 — 12 px as a field's label, 5E as the marks, starting at 8 like a field's text */
.line.is-res > .fld.is-code > .uses { position: absolute; top: 2px; left: 8px; font-size: var(--fs-small); line-height: 14px; color: var(--btn-text); pointer-events: none; }
/* a resource's words that open a popup (Dominik, 2026-09-17; a price box stands in its form, so the rules look one level deeper): "Pack 3" over the unit, "Lib 10,00" over the unit price — 12 px, 5E, where a field's
   label stands; a quiet "Pack" (pack size 1) only while the mouse is on the row; the popup under the cell, over the rows below */
.line.is-res .fld > details.words { position: absolute; top: 2px; left: 0; right: 0; height: 14px; }
.line.is-res .fld > details.words > summary { list-style: none; cursor: pointer; font-size: var(--fs-small); line-height: 14px; color: var(--btn-text); text-align: center; white-space: nowrap; }
.line.is-res .fld > details.words > summary::-webkit-details-marker { display: none; }
.line.is-res .fld > details.words > summary:hover { text-decoration: underline; }
.line.is-res .fld > details.words.is-quiet > summary { color: var(--muted); }
.line.is-res:not(:hover) .fld > details.words.is-quiet:not([open]) { visibility: hidden; }
.line.is-res .fld > details.words > .menu { top: 42px; left: 50%; right: auto; transform: translateX(-50%); z-index: 20; width: var(--half-col); display: flex; flex-direction: column; gap: var(--row-gap); padding: 8px; text-align: left; }
.line.is-res .fld > details.words > .menu > .fld > .lbl { right: 8px; }
/* the note after a change that touched several resources (Dominik, 2026-09-17): frosted, at the bottom, gone after 3 s */
.notice { position: fixed; left: 50%; bottom: 24px; z-index: 50; transform: translateX(-50%); padding: 8px 16px; border: var(--border) solid var(--line); border-radius: 25px; background: rgba(255, 255, 255, 0.72); -webkit-backdrop-filter: blur(18px) saturate(1.4); backdrop-filter: blur(18px) saturate(1.4); font-size: var(--fs-small); color: var(--btn-text); transition: opacity 400ms; pointer-events: none; }
.notice.is-gone { opacity: 0; }
.notice .undo { pointer-events: auto; padding: 0; border: 0; background: none; font: inherit; font-weight: 600; color: var(--btn-text); cursor: pointer; }   /* "Arbeid removed · Undo" (Dominik, 2026-09-17): the one thing on the note to click */
.notice .undo:hover { text-decoration: underline; }
.notice.is-stay { bottom: 72px; pointer-events: auto; cursor: pointer; color: var(--ink); }   /* a note shown once (a temporary password): it waits until clicked away (Dominik, 2026-09-19) */
/* Done's one question on an edit screen with a red box (Dominik, 2026-09-19): "<reason> Finish and keep '<old value>'?" — a frosted card over
   the dimmed screen, as the picker windows, Go back first */
dialog.ask { width: 424px; max-width: calc(100vw - 32px); padding: 24px; border: var(--border) solid var(--line); border-radius: 25px; background: rgba(255, 255, 255, 0.72); -webkit-backdrop-filter: blur(18px) saturate(1.4); backdrop-filter: blur(18px) saturate(1.4); box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08); color: var(--ink); }
dialog.ask::backdrop { background: rgba(255, 255, 255, 0.5); }
dialog.ask p { margin: 0 0 20px; }
dialog.ask .ask-buttons { display: flex; justify-content: flex-end; gap: 8px; }
dialog.ask .btn { min-width: 80px; }
.notice.is-gone .undo { pointer-events: none; }
.row .sub { display: block; font-size: var(--fs-small); color: var(--muted); line-height: 14px; overflow: hidden; text-overflow: ellipsis; }  /* optional second line */
/* **the marks stand on the second line** (Dominik, 2026-10-16: *"move all icons from right side of the row to the
   second grey line … when there is no translation only icons are there"*). Only that line changes shape — every other
   row's second line (a client's, an estimate's, a file's) stays the plain clipped text it was. The right end keeps
   what is **left to do** alone — the pending mark and a group's number — so running the eye down the right edge still
   shows the work, while the grey line says what the record **is**. The words give way to the marks, never the marks
   to the words. */
.row .sub:has(> .marks) { display: flex; align-items: center; gap: 6px; line-height: 16px; min-width: 0; overflow: visible; }
.row .sub > .say { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.row .sub > .marks { flex: none; }

/* long rows (STABU texts up to 170 chars): "…" always; on hover (or tap on the disc) the name opens to its whole text
   **taller, inside its own column** (Dominik, 2026-10-11: "so it stays in one column instead of going to the right
   column") — it wraps over as many lines as it needs and **lies over the rows below**, which do not move: the row keeps
   its 40 px place on the grid, as every box that grows in App does (decisions.md → "A box that grows over what is under
   it", 2026-09-29). Pushing the rows down was refused: the row under the mouse would move away from it and the list
   would twitch on every hover. Until 2026-10-11 it ran on to the **right**, over the next column, one line.
   Marked by the screen (is-long) so short rows never pay for it. `position: fixed` lets the text escape the list's
   scrolling frame; app.js hands over the row's screen position and width (--row-x / --row-y / --row-b / --row-w) and
   says with `is-up` when the row stands low enough that the text must open **upward** instead — then its last line
   keeps the row's own line and the text grows up over what is above, as a name grows up over the face. */
.row.is-long:hover .label, .row.is-long.is-open .label, .row.is-long.is-measuring .label {
  position: fixed;
  left: calc(var(--row-x, 0px) + var(--border));
  top: calc(var(--row-y, 0px) + var(--border));
  z-index: 2;
  /* **the row's own width, to its very edges** (Dominik, 2026-10-11: "it does make it a bit weird, not full width of
     the row, looks strange"): the open text is the row grown taller, so it is the same pill, and the rows it covers
     are covered whole — no disc of theirs peeping out beside it. The text keeps the row's own inset, 40 + 16 */
  width: calc(var(--row-w, var(--col)) - 2 * var(--border));
  white-space: normal;
  min-height: calc(var(--bar) - 2 * var(--border));
  /* whole rows, never half of one (Dominik, 2026-10-11): app.js measures the open text and hands back the next whole
     number of rows — one row, then 44 for each it covers. Free (`auto`) only while it is being measured. */
  height: var(--row-h, auto);
  line-height: 20px;
  padding: 9px var(--inset) 9px calc(var(--bar) + var(--inset));
  overflow: visible;
  background: var(--field);
  border-radius: var(--r-pill);
}
/* the room the marks stand in, kept clear on the **first line only** — the rest of the text runs the row's full width */
.row.is-long:is(:hover, .is-open) .label::before { content: ""; float: right; width: var(--open-marks); height: 20px; }
.row.is-long.is-up:hover .label, .row.is-long.is-up.is-open .label {
  top: auto;
  bottom: calc(100vh - var(--row-b, 0px) + var(--border));
}
/* The row's own disc, marks and number stand **on** the open text, not under it: the text box covers the whole row, and
   those three are what the row says about itself. The label leaves the row's flow while it is open, so what stands after
   it would slide left to the disc and back on every hover — the first of them takes the room the label gave up. */
.row.is-long:is(:hover, .is-open) > :is(.disc, .marks, .todo) { position: relative; z-index: 3; }
.row.is-long:is(:hover, .is-open) > :is(.marks, .todo) { margin-left: auto; }
.row.is-long:is(:hover, .is-open) > .marks + .todo { margin-left: 4px; }   /* the second one keeps the marks' own gap */

/* the disc: round 40 px at the row's left edge; holds an image, initials or a code */
.disc {
  flex: 0 0 var(--bar);
  width: var(--bar);
  height: var(--bar);
  margin: calc(-1 * var(--border));
  margin-right: 0;
  border: var(--border) solid var(--line);
  border-radius: 50%;
  background: var(--surface);
  color: var(--line);          /* code or initials in E9 */
  display: grid;
  place-items: center;
  font-size: var(--fs);
  overflow: hidden;
}

.disc img { width: 100%; height: 100%; object-fit: cover; }
/* a library record's own picture lies **over** its trade's drawing (library/pictures.py → shown): a picture that does not arrive
   leaves the drawing standing instead of a broken picture. The disc keeps its tint under it, and the drawing keeps its size. */
.disc.is-image.is-picture { position: relative; }
.disc.is-image.is-picture > img { position: absolute; inset: 0; }
.disc.is-image { background: var(--surface); } /* image (80×80 thumbnail) or its placeholder; white like every other disc */
.disc svg { width: 20px; height: 20px; }
.disc.is-image svg { color: var(--muted); }
.disc.is-picture { background: hsl(var(--hue) 45% 95%); color: var(--btn-text); }   /* a library record's stand-in drawing on its own tint (library/pictures.py, Dominik 2026-09-13); selected = E9 disc, white lines, as every disc */
.disc.is-picture svg { width: 24px; height: 24px; stroke-width: 1.4px; }
.disc.is-on { background: var(--green); border-color: var(--green); color: var(--surface); }   /* ticked: white check on green — the row is already linked (picker, Dominik 2026-09-07) */
/* the Go-to chevron on a disc (Dominik, 2026-09-27): a row that stands for a library record — an element on its template, a calc line on its
   STABU item, a resource on its shop item, a library line on the record it points at — opens it in the one library tab. The disc is the press:
   its drawing while the row is read, the chevron in its place while the mouse is **on the disc itself** (Dominik, 2026-09-27: the row's hover
   opens the frame, the disc's own hover opens the library — each press says what it does). Drawn here as the fold mark and `.row.is-jump` are,
   so a row carries its address and not an icon (the estimate would carry hundreds); "Go to" keeps the chevron that never turns (design.md).
   A row with no library record behind it keeps its plain disc — nothing follows, nothing is drawn (2026-09-26). */
.disc.is-goto { position: relative; cursor: pointer; }
.disc.is-goto::after { content: ""; position: absolute; inset: 0; background: var(--btn-text); opacity: 0; transition: opacity 120ms;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='155.1 -803.4 646.8 646.8'%3E%3Cpath d='M546.405 -480 357.405 -669 384 -695.595 599.595 -480 384 -264.405 357.405 -291Z'/%3E%3C/svg%3E") center / 24px no-repeat; mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='155.1 -803.4 646.8 646.8'%3E%3Cpath d='M546.405 -480 357.405 -669 384 -695.595 599.595 -480 384 -264.405 357.405 -291Z'/%3E%3C/svg%3E") center / 24px no-repeat; }
.disc.is-goto:hover::after, .disc.is-goto:focus-visible::after { opacity: 1; }
.disc.is-goto:hover > :is(svg, img), .disc.is-goto:focus-visible > :is(svg, img) { opacity: 0; }   /* one thing at a time in a 40 px disc: the drawing — or an uploaded picture — steps aside for the press */

.empty {
  height: var(--bar);
  display: flex;
  align-items: center;
  padding: 0 var(--inset);
  color: var(--muted);
}

/* --- detail card --------------------------------------------------------- */
/* Right column: 304 px picture/field area, title, subtitle, button rows. */

.card { display: flex; flex-direction: column; gap: 0; }
.col > .card { flex: 1 1 0; min-height: 0; overflow-y: auto; }   /* the A4 figure is tall: the card scrolls inside its column */
.card .figure { flex: none; }
.card .figure.is-image img { width: 100%; height: 100%; object-fit: cover; display: block; }
.card .figure.is-image.is-picture { position: relative; }                                    /* the same rule large: the drawing under the picture */
.card .figure.is-image.is-picture > img { position: absolute; inset: 0; }
/* no image yet: the placeholder drawing (static/img), transparent on the white page: no frame, no fill */
.card .figure.is-placeholder { border: 0; background: transparent; }
.card .figure.is-placeholder img { width: 84%; height: 84%; object-fit: contain; display: block; margin: auto; }   /* the drawing a little smaller than the field */
.card .figure.is-picture { border: 0; background: hsl(var(--hue) 45% 95%); color: var(--btn-text); }   /* the stand-in drawing, large, on the record's tint */
.card .figure.is-picture svg { width: 45%; height: 45%; stroke-width: 2.5px; }
.card .figure.is-icon { display: grid; place-items: center; color: var(--line); }                                 /* an icon as the figure (users: the person, for now): E9 like a placeholder */
.card .figure.is-icon svg { width: 40%; height: 40%; }
/* a document showing its own first page: A4 inside a square area, so it is fitted **whole and never cropped**, on white
   (Dominik, 2026-10-06). Every other figure is cropped to fill, which is right for a photo and wrong for a page. */
/* pinned to the box the way a stand-in drawing's picture is: the figure is a grid, so `height: 100%` on the image
   resolves against a row the image itself sizes — it overflowed and was clipped, which read as cropping */
.card .figure.is-image.is-fit { position: relative; background: var(--surface); border: 0; }   /* a document's own page stands free, no frame (Dominik, 2026-10-07) — as a picture, a logo and a map already do: the frame is for a box with nothing in it */
.card .figure.is-image.is-fit > img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; }
/* and where it cannot show itself at all, the sort of file under the drawing: DOC · DOCX · PAGES, in the placeholder's
   own grey, so the face says what it is instead of saying nothing */
.card .figure.is-icon { grid-auto-flow: row; gap: 8px; }
.card .figure.is-icon .kind { font-size: 13px; font-weight: 500; letter-spacing: .08em; color: var(--line); }
.card .figure.is-logo { background: var(--surface); border: 0; }   /* a logo and the standard logo stand free, no frame (Dominik, 2026-09-18) */
.card .figure.is-logo img { width: 84%; height: 84%; object-fit: contain; margin: auto; }   /* a contractor's logo whole, never cropped (Dominik, 2026-09-18) */
/* a client's figure (CON, Dominik 2026-09-19): the whole bust, standing on the figure's foot like a portrait, free on the screen's
   white — no ground, no frame (a tint was tried and dropped the same day); the edit screen's 424 × 304 face shows it smaller, never cut */
.card .figure.is-con { background: transparent; border: 0; }
.card .figure.is-con img { object-fit: contain; object-position: center bottom; }
/* a project's map (PRO, Dominik 2026-09-19): the Netherlands in E9, free on the white like the placeholder, the same 84 % of the figure;
   the dot where the address is, placed in % of the map's own box (estimation/geo.py → FRAME, whose sides are 266 × 314 km) */
.card .figure.is-map { background: transparent; border: 0; }
.card .figure.is-map .map { position: relative; height: 84%; aspect-ratio: 266 / 314; }
.card .figure.is-map .map img { width: 100%; height: 100%; display: block; }
.card .figure.is-map .dot { position: absolute; width: 14px; height: 14px; margin: -7px 0 0 -7px; border-radius: 50%;
  background: var(--btn-text); box-shadow: 0 0 0 2px var(--surface); }
/* the standard logo: the full name in spaced capitals, as the print's K O S T E N R A M I N G, on two lines broken near its middle
   (Aan.wordmark) so it stands more square; the longer line shrinks to 84 % of the figure's width — one capital and its
   spacing is about 1,1 em — a line never wraps, never cut (Dominik, 2026-09-18) */
.card .figure.is-wordmark { background: var(--surface); border: 0; container-type: inline-size; }
.card .figure.is-wordmark > span { text-align: center; text-transform: uppercase; letter-spacing: .3em; line-height: 1.5; color: var(--ink);
  font-size: min(24px, calc(84cqi / (var(--chars) * 1.1))); }
.card .figure.is-wordmark > span > span { display: block; white-space: nowrap; margin-right: -.3em; }
.card.is-centred .title, .card.is-centred .subtitle { justify-content: center; text-align: center; }
.card.is-centred .title { align-items: flex-end; padding-bottom: 2px; }      /* name sits low, address right under it */
.card.is-centred .subtitle { min-height: 0; padding-top: 2px; }
.card .card-line { margin: 0; padding: 2px 1px; text-align: center; font-size: var(--fs-small); color: var(--muted); word-break: break-all; }   /* a row's extra card lines (library/models.py card_lines) */
.card .subtitle.is-on { color: var(--green); }    /* Active / Active until … in green, Inactive stays A9 (users card, Dominik 2026-09-07) */
.card .subtitle.is-off { color: var(--danger); }  /* Expired … in red: the subscription ended */
.fld.is-file { cursor: pointer; }
.fld.is-file > input[type=file] { position: absolute; inset: 0; opacity: 0; cursor: pointer; }

.card .figure {
  height: var(--col);   /* square, 424 × 424 (Dominik, 2026-09-06): logo, photo */
  border: var(--border) solid var(--line);
  border-radius: var(--r-card);
  background: var(--field);
  display: grid;
  place-items: center;
  color: var(--muted);
  overflow: hidden;
}

.card .title {
  min-height: var(--bar);
  display: flex;
  align-items: center;
  padding: 0 1px;
  font-size: var(--fs);
  font-weight: normal;
  margin: 0;
}

/* The two-line clamp of 2026-09-27 is gone (Dominik, 2026-09-29): the name now grows **upwards** into the face instead of being cut at two
   lines, so a sentence name reads whole and still nothing below it moves. `.card.hero > .nameblock` above holds it. */

.card .subtitle {
  min-height: var(--bar);
  display: flex;
  align-items: center;
  padding: 0 1px;
  font-size: var(--fs-small);
  color: var(--muted);
  margin: 0;
}

.card .meta { font-size: var(--fs-small); color: var(--muted); line-height: 18px; margin: 0; padding: 0 1px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }  /* code_full, sort_path */
.card .field { display: flex; align-items: center; justify-content: space-between; min-height: var(--bar); padding: 0 var(--inset); border: var(--border) solid var(--line); border-radius: var(--r-card); }
.card .field span:first-child { color: var(--muted); }
.card .field b { font-weight: normal; font-variant-numeric: tabular-nums; }
.card .spacer { height: var(--bar); flex: 0 0 var(--bar); }
.card .spacer-half { height: calc(var(--bar) / 2); }

/* --- auth pages (login, password) ---------------------------------------- */

.auth {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: var(--margin);
}

.auth-card {
  width: var(--col);
  display: flex;
  flex-direction: column;
  gap: var(--row-gap);
}

.auth-card .wordmark { font-size: 24px; text-align: center; margin-bottom: var(--bar); }
.auth-card h1 { font-size: var(--fs); font-weight: normal; margin: 0; padding: 0 var(--inset); height: var(--bar); display: flex; align-items: center; }
.auth-card label { display: block; padding: 0 var(--inset); font-size: var(--fs-small); color: var(--muted); height: 20px; line-height: 20px; }
.auth-card .btn { margin-top: calc(var(--bar) / 2); }
.auth-card .links { display: flex; justify-content: center; gap: var(--inset); padding-top: var(--inset); font-size: var(--fs-small); color: var(--muted); }
.auth-card ul { margin: 0; padding: 0 var(--inset); list-style: none; font-size: var(--fs-small); color: var(--muted); }
.auth-card .errorlist { color: var(--danger); }
.auth-card .helptext { padding: 0 var(--inset); font-size: var(--fs-small); color: var(--muted); }
.auth-card .helptext ul { padding: 0; }
.auth-card p { margin: 0; }

.input {                     /* every input box except the quick find: 5 px corners, not a pill */
  width: 100%;
  height: var(--bar);
  padding: 0 var(--inset);
  border: var(--border) solid var(--line);
  border-radius: var(--r-card);
  background: var(--surface);
  font: inherit;
  color: var(--ink);
}

.input:focus { outline: none; background: var(--field); }
/* date fields keep their type (typing, the phone's date wheel, the browser's own check) but lose the calendar
   icon: no pickers of that kind anywhere in the app (Dominik, 2026-09-08) */
.input::-webkit-calendar-picker-indicator { display: none; -webkit-appearance: none; }

/* the sign-in card, approved 2026-09-06: one line, two edit fields 4 px apart, the pill button dead until
   both fields have text, then the add-button look; an × in the page corner back to the website. */
.signin { gap: var(--row-gap); }   /* 4 px between the boxes, like rows */
.signin .close { position: fixed; top: var(--top); right: var(--margin); width: var(--bar); height: var(--bar); display: grid; place-items: center; color: var(--btn-text); text-decoration: none; font-size: 20px; border-radius: 50%; }   /* × back to the website, like the quick-find × */
.signin .close:hover { background: var(--line); }
.signin h1 { display: block; font-size: var(--fs); height: auto; padding: 0; line-height: var(--bar); }   /* Inter 16, like everything on this screen */
.signin .fld { font-size: var(--fs); padding: 0; height: auto; line-height: inherit; }   /* not the 12 px label rule of .auth-card */
.signin .alert { font-size: var(--fs-small); min-height: 0; padding: 0 8px; line-height: 14px; }   /* 12 px, like the field label */
.signin .alert.success, .signin .alert.info { color: var(--btn-text); word-break: break-all; }         /* "link sent": grey, not red */
.signin .btn.link { margin-top: 0; }
.signin .btn { margin-top: var(--inset); }   /* the app's pill button, 16 + gap 4 = 20 px above it */

/* the field: FM's edit-field anatomy (Rho): a 40 px box, 5 px corners, E9 frame; the label inside at the
   top in 12 px A9, the value below in 16 px ink; optional picker button at the right end. */
.fld { position: relative; display: block; min-height: var(--bar); border: var(--border) solid var(--line); border-radius: var(--r-card); background: var(--pink); }   /* editable = pink, FM's way (Dominik, 2026-09-12): the border stays the marker, the tint says "fill here" */
.fld:hover { border-color: var(--muted); }                                              /* hover: the frame a step darker, the tint untouched (Dominik, 2026-09-12) */
.fld:focus-within { border-color: var(--btn-text); background: var(--surface); }
.fld.is-readonly:not(.is-error, :focus-within):hover { border-color: var(--line); }      /* nothing to fill: no hover — an error frame or a focus frame still wins */
.fld > .lbl { position: absolute; top: 2px; left: 8px; right: 8px; font-size: var(--fs-small); line-height: 14px; color: var(--muted); pointer-events: none; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fld > input, .fld > textarea, .fld > select, .fld > .val {
  display: block; width: 100%; height: calc(var(--bar) - 2 * var(--border)); margin: 0; padding: 18px 8px 3px;
  border: 0; background: transparent; font: inherit; line-height: 17px; color: var(--ink); outline: none;
  -webkit-appearance: none; appearance: none;   /* every single-line control is exactly the row height, in every browser (Safari gave each its own — Dominik, 2026-09-07) */
}
.fld > textarea { height: auto; resize: vertical; min-height: 96px; }
.fld > textarea[rows="1"] { min-height: var(--bar); }
/* a box exactly --rows fields tall, gaps included, so it lines up with a column of that many boxes (Dominik, 2026-09-12):
   STORE's description (4, since the mark's toggle stands under it), STABU's (3), and the text row's name that grows (app.js sets --rows). forms.py → tall() */
.fld > textarea[data-rows] { min-height: 0; height: calc(var(--rows) * var(--bar) + (var(--rows) - 1) * var(--row-gap) - 2 * var(--border)); }
.fld > textarea[data-grow], .fld > textarea[data-over] { resize: none; overflow: hidden; }   /* grows with its text (app.js), never pulled: a sentence read whole (Dominik, 2026-09-12) */
/* **A box that grows over what is under it** (Dominik, 2026-09-29, the shop note: "so i can see what is in it"). The box around it keeps its
   place on the grid — exactly --rows rows — and the field floats inside that place, free to run past it while you are in it. **Nothing on the
   screen moves**: Delete and the fields under it keep their rows, and the text lies over them until you leave. A box that pushed instead would
   have run the column into Delete's own place at 715, which is the one thing a pinned item cannot defend itself against. */
.overbox { position: relative; height: calc(var(--rows) * var(--bar) + (var(--rows) - 1) * var(--row-gap)); }
.overbox > .fld { position: absolute; top: 0; left: 0; right: 0; }
.overbox > .fld:focus-within { z-index: 20; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15); }   /* lifted, so it reads as lying over the rows under it */
.fld > .input, .fld > .input:focus { border: 0; background: transparent; }
.fld > select { appearance: none; -webkit-appearance: none; cursor: pointer; }   /* the picker look: the fold mark at the right */
.fld > .mark { position: absolute; right: 14px; top: 14px; width: 12px; height: 12px; color: var(--muted); pointer-events: none; line-height: 0; }   /* the fold mark: a small sign that the field opens, not a button — the whole field is the target (Dominik, 2026-09-07) */
.fld > .mark svg { width: 12px; height: 12px; }
.edit { display: flex; flex-direction: column; gap: var(--row-gap); }
.screen-title { height: var(--bar); display: block; line-height: var(--bar); }   /* the edit screen's title in the top bar, where the quick find would be; a block, so the … of a long title draws */
.col > .edit { flex: 1 1 0; min-height: 0; overflow-y: auto; }   /* a long form scrolls inside its column, like a list */
.edit .btn-placeholder { gap: 8px; }
.message { min-height: var(--bar); display: flex; align-items: center; padding: 0 var(--inset); border: var(--border) solid var(--line); border-radius: var(--r-card); background: var(--field); font-size: var(--fs-small); color: var(--btn-text); word-break: break-all; }
.message.error { color: var(--danger); }   /* a form widget inside the field: the field draws the box */
.message.warning { background: #FFF6CC; border-color: var(--highlight); color: var(--ink); }   /* saved, with something to know: a logo that may print blurry (Dominik, 2026-09-18) */
.fld > input::placeholder, .fld > textarea::placeholder { color: var(--line); }
.fld.has-pick { cursor: pointer; }                                     /* a picker field: read-only value + fold mark, the whole field opens the picker */
.fld.has-pick > input, .fld.has-pick > .val, .fld > select { padding-right: 32px; }
.fld.is-num > input { font-variant-numeric: tabular-nums; }
.fld > input[type=number] { -moz-appearance: textfield; appearance: textfield; }                                     /* no spinner arrows on a number box, anywhere (Dominik, 2026-09-11) */
.fld > input[type=number]::-webkit-inner-spin-button, .fld > input[type=number]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }   /* a number in a field reads left like every field; right alignment is for columns in a table (Dominik, 2026-09-11) */
.fld.is-readonly { background: var(--surface); }   /* read-only = white: a box in the grid, nothing to fill */
/* the group picker (Dominik, 2026-09-11): a card window over the dimmed screen — one column wide for a flat kind, two
   for material — × and the title on top, the lists scrolling inside like every column */
.pickscreen { position: fixed; inset: 0; z-index: 30; background: rgba(0, 0, 0, 0.25); display: grid; place-items: center; }
.pickscreen .window { display: flex; flex-direction: column; gap: var(--row-gap); width: calc(var(--col) + 2 * var(--margin)); height: calc(100vh - 2 * var(--margin));   /* every picker window opens the same size (Dominik, 2026-09-24): its list scrolls inside it and empty space stands under the last row, so the window, its ×, its search and its pills never move while rows come and go */ padding: var(--margin); border: var(--border) solid var(--line); border-radius: var(--r-card); background: var(--surface); box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15); }
.pickscreen[data-cols="2"] .window { width: calc(2 * var(--col) + var(--gutter) + 2 * var(--margin)); }
.pickscreen .window > .columns { grid-template-columns: var(--col); grid-template-rows: minmax(0, 1fr); flex: 0 1 auto; min-height: 0; }
.pickscreen[data-cols="2"] .window > .columns { grid-template-columns: repeat(2, var(--col)); }   /* as tall as the lists, never taller than the window: the lists scroll */
.pickscreen .col > .list { flex: 0 1 auto; min-height: 0; overflow-y: auto; }

.pickscreen .pickbar > .screen-title { flex: 1 1 auto; margin-right: var(--bar); }
button.row { width: 100%; font: inherit; color: inherit; background: transparent; text-align: left; cursor: pointer; }
button.row .label { flex: 1 1 auto; }
.fld[data-picker] { background: var(--surface); }                                        /* picked, not typed: a white box with its button (Dominik, 2026-09-12) */
.fld[data-picker]:not(.is-error, :focus-within):hover { border-color: var(--line); }      /* only its button reacts (Dominik, 2026-09-12) */
.fld > button.tool { border: 0; background: transparent; cursor: pointer; padding: 0; }
.fld > button.tool:hover { background: var(--line); }
.fld.is-readonly > .val { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* an answer read out on the user's screen (Dominik, 2026-10-01): the same box a tall textarea gets, exactly --rows fields
   tall with the gaps inside it, but holding text — it wraps, starts at the top, and has no label line over it, because the
   question is the section's title. library/forms.py → ReadingForm, accounts/forms.py → box(rows=) */
.fld.is-readonly.is-answer { height: calc(var(--rows) * var(--bar) + (var(--rows) - 1) * var(--row-gap)); }
.fld.is-readonly.is-answer > .val { height: 100%; padding: 8px; white-space: pre-wrap; overflow: hidden; text-overflow: clip; }
.fld.is-readonly.is-answer:has(> .lbl) > .val { padding-top: 18px; }   /* with a label over it: the text starts on the line every other box starts on */
/* a read-out box whose words run past it **scrolls inside** (Dominik, 2026-10-07, the note): the box keeps its rows — the
   left column of DOC's edit screen is exactly seven — and the text moves inside it, as a list scrolls inside its column.
   Not the hover-unfold of a one-line value: twenty lines dropping over the screen is not reading, it is a surprise. */
.fld.is-readonly.is-answer.is-scroll > .val { overflow-y: auto; overscroll-behavior: contain; }
.fld.is-readonly.is-long:hover { height: auto; }                                          /* a long value unfolds on hover, the box grows (Dominik, 2026-09-11) */
.fld.is-readonly.is-long:hover > .val { white-space: normal; height: auto; }
.fld.has-tool > input, .fld.has-tool > .val { padding-right: 44px; }
.fld > .tool { position: absolute; right: 3px; top: 3px; width: 32px; height: 32px; display: grid; place-items: center; color: var(--btn-text); border-radius: var(--r-card); }   /* a button inside a field, at the right, its own 5 px box (Dominik, 2026-09-11): open the page, the VAT tool */
.fld > a.tool:hover { background: var(--line); }
.fld > .tool svg { width: 24px; height: 24px; }   /* like every button (Dominik, 2026-09-11) */
.fld > details.tool { overflow: visible; }
.fld > details.tool { display: block; }
.fld > details.tool > summary { list-style: none; cursor: pointer; width: 32px; height: 32px; display: grid; place-items: center; line-height: 0; border-radius: var(--r-card); }
.fld > details.tool > summary::-webkit-details-marker { display: none; }
.fld > details.tool > summary:hover, .fld > details.tool[open] > summary { background: var(--line); }
.fld > details.tool > .menu { right: 0; left: auto; width: 280px; display: flex; flex-direction: column; gap: var(--row-gap); padding: 8px; }
.fld.is-readonly > .val { color: var(--btn-text); }
.fld.is-readonly.is-on > .val { color: var(--green); }    /* a status line that reads from the data (users: Active until …) */
.fld.is-readonly.is-off > .val { color: var(--danger); }  /* … Not active since … */
.fld.is-error { border-color: var(--danger); }
.fld.is-error > .lbl { color: var(--danger); }
.fld-msg { padding: 2px 8px 0; font-size: var(--fs-small); color: var(--danger); line-height: 14px; }

/* the switch: an Apple-style toggle, a real on/off control (checkbox underneath), no script */
.toggle { position: relative; display: inline-block; width: 46px; height: 28px; flex: none; }
.toggle input { position: absolute; inset: 0; margin: 0; opacity: 0; cursor: pointer; }
.toggle .track {
  position: absolute; inset: 0; border-radius: 14px; background: var(--line);
  transition: background 0.2s;
}
.toggle .track::after {
  content: ""; position: absolute; top: 2px; left: 2px; width: 24px; height: 24px; border-radius: 50%;
  background: var(--surface); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); transition: transform 0.2s;
}
.toggle input:checked + .track { background: var(--green); }
.toggle input:checked + .track::after { transform: translateX(18px); }
.toggle input:active + .track::after { width: 28px; }                         /* the knob stretches while pressed, like iOS */
.toggle input:checked:active + .track::after { transform: translateX(14px); }
.toggle input:focus-visible + .track { outline: 2px solid var(--btn-text); outline-offset: 2px; }
.toggle input:disabled + .track { opacity: 0.4; }
.toggle input:disabled { cursor: default; }
/* the same switch with no box of its own: it says the state the button posts (the ready mark in the top bar,
   Dominik 2026-09-28) — one press, and the screen comes back with it moved. The look is the toggle's, to the pixel. */
.toggle.is-flat.is-on .track { background: var(--green); }
.toggle.is-flat.is-on .track::after { transform: translateX(18px); }

/* "Ready for the user", beside Done in the top bar (Dominik, 2026-09-28): the record's state, not one of its fields.
   Half a column like Done, so the two make one column between them. */
.readymark { flex: 0 0 auto; }
.readymark > .btn { display: flex; align-items: center; justify-content: space-between; gap: 8px; width: 208px; }

/* a mark beside the name that opens a window (the old FileMaker text, Dominik 2026-09-28): small, quiet, and gone the
   moment the window is emptied — a record that has none carries nothing */
.textmark { align-self: center; width: fit-content; margin-top: 4px; height: 24px; padding: 0 10px; border: var(--border) solid var(--line); border-radius: var(--r-pill);
            background: var(--surface); color: var(--muted); font-size: var(--fs-small); cursor: pointer; }
.textmark:hover { border-color: var(--muted); color: var(--ink); }

/* a note the top bar's own control left (the ready mark's refusal): it stands over the title, with the bar's width */
.topbar .message { position: absolute; inset: 0; z-index: 2; display: flex; align-items: center; justify-content: center;
                   padding: 0 var(--inset); border-radius: var(--r-pill); text-align: center; }
.topbar .col { position: relative; }

/* a toggle inside a field row: label left, switch right, 40 px like every field */
.field-toggle { display: flex; align-items: center; justify-content: space-between; gap: var(--inset); height: var(--bar); padding: 0 var(--inset); }   /* a switch row: label and switch, no frame (Dominik, 2026-09-07) */
.field-toggle span:first-child { color: var(--ink); }
.field-toggle.is-error span:first-child { color: var(--danger); }   /* refused (Dominik, 2026-09-28: the ready switch): the words red, as a refused box's label — the switch has no frame to turn */

.alert { padding: 0 var(--inset); min-height: var(--bar); display: flex; align-items: center; color: var(--danger); }

/* --- phone mode ---------------------------------------------------------- */
/* One column at a time, as wide as the phone; the switch bar picks the column.
   The page scrolls as a whole (no inner list scrolling on a phone). */

.switch { display: none; }

@media (max-width: 900px) {
  .canvas {
    width: 100%;
    height: auto;
    min-height: 100vh;
    padding: 8px;
    grid-template-rows: auto auto 1fr auto;
  }
  .columns { grid-template-columns: 1fr; row-gap: var(--row-gap); }
  /* two rows on top (Dominik, 2026-09-10): the quick find beside the branch bar shrunk to the user's initial and
     the branch icons; the nav bar under them. The column switch sits fixed at the bottom,
     thumb reach, like an iPhone tab bar; the canvas keeps room for it. */
  .topbar { height: auto; margin-bottom: var(--row-gap); grid-template-columns: 1fr auto; column-gap: var(--row-gap); }
  .topbar > .col:nth-child(3) { grid-column: 1 / -1; }
  .branchbar .who .line { display: none; }
  .branchbar .initial { display: block; color: var(--btn-text); }
  .branchbar .who { flex: 0 0 var(--bar); min-width: var(--bar); }
  .branchbar .who summary { align-items: center; padding: 0; }
  .canvas { padding-bottom: calc(var(--bar) + 16px); }
  .switch { display: flex; position: fixed; left: 8px; right: 8px; bottom: 8px; z-index: 3; margin: 0; }
  main .col { display: none; }
  .canvas[data-view="left"]   main [data-col="left"],
  .canvas[data-view="middle"] main [data-col="middle"],
  .canvas[data-view="right"]  main [data-col="right"] { display: flex; }
  .list { overflow: visible; }
  .col > .list { flex: none; }   /* the page scrolls as a whole: a list takes its own height, so what stands under it (the Removed switch) stays under it */
  .col > .card, .col > .edit { flex: none; min-height: auto; overflow: visible; }   /* the page scrolls as a whole: a card or a form takes its own height (a column of no height left the card blank, 2026-09-13) */
  .colhead .list-title { display: none; }   /* the switch bar names the column; the corner buttons stay */
  .colhead:not(:has(.cardbtns, .kinds)) { display: none; }
  .sheet { width: 100%; }
  .sheet .demo { grid-template-columns: 1fr; row-gap: var(--bar); }
  .sheet .swatches { grid-template-columns: repeat(3, 1fr); }
  .sheet .icons { grid-template-columns: repeat(2, 1fr); }
  .auth-card { width: 100%; }
}

/* --- components sheet ---------------------------------------------------- */

.sheet { width: var(--canvas); margin: 0 auto; padding: var(--top) var(--margin) 80px; display: flex; flex-direction: column; gap: var(--bar); }
.sheet h1 { font-size: var(--fs); font-weight: normal; margin: 0; height: var(--bar); display: flex; align-items: center; }
.sheet h2 { font-size: var(--fs-small); font-weight: normal; color: var(--muted); text-transform: uppercase; letter-spacing: 0.06em; margin: 0; height: var(--bar); display: flex; align-items: center; border-bottom: var(--border) solid var(--line); }
.sheet section { display: flex; flex-direction: column; gap: var(--row-gap); }
.sheet .demo { display: grid; grid-template-columns: repeat(3, var(--col)); column-gap: var(--gutter); align-items: start; }
.sheet .demo > * { min-width: 0; }
.sheet .note { color: var(--muted); font-size: var(--fs-small); padding: 0 var(--inset); min-height: 20px; line-height: 20px; }
.sheet .swatches { display: grid; grid-template-columns: repeat(6, 1fr); gap: var(--gutter); }
.sheet .swatch { display: flex; flex-direction: column; gap: 4px; font-size: var(--fs-small); }
.sheet .swatch i { display: block; height: var(--bar); border-radius: var(--r-pill); border: var(--border) solid var(--line); }
.sheet .swatch b { font-weight: normal; }
.sheet .swatch span { color: var(--muted); }
.sheet .icons { display: grid; grid-template-columns: repeat(6, 1fr); gap: var(--gutter); }
.sheet .icontile { display: grid; grid-template-columns: var(--bar) 20px 1fr; grid-template-rows: auto auto auto; column-gap: 8px; align-items: center; font-size: var(--fs-small); }
.sheet .icontile .btn40 { grid-row: 1 / 4; width: var(--bar); height: var(--bar); display: grid; place-items: center; border: var(--border) solid var(--line); border-radius: var(--r-pill); color: var(--btn-text); }
.sheet .icontile .btn40 svg { width: 24px; height: 24px; }
.sheet .icontile .small { grid-row: 1 / 4; display: grid; place-items: center; color: var(--btn-text); line-height: 0; }
.sheet .icontile .small svg, .sheet .icontile .small .icon svg { width: 20px; height: 20px; }
.sheet .icontile b { font-weight: normal; color: var(--ink); }
.sheet .icontile > span:not(.btn40):not(.small) { color: var(--muted); }
.sheet .type-sample { min-height: var(--bar); display: flex; align-items: baseline; gap: var(--inset); padding: 0 var(--inset); }
.sheet .type-sample span { color: var(--muted); font-size: var(--fs-small); min-width: 220px; }

/* --- not available ------------------------------------------------------- */
/* Everything that is not available — a placeholder, not built yet, frozen (the owner's rings) — does nothing: the hover
   and pressed rules above match only live controls (`:where(:not([aria-disabled="true"]))`, no extra specificity), so the rest look simply stays;
   here only the pointer (Dominik, 2026-09-07). */
[aria-disabled="true"], :disabled { cursor: default; }

/* --- the children screen: the resources of a STABU item (step 9b, Dominik 2026-09-12/13, after Omicron's xRES) ---- */
/* A full-width sheet of lines on one grid — disc · code · name · norm · qty · unit · unit price · hours · oa · lab · mat ·
   total · action — the same drawing for XRES, XCAL, CAL and RES (Dominik, 2026-09-13); the parent is the head line on the
   same grid, framed, so every value stands under its own heading. A box is a field (the FM anatomy); a slot with no value
   yet is blank space of the same width. The norm box and the head line's unit save at once (form[data-autosave], app.js
   block 12); a refusal is a red frame with the words as the box's title. */
.lines { --bleed: var(--margin); display: flex; flex-direction: column; gap: var(--row-gap); min-height: 0; min-width: 0; overflow: auto; margin: 0 calc(-1 * var(--bleed)); padding: 0 var(--bleed); }   /* min-width 0: a grid item never widens the page; scrolls inside itself, down and — on a narrow screen — sideways; bleeds to the canvas edges so the head band runs the full 1368 (Dominik, 2026-09-13) */
/* **a row keeps its height, however long the list is** (Dominik, 2026-09-26: "open merken frame squize strangley other acordeon lines"). The
   list is a flex column that scrolls inside itself, and a flex child shrinks by default: the moment the rows were taller than the column —
   which the merken frame made easy to hit — every row below squeezed itself thinner instead of letting the list scroll. A row is its own
   height and the column scrolls, as `.col > *` has always said for the page's columns. */
.lines > * { flex: none; min-width: calc(var(--canvas) - 2 * var(--margin)); }   /* the twelve columns need the desktop's width; a phone scrolls the sheet sideways */
.lines > .list-title, .suggblock > .list-title, .calframe > .frame-body > .list-title { margin-top: var(--row-gap); padding-left: calc(var(--bar) + var(--code-box) + 2 * var(--row-gap) + var(--mark) + 8px); }   /* a kind caption (LAB · MAT) starts where the name column's text starts, as FM's dividers (Dominik, 2026-09-13) */
/* the suggestions of a template (Dominik, 2026-10-13: "sugestions are ussaly replacments, and they should not be caclutaed
   in TEM TOITAL. JUST VISIBLE WAY TO ARANGE IT"): the block stands **under the add row** — the price ends where the next line
   is typed — and on a faint tint, so the eye reads it as off the sum. The divider says it in words ("SUGGESTIONS · not in the
   price") and the block carries **no total of its own**: a second sum on the screen would read as part of the price. The tint
   is drawn behind the rows the way an open element's frame is (--field, 8 out on each side), so no row moves and every column
   stays under its heading. */
.suggblock { position: relative; display: flex; flex-direction: column; gap: var(--row-gap); margin-top: var(--row-gap); }
.suggblock::before { content: ""; position: absolute; inset: calc(-1 * var(--row-gap)) -8px; border-radius: var(--r-card); background: var(--field); pointer-events: none; }
.suggblock > * { position: relative; }   /* the rows and the divider lie over the tint, as a calc line lies over its frame */
.suggblock > .list-title { margin-top: 0; }
.line { position: relative; display: grid; grid-template-columns: var(--bar) var(--code-box) minmax(0, 1fr) repeat(8, 86px) 106px; align-items: center; gap: var(--row-gap); }   /* FM's grid (the DDR, 2026-09-13): eight boxes of 86, the total 106 ending at the canvas edge; the code box ours; the row's actions lie over the total cell (Dominik, 2026-09-13) */
.line > .disc { margin: 0 0 0 calc(-1 * var(--border)); }   /* the disc starts where it starts on every list row: its frame over the edge */
/* the fold mark (Dominik, 2026-09-25): a row that folds carries a chevron in its own slot between the number and the name — pointing
   right while it is closed, down while it is open. **The slot is reserved on every row of every lines screen**, whether that row folds or
   not, so the names keep one left edge; **the name column pays for it**, the only column that can give room, so no number column moves
   anywhere in App. Drawn here and not in the page: the Levels page would carry 50 icons and the estimate more, as a jump row's chevron and
   a frame part's mark are drawn here. **No mark where the row does not open** — a resource, an object, an add row, a calc line whose
   resources the person may not see (access.md R16) — and not a greyed one: greyed means "not built yet" in App. The mark is a sign, not a
   button: the row stays the target it is, and a click on the mark lands on the row. **The turning chevron stays what it is** on a frame's
   parts, on a field that opens and on a menu (down closed, up open) — a tree of records against sections of one record — and "Go to"
   keeps its own rule (.row.is-jump): the right end, on hover, never turning.
   The mark is measured from the row's own left edge, so it holds for every row that folds today — the estimate's group row included,
   which is a row like every row and not a band (it takes its padding back, below). A band that folded one day would carry the page's
   bleed as padding and would need that added here; none does. */
/* the correcting screen of the translations (Step B, 2026-09-27): four columns, not the amounts' twelve — the Dutch, the
   translation, the note the fill left, and the tick. The two text columns share the width evenly, so a long Dutch name and
   its long translation read side by side */
.line.is-phrase { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 20%) var(--bar); }
.line.is-phrase > .fld.is-name, .line.is-phrase.is-head > .fld.is-name { grid-column: span 1; }   /* four cells, four columns: a name cell here does not run over the norm and qty slots it has on the lines screen */
.line.is-phrase .fld.is-note > .val { color: var(--muted); font-size: var(--fs-small); }
.line.is-phrase .fld.is-note:not(.is-empty) > .val { color: var(--danger); }   /* a word nobody has established: read this one first — and the red goes the moment the words do (`applyAnswer` empties the cell) */
.line.is-phrase.is-read .fld.is-readonly > .val { color: var(--muted); }   /* a line a person has been through steps back */
.line.is-phrase .fld.is-tick { display: grid; place-items: center; cursor: pointer; }
.line.is-phrase .fld.is-tick > .lbl { display: none; }
.line.is-phrase .fld.is-tick input { width: 20px; height: 20px; accent-color: var(--green); cursor: pointer; }   /* a switch, not a text box: it keeps its own size in a cell that fills the line */
.line :is(.fld, .lbl).is-name:not(.is-mod), .line > :is(.types, .tags) { margin-left: var(--mark); }
.line[data-fold]:not(.is-part)::after { content: ""; position: absolute; top: 50%; left: calc(var(--bar) + var(--code-box) + 2 * var(--row-gap)); width: 16px; height: 16px; margin: -8px 0 0 calc((var(--mark) - 16px) / 2); background: var(--muted); transition: transform 0.2s ease; pointer-events: none;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='155.1 -803.4 646.8 646.8'%3E%3Cpath d='M546.405 -480 357.405 -669 384 -695.595 599.595 -480 384 -264.405 357.405 -291Z'/%3E%3C/svg%3E") center / contain no-repeat; mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='155.1 -803.4 646.8 646.8'%3E%3Cpath d='M546.405 -480 357.405 -669 384 -695.595 599.595 -480 384 -264.405 357.405 -291Z'/%3E%3C/svg%3E") center / contain no-repeat; }
.line[data-fold]:not(.is-part)[aria-expanded="true"]::after { transform: rotate(90deg); }   /* open: the chevron turns down, in the 0,2 s the rows take to slide */
.line[data-fold]:not(.is-part):hover::after { background: var(--btn-text); }   /* it darkens with the row it belongs to */
.line form { display: contents; }
.line .fld { height: var(--bar); min-height: 0; }
.line:not(.is-head, .is-open) > .slot:not(.is-action) { display: block; height: var(--bar); border: var(--border) solid var(--line); border-radius: var(--r-card); background: var(--surface); }   /* an empty cell keeps its frame: a list is a table (Dominik, 2026-09-13); the band stays plain */
.line .is-cell.is-empty > .lbl { display: none; }                                                        /* an amount cell with nothing in it: the frame alone, no label */
.line:is(.is-head, .is-open) > .is-cell.is-empty { border-color: transparent; background: transparent; }                /* … and on the band not even the frame */
.line .fld.is-name > input, .line .fld.is-name > .val { padding-left: 8px; }   /* the name reads from the left, 8 in */
/* code and name carry no label (Dominik, 2026-09-13: no noise) — and the value stays on the line every other value is on,
   as in FM: taking the label away moves nothing */
.line .fld:not(.is-name) > input, .line .fld:not(.is-name) > select, .line .fld:not(.is-name) > .val { padding-left: 0; padding-right: 0; text-align: center; text-align-last: center; }   /* text-align-last: a dropdown centres its text only with it (a resource's unit, 2026-09-17) */   /* a number or a code is centred, no padding: the box's width is the number's (Dominik, 2026-09-13) */
.line .fld:not(.is-name) > .lbl { left: 0; right: 0; text-align: center; }
/* the modifier column is centred like every column beside it (Dominik, 2026-09-25): a level's short name, a room's and an object's modifier
   and a merk's note are one short word standing under a heading, not a name read from the left */
.line .fld.is-name.is-mod > :is(input, .val) { padding-left: 0; padding-right: 0; text-align: center; }
.line.is-labels > .lbl.is-name.is-mod { padding-left: 0; text-align: center; }
.line .fld.is-error { border-color: var(--danger); }
/* the parent: the print's black band (Dominik, 2026-09-13, chosen over a grey band and a frame) — the disc white with
   the code, the values in white with their labels in E9, no boxes; read-only for now */
.line:is(.is-head, .is-open) { padding: 4px var(--bleed); margin: 0 calc(-1 * var(--bleed)); min-width: calc(var(--canvas) - 2 * var(--margin) + 2 * var(--bleed)); background: var(--band, var(--ink)); }   /* its side padding on top of the lines' width, so its grid is the lines' grid */   /* 48 tall: the disc and the values 4 in from the band's edges — never pulled up, the container would clip it */   /* the full width of the layout, edge to edge, square like the print */
.line > .is-name { grid-column: span 2; }           /* a resource line has no qty (no indicative costs here, Dominik 2026-09-13): the name takes that room, the columns from the unit on stand where CAL has them */
.line.is-head > .is-name { grid-column: span 3; }   /* the parent's name runs over the norm slot too, as FM's head row */
.line:is(.is-head, .is-open) > .disc { background: var(--surface); color: var(--ink); border-color: var(--surface); }
.line:is(.is-head, .is-open) > .disc svg { color: var(--ink); }
.line:is(.is-head, .is-open) > .fld.is-readonly { background: transparent; border-color: transparent; }
.line:is(.is-head, .is-open) > .fld.is-readonly > .val { color: var(--surface); }
.line:is(.is-head, .is-open) > .fld.is-readonly > .lbl { color: var(--line); }
/* an opened element on the estimate's elements page (Dominik, 2026-09-14): the dark grey band, drawn as the black band — the
   parent of what shows under it; the light grey one (--band-open-2) is for an opened calc line. Folding happens in the
   browser, nothing is fetched (app.js block 17). */
.line.is-open { --band: var(--band-open); }
/* the estimate's band on the elements page (Dominik, 2026-09-15: "the black bar does not look nice … final price is important to see"):
   not the black band of the other lines screens but the frosted white of the menus, sticking to the top of the list while the rows
   scroll under it; disc, name in ink, and at its right end the price (16 ink) over the price with VAT (16 A9), labels and values each in their own column */
.lines > .line.is-head.is-est { position: sticky; top: 0; z-index: 3; background: rgba(255, 255, 255, 0.72); -webkit-backdrop-filter: blur(18px) saturate(1.4); backdrop-filter: blur(18px) saturate(1.4); border-bottom: var(--border) solid var(--line); }
.line.is-head.is-est > .disc { background: var(--surface); color: var(--btn-text); border-color: var(--line); }
.line.is-head.is-est > .disc svg { color: var(--btn-text); }
.line.is-head.is-est > .fld.is-readonly > .val { color: var(--ink); }
.line.is-est > .prices { grid-column: span 2; display: grid; grid-template-columns: auto auto; justify-content: end; align-content: center; column-gap: 8px; }   /* two columns: the labels one under the other, the values right-aligned under each other */
.line.is-est .price { display: contents; }
.line.is-est .price > .lbl { font-size: var(--fs-small); line-height: 18px; color: var(--muted); align-self: baseline; }
.line.is-est .price > .val { font-size: var(--fs); line-height: 18px; color: var(--muted); text-align: right; font-variant-numeric: tabular-nums; align-self: baseline; }
.line.is-est .price.is-main > .val { color: var(--ink); }
/* a main group of the estimate (Dominik, 2026-09-14, "keep the frame"): a row like every row — 40 tall, so the list steps by 44 throughout —
   holding one long black frame, 32 in its middle, instead of the black strip: no cells, the name and the total in ink inside it;
   the frame runs the lines' width, starting under the disc's centre so the white 40 disc with its black frame closes its end.
   The estimate's own band keeps its 48 and its black. */
.line.is-head.is-group { height: var(--bar); padding-inline: 0; padding-block: 0; margin-inline: 0; min-width: calc(var(--canvas) - 2 * var(--margin)); background: none; }
.line.is-head.is-group::before { content: ""; position: absolute; top: 4px; bottom: 4px; left: calc(var(--bar) / 2); right: 0; border: var(--border) solid var(--ink); border-radius: var(--r-card); pointer-events: none; }
.line.is-head.is-group > * { position: relative; }   /* over the frame: the disc's white closes the frame's end */
.line.is-head.is-group:hover::before { background: var(--field); }   /* hover lights the whole frame, as a list row (F9), never its cells one by one (Dominik, 2026-09-14) */
.line.is-head.is-group > .disc { border-color: var(--ink); }
.line.is-head.is-group > .fld { height: 32px; }
.line.is-head.is-group > .fld > .val { height: 30px; padding-block: 0; line-height: 30px; color: var(--ink); }
.line:is(.is-head, .is-open) .marks { color: var(--line); }
/* an element's frame (Dominik, 2026-09-14, chosen over the dark grey band and over a frame under the element): the element row and its
   calc lines in one box. Closed, nothing is drawn and the row stands as every row; opened, an E9 frame with r5 corners, like a field's,
   runs around the element row, the title line and the lines, 8 outside them on either side (the canvas keeps 16) and 4 above and below,
   so every row inside keeps the same left edge and its cells stand under the element's. The title line between the element and its
   lines (Dominik, 2026-09-14): "Calculation lines · <element>" where the name text starts, the × at its right */
.lines > .eleframe { position: relative; display: flex; flex-direction: column; gap: var(--row-gap); }   /* the element, its row and the calc part */
/* the frame (Dominik, 2026-09-15: "as if the ELE is on top of the frame without touching its position"): its top line runs in the 4 px gap above the element
   row (Dominik, 2026-09-17, as the calc line's frame), holding the whole row, and runs down around the calc part, the F9 fill behind the
   row too, so the element's white cells rest on it; 8 outside the lines on either side; drawn while open and while
   it shrinks closed; the rows lie over it */
.lines > .eleframe:is(.is-open, .is-closing)::before { content: ""; position: absolute; top: calc(-1 * var(--row-gap) / 2);   /* its top line in the gap above the element row, as the calc line's (Dominik, 2026-09-17) */ bottom: 0; left: -8px; right: -8px; border: var(--border) solid var(--muted); border-radius: var(--r-card); background: var(--field); pointer-events: none; }
.lines > .eleframe.is-open { margin-bottom: calc(16px - var(--row-gap)); }   /* room under the open frame only: nothing above the clicked element changes, so it stays exactly where it was (Dominik, 2026-09-15) */   /* where the frame ends and the next element starts (Dominik, 2026-09-14): A9, a field's hover frame, and 16 clear above and below — the list's own gap included */
.lines > .eleframe > .frame-body { display: flex; flex-direction: column; gap: var(--row-gap); padding-bottom: calc(var(--row-gap) * 2); }   /* the calc part: its frame is drawn by the element's frame below, from the middle of the element row down */
.eleframe > *, .calframe > *, .frame-body > * { min-width: calc(var(--canvas) - 2 * var(--margin)); }
/* the open frame is a track (Dominik, 2026-09-14: "i like when the frame has a fill all the time"), as the nav bar's: F9 all the time, every
   cell inside white with its E9 frame, the element's and the lines' alike (frameless line cells were tried and put back the same day).
   Closing: a click outside the frame or Escape — never a click inside it (Dominik, 2026-09-14); the dimmed rows around say where */
.lines > .eleframe.is-open > .line.is-ele > .fld:is(.is-name, .is-unit) { background: var(--pink); }   /* an open element's name and unit: typed in the editing step, pink already; in the closed list white — the unit is rarely changed, the element is opened for it (Dominik, 2026-09-14: pink is editable, or planned to be) */
.frame-body > .line.is-add > .is-name { grid-column: span 1; }   /* the add row's name cell as long as a calc line's, ending where the norm starts (Dominik, 2026-09-14: "too long") */
.lines:has(> .eleframe.is-open) > .eleframe:not(.is-open) > .line.is-ele > .fld:not(.is-readonly) { background: var(--surface); }
/* an element's boxes (R12, Dominik 2026-09-17): its name and unit answer a click only in its open frame — closed, the click reaches the row and
   opens it; while another frame is open no element box outside it takes a click, which only closes that frame */
.lines > .eleframe:not(.is-open) > .line.is-ele > .fld:is(.is-name, .is-unit) > .input,
.lines:has(> .eleframe.is-open) > .eleframe:not(.is-open) > .line.is-ele > .fld > .input { pointer-events: none; }
.lines:has(> .eleframe.is-open) > .eleframe:not(.is-open) > .line.is-ele > .fld > .input { color: var(--muted); }
.lines > .eleframe:not(.is-open) > .line.is-ele > .fld.is-unit > select.input { appearance: none; text-align: center; text-align-last: center; }   /* closed: the unit reads as a value, no dropdown mark */   /* while a frame is open it is the one place that is edited: outside it every cell is white; with none open the elements' quantities stay pink, typed one after the other down the list (Dominik, 2026-09-14) */
/* focus (Dominik, 2026-09-14: "lots of numbers for a first time user"): while a frame is open everything around it in the list dims —
   the other elements and the group rows, in A9 with their cells unframed and their drawings faded — it cannot be edited and a click
   there closes the frame; the estimate's black band stays as it is */
.lines:has(> .eleframe.is-open) > .eleframe:not(.is-open) > .line.is-ele > .fld { border-color: transparent; }
.lines:has(> .eleframe.is-open) > .eleframe:not(.is-open) > .line.is-ele > .fld > .val,
.lines:has(> .eleframe.is-open) > .line.is-group > .fld > .val { color: var(--muted); }
.lines:has(> .eleframe.is-open) > .eleframe:not(.is-open) > .line.is-ele > .fld > .lbl { color: var(--line); }
.lines:has(> .eleframe.is-open) > .eleframe:not(.is-open) > .line.is-ele > .disc, .lines:has(> .eleframe.is-open) > .eleframe:not(.is-open) > .line.is-ele > .fld > .marks { opacity: 0.5; }
.lines:has(> .eleframe.is-open) > .line.is-group > .disc { border-color: var(--muted); color: var(--muted); }   /* not faded: a see-through disc would show the group's frame line running under it (Dominik, 2026-09-15) */
.lines:has(> .eleframe.is-open) > .line.is-head.is-group::before { border-color: var(--muted); }
.fld[aria-disabled="true"]:hover { border-color: var(--line); }                           /* a pink cell planned for the editing step does nothing yet: no hover (design.md → Not available) */
/* the labels once, not in every cell (Dominik, 2026-09-14, as Pi's print): a thin line of 20 under each group row, each label over its
   column — the name's where the name text starts, the numbers' centred as their values; the frame's title line (40) carries the calc
   lines' labels the same way, its words in the name's column. A cell keeps its value on its own line without its label. */
.line.is-labels { height: 20px; }
.line.is-labels.is-title { height: var(--bar); }
/* the frame's × in its top right corner (Dominik, 2026-09-15): the size of every close cross, no frame of its own. One look on every frame
   of the app (Dominik, 2026-09-25) — on the title line where a frame has one (the merken frame, an element, a calc line), and on its own
   where a frame has none: a room's frame lost its title line, so the × floats level with the frame's first line, at the same right end. */
.line.is-labels.is-title > .frame-close, .frame-body > .frame-close.is-floating { width: var(--bar); height: var(--bar); display: grid; place-items: center; padding: 0; border: 0; border-radius: 50%; background: transparent; color: var(--btn-text); cursor: pointer; }
.line.is-labels.is-title > .frame-close { grid-column: -2 / -1; justify-self: end; }
.frame-body:has(> .frame-close.is-floating) { position: relative; }   /* only a frame that carries the floating × becomes the corner it hangs in: every other frame keeps the eleframe as its own */
.frame-body > .frame-close.is-floating { position: absolute; top: 0; right: 0; z-index: 2; min-width: 0; }   /* min-width: the frame's rows are a canvas wide, a button is not */
.line.is-labels.is-title > .frame-close:hover, .frame-body > .frame-close.is-floating:hover { background: var(--line); }   /* E9: the frame's own fill is F9 */
.line.is-labels.is-title > .frame-close svg, .frame-body > .frame-close.is-floating svg { width: 24px; height: 24px; }
/* a frame's parts fold (Dominik, 2026-09-24): the caption of each part of a room's frame — the fold mark in the bar column, the part's name
   and its count, then its rows' labels — and under it the rows it holds. A room opens on its Objects; More measures, Tags and Sloped sides
   are closed, each on its own. The mark (icons/pick.html) is drawn here and turns when its part is open: no weight per caption. */
.line.is-labels.is-title.is-part[data-fold] { cursor: pointer; }
.line.is-labels.is-title.is-part[data-fold]::before { content: ""; width: 16px; height: 16px; justify-self: center; background: var(--muted);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='156.6 -801.2 646.8 646.8'%3E%3Cpath d='M480 -356.727 264.405 -572.322 291 -598.917 480 -409.917 669 -598.917 695.595 -572.322Z'/%3E%3C/svg%3E") center / contain no-repeat; mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='156.6 -801.2 646.8 646.8'%3E%3Cpath d='M480 -356.727 264.405 -572.322 291 -598.917 480 -409.917 669 -598.917 695.595 -572.322Z'/%3E%3C/svg%3E") center / contain no-repeat; }
.line.is-labels.is-title.is-part[aria-expanded="true"]::before { transform: rotate(180deg); }
.line.is-labels.is-title.is-part[data-fold]:hover::before { background: var(--btn-text); }
.line.is-labels.is-title.is-part[data-fold]:hover > .lbl.is-name { color: var(--btn-text); }   /* a caption that does not fold does not answer the mouse: its count alone does */
/* a closed part shows its name and its count, never its rows' column labels (Dominik, 2026-09-24: labels over rows you cannot see) */
.line.is-labels.is-title.is-part[aria-expanded="false"] > .lbl:not(.is-name) { visibility: hidden; }
/* a part you cannot add to and that holds nothing does not fold at all (Dominik, 2026-09-26): no fold mark, no cursor, no hover, out of the
   tab order (frame_part.html `fold=False`) — an empty part would open on nothing. Only the Objects part of a room is ever read-only, so this is
   the room with no objects: its count, grey with the caption, is the way to make one. */
/* and **a part that holds nothing never shows its rows' column labels**, open or not (Dominik, 2026-09-26: "sloped sides when open and 0
   entries shows labels — no need when no entries"). One rule for both kinds of empty: the read-only one that cannot open, and Sloped sides,
   which opens on its add row alone. The words go, the columns stay where they are. */
.line.is-labels.is-title.is-part[data-empty] > .lbl:not(.is-name) { visibility: hidden; }
.part-body { display: flex; flex-direction: column; gap: var(--row-gap); }
.part-body > * { min-width: calc(var(--canvas) - 2 * var(--margin)); }
.lines > .eleframe.is-open > .line.is-ele { cursor: default; }   /* inside the open frame a click closes nothing (Dominik, 2026-09-14): no pointer, no hover */
.lines > .eleframe.is-open > .line.is-ele:hover > .fld.is-readonly:not(.is-name, .is-unit) { background: var(--surface); }   /* not the pink name and unit: they keep their tint on hover */
.line.is-labels > .lbl { font-size: var(--fs-small); line-height: 14px; color: var(--muted); text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.line.is-labels > .lbl.is-name { text-align: left; padding-left: 8px; }
.line.is-labels.is-title > .is-name { grid-column: span 1; }   /* the calc lines have their norm: the words keep the name's own column */
.line:is(.is-cal, .is-res, .is-mea) > .is-name { grid-column: span 1; }   /* a calc line has its norm (Dominik, 2026-09-14), and so has a resource: the name keeps its own column, so qty and every cell after it stand under the element's */
/* a calc line's frame (Dominik, 2026-09-15): its resources open in a frame of the element frame's own shape, one level down —
   its top line in the 4 px gap above the calc line row, holding the whole line (Dominik, 2026-09-15) — 5E (darker than the element's A9), r5, filled white so it reads apart from the F9 around it; the calc line
   does not move; one at a time. The frame is `.calframe`, its inner box the same `.frame-body`, loaded when first opened (app.js block 17) */
.frame-body > .calframe { position: relative; display: flex; flex-direction: column; gap: var(--row-gap); }
.frame-body > .calframe:is(.is-open, .is-closing)::before { content: ""; position: absolute; top: calc(-1 * var(--row-gap) / 2); bottom: 0;   /* its top line in the gap above the calc line, which does not move (Dominik, 2026-09-15: option A) */ left: -4px; right: -4px; border: var(--border) solid var(--btn-text); border-radius: var(--r-card); background: var(--surface); pointer-events: none; }
.frame-body > .calframe.is-open { margin-bottom: calc(16px - var(--row-gap)); }   /* room under the open frame only, as the element's: the clicked line stays where it was */
.calframe > .frame-body { display: flex; flex-direction: column; gap: var(--row-gap); padding-bottom: calc(var(--row-gap) * 2); }
.calframe > .frame-body > .list-title { position: relative; }   /* a kind's divider (LAB · MAT · EQU · OA) lies over the frame's white fill, as the rows do */
.frame-body > .calframe.is-open > .line.is-cal { cursor: default; }   /* inside its open frame a click closes nothing */
.frame-body > .calframe.is-open > .line.is-cal:hover > .fld.is-readonly { background: var(--surface); }
/* focus one level down (Dominik, 2026-09-15): while a calc line is open the rest of its element's frame dims as the list around an open
   element does — the element row, the other lines and the add rows in A9, cells white and unframed, drawings at half */
.eleframe:has(.calframe.is-open) > .line.is-ele > .fld,
.frame-body:has(> .calframe.is-open) > .calframe:not(.is-open) > .line.is-cal > .fld,
.frame-body:has(> .calframe.is-open) > .line.is-add > .fld { border-color: transparent; background: var(--surface); }
.eleframe:has(.calframe.is-open) > .line.is-ele > .fld > .val,
.frame-body:has(> .calframe.is-open) > .calframe:not(.is-open) > .line.is-cal > .fld > .val { color: var(--muted); }
.eleframe:has(.calframe.is-open) > .line.is-ele > .disc, .eleframe:has(.calframe.is-open) > .line.is-ele > .fld > .marks,
.frame-body:has(> .calframe.is-open) > .calframe:not(.is-open) > .line.is-cal > .disc, .frame-body:has(> .calframe.is-open) > .calframe:not(.is-open) > .line.is-cal > .fld > .marks,
.frame-body:has(> .calframe.is-open) > .line.is-add > .disc { opacity: 0.5; }   /* the marks sit in a cell, so the rules name them there (they were never dimmed: 2026-09-17) */
.line > .is-name.is-text { grid-column: 3 / -1; }   /* a text-only element (group 00), a text calc line (Dominik 2026-09-14) and a sentence on a library lines screen (Dominik, 2026-10-10): one long cell to the end of the row, no number cells */
.line[data-fold] { cursor: pointer; }
.line[data-fold]:not(.is-open, .is-group):hover > .fld.is-readonly { background: var(--field); }   /* a pink cell keeps its tint on hover */
.line[data-fold]:focus-visible { outline: 2px solid var(--btn-text); outline-offset: 2px; }
[data-fold-all] svg { transition: transform 120ms; }
[data-fold-all][aria-expanded="true"] svg { transform: rotate(180deg); }   /* the fold mark points up while every group is open */
/* the way to add a line: a disc with a + in the disc column, under the last line, no footer (Dominik, 2026-09-13); green on hover like every "new" */
.line.is-add > .disc.is-add { color: var(--btn-text); text-decoration: none; cursor: pointer; }
.line.is-add > .disc.is-add:hover { background: var(--green); border-color: var(--green); color: var(--surface); }
/* the + of the sloped sides' add row stands in the **name column**, under the names it adds to (Dominik, 2026-09-25) — not alone in the disc
   column, where it was left stranded once the row's two empty boxes went. It takes the fold mark's slot with the names, so its left edge is
   theirs. The objects' and the merken's add rows carry no + at all: they are one box in the name column. */
.part-body > .line.is-add > .disc.is-add { grid-column: 3; justify-self: start; margin-left: var(--mark); }
.line.is-add > .disc.is-add svg { width: 24px; height: 24px; }
.line.is-add > .disc.is-add[aria-disabled="true"], .line.is-add > .disc.is-add[aria-disabled="true"]:hover { color: var(--muted); background: var(--surface); border-color: var(--line); cursor: default; }   /* not available yet (the element's frame, Dominik 2026-09-14): A9, no green — it does nothing (design.md → Not available) */
.pickscreen.is-wide .window > .columns { grid-template-columns: 1fr; }   /* the shop-item picker: one list, two columns wide */
.pickscreen.is-wide .window { width: calc(2 * var(--col) + var(--gutter) + 2 * var(--margin)); }
.pickscreen .pickbar > .quickfind { flex: 1 1 0; }
.pickscreen .pickbar > .quickfind { flex: 0 0 var(--col); margin-right: auto; }   /* the quick find left, a column wide (424), as on every screen; Done and the × at the far right */
@media (max-width: 900px) {   /* a phone: a one-list picker window as wide as the screen less its 8 px edge, never past it (2026-09-18) */
  .pickscreen:not([data-cols="2"]) .window { width: calc(100vw - 16px); }
  .pickscreen:not([data-cols="2"]) .window > .columns { grid-template-columns: minmax(0, 1fr); }
}
.canvas.is-page:has(> .lines) { grid-template-rows: auto minmax(0, 1fr) auto; grid-template-columns: minmax(0, 1fr); }   /* one column that never grows past the screen: the sheet scrolls inside instead */   /* the sheet takes the page's height and scrolls inside; the STORE pill stands under it */
@media (max-width: 900px) { .lines { --bleed: 8px; } }   /* the phone canvas has an 8 px edge: the head band bleeds to it, not past it (after the desktop rule, so it wins) */

/* the actions of a line (Dominik, 2026-09-13, FM's popover without the ⋯): while the mouse is on the buttons' own place — not anywhere on the
   row (Dominik, 2026-09-17: a price typed in the row must not hide its totals) — or the keyboard is in them, always on a phone; they stand over
   the total cell at the row's right end, icons only, the name as tooltip, side by side */
.rowbar { position: absolute; top: 0; right: 0; min-width: 106px; height: var(--bar); display: flex; align-items: center; justify-content: center; gap: var(--row-gap); padding: 0 4px; border: var(--border) solid var(--line); border-radius: var(--r-card); background: var(--surface); opacity: 0; transition: opacity 120ms; }   /* a cell itself: the total box turns into the button(s), framed like every cell; more buttons widen it over the cells to its left */
.rowbar:hover, .rowbar:focus-within { opacity: 1; }
.line:is(.is-ele, .is-cal, .is-res, .is-mea) > .rowbar { min-width: calc(2 * 86px + 106px + 2 * var(--row-gap)); justify-content: space-evenly; }   /* on the elements page every bar covers exactly Lab · Mat · Total (Dominik, 2026-09-14), its icons spread over them */
@media (hover: none) { .rowbar { opacity: 1; } }
.rowbar form { display: contents; }
.rowbar .act { width: 32px; height: 32px; display: grid; place-items: center; border: 0; border-radius: 50%; background: transparent; color: var(--btn-text); cursor: pointer; padding: 0; }
.rowbar .act:hover { background: var(--field); }
.rowbar .act.is-danger:hover { background: var(--danger); color: var(--surface); }
.rowbar .act svg { width: 24px; height: 24px; }
.rowbar .act[hidden] { display: none; }   /* a text line's bar leaves out what does nothing there (Dominik, 2026-09-17) */
.rowbar:not(:has(> .act:not([hidden]), > form)) { display: none; }   /* a bar with nothing left to do is no bar (2026-09-26): a merk objects follow has no bin, so its row gets no empty box on hover */
.rowbar .act[aria-disabled="true"] { color: var(--muted); }                 /* not available yet (the element's bar, Dominik 2026-09-14): A9, the "not built yet" grey */
.rowbar .act[aria-disabled="true"]:hover { background: transparent; }     /* … and no hover: it does nothing (design.md → Not available) */
/* the quick add by code (Dominik, 2026-09-13): a pink code cell on the add row; the matches drop down under it as rows */
.line.is-add > .fld.is-quick { position: relative; overflow: visible; }
.line.is-add > .fld.is-quick > .menu.quick { left: 0; right: auto; width: calc(var(--code-box) + var(--row-gap) + 462px); max-height: calc(8 * (var(--bar) + var(--row-gap))); overflow-y: auto; }
.line.is-add > .fld.is-quick.is-name > .menu.quick { width: 100%; }
.line.is-add > .fld.is-quick.is-code > input { text-transform: uppercase; }   /* the code cell reads L.03. */
.line.is-add > .fld.is-quick.is-name > input { padding-left: 8px; text-align: left; }
.menu.quick .row { height: var(--bar); }
.menu.quick .row.is-on { color: var(--muted); cursor: default; }          /* already a line: shown, not added again */
.menu.quick .row.is-lit { background: var(--field); }                        /* the one the arrow keys stand on */
/* a hairline between the groups of a list, no words over them (Dominik, 2026-09-25): under "Add an object" the doors already standing on this
   level, then the five types, then the project's merken — the order and what each row says carry the meaning, as the room's add row separated
   its merken from its types before it */
.menu.quick > .sep { display: block; height: 1px; margin: var(--row-gap) 8px; background: var(--line); }

/* --- a project's files (FIL, Dominik 2026-09-19) ------------------------- */
.cardbtns > .is-add:hover { color: var(--surface); border-color: var(--green); background: var(--green); }    /* + adds files: green, as every + */
.cardbtns > .is-add:active { color: var(--surface); border-color: var(--green-dark); background: var(--green-dark); }
.cardbtns > button { padding: 0; font: inherit; }
.card .figure.is-file { background: var(--surface); cursor: zoom-in; }
.card .figure.is-file img { width: 100%; height: 100%; object-fit: contain; display: block; }   /* a picture whole, never cropped */
.card .figure.is-pdf iframe { width: 100%; height: 100%; border: 0; pointer-events: none; }     /* the first page; the double click reaches the figure */
.card .figure.is-icon[data-full] { cursor: zoom-in; }
.card form[data-autosave] > .fld { margin: 0 0 var(--row-gap); }
.row.is-removed { color: var(--muted); }                          /* a removed file: greyed, its days left under it (Dominik, 2026-09-19) */
.row.is-removed .disc { opacity: 0.5; }
.list[data-drop].is-dropping { outline: 2px dashed var(--green); outline-offset: -2px; border-radius: var(--r-card); }   /* files held over the list */
a.field-toggle { text-decoration: none; }
.menu hr { height: 0; margin: 2px var(--inset); border: 0; border-top: var(--border) solid var(--line); }   /* a line in a menu: what stands under it filters differently (the glass: the types, then Removed) */
.quickfind .is-dead { display: contents; color: var(--line); }   /* a glass with nothing to drop out: in its place, E9, dead (Dominik, 2026-09-20) */
/* the one screen whose picture area is not square (Dominik, 2026-09-20; design.md): a document's preview stands in A4, so a page is shown
   whole and large. The card then scrolls inside its column, as it may. */
.card.is-doc .figure { height: auto; aspect-ratio: 1 / 1.414; }
/* the type a list is filtered by, over its rows (Dominik, 2026-09-20): the funnel, the type in 12 A9 and the × back to all of them — a
   line that tells, never an eye-catcher, so no fill and no frame; the twin of the Removed switch under the list */
.filter-row { display: flex; align-items: center; height: var(--bar); border-radius: var(--r-pill); padding-right: var(--inset);
  font-size: var(--fs-small); color: var(--muted); text-decoration: none; }
.filter-row > span:nth-child(2) { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.filter-row .ico { flex: none; width: var(--bar); display: grid; place-items: center; line-height: 0; }
.filter-row .ico svg { width: 20px; height: 20px; }   /* as a disc's icon: the line starts where every row's disc starts */
.filter-row .clear { flex: none; font-size: 16px; line-height: 1; }
.filter-row:hover { color: var(--btn-text); background: var(--field); }   /* the Removed switch is a link to the list with or without them: it reads as a switch */

/* --- a project's Levels (LVL step 1, Dominik 2026-09-21; design.md) ------------------------- */
/* one grid for every row of the page, the lines grid's own pieces: disc · number · name · modifier (a level: its short name, 120) ·
   length · width · floor · perimeter · height · ceiling · walls — seven of 86. Skirting and volume stand in a room's frame,
   under the first two number columns (Dominik, 2026-09-21: "lots of numbers that I hardly touch"); the name takes the room they left.
   The last column is 86 like the rest (Dominik, 2026-09-25): 106 is the estimate's Total, which is rightly wider — walls is a measure */
.lines.is-lvl .line { grid-template-columns: var(--bar) var(--code-box) minmax(0, 1fr) 120px repeat(7, 86px); }
/* **the Objects screen has no modifier column** (Dominik, 2026-09-26): a level's short name and a room's modifier live on Levels, and here that
   120 stood empty on every row — a hole between the rooms and Height. Dropped, the rooms field takes it: one long field from the merk to the
   first measure, its names left with their 8 as every name in App. */
.lines.is-objects .line { grid-template-columns: var(--bar) var(--code-box) minmax(0, 1fr) repeat(7, 86px); }
.lines.is-lvl .line:not(.is-head) > .is-name { grid-column: span 1; }   /* the name keeps its own column: the modifier stands beside it */
/* a box on this page: ink = typed and it wins, **A9 = worked out** (Dominik, 2026-09-26, looking at the two side by side: "there is hardly
   any difference between black and 5E"). It was 5E from 2026-09-21, chosen because the results are what the page is read for; at 16 px on
   white the two read as one colour, and a level's row is mostly worked-out numbers, so nothing on it said which few numbers were his. The
   cost, said before the go: the results are now the quietest thing on the page. The grey line above a box never collides with them — it only
   ever appears over a number that is typed, and so black. */
.lines.is-lvl .fld > .input::placeholder { color: var(--muted); opacity: 1; }
.lines.is-lvl .fld.is-neg > :is(.input, .val), .lines.is-lvl .fld.is-neg > .input::placeholder { color: var(--danger); }   /* below zero: red, never refused (Pi's rule) */
.lines.is-lvl .fld > .why { color: var(--danger); }
/* a level: a row of its own kind — a record with boxes, read as a group by its capitals and its black disc (black = the parent, as the
   band). Between two levels 16 clear on either side of a hairline, the corner's own (`.sep`), so where a level ends is seen before it is
   read (Dominik, 2026-09-21: "I get lost with levels and rooms") — the eye groups by distance first, by lines second */
.line.is-lvl { margin-top: calc(32px - var(--row-gap)); }
.line.is-lvl::before { content: ""; position: absolute; left: 0; right: 0; top: -16px; height: 1px; background: var(--line); pointer-events: none; }
.line.is-head + .line.is-lvl { margin-top: var(--row-gap); }   /* the first level stands under the band: no line, no air */
.line.is-head + .line.is-lvl::before { content: none; }
/* a level's disc is a disc like every other (Dominik, 2026-09-25): it was black to tell a level from the rooms under it, and with a room
   carrying no disc at all there is nothing left to tell apart — so it stops being the loudest thing on the page. The Levels window has drawn
   its levels this way since 2026-09-24 (.picksource, below), so the two now read alike. */
.line.is-lvl > .fld.is-name:not(.is-mod) > :is(.input, .val) { text-transform: uppercase; }
.line.is-lvl > .fld.is-name > .lbl { left: 8px; right: 8px; text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }   /* "rooms 54,2 of level 68,0", the label's place in the name box */
.line:is(.is-lvl, .is-room) > .disc { font-size: var(--fs-small); }
/* a room's row is an element row in shape, and it follows the level's rule (Dominik, 2026-09-25: "all rooms start on white fields, only open
   gets pink"): **closed, every box on it is white and answers no click** — the click opens its frame — and **the row hovers as one**, its boxes
   taking no hover of their own; **open, every box is pink** and typed where it stands, the number and name included. A room added opens with
   its frame, so it is pink at once with the cursor in its length (app.js). Before this the number, name and modifier were white but the seven
   measures were pink all the time, so a closed room asked to be typed in down the list. */
.lines > .eleframe.is-room:not(.is-open) > .line.is-ele > .fld { background: var(--surface); }
.lines > .eleframe.is-room:not(.is-open) > .line.is-ele > .fld > .input { pointer-events: none; }
.lines > .eleframe.is-room:not(.is-open) > .line.is-ele > .fld:not(:focus-within):hover { border-color: var(--line); }
.lines > .eleframe.is-room:not(.is-open) > .line.is-ele:hover > .fld:not(:focus-within) { background: var(--field); }
.lines > .eleframe.is-room.is-open > .line.is-ele > .fld.is-code { background: var(--pink); }
/* a level's boxes are pink while it is open and white while it is closed (Dominik, 2026-09-25: "no point to edit a closed LVL"). Closed, a
   click anywhere on the row **opens** it, as a room's row opens its frame and an element's row its calc lines — one rule on all three screens;
   the boxes keep their E9 frames, so the list still reads as a table. A level added is added open, so it is pink at once with the cursor in its
   name, and a level folded by hand stays folded when the list is drawn again (app.js). */
.lines.is-lvl > .line.is-lvl[aria-expanded="false"] > .fld { background: var(--surface); }
.lines.is-lvl > .line.is-lvl[aria-expanded="false"] > .fld > .input { pointer-events: none; }
/* and it hovers as **one row**, as a closed element's does (Dominik, 2026-09-25: "it should work as on ELEs — the mouse hovers the level, then
   a click opens it; now when I go over it, it hovers the field"): the boxes take no hover of their own — there is nothing to fill in them —
   and the row's own hover fills them F9 together, the two rules a read-only box has had since 2026-09-12 (.fld.is-readonly, above) */
.lines.is-lvl > .line.is-lvl[aria-expanded="false"] > .fld:not(:focus-within):hover { border-color: var(--line); }
.lines.is-lvl > .line.is-lvl[aria-expanded="false"]:hover > .fld:not(:focus-within) { background: var(--field); }
/* while a room's frame is open it is the one place that is edited: the levels' boxes around it go quiet as the other rooms' do */
.lines:has(> .eleframe.is-open) > .line.is-lvl > .fld { border-color: transparent; background: var(--surface); }
.lines:has(> .eleframe.is-open) > .line.is-lvl > .fld > .input { pointer-events: none; color: var(--muted); }
.lines:has(> .eleframe.is-open) > .line.is-lvl > .fld > .input::placeholder { color: var(--line); }
.lines:has(> .eleframe.is-open) > .eleframe:not(.is-open) > .line.is-ele > .fld > .input::placeholder { color: var(--line); }
.lines:has(> .eleframe.is-open) > .line.is-lvl > .disc { border-color: var(--line); color: var(--muted); }
.lines:has(> .eleframe.is-open) > .line.is-lvl > .fld > .lbl { color: var(--line); }   /* the grey line above a box goes quiet with its row */
/* **the add row goes quiet with them** (Dominik, 2026-09-26: "when a room is open it is still possible to add a room to the level — that
   should be frozen as the rest"): it makes a record, so while the one place that is edited is open its box is grey, takes no click and, by
   app.js, no Tab either. On the Objects screen no frame of this kind opens, so its own add row is never touched by this. */
.lines:has(> .eleframe.is-open) > .line.is-add > .fld { border-color: transparent; background: var(--surface); }
.lines:has(> .eleframe.is-open) > .line.is-add > .fld > .input { pointer-events: none; color: var(--muted); }
.lines:has(> .eleframe.is-open) > .line.is-add > .fld > .input::placeholder { color: var(--line); }
/* the bin: one button, so the bar is one button wide — it lies over the right end of the last box (walls) only while the mouse is there */
.line:is(.is-lvl, .is-room, .is-slope) > .rowbar { min-width: var(--bar); width: var(--bar); padding: 0; }
/* a room's tags in its frame: the eight switches in one line, from where the name text starts */
.line.is-tags { height: var(--bar); }
.line.is-tags > .tags { grid-column: 3 / -1; display: flex; flex-wrap: wrap; align-items: center; column-gap: 24px; padding-left: 8px; }
.line.is-tags .field-toggle { width: auto; height: var(--bar); padding: 0; border: 0; background: none; gap: 8px; font-size: var(--fs-small); color: var(--btn-text); }
.line.is-room > .fld.is-quick { position: relative; overflow: visible; }   /* a room's name box in its open frame: the room names drop under it, as under the add row's */
.line.is-room > .fld.is-quick > .menu.quick { left: 0; right: auto; width: 100%; max-height: calc(8 * (var(--bar) + var(--row-gap))); overflow-y: auto; z-index: 20; }
/* a room's objects (Levels step 2, Dominik 2026-09-21), on the frame's grid: the type's short name on the disc; "also in Hal 0.02" on a shared
   door where a field's label stands, as a level's check */
.line.is-obj > .disc { font-size: var(--fs-small); }
.line.is-obj > .fld.is-name > .lbl { text-align: left; }
/* its bar: Duplicate, join (a door), Merk, the bin — as wide as its buttons (the six size digits went on 2026-09-25). It lies over the right
   end of the name cell, not over the number boxes as a level's or a room's bin does: buttons over the last boxes would hide threshold,
   molding and a roof window's Slope | Flat from the mouse whenever it is on the line. The name is short ("Door D1 (2)") and starts at the
   left, so the bar hides nothing there; the right edge = the name column's, before the modifier column (120) and the seven numbers */
/* **the bar lies over the right end of the rooms field** (Dominik, 2026-09-26), not over the measures: there it would hide threshold, molding
   and Slope | Flat from the mouse, and the rooms field is long and starts at the left, so nothing of it is covered. Measured from the row's right
   edge **by the seven measure columns and their gaps** — it used to be a hand-added stack that carried the 120 of the modifier column this screen
   no longer has (and a 106 last column it never had), so the bar sat 120 short of the field's end. */
.line.is-obj > .rowbar { min-width: 0; padding: 0 4px; right: calc(7 * 86px + 7 * var(--row-gap)); }
.rowbar .act.is-text { font: inherit; font-size: var(--fs-small); font-variant-numeric: tabular-nums; }
/* the project's merken (Levels step 3, Dominik 2026-09-21): one line under the band; open, a merk is an object's row in shape (`is-obj`).
   (Dominik, 2026-09-25: "most of the time it will never be used — just on bigger projects where there is a specification
   of windows"): a **caption**, not a record row. It stood among the levels as a record — a black-framed disc and 16 ink — and caught the eye on
   every project that has no merken at all. It reads as the captions in a room's frame do (12 A9, 40 tall), and its **chevron is the rows' own
   fold mark in the rows' own slot**: it stands among the levels and holds records under it, so it folds as they do, only quieter. Its words
   carry **how many merken there are, at 0 too** — the caption is closed most of the time and is the only thing saying whether any exist; the
   room's captions show no number when they hold nothing (2026-09-24), because they are already inside an opened frame. */
/* a caption that folds records under it: the project's merken, and a level on the Objects screen (Dominik, 2026-09-25 — a level is a record on
   Levels and is typed there; here it only says which floor the rows are read against). A frame's parts keep their own turning chevron. */
.line.is-labels.is-title[data-fold]:not(.is-part) { cursor: pointer; }
.line.is-labels.is-title[data-fold]:not(.is-part):hover > .lbl.is-name { color: var(--btn-text); }
/* **Nothing on the Objects screen opens except its level captions** (Dominik, 2026-09-25: "there is no open obj, so the chevron is not needed,
   the fields can be next to each other"), so the rows that do not fold give the fold mark's slot back and their boxes stand side by side. The
   captions keep it — they carry the mark and fold what is under them — and the labels line follows the rows it names. */
.lines.is-objects .line:not([data-fold]) > :is(.fld, .lbl).is-name:not(.is-mod) { margin-left: 0; }
/* an object's kind, drawn on its disc (step C, Dominik 2026-09-25): the figure at 24 px in 5E — a disc's code or initials are E9, but a line
   drawing needs that weight to read at all */
.disc.is-kind { color: var(--btn-text); }
.disc.is-kind > svg { width: 24px; height: 24px; }
/* a list dropped from a button (join, the + that adds an object): under the cell or the disc, over the rows below. A disc clips what it holds
   — its figure is round — so the one that drops a list lets it out (Dominik, 2026-09-25: the + on the last row drops the five kinds). */
.is-drop { position: relative; overflow: visible; }
.disc.is-add.is-drop { overflow: visible; }
.is-drop > .menu.quick { left: 0; right: auto; top: calc(100% + 4px); max-height: calc(8 * (var(--bar) + var(--row-gap))); overflow-y: auto; z-index: 20; }
/* **how wide, by where it hangs** (Dominik, 2026-09-26: "the drop down is not as we made rules — there was a fix width for it" → 208):
   dropped from a **button or a disc** it is a dropdown, so it takes the dropdown's own 208, half a column, not the 40 of the button (which is
   why it used to sit at its 280 minimum); dropped over a **box** it keeps the box's width, because it is that box's own list and a room's full
   name is what tells two doors apart (2026-09-25) */
.disc.is-drop > .menu.quick { width: 208px; }
.fld.is-drop > .menu.quick { width: 100%; }
/* a roof window's place, in its last cell: Slope | Flat, the segments of the design system in a cell's size */
.segments.is-place { font-size: var(--fs-small); }
/* --- the Objects screen (step B, Dominik 2026-09-25): the project's kozijnstaat -------------------------------------------------- */
/* the two counts that lead into it — a merk's **Placed** in the frame on top, a room's "Objects · 3" on its caption on Levels. */
/* **Placed is a column of its own** (Dominik, 2026-09-26: "make extra column for placed field"). It stood in the label over the kind, where
   the link around it had to be taken out of the flow — and there the two lines, the count and the kind, lay on each other at one size. Now the
   whole cell is the link: the number is its value and reads as one, "used twice" stands in its label where a cell's label stands. */
.line.is-merk > a.fld.is-placed { text-decoration: none; }
.line.is-merk > a.fld.is-placed > .val { color: var(--btn-text); cursor: pointer; }
.line.is-merk > a.fld.is-placed:hover { border-color: var(--muted); }
.line.is-merk > a.fld.is-placed:hover > .val { text-decoration: underline; }
/* the kind in words, beside the merk's name: **small and grey** (Dominik, 2026-09-26: "just make kind smaller"). The disc says it short
   already, and the row is read for its name, its size and how many follow it — the kind only tells two same-sized merken apart. */
.line.is-merk > .fld.is-kind > .val { font-size: var(--fs-small); color: var(--muted); }
.line.is-part .lbl.is-name > a { text-decoration: none; }   /* the underline is the anchor's own: a child cannot take it off */
.line.is-part .lbl.is-name > a { pointer-events: auto; cursor: pointer; color: var(--btn-text); }
/* a room's count is part of its caption, not a dark word in a grey line (Dominik, 2026-09-26): it is the caption's own grey, and
   darkens with the caption when the mouse is on the row — the underline on hover is what says it is a link */
.line.is-part .lbl.is-name > a { color: inherit; }
.line.is-part .lbl.is-name > a:hover { text-decoration: underline; color: var(--btn-text); }   /* on a caption that does not fold the count darkens by itself */
/* **the two lists of an object's row**, both under their own box and over the rows below (Dominik, 2026-09-26). They had no rule of their own:
   the one written here in step B named the **modifier** column, which this screen lost when step C moved the merk into the code cell and the
   rooms into the name cell — so both fell back to the house default, 240 wide hung from the right, and the merk's list (its box is 86) ran
   leftwards off the row. The **merk** takes the dropdown's 208, because a name and a size do not fit in 86; the **rooms** list is its box's own
   width, which is a long field now, so a room's full name reads whole — what tells two doors apart (2026-09-25). */
.line.is-obj > .fld.is-quick { position: relative; overflow: visible; }
.line.is-obj > .fld.is-quick > .menu.quick { left: 0; right: auto; max-height: calc(8 * (var(--bar) + var(--row-gap))); overflow-y: auto; z-index: 20; }
.line.is-obj > .fld.is-quick.is-code > .menu.quick { width: 208px; }
.line.is-obj > .fld.is-quick.is-name > .menu.quick { width: 100%; }
/* the band of a filtered list ("Merk D1 · 2 objects"), a caption in shape with the × of a frame at its end: what the list is filtered to, and
   the one click back to the whole project */
.line.is-labels.is-title.is-filter > .lbl.is-name { color: var(--btn-text); }
.line.is-labels.is-title.is-filter > .frame-close { display: grid; }

/* --- Levels step 5: the proposals under an element, the Levels list in its frame (Dominik, 2026-09-22) ---------------------------- */
/* how a measurement from Levels came about, small and grey in its name box's label place, always ("Badkamer 1e.02 · Walls: 11,4 × 2,60 − 3,8 = 25,8") */
.line.is-mea > .fld.is-name > .lbl.is-sum { text-align: left; }
/* a tick in the disc slot, as the picker's: white check on green = taken; a white disc with an E9 check = left out */
.line.is-mea > .disc.is-tick { position: relative; padding: 0; font: inherit; cursor: pointer; }
.line.is-mea > .disc.is-tick svg { color: var(--line); }
.line.is-mea > .disc.is-tick.is-on svg { color: var(--surface); }
.line.is-mea > .disc.is-tick:not(.is-on):hover { border-color: var(--green); }
.line.is-mea > .disc.is-tick > input { position: absolute; inset: 0; margin: 0; opacity: 0; cursor: pointer; }
/* a proposed row is grey until the Confirm; one left out a step lighter */
.line.is-mea.is-proposed > .fld > .val { color: var(--muted); }
.line.is-mea.is-unticked > .fld > .val, .line.is-mea.is-unticked > .fld > .lbl { color: var(--line); }
/* the Levels list: a row is its tick; one already under the element with this view is ticked, greyed, not taken again */
.line.is-mea.is-source { cursor: pointer; }
.line.is-mea.is-source.is-here { cursor: default; }
.line.is-mea.is-source.is-here > .fld > .val { color: var(--muted); }
.line.is-mea.is-source.is-here > .disc.is-tick > input { cursor: default; }
/* Confirm on the title line, over Mat, beside the ×'s place */
.line.is-labels.is-title > .is-confirm { grid-column: -3 / -2; justify-self: stretch; height: 32px; padding: 0 8px; font-size: var(--fs-small); }
/* the panel's and the view's pills under the list's title */
.line.is-pills { height: auto; min-height: var(--bar); }
.line.is-pills > .mea-choice { grid-column: 3 / -1; display: flex; flex-wrap: wrap; gap: 8px; }
.line.is-pills > .mea-choice > .segments { flex: none; font-size: var(--fs-small); }
.line.is-pills > .mea-choice > .segments > * { flex: none; }
.mea-levels { margin-top: 16px; }
/* the band: "3 wait for measurements ›" in its name box's label place; a click opens the next waiting element */
.line.is-est > .fld.is-name > .waiting { position: absolute; top: 2px; left: 8px; padding: 0; border: 0; background: none; font: inherit; font-size: var(--fs-small); line-height: 14px; color: var(--ink); cursor: pointer; }
.line.is-est > .fld.is-name > .waiting:hover { text-decoration: underline; }

/* --- Levels step 5b: the Levels window, and a taken number grey or ink (Dominik, 2026-09-24) -------------------------------------- */
/* the view bar: the room views and the object views in one track, a hairline between the groups (as a room's add row: merken │ types) */
.segments.is-views { font-size: var(--fs-small); overflow-x: auto; }
.segments.is-views > a { flex: 0 0 auto; overflow: visible; }   /* a view is read whole: the bar scrolls, its pills are not squeezed */
.segments.is-views > .apart { flex: none; width: var(--border); align-self: center; height: 60%; background: var(--line); }
/* a row of the window: the picker's row with the view's number at its right end, and the chevron that opens what it holds */
/* a row and its explanation are the window's two columns, each a column wide, hugging their own edge — the same measure as the head's
   search and Done, and the panel bar and its dropdowns (Dominik, 2026-09-24) */
.picksource { position: relative; display: flex; align-items: center; justify-content: space-between; }
/* a row is no wider than its words need (Dominik, 2026-09-24: "the names are never that big"): the name, then the number in its own
   column at the row's end — the window keeps its width, the list reads as one block */
/* a row is one column wide (Dominik, 2026-09-24): the disc, the name, the number at its right end — the window's second column stays empty */
.picksource > form, .picksource > .row { flex: 0 0 var(--col); min-width: 0; }
.picksource .row { width: 100%; }
/* the number stands in a column of its own, centred in it as every number in App is (design.md), and as short as it is */
.picksource .row > .num { flex: none; width: var(--code-box); margin-left: auto; text-align: center; color: var(--ink); font-variant-numeric: tabular-nums; }
/* the window's second column explains the row (Dominik, 2026-09-24): how its number comes about, and how many the element holds of it —
   12 A9, beside the row, where there was empty space */
/* its words start where the pill's words above it start (the pill's own inset), so the two read down one line (Dominik, 2026-09-24) */
.picksource > .why { flex: 0 0 var(--col); min-width: 0; padding: 0 var(--inset); color: var(--muted); font-size: var(--fs-small); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.picksource.is-dim .row, .picksource.is-dim .row > .num { color: var(--muted); cursor: default; }   /* the view is not a measure of this row (design.md → Not available) */
.picksource.is-dim .row:hover { background: transparent; }
/* a level's disc is a disc like every other (Dominik, 2026-09-24): on LVL the whole list is levels, so nothing has to be told apart */
.picksource .row:is(.is-level, .is-room) > .disc { font-size: var(--fs-small); }
/* a measurement's number: grey while it is the room's, ink once typed over (the Levels page's rule) */
.line.is-mea > .fld > .val.is-grey { color: var(--muted); }

/* --- Levels step 6: what is behind Levels (Dominik, 2026-09-27) -------------------------------------------------------------------- */
/* the sum line says it instead of the sum, which is the old one ("Badkamer 1e.02 · Walls, room changed: 6,2 → 6,8"): orange, so it is
   found while scrolling; the press sits at its end, the one thing on a label line that answers the mouse */
.line.is-mea > .fld.is-name > .lbl.is-sum.is-behind { color: var(--behind); }
.line.is-mea > .fld.is-name > .lbl.is-sum > button { margin-left: 6px; padding: 0; border: 0; background: none; color: inherit; line-height: 0;
  vertical-align: -2px; pointer-events: auto; cursor: pointer; }
.line.is-mea > .fld.is-name > .lbl.is-sum > button svg { width: 12px; height: 12px; }
.line.is-mea > .fld.is-name > .lbl.is-sum > button:hover { color: var(--ink); }
/* the element's third ribbon, beside the bookmark's and the status's (Dominik, 2026-09-27: "its own ribbon") */
.line.is-ele > .fld.is-code > .marks > [data-mark="bookmark_filled"].is-behind { color: var(--behind); }
/* "1 new matching room" on the measurements' title line, left of Confirm's place: it makes a proposal, so it is no green Done. Its words are
   longer than a button column, so it takes the two columns before Confirm and hugs their right end (a column alone cut it to "1 new ma…") */
.line.is-labels.is-title > .is-new { grid-column: -5 / -3; justify-self: end; height: 32px; padding: 0 8px; font-size: var(--fs-small);
  white-space: nowrap; color: var(--behind); border-color: var(--behind); }
.line.is-labels.is-title > .is-new:hover { background: var(--behind); color: var(--surface); }
/* the band: "5 measurements behind Levels · Update all" at the right end of the name box's label line, opposite the waiting line */
.line.is-est > .fld.is-name > .behind { position: absolute; top: 2px; right: 8px; font-size: var(--fs-small); line-height: 14px; color: var(--behind); }
.line.is-est > .fld.is-name > .behind > button { margin-left: 6px; padding: 0; border: 0; background: none; font: inherit; font-size: var(--fs-small);
  color: var(--behind); text-decoration: underline; cursor: pointer; }
.line.is-est > .fld.is-name > .behind > button:hover { color: var(--ink); }

/* --- every picker validates (Dominik, 2026-09-24): a click chooses, Done adds ------------------------------------------------------ */
/* Done takes the norm of a standalone pill: half a column (Dominik, 2026-09-24), so it stands exactly above the filter pill under it */
.pickbar .pick-done { flex: none; }
.pickbar .pick-done .btn { width: var(--half-col); justify-content: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.row.is-chosen, .picksource.is-chosen > .row { background: var(--field); }   /* chosen: the row's own hover fill, so the choice is read down the list */
/* **a record reads as a record, held or not** (Dominik, 2026-10-14: *"same gray for subgroup and already in stabu is
   confusing. keep all records ink"*): the name stays ink and only its disc says it is there already, so grey in this
   window means a caption and nothing else. On a template it cannot be clicked — a record stands there once — and on
   the estimate it still can, since a second line is allowed. */
.row.is-held { cursor: default; }
/* …and it keeps its ink against the rule that greys anything carrying aria-disabled (line above): the row really is not
   a button on a template, but it is still a record, and grey in this window says "caption" (Dominik, 2026-10-14) */
.pickscreen .row.is-held[aria-disabled="true"], .pickscreen .row.is-held { color: var(--ink); }
/* a picker that takes more than one (the chapters a document serves, library.md D-4): a taken row wears the same green disc
   a chosen row wears everywhere else, its own code still read in white over it — a click takes it, a click takes it off */
.row.is-chosen > .disc { background: var(--green); border-color: var(--green); color: var(--surface); }
.row.is-chosen > .disc svg { color: var(--surface); }
/* **a chosen row wears the white check**, drawn in the stylesheet and not in the row (Dominik, 2026-09-24 for the look;
   2026-10-14 for where it is drawn). The browser ticks a row itself now — no request, no redraw — so the check cannot be a
   figure the server puts inside the disc: it is one rule here, as the `pending` mark has been since 2026-10-11, and a row
   pays nothing for it. The record's own picture gives way to it while the row is chosen. */
a.row.is-chosen > .disc { position: relative; }
a.row.is-chosen > .disc > :is(svg, img) { visibility: hidden; }
a.row.is-chosen > .disc::after { content: ""; position: absolute; inset: 0; background: var(--surface);
  -webkit-mask: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='83.3%20-875.9%20793.5%20793.5'%3E%3Cpath%20d='M389.0%20-286.42822265625%20215.507568359375%20-458.920654296875%20246.5396728515625%20-490.9527587890625%20389.0%20-348.492431640625%20713.4603271484375%20-671.9527587890625%20744.492431640625%20-640.920654296875Z'/%3E%3C/svg%3E") center / 20px no-repeat; mask: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='83.3%20-875.9%20793.5%20793.5'%3E%3Cpath%20d='M389.0%20-286.42822265625%20215.507568359375%20-458.920654296875%20246.5396728515625%20-490.9527587890625%20389.0%20-348.492431640625%20713.4603271484375%20-671.9527587890625%20744.492431640625%20-640.920654296875Z'/%3E%3C/svg%3E") center / 20px no-repeat; }
/* the Levels window's three lists (Dominik, 2026-09-24): the panel bar LVL · ROOM · OBJ, and beside it the view bar of that panel */
.pickmodes { display: flex; gap: 8px; align-items: center; }
.pickmodes > .pickmenu:first-of-type { margin-left: auto; }   /* the view and the filter at the right, under Done (Dominik, 2026-09-24) */
.pickmodes > .segments.is-modes { flex: 0 0 var(--col); }   /* the panel bar is the nav bar's shape and its width (424), as AAN · CON · PRO (Dominik, 2026-09-24) */
.pickmodes > .segments.is-modes > a { flex: 1 1 0; }
.pickmodes > .segments.is-views { flex: 1 1 auto; min-width: 0; }
/* a level in the Objects list is a header: it opens its objects and is never chosen (Dominik, 2026-09-24) */
.picksource.is-header .row { color: var(--ink); cursor: default; }
.picksource.is-header .row > .num { display: none; }
/* a picker's row says its state on its disc (Dominik, 2026-09-24; the held disc redrawn 2026-10-14): **green with a white
   check = chosen**, **the check in A9 on a white disc = the parent holds it already** — the same drawing twice over, in
   the register each state deserves. The Levels window keeps the filled grey disc it has always had (it holds no picture
   to give way, and that window is left alone, Dominik 2026-10-13). */
.pickscreen .row.is-held > .disc { position: relative; background: var(--surface); }   /* white under the grey check: the three states read apart — a picture is free, this one is there already, green is chosen */
.pickscreen .row.is-held > .disc > :is(svg, img) { visibility: hidden; }
.pickscreen .row.is-held > .disc::after { content: ""; position: absolute; inset: 0; background: var(--muted);
  -webkit-mask: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='83.3%20-875.9%20793.5%20793.5'%3E%3Cpath%20d='M389.0%20-286.42822265625%20215.507568359375%20-458.920654296875%20246.5396728515625%20-490.9527587890625%20389.0%20-348.492431640625%20713.4603271484375%20-671.9527587890625%20744.492431640625%20-640.920654296875Z'/%3E%3C/svg%3E") center / 20px no-repeat; mask: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='83.3%20-875.9%20793.5%20793.5'%3E%3Cpath%20d='M389.0%20-286.42822265625%20215.507568359375%20-458.920654296875%20246.5396728515625%20-490.9527587890625%20389.0%20-348.492431640625%20713.4603271484375%20-671.9527587890625%20744.492431640625%20-640.920654296875Z'/%3E%3C/svg%3E") center / 20px no-repeat; }
.picksource.is-held > .row > .disc { background: var(--muted); border-color: var(--muted); color: var(--surface); }
.picksource.is-held > .row > .disc svg { color: var(--surface); }
/* chosen beats held: on the estimate a record already on the line may be taken a second time, and then it is the green one */
.pickscreen .row.is-chosen.is-held > .disc { background: var(--green); border-color: var(--green); }
.pickscreen .row.is-chosen.is-held > .disc::after { background: var(--surface); }
.row.is-fixed { cursor: default; }
.picksource.is-chosen > .row > .disc { background: var(--green); border-color: var(--green); color: var(--surface); }
.picksource.is-chosen > .row > .disc svg { color: var(--surface); }
/* a caption of the Levels window's accordion (Dominik, 2026-09-24): the level, 12 pt and A9 as every divider, with the arrow that folds
   what is under it — never a row to choose. The same shape is meant for the other screens' groups. */
.pickgroup { display: flex; align-items: center; min-height: 32px; }
/* the arrow stands in the disc's own column, centred on it, and the caption's words start where a row's name starts (40 + 16 = L56, FM's
   inset) — so a caption and the rows under it read down one line (Dominik, 2026-09-24) */
.pickgroup > .fold, .pickgroup > .list-title { display: flex; align-items: center; gap: var(--inset); color: var(--muted); }
.pickgroup > .fold { text-decoration: none; }
.pickgroup > .fold:hover { color: var(--btn-text); }
.pickgroup > :is(.fold, .list-title) > svg, .pickgroup > .list-title::before { flex: none; width: var(--bar); }
.pickgroup > .fold > svg { height: 16px; transition: transform 0.2s ease; }
.pickgroup > .list-title::before { content: ""; }
.pickgroup > .fold[aria-expanded="true"] > svg { transform: rotate(90deg); }   /* a level holding its rooms is the page's own hierarchy (Dominik, 2026-09-25): the chevron points right while the caption is closed and down while it is open, never down → up */
/* the Levels window's view and filter, as Pi's dropdowns (Dominik, 2026-09-24): one word with the fold mark, its choices over it */
.pickmenu { position: relative; flex: none; }
/* a dropdown that stands on its own is half a column wide (Dominik, 2026-09-24): two of them and their gap make one column (424), so the
   window's head reads as its own two columns — the panel bar 424, the view and the filter 208 + 208 */
.pickmenu { flex: 0 0 var(--half-col); }
.pickmenu > summary { list-style: none; display: flex; align-items: center; justify-content: space-between; gap: 8px; height: var(--bar); padding: 0 var(--inset); border: var(--border) solid var(--line); border-radius: var(--r-pill); background: var(--surface); color: var(--ink); font-size: var(--fs); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: pointer; }
.pickmenu > summary::-webkit-details-marker { display: none; }
.pickmenu > summary:hover { background: var(--field); }
.pickmenu[open] > summary { border-color: var(--btn-text); }
.pickmenu > summary svg { width: 16px; height: 16px; }
.pickmenu > .menu { left: 0; right: auto; width: 100%; max-height: 60vh; overflow-y: auto; }   /* the menu takes its pill's width, so the two line up */
/* a picker window's own title, as FileMaker's window title (Dominik, 2026-09-24): the record it adds to, once, above the head — every
   picker alike, so the head holds only the search, Done and the × */
.pickscreen .windowtitle { position: relative; height: var(--bar); display: flex; align-items: center; justify-content: center; font-size: var(--fs-small); letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); }
.pickscreen .windowtitle > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* the × in the window's title bar, as FileMaker's (Dominik, 2026-09-24): the head then holds the search and Done alone, each on its column */
.pickscreen .windowtitle > .close-picker { position: absolute; right: 0; top: 0; width: var(--bar); height: var(--bar); display: grid; place-items: center; border-radius: 50%; color: var(--btn-text); }
.pickscreen .windowtitle > .close-picker svg { width: 24px; height: 24px; }   /* the × is the size it is in every corner (Dominik, 2026-09-24) */
.pickscreen .windowtitle > .close-picker:hover { background: var(--field); }

/* *(gone 2026-10-09.)* A library record's picture was put on and taken off from the **face itself** — the record's own
   disc in one corner, a picture menu in the other, a file dropped on it (Dominik, 2026-09-29) — on the record's edit
   screen, which TEM · STABU · STORE no longer have. `manage.py pictures on|off` is the one hand now (access.md L7), so
   the wrapper and its two corners went with the screen. */

/* the Inbox's paste box (Dominik, 2026-10-21): in the picture's place on the batch's card — the 424 square, pink, saving as it is left */
.card .figure.is-paste { display: block; width: 100%; align-self: stretch; border: 0; background: transparent; padding: 0; }   /* the whole 424 square, however the card centres the rest */
.card .figure.is-paste > .fld { width: 100%; height: 100%; justify-self: stretch; }   /* the figure's place-items: center would shrink the box to half the square */
.card .figure.is-paste > .fld > textarea { height: 100%; min-height: 0; resize: none; padding-top: 18px; }
/* the batch's card: the chain's three boxes stand under the name with the row gap between them, as fields on an edit screen */
.card.is-batch > form, .card.is-batch > .fld { margin-top: var(--row-gap); }
