/* ==========================================================================
   HBS · Home Builder Software — design system
   Based on Leo's approved "BuildSphere minimal" mockup.

   Rules (do not break):
   - Only three surfaces: orange (navigation), white (data), light grey (heads).
   - No shadows, no border radius, no gradients.
   - One font family (Inter), three weights. Tabular numerals everywhere.
   - Every visual pattern lives HERE or in a Blade component under
     resources/views/components/hbs — never inline in a page view.
   ========================================================================== */
/* Colour tokens. Every value carries its measured contrast ratio against the
   surface it is used on, so a later change cannot quietly drop below the line.
   Text needs 4.5:1 (WCAG 1.4.3), controls and meaningful graphics 3:1 (1.4.11).
   The palette is unchanged in character — the orange stays the brand — only
   the values that could not be read were corrected. */
:root{
  --orange:#FFA500;        /* fill only: 1.97:1, never carries meaning alone */
  --orange-edge:#B87400;   /* boundary of every orange fill · 3.8:1 on white */
  --orange-deep:#5A3700;   /* text/markers on orange · 5.4:1 · 10.6:1 on white */
  --link:#945A00;          /* links on white · 5.6:1 */

  --ink:#1A1714;           /* body text · 17.9:1 */
  --ink-2:#6B655D;         /* secondary text · 5.8:1 */
  --ink-3:#7A736A;         /* tertiary text · 4.7:1 (was #918B82 · 3.4:1) */

  --bg:#FFFFFF;
  --head:#F2F1EE;          /* table heads, toolbars */
  --zebra:#FCFCFB;
  --grid:#DFDCD6;          /* decorative rules inside a table · 1.4:1 */
  --grid-strong:#B8B3AA;   /* sum lines and container edges */
  --control-line:#767068;  /* border of every control · 4.9:1 (1.4.11 needs 3) */
  --sel:#FFF3DC;           /* selected row */

  /* These four were referenced 28 times and never defined, so the browser
     dropped every declaration that used them: cards had no border at all and
     disabled fields were indistinguishable from editable ones. They are
     aliases of the tokens they were always meant to be. */
  --line:var(--grid-strong);
  --line-soft:var(--grid);
  --paper:var(--zebra);
  --muted:var(--ink-2);

  /* Semantic colours. Orange is the brand and the navigation; it is not a
     meaning. Everything that carries one — a state, a phase, a kind of thing —
     takes a colour from here, so "orange" stops being the answer to every
     question and a screen can be read by its colours instead of its labels.

     Each family has three values:
       --x        ink on white, >= 4.5:1, so it can carry a label (1.4.3)
       --x-edge   border of a filled surface, >= 3:1 (1.4.11)
       --x-tint   fill light enough that --ink still reads on it at >= 7:1 */
  --red:#A32A1E;
  --red-edge:#C4483A;
  --red-tint:#FBEAE8;
  --amber:#8A5A00;
  --amber-edge:#B58020;
  --amber-tint:#FDF1DC;
  --green:#2E6B3E;
  --green-edge:#4C8A5C;
  --green-tint:#E8F3EA;
  --blue:#2A5580;
  --blue-edge:#4A7BAE;
  --blue-tint:#E7EFF7;
  --violet:#5B3E8C;
  --violet-edge:#7B5EB0;
  --violet-tint:#EFEAF8;
  --teal:#1F6362;
  --teal-edge:#3E8887;
  --teal-tint:#E3F1F1;

  /* Phase / stage palette. Eight hues that stay apart from each other and
     from the orange, for anything the user colours themselves: template
     phases, Gantt bars, calendar entries, legends. Ordered so the first four
     — the ones most schedules ever use — are the most distinguishable, also
     under the common forms of colour blindness. */
  --stage-1:#2A5580;  /* blue    */
  --stage-2:#8A5A00;  /* amber   */
  --stage-3:#2E6B3E;  /* green   */
  --stage-4:#A32A1E;  /* red     */
  --stage-5:#5B3E8C;  /* violet  */
  --stage-6:#1F6362;  /* teal    */
  --stage-7:#8C4A6B;  /* plum    */
  --stage-8:#4A5568;  /* slate   */

  --gantt-label:220px;     /* width of the Gantt's sticky label column */
  --gantt-row:44px;        /* two lines: the activity, and what it says about it */
  --row:32px;              /* slightly airier than the mockup (30px) */
  --pad:16px;              /* horizontal rhythm */
  --font:Inter,-apple-system,'Segoe UI',system-ui,sans-serif;
}
*{box-sizing:border-box;margin:0;padding:0}
/* The browser hides [hidden] with a user-agent rule, and any author rule that
   sets a display beats it. So an element with both a hidden attribute and a
   class that says display:flex is simply always on screen — which is how the
   drag-and-drop hint came to be drawn permanently across the file list, and
   what every panel, sheet and overlay in this stylesheet risks the moment it
   is given a display. One rule removes the whole class of bug.
   !important because it must outrank the class, whatever the class says. */
[hidden]{display:none!important}
/* Font sizes are expressed in rem against this, so the whole interface follows
   the reader's browser setting. It used to be fixed px throughout, which meant
   "larger text" in the browser did nothing at all. */
html{font-size:100%;-webkit-text-size-adjust:100%}

/* Every control can be hit with a gloved finger. 2.5.8 asks for 24x24 CSS
   pixels; icon-only buttons were 15px wide.

   `a.btn` is deliberately NOT in this list. Its specificity (0,1,1) beat the
   phone rule `.btn,.txtbtn{min-height:44px}` (0,1,0), so every link that looks
   like a button — "Projekt anlegen", "Neuen Vorgang anlegen", "Nächster Monat",
   about 326 of them — stayed 24px on a phone while the <button> next to it was
   44px. `.btn` below already gives the link a floor of 30px on the desk, and
   the phone block raises the same class to 44. */
button,[role="button"],.txtbtn,summary,input[type="checkbox"],input[type="radio"]{
  min-width:24px;min-height:24px;
}
/* A tick is a positive answer, so it is green — the one colour everyone on a
   site reads without being told. Orange stays on the navigation, where it
   means "you are here" and not "yes". */
input[type="checkbox"]{width:18px;height:18px;accent-color:var(--green)}
input[type="radio"]{width:18px;height:18px;accent-color:var(--blue)}

/* Someone who has asked the system for less movement gets less movement. */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:.001ms!important;animation-iteration-count:1!important;
    transition-duration:.001ms!important;scroll-behavior:auto!important;
  }
}

/* Windows high-contrast mode replaces every colour, so anything carried by a
   background or a box-shadow disappears. These re-state the same information
   with system colours and real borders. */
@media (forced-colors:active){
  .nav__item--on,.vtog .on,.ph--now{forced-color-adjust:none;background:Highlight;color:HighlightText}
  .btn,.txtbtn,.field,.card,.edit-card,.sheet td,.sheet th{border-color:CanvasText}
  :focus-visible{outline:3px solid Highlight;outline-offset:2px}
  .miniprog{border:1px solid CanvasText}
  .miniprog i{background:CanvasText}
  .status-cell{border-left-color:CanvasText!important}
}
/* The application is a fixed shell with exactly one scrolling area, .page.
   Without this the document scrolled as well: two scrollbars, and a wheel in
   the wrong place moved the whole frame instead of the content. Pages that
   are not the app shell (login, error pages) set their own height. */
body{
  font-family:var(--font);font-size:0.8125rem;line-height:1.45;
  color:var(--ink);background:var(--bg);
  font-variant-numeric:tabular-nums;-webkit-font-smoothing:antialiased;
}
html:has(.app),body:has(.app){height:100%;overflow:hidden}
button,select,input,textarea{font:inherit;color:inherit;font-variant-numeric:tabular-nums}
button{background:none;border:0;cursor:pointer}
.inline-form{display:inline-flex;margin-left:5px}
.field--color{width:52px!important;padding:2px;cursor:pointer}
a{color:var(--link)}
/* The ring sits OUTSIDE the element. It used to sit inside (offset -2px), which
   put it on top of the orange and blue button fills at 1.4:1 — invisible
   exactly on the buttons that trigger something irreversible. Near-black keeps
   at least 3:1 on every surface this application uses (white, orange, head,
   zebra, selected row). */
:focus-visible{outline:2px solid var(--ink);outline-offset:2px}
/* A focused control inside a scroll container must not be clipped by it. */
:focus-visible{scroll-margin:8px}
.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}

/* Skip link. Without it every page starts with 35 navigation links that a
   keyboard or screen-reader user has to walk past, on every single page
   (2.4.1). Hidden until focused, then a plain block at the top left. */
.skip{
  position:absolute;left:8px;top:-100px;z-index:200;
  padding:10px 14px;background:#fff;color:var(--ink);
  border:2px solid var(--ink);font-weight:600;text-decoration:none;
}
.skip:focus{top:8px}
.num{text-align:right;font-variant-numeric:tabular-nums}
.dim{color:var(--ink-2)}
.pos{color:var(--green)}
.neg{color:var(--red)}

/* Icons — inline SVG, 1.5px strokes, square caps. Sized via .ic */
.ic{width:15px;height:15px;flex:none;stroke:currentColor;fill:none;stroke-width:1.5;stroke-linecap:square;stroke-linejoin:miter;vertical-align:-2px}
.ic--lg{width:18px;height:18px}

/* ==========================================================================
   Frame
   ========================================================================== */
/* height:100dvh with a 100vh fallback. With the document scroll locked, a
   frame sized to 100vh loses the bottom 60-100px on a phone — footer and
   last table row are simply gone and cannot be reached. */
.app{display:grid;grid-template-columns:224px minmax(0,1fr) 340px;height:100vh;height:100dvh;overflow:hidden}
.app.side-panel-collapsed{grid-template-columns:224px minmax(0,1fr) 0}
/* display:none, not visibility:hidden. A collapsed panel was still laid out
   at its natural width inside a zero-width column, so its buttons and text
   sat 120px outside the window and the page scrolled sideways. Hidden also
   has to mean "not reachable with the keyboard", which visibility:hidden
   gives but a zero-width overflow does not. */
.app.side-panel-collapsed .agent-rail{display:none}
.app>.nav{grid-column:1;grid-row:1}.app>.main{grid-column:2;grid-row:1}.app>.agent-rail{grid-column:3;grid-row:1}

/* Navigation ------------------------------------------------------------- */
.nav{background:var(--orange);color:var(--ink);display:flex;flex-direction:column;overflow-y:auto;border-right:1px solid var(--grid)}
.nav__brand{padding:13px var(--pad) 14px;font-size:0.9375rem;font-weight:600;letter-spacing:-.01em;border-bottom:1px solid rgba(90,55,0,.22);display:flex;align-items:center;gap:9px}
.nav__brand{color:var(--ink);text-decoration:none}
.nav__brand .ic{width:17px;height:17px;flex:0 0 auto}
/* A real company name is longer than "HBS" and must not push the sidebar
   wider or wrap onto a second line. */
.nav__brand-co{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.nav__list{flex:1;padding:8px 0 10px}
.nav__section{border-bottom:1px solid rgba(90,55,0,.14)}
.nav__section:last-child{border-bottom:0}
.nav__group{list-style:none;font-size:0.6875rem;font-weight:700;letter-spacing:.09em;text-transform:uppercase;color:var(--orange-deep);padding:10px var(--pad);display:flex;align-items:center;justify-content:space-between;cursor:pointer}
.nav__group::-webkit-details-marker{display:none}
.nav__group:hover{background:rgba(255,255,255,.28)}
.nav__group .ic{width:12px;height:12px;transition:transform .12s}
.nav__section[open]>.nav__group .ic{transform:rotate(90deg)}
/* A closed <details> stops rendering its content only while no author rule
   says otherwise — the same trap as .fbox__panel further down. Without this
   every area's destinations kept their height whatever the folder said, so
   the sidebar was all 38 of them on every page and the accordion in
   public/js/hbs.js had no visible effect at all. */
.nav__section:not([open])>.nav__items{display:none}
.nav__items{padding-bottom:5px}
/* Pinned destinations: no header, separated from the collapsible groups. */
.nav__items--pinned{padding:5px 0;border-bottom:1px solid rgba(90,55,0,.14)}
.nav__item{
  display:flex;align-items:center;gap:9px;width:100%;
  min-height:30px;padding:5px var(--pad);text-align:left;font-size:0.8125rem;
  border-left:3px solid transparent;color:inherit;text-decoration:none;
}
.nav__item .ic{color:var(--orange-deep)}
.nav__item:hover{background:rgba(255,255,255,.4)}
.nav__item--on{background:#fff;border-left-color:var(--orange-deep);font-weight:600}
.nav__item--on .ic{color:var(--ink)}
.nav__n{margin-left:auto;font-size:0.7188rem;color:var(--orange-deep)}
.nav__item--on .nav__n{color:var(--ink-2)}
/* A destination that the unfinished first-run wizard has not opened yet. It
   is not a link, it does not react to the pointer, and it carries the reason
   in its title — the alternative was 34 links that all bounced back to the
   wizard. Not colour alone: it is also the only entry with no hover and no
   pointer cursor. */
.nav__item--locked{color:rgba(90,55,0,.55);cursor:default}
.nav__item--locked .ic{color:rgba(90,55,0,.45)}
.nav__item--locked:hover{background:none}
.nav__foot{border-top:1px solid rgba(90,55,0,.22);padding:10px var(--pad) 13px;font-size:0.7188rem;color:var(--orange-deep);line-height:1.8}
.nav__foot b{color:var(--ink);font-weight:600}

/* Top bar ---------------------------------------------------------------- */
.main{display:flex;flex-direction:column;min-width:0;overflow:hidden}
.top{
  height:42px;flex:none;display:flex;align-items:center;gap:10px;
  padding:0 14px;border-bottom:1px solid var(--grid);background:var(--head);
}
/* The crumb yields to the switcher: it says where you are inside the module,
   which matters less than which job you are looking at. */
.top__crumb{flex:1 1 auto;min-width:0;font-size:0.7813rem;color:var(--ink-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.top__crumb a{color:var(--ink-2)}
.top__crumb a:hover{color:var(--ink)}
.top__crumb-sep{padding:0 5px;color:var(--ink-3)}
.top__crumb b{color:var(--ink);font-weight:600}
.top__right{margin-left:auto;display:flex;align-items:center;gap:5px}
.top__search{flex:none;position:relative;display:flex;align-items:center}
/* The magnifier sits inside the field so the control explains itself
   without a visible label eating width in a 42px bar. */
.top__search-icon{position:absolute;left:7px;color:var(--ink-2);pointer-events:none}
/* Two classes on purpose: the generic .field below this block sets
   `padding:0 8px` at the same specificity and, being later in the file, won
   the cascade — so the magnifier sat on top of the word "Suchen". */
.top__search .top__search-field{width:180px;min-width:0;padding-left:28px}
.top__search{flex:0 1 180px;min-width:0}
/* Only rendered when there is something to report, so it may be loud. */
.top__badge{display:inline-grid;place-items:center;min-width:18px;height:18px;padding:0 4px;background:var(--red);color:#fff;border:1px solid var(--red);font-size:0.6875rem;font-weight:700}
/* No fixed 30px square any more: the page help carries a word like the two
   controls beside it, so it sizes itself the way they do. */
.top__help{background:transparent;border-color:transparent}
/* The same button with its border turned off, so a toolbar mixing the two
   does not step up and down by 2px per control. */
.txtbtn{min-height:30px;padding:0 10px;gap:7px;font-size:0.7813rem;border:1px solid transparent;display:inline-flex;align-items:center;color:inherit;text-decoration:none;white-space:nowrap}
.txtbtn:hover{background:#fff;border-color:var(--control-line)}
.field{min-height:26px;padding:0 8px;border:1px solid var(--control-line);background:#fff;font-size:0.7813rem;min-width:0}
/* No outline:none here — the border colour alone is not a focus indicator
   (2.4.7), and removing the outline took the only one away. */
.field:focus{border-color:var(--orange-deep)}
/* Relation picker: one combobox. The search used to be a second field above
   the select — two controls for one answer. Now the input IS the control and
   the select only carries the value for the form, so it is taken off screen
   without being removed. */
.relation-picker{width:100%}
/* 340px is the width of every other field in a form row, and the list has to
   line up with the box it drops out of. */
/* Flex, not a fixed three-column grid: the "+" is only rendered where
   creating makes sense, and with two children the grid's third column still
   claimed its share of the row — 680px of empty space between the field and
   the refresh button, which read as two unrelated controls. */
.relation-picker__row{display:flex;align-items:flex-start;gap:5px}
.relation-picker__field{flex:1 1 auto;max-width:340px}
.relation-picker__row>.btn{flex:none}
.relation-picker__field{position:relative;display:flex;min-width:0;flex:1 1 auto;max-width:340px}
.relation-picker__input{width:100%;padding-right:26px}
.relation-picker__value{position:absolute;width:1px;height:1px;margin:-1px;padding:0;border:0;overflow:hidden;clip-path:inset(50%)}
.relation-picker__toggle{position:absolute;right:0;top:0;bottom:0;width:26px;display:flex;align-items:center;justify-content:center;border:0;background:none;color:var(--ink-2);cursor:pointer}
.relation-picker__toggle .ic{width:12px;height:12px;transform:rotate(90deg)}
.relation-picker__list{position:absolute;top:100%;left:0;right:0;z-index:40;margin:-1px 0 0;padding:0;list-style:none;max-height:264px;overflow-y:auto;background:#fff;border:1px solid var(--control-line)}
.relation-picker__option{display:flex;align-items:center;min-height:26px;padding:5px 8px;font-size:0.7813rem;cursor:pointer;border-left:3px solid transparent}
.relation-picker__option:hover{background:var(--sel)}
/* Background alone is 1.09:1 against white — not a state a low-vision reader
   can see (1.4.11). The marker carries it, and the transparent one above keeps
   the text from jumping sideways. */
.relation-picker__option.is-active{background:var(--sel);border-left-color:var(--orange-edge)}
.relation-picker__none{padding:5px 8px;font-size:0.7813rem;color:var(--ink-2)}
@media(max-width:700px){.relation-picker__option{min-height:44px;padding:10px}}
.btn--icon{min-width:32px;padding-inline:8px;justify-content:center}
.filters--reports{display:grid;grid-template-columns:auto minmax(130px,1fr) auto minmax(130px,1fr) auto minmax(210px,1.5fr);gap:6px 8px;align-items:center;padding:10px 12px;border:1px solid var(--line);background:var(--paper);margin-bottom:10px}
.filters--reports .btn{justify-self:end}
@media(max-width:900px){.filters--reports{grid-template-columns:1fr 1fr}.filters--reports label{align-self:end}.filters--reports .btn{justify-self:stretch}}
.role-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:10px}
.role-card{border:1px solid var(--line);background:var(--paper);padding:10px;display:grid;gap:10px}
.role-card header{display:grid;grid-template-columns:auto 1fr auto;gap:8px;align-items:center}
.role-card footer{display:flex;gap:6px;justify-content:flex-end}
.permission-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:5px;padding:10px}
.role-assignment-grid{display:grid;gap:6px}.role-assignment-grid .bar{display:grid;grid-template-columns:auto minmax(160px,1fr) minmax(180px,1fr) auto;gap:8px;align-items:center}
.wizard-stage-row{background:color-mix(in srgb,var(--stage-color) 12%,white);box-shadow:inset 5px 0 0 var(--stage-color)}
.stage-title-field{display:grid;grid-template-columns:30px minmax(150px,1fr);gap:6px;align-items:center}.stage-title-field input[type=color]{width:30px;height:30px;border:1px solid var(--line);padding:1px}
.external-service-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:8px;padding:10px}.external-service-option{display:flex;gap:9px;border:1px solid var(--line);padding:10px;background:var(--paper);cursor:pointer}.external-service-option span{display:grid;gap:3px}.external-service-option small{color:var(--muted)}.privacy-ack{margin:10px;padding:10px;border-left:4px solid var(--orange)}
.capability-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:8px;margin:10px 0}.capability-card{display:grid;grid-template-columns:auto 1fr auto;gap:8px;border:1px solid var(--line);border-left:4px solid #9B2C2C;padding:10px;background:var(--paper)}.capability-card--ok{border-left-color:#2F7D4A}.capability-card div{display:grid;gap:2px}.capability-card small{color:var(--muted)}
.app-version{margin-left:auto;font-family:ui-monospace,SFMono-Regular,Consolas,monospace;color:var(--muted)}
.license-key-input{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;letter-spacing:.08em;text-transform:none}
.license-overview{display:grid;grid-template-columns:repeat(4,minmax(150px,1fr));border-bottom:1px solid var(--grid);border-left:4px solid var(--red);background:#fff}
.license-overview--valid{border-left-color:var(--green)}
.license-overview>div{display:grid;gap:3px;padding:12px 14px;border-right:1px solid var(--grid)}
.license-overview span{font-size:0.6875rem;text-transform:uppercase;letter-spacing:.06em;color:var(--ink-2)}
.license-overview strong{font-size:0.8125rem}
.license-help{display:flex;gap:10px;align-items:flex-start;margin:14px;border:1px solid var(--grid);border-left:4px solid var(--blue);padding:12px;background:#f4f8fb}
.license-help p{margin-top:3px;color:var(--ink-2)}
.avatar img,.channel-message__avatar img{display:block;width:100%;height:100%;object-fit:cover;border-radius:inherit}.avatar-upload{display:flex;gap:10px;align-items:center}.avatar-upload .avatar{width:100px;height:100px;font-size:1.5rem;flex:0 0 100px}
select.field{
  appearance:none;-webkit-appearance:none;
  padding-right:30px;
  background-color:#fff;
  background-image:
    linear-gradient(45deg,transparent 50%,var(--ink-2) 50%),
    linear-gradient(135deg,var(--ink-2) 50%,transparent 50%),
    linear-gradient(to right,var(--grid),var(--grid));
  background-position:
    calc(100% - 13px) 10px,
    calc(100% - 9px) 10px,
    calc(100% - 25px) 0;
  background-size:4px 4px,4px 4px,1px 100%;
  background-repeat:no-repeat;
}
select.field:hover{border-color:var(--grid-strong);background-color:var(--zebra)}
select.field:focus{background-color:#fff}
.multi-field{height:auto!important;min-height:70px;padding:4px;background-image:none!important}
/* Disabled has to be recognisable without reading the label: lighter border,
   grey surface, grey text. Before, an undefined --muted meant the text stayed
   black and the field looked editable. */
.field:disabled,.field[readonly]{background:var(--head);color:var(--muted);border-color:var(--grid-strong);cursor:not-allowed}
.form__hint{font-size:0.6875rem;color:var(--muted);margin:4px 0 0}

/* Validation summary above a form that failed. Red is not the only signal:
   the icon, the heading and the list carry it too (WCAG 1.4.1). */
.form-errors{
  margin:0 12px 12px;border:2px solid var(--red);background:#FDF4F3;
  padding:10px 12px;
}
.form-errors:focus{outline:2px solid var(--ink);outline-offset:2px}
.form-errors__head{
  font-weight:700;color:var(--red);display:flex;gap:7px;align-items:center;
  font-size:0.8125rem;margin:0 0 6px;
}
.form-errors__list{margin:0;padding-left:26px;font-size:0.78125rem;line-height:1.7}
.form-errors__list a{color:var(--red)}
/* A required field is marked in the label, not only by the browser. */
.frow__req{color:var(--red);margin-left:3px;font-weight:700}
.form-actions{display:flex;gap:6px;align-items:center;padding:10px 12px;border-top:1px solid var(--grid);background:var(--zebra)}
.edit-card{margin:0 12px 12px;border:1px solid var(--grid);background:#fff}
.edit-card__head{padding:8px 10px;border-bottom:1px solid var(--grid);font-weight:700}
.status-cell{position:relative;border-left:3px solid transparent!important}
.status-cell:before{display:inline-block;width:12px;margin-right:3px;font-weight:800}
.status-cell--ok{background:#edf8f0!important;border-left-color:#2e7d46!important}
.status-cell--ok:before{content:"✓";color:#2e7d46}
.status-cell--info{background:#edf5fb!important;border-left-color:#2472a4!important}
.status-cell--info:before{content:"●";color:#2472a4}
.status-cell--warn{background:#fff8e5!important;border-left-color:#b77900!important}
.status-cell--warn:before{content:"!";color:#8c5d00}
.status-cell--risk{background:#fff0ee!important;border-left-color:#b6372d!important}
.status-cell--risk:before{content:"!";color:#b6372d}
.status-cell--idle{background:#f4f5f6!important;border-left-color:#8a9096!important}
.status-cell--idle:before{content:"–";color:#737980}
.calendar-head{display:flex;align-items:center;gap:8px;padding:8px 10px;border-bottom:1px solid var(--grid);background:var(--zebra)}
.calendar-head strong{min-width:150px;text-align:center;text-transform:capitalize}
/* Seven columns at a 110px floor plus a 780px minimum meant the month
   always scrolled sideways inside the main area, however wide the window
   was. The columns now share whatever width there is. */
.calendar{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));border-bottom:1px solid var(--grid)}
.calendar__weekday{padding:6px 8px;background:var(--zebra);border-right:1px solid var(--grid);border-bottom:1px solid var(--grid);font-size:0.6875rem;font-weight:700;text-transform:uppercase;color:var(--muted)}
.calendar__day{min-height:110px;padding:5px;border-right:1px solid var(--grid);border-bottom:1px solid var(--grid);background:#fff}
.calendar__day--outside{background:#fafafa;color:var(--muted)}
.calendar__day--off{background:repeating-linear-gradient(135deg,#fafafa,#fafafa 7px,#f4f4f4 7px,#f4f4f4 8px)}
.calendar__day--today{box-shadow:inset 0 0 0 2px var(--orange-deep)}
.calendar__day--drop{box-shadow:inset 0 0 0 3px var(--blue);background:#eef6ff}
.calendar__date{display:flex;justify-content:space-between;gap:4px;font-weight:700}
/* The weekday only earns its place where the column headings are gone. */
.calendar__dow{display:none}
.calendar__date small{font-size:0.6875rem;color:var(--muted);font-weight:400;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.calendar__events{display:grid;gap:3px;margin-top:4px}
.calendar-event{display:block;padding:2px 4px;border-left:3px solid #8a9096;background:#f3f4f5;color:inherit;font-size:0.6875rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.calendar-event--ok{border-color:#2e7d46;background:#edf8f0}.calendar-event--info{border-color:#2472a4;background:#edf5fb}
.calendar-event--warn{border-color:#b77900;background:#fff8e5}.calendar-event--risk{border-color:#b6372d;background:#fff0ee}
.calendar-event--idle{border-color:#8a9096;background:#f3f4f5}
.work-event{
  display:grid;gap:1px;padding:3px 4px;border-left:3px solid var(--event-color);
  background:color-mix(in srgb,var(--event-color) 12%,#fff);font-size:0.6875rem;overflow:hidden;
}
.work-event b,.work-event span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.work-event--sick{--event-color:var(--red)}
/* Moving a shift to another day without a mouse (built by public/js/hbs.js
   below the touch breakpoint, where drag & drop does not exist). */
.work-event__move{width:100%;margin-top:5px;min-height:44px}
.color-swatch{display:inline-block;width:12px;height:12px;margin-right:6px;background:var(--swatch);border:1px solid var(--grid-strong);vertical-align:-2px}
.schedule-split{flex:1;min-height:460px;overflow:hidden}.schedule-split>.split__l{overflow:auto}.schedule-split>.split__r{overflow:auto}.row-critical td:first-child{border-left:3px solid #b6372d!important}
.schedule-legend{display:flex;gap:18px;flex-wrap:wrap;padding:7px 10px;border-bottom:1px solid var(--grid);font-size:0.6875rem}
.legend-dot,.legend-line,.legend-bar,.legend-milestone,.legend-today{display:inline-block;margin-right:5px;vertical-align:middle}
.legend-dot--critical{width:8px;height:8px;background:#b6372d;transform:rotate(45deg)}
.legend-line{width:18px;border-top:2px dashed #55606a}
/* The swatch takes the colour of the thing it stands for, passed in as
   --bar by <x-hbs.schedule-legend>. A legend printed in one fixed colour
   while the bars carry six is worse than no legend. */
.legend-bar{width:18px;height:9px;background:var(--bar,var(--orange));border:1px solid var(--bar-edge,var(--orange-edge))}
.legend-bar--done{background:var(--head);border-color:var(--grid-strong)}
.legend-milestone{width:9px;height:9px;background:var(--orange-deep);transform:rotate(45deg)}
.legend-today{width:0;height:11px;border-left:2px solid var(--red)}
.gantt__baseline{position:absolute;top:25px;height:2px;border-top:2px dashed #55606a;z-index:2}
/* Critical path is a ring, not a repaint. It used to be
   background:#b6372d!important, so on a plan where most activities are
   critical — which is most plans — every bar came out red and the phase
   colours the user had just chosen were invisible. The ring says "critical"
   without taking the colour that says "which phase". */
.gantt__bar--critical,.gantt__milestone--critical{box-shadow:0 0 0 2px var(--red)}
.settings-details{margin-top:8px;border-top:1px solid var(--grid);border-bottom:1px solid var(--grid)}
.settings-details summary{cursor:pointer;padding:9px 12px;background:var(--zebra);font-weight:700}
.check-grid{display:flex;flex-wrap:wrap;gap:8px 14px}

/* Menu (user/language dropdown) — a bordered white panel, no shadow */
.menu{position:relative}
.menu__panel{position:absolute;right:0;top:calc(100% + 4px);background:#fff;border:1px solid var(--grid-strong);min-width:180px;z-index:60;display:none}
.menu[data-open="true"] .menu__panel{display:block}
.menu__item{display:flex;align-items:center;gap:8px;width:100%;padding:7px 12px;font-size:0.7813rem;text-align:left;border-bottom:1px solid var(--grid);color:inherit;text-decoration:none}
.menu__item:last-child{border-bottom:0}
.menu__item:hover{background:var(--sel)}
.menu__item--on{font-weight:600}

/* Page ------------------------------------------------------------------- */
.page{flex:1;overflow:auto;display:flex;flex-direction:column}
/* flex:1 0 auto, not flex:1.
   With flex:1 (= 1 1 0%) plus min-height:0 the view was allowed to shrink
   BELOW its own content, and because it does not clip, the content spilled
   out of it: on a page taller than the window — the month calendar is 731px
   inside a 510px box — the footer was painted in the middle of the page, the
   calendar carried on underneath it, and the document grew to 2337px so there
   was a large empty area to scroll into.
   Not shrinking means the view is always at least as tall as what is in it,
   .page does the scrolling as intended, and the footer stays after the
   content. It still grows to fill the window when a page is short. */
.view{display:flex;flex-direction:column;flex:1 0 auto}
/* A list page is the one exception, and it needs the opposite rule.
   Its grid asks to be the scroller (.grid-wrap: flex:1;min-height:0;overflow:
   auto) and its sum row asks to sit at the bottom of that scroller
   (.sheet:has(tfoot){height:100%} plus .sheet__spacer). Both need the wrap to
   have a height of its own, and inside a view that never shrinks it has none:
   the table grew to its full length, the page took over the scrolling, and the
   100% resolved against nothing. What that looked like was a list with no
   rows — a plate of empty space with the sum row under it and the records
   scrolled out of sight above — and a scrollbar with nothing at the end of it.
   Only lists are constrained, and only where .grid-wrap is the view's own
   child: a table inside a detail page is content and scrolls with the page. */
@media (min-width:701px){
  .page:has(> .view > .grid-wrap){overflow:hidden}
  .view:has(> .grid-wrap){flex:1 1 auto;min-height:0}
}
.app-footer{flex:none;display:flex;align-items:center;justify-content:space-between;gap:16px;min-height:30px;padding:6px 12px;border-top:1px solid var(--grid);background:var(--head);font-size:0.6875rem;color:var(--ink-2)}
.app-footer a{color:inherit}
/* The support address is the line that must survive on a phone; the
   developer credit is the one that gives way. */
.app-footer__support{min-width:0;overflow:hidden;text-overflow:ellipsis}
.head{padding:18px var(--pad) 14px;border-bottom:1px solid var(--grid);display:flex;align-items:flex-end;gap:16px;flex-wrap:wrap;flex:none}
h1{font-size:1.1875rem;font-weight:600;letter-spacing:-.02em}
.head__sub{font-size:0.7813rem;color:var(--ink-2);margin-top:3px}
.head__act{margin-left:auto;display:flex;gap:7px;flex-wrap:wrap}
/* Buttons — four meanings, and a rule you can read off the screen.

   There were seven filled variants and no rule behind them: "Save" was green
   on twenty-three screens, yellow on one and orange on fifty-eight; blue meant
   a portal link, a zoom toggle and a microphone; yellow meant save, reject and
   report sick. A colour that means three things means nothing, and the person
   reading it is a site foreman, not the developer who chose it.

     orange  the main thing on this page — create it, save it. One per screen.
     green   yes: approve, accept, release, sign off.
     red     no: reject, decline, delete.
     violet  the assistant does it for you.
     plain   everything else, however important it feels.

   All of them share one geometry, so a row of buttons is a row of equal
   objects and only the colour differs. */
.btn{
  min-height:30px;padding:0 13px;gap:7px;
  border:1px solid var(--control-line);background:#fff;
  font-size:0.7813rem;font-weight:400;
  display:inline-flex;align-items:center;justify-content:center;
  color:inherit;text-decoration:none;white-space:nowrap;
}
.btn:hover{background:var(--head);border-color:var(--grid-strong)}
.btn--go,.btn--primary{background:var(--orange);border-color:var(--orange-edge);font-weight:600;color:var(--ink)}
.btn--go:hover,.btn--primary:hover{background:#FFB733;border-color:var(--orange-deep)}
.btn--success{background:var(--green);border-color:#1F4E2C;color:#fff;font-weight:600}
.btn--success:hover{background:#3B8050;border-color:#193F24}
.btn--danger{background:var(--red);border-color:#7C1E15;color:#fff;font-weight:600}
.btn--danger:hover{background:#BC3B30;border-color:#671811}
.btn--ai{background:var(--violet);border-color:#42295F;color:#fff;font-weight:600}
.btn--ai:hover{background:#7B5EB0;border-color:#3E286A}
/* A button that is switched on — one of a set of choices, like the Gantt's
   day/week/month. That is a state, not an action, so it does not take one of
   the four action colours: it is the plain button, pressed in. */
.btn--on{background:var(--sel);border-color:var(--orange-edge);font-weight:600}
.btn--on:hover{background:var(--sel)}
/* Kept so an old class cannot silently lose its styling; both now render as
   the plain button, which is what they always meant. */
.btn--info,.btn--warning{background:#fff;border-color:var(--control-line);color:inherit;font-weight:400}
.btn--info:hover,.btn--warning:hover{background:var(--head);border-color:var(--grid-strong)}

.notice{display:flex;gap:8px;align-items:flex-start;margin:10px 0;padding:10px 12px;border:1px solid var(--line);background:#F8F8F6;font-size:0.75rem}
.notice strong{white-space:nowrap}
.notice--info{border-left:4px solid var(--blue);background:#EFF5FA}
.notice--warn{border-left:4px solid var(--orange-deep);background:#FFF7E4}
.detail-grid{display:grid;grid-template-columns:minmax(0,1.7fr) minmax(260px,1fr);gap:12px;margin-bottom:12px}
.card{border:1px solid var(--line);background:#fff;padding:14px;margin-bottom:12px}
.card>h2{font-size:0.75rem;text-transform:uppercase;letter-spacing:.04em;margin:0 0 12px;color:var(--muted)}
.pre-line{white-space:pre-line;line-height:1.55;margin:0}
.key-values{margin:0}
.key-values>div{display:grid;grid-template-columns:110px 1fr;gap:10px;padding:6px 0;border-bottom:1px solid var(--line-soft)}
.key-values>div:last-child{border-bottom:0}
.key-values dt{color:var(--muted)}
.key-values dd{margin:0;font-weight:600}
.private-photo-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:10px}
.private-photo-grid a{display:flex;flex-direction:column;border:1px solid var(--line);background:#FAFAF8;color:var(--ink);text-decoration:none}
.private-photo-grid img{display:block;width:100%;height:130px;object-fit:cover;background:#eee}
.private-photo-grid span{padding:7px 8px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:0.6875rem}
.linked-records{display:flex;flex-wrap:wrap;gap:7px}
.chip{display:inline-flex;align-items:center;min-height:28px;padding:4px 9px;border:1px solid #C6D8E8;background:#EFF5FA;color:#1D4164;text-decoration:none}
.chip:hover{border-color:var(--blue);background:#E0EDF7}

@media(max-width:760px){.detail-grid{grid-template-columns:1fr}.notice{display:block}.notice strong{display:block;margin-bottom:4px}.key-values>div{grid-template-columns:1fr}.key-values dd{margin-top:-4px}}
.btn[disabled]{color:var(--ink-3);pointer-events:none}

/* KPI band: plain text values, no tiles */
.figs{display:flex;flex-wrap:wrap;border-bottom:1px solid var(--grid);background:var(--head);flex:none}
.fig{padding:10px var(--pad);border-right:1px solid var(--grid);min-width:148px}
.fig__k{font-size:0.6875rem;color:var(--ink-2)}
.fig__v{font-size:1rem;font-weight:600;letter-spacing:-.01em;margin-top:2px}

/* Toolbar above tables/boards */
.bar{display:flex;align-items:center;gap:7px;padding:9px var(--pad);border-bottom:1px solid var(--grid);flex-wrap:wrap;flex:none}
.bar label{font-size:0.75rem;color:var(--ink-2)}
/* Filter forms were written as .filters and only .filters--reports existed,
   so four lists laid their filters out with no rules at all. */
.filters{display:flex;align-items:end;gap:7px;padding:9px var(--pad);border-bottom:1px solid var(--grid);flex-wrap:wrap;flex:none;background:var(--head)}
.filters label{display:grid;gap:3px;font-size:0.75rem;color:var(--ink-2)}
.bar__r{margin-left:auto;display:flex;gap:7px;align-items:center}

/* View toggle (table | kanban | diagram) */
.vtog{display:inline-flex;border:1px solid var(--grid)}
.vtog a,.vtog button{height:26px;padding:0 10px;display:inline-flex;align-items:center;gap:6px;font-size:0.7813rem;border-right:1px solid var(--grid);color:inherit;text-decoration:none;background:#fff}
.vtog a:last-child,.vtog button:last-child{border-right:0}
.vtog .on{background:var(--orange);font-weight:600}

/* ==========================================================================
   Spreadsheet tables — full grid, sticky head/first column, sums, statusbar
   ========================================================================== */
.grid-wrap{flex:1;min-height:0;overflow:auto;border-bottom:1px solid var(--grid)}
/* The line-item tables inside the invoice and order forms were wrapped in a
   .table-wrap that did not exist, so a wide position table pushed the whole
   form sideways instead of scrolling inside its own box. It does not stretch
   like a list — it is as tall as its rows. */
.table-wrap{min-width:0;overflow-x:auto;border-bottom:1px solid var(--grid)}
table.sheet{border-collapse:separate;border-spacing:0;width:100%;min-width:100%}
.sheet:has(tfoot){height:100%}
.sheet th,.sheet td{
  border-right:1px solid var(--grid);border-bottom:1px solid var(--grid);
  padding:0 9px;height:var(--row);white-space:nowrap;text-align:left;vertical-align:middle;
}
.sheet thead th{
  position:sticky;top:0;z-index:3;background:var(--head);
  font-size:0.75rem;font-weight:600;color:var(--ink);
  border-bottom:1px solid var(--grid-strong);
}
.sheet thead th.sortable{cursor:pointer;user-select:none}
.sheet thead th.sortable:hover{background:#E9E7E3}
.sheet thead th .caret{color:var(--ink-3);font-size:0.6875rem;margin-left:3px}
.sheet thead th[aria-sort] .caret{color:var(--ink)}
.sheet tbody tr:hover td{background:var(--sel)}
/* A row that names its record's address opens it anywhere along its length
   (public/js/hbs.js), so the pointer has to say so. The link in the first
   cell is still there — this is a second, larger target, never the only one. */
.sheet tbody tr[data-href]{cursor:pointer}
.sheet tbody tr[aria-selected="true"] td{background:var(--sel)}
.sheet tbody tr[aria-selected="true"] td:first-child{box-shadow:inset 3px 0 0 var(--orange-deep)}
.sheet tbody tr.status-row--ok td{background:#F1F8F3}
.sheet tbody tr.status-row--info td{background:#F0F6FB}
.sheet tbody tr.status-row--warn td{background:#FFF9E9}
.sheet tbody tr.status-row--risk td{background:#FFF1EF}
.sheet tbody tr.status-row--idle td{background:#F6F7F7}
.sheet tbody tr.status-row--ok td:first-child{box-shadow:inset 3px 0 0 var(--green)}
.sheet tbody tr.status-row--info td:first-child{box-shadow:inset 3px 0 0 var(--blue)}
.sheet tbody tr.status-row--warn td:first-child{box-shadow:inset 3px 0 0 var(--amber)}
.sheet tbody tr.status-row--risk td:first-child{box-shadow:inset 3px 0 0 var(--red)}
.sheet tbody tr.status-row--idle td:first-child{box-shadow:inset 3px 0 0 var(--ink-3)}
.sheet tbody tr.status-row--ok:hover td{background:#E4F2E8}
.sheet tbody tr.status-row--info:hover td{background:#E2EEF8}
.sheet tbody tr.status-row--warn:hover td{background:#FFF2CC}
.sheet tbody tr.status-row--risk:hover td{background:#FCE2DE}
.sheet tbody tr.status-row--idle:hover td{background:#ECEEEF}
.sheet tbody tr.status-row--custom td{background:color-mix(in srgb,var(--status-color) 12%,#fff)}
.sheet tbody tr.status-row--custom td:first-child{box-shadow:inset 3px 0 0 var(--status-color)}
.sheet tbody tr.status-row--custom:hover td{background:color-mix(in srgb,var(--status-color) 20%,#fff)}
.sheet tbody tr.group-row--custom td{background:color-mix(in srgb,var(--status-color) 20%,#fff);box-shadow:inset 4px 0 0 var(--status-color)}
.kb-col--custom{background:color-mix(in srgb,var(--status-color) 7%,#fff)}
.kb-col--custom .kb-col__h{background:color-mix(in srgb,var(--status-color) 18%,#fff);color:var(--status-color)}
.sheet td.stick,.sheet th.stick{position:sticky;left:0;background:#fff;z-index:2}
.sheet thead th.stick{z-index:4;background:var(--head)}
.sheet tbody tr:hover td.stick{background:var(--sel)}
.sheet tfoot td{
  position:sticky;bottom:0;z-index:3;background:var(--head);font-weight:600;
  border-top:3px double var(--grid-strong);
}
.sheet__spacer td{
  height:100%;min-height:0;padding:0;border-bottom:0;background:#fff!important;
  pointer-events:none;
}
.sheet a.rowlink{color:inherit;text-decoration:none}
.sheet a.rowlink:hover{text-decoration:underline}
.field--table{width:100%;min-width:120px}
.table-actions{display:flex;align-items:center;gap:5px}
.custom-set__head{display:flex;align-items:center;justify-content:space-between;gap:12px}
.custom-set__head>div{display:flex;align-items:baseline;gap:8px}
.w-num{width:110px}
.w-date{width:100px}
.w-money{width:120px}
.w-pct{width:74px}
.w-state{width:150px}
.sheet caption{position:absolute;width:1px;height:1px;overflow:hidden}

/* Cell content helpers */
.id{color:var(--ink-2);font-size:0.7813rem}
.strong{font-weight:600}
/* The quiet second line under a value. It is a second LINE: without this it
   ran on from the value before it, and 80 places across 48 views printed
   things like "Vorbereitungplanning" and "P-2026-0325Vorbereitung". The three
   contexts that deliberately keep it inline (a chip beside a bar label) set
   display back themselves. */
.sub{display:block;font-size:0.7188rem;color:var(--ink-2)}
.state{display:inline-flex;align-items:center;gap:6px;font-size:0.7813rem}
.state::before{content:"";width:7px;height:7px;flex:none;background:currentColor}
.s-ok{color:var(--green)}
.s-warn{color:var(--amber)}
.s-risk{color:var(--red)}
.s-info{color:var(--blue)}
.s-idle{color:var(--ink-3)}
.state span{color:var(--ink)}
.miniprog{display:inline-block;width:52px;height:7px;background:var(--head);border:1px solid var(--grid-strong);vertical-align:-1px;margin-right:7px}
.miniprog i{display:block;height:100%;background:var(--orange)}
/* Above the body's sticky first column, below the sticky head.
   All three stick, and the group row and the frozen first column both sat at
   z-index 2 — a tie, which the later element in the document wins. That is
   always the data row, so scrolling printed "A-2026-0088" straight through
   "03 Angebot · 3 items". The stack is: head 4, group 3, frozen column 2. */
.group-row td{background:var(--head);font-weight:600;font-size:0.75rem;border-bottom:1px solid var(--grid-strong);position:sticky;top:var(--row);z-index:3}
.group-row td.stick{z-index:3}

/* Status bar under a table/board */
.statusbar{
  flex:none;display:flex;align-items:center;gap:18px;padding:0 var(--pad);height:28px;
  border-top:1px solid var(--grid);background:var(--head);font-size:0.75rem;color:var(--ink-2);
}

/* Page navigation for long lists (<x-hbs.pager>). */
.pager{
  flex:none;display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;
  padding:6px var(--pad);border-top:1px solid var(--grid);background:var(--zebra);
  font-size:0.7188rem;color:var(--ink-2);
}
.pager__info{color:var(--ink-2)}
.pager__pages{display:flex;align-items:center;gap:2px}
.pager__link{
  min-width:26px;height:24px;padding:0 6px;display:inline-flex;align-items:center;justify-content:center;
  border:1px solid var(--grid);background:var(--bg);color:var(--link);text-decoration:none;
}
.pager__link--on{background:var(--sel);border-color:var(--grid-strong);color:var(--ink);font-weight:600}
.pager__link--off{color:var(--ink-3);background:var(--head)}
.pager__gap{padding:0 2px;color:var(--ink-3)}
.statusbar b{color:var(--ink);font-weight:600}
.statusbar .r{margin-left:auto}

/* Empty state — one quiet line, optional action */
.empty{padding:42px var(--pad);text-align:center;color:var(--ink-2);font-size:0.7813rem}
.empty__icon{color:var(--ink-3);margin-bottom:8px}
.empty__text{font-size:0.875rem;color:var(--ink);font-weight:600;margin:0}
.empty__hint{margin:6px auto 0;max-width:52ch;line-height:1.55}
.empty__act{margin-top:12px}
.empty .btn{margin-top:12px}

/* Flash messages */
.flash{padding:9px var(--pad);border-bottom:1px solid var(--grid);font-size:0.7813rem;display:flex;gap:8px;align-items:center}
.flash--ok{background:#F1F6F2;color:var(--green)}
.flash--err{background:#F9F0EF;color:var(--red)}
/* Neither a success nor a failure: "das gibt es, sobald die Einrichtung fertig
   ist" is a statement of fact and was being delivered as a green tick. */
.flash--info{background:#EEF4FA;color:var(--blue)}
/* Not an error and not a confirmation: something came back and waits. */
.flash--warn{background:#FBF5EA;color:var(--amber)}

/* ==========================================================================
   Detail views
   ========================================================================== */
.split{flex:none;display:grid;grid-template-columns:minmax(0,1fr) 360px;overflow:visible}
.split__l{min-width:0;overflow:visible}
.split__r{overflow:visible;border-left:1px solid var(--grid);background:var(--zebra)}
.split__r .edit-card .frow{grid-template-columns:minmax(0,1fr)}
.split__r .edit-card .frow__l{border-right:0;padding-bottom:2px}
.split__r .edit-card .frow__f{padding-top:4px;min-width:0}
.sect{font-size:0.6875rem;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-2);
  padding:10px 14px;background:var(--head);border-bottom:1px solid var(--grid);border-top:1px solid var(--grid);
  display:flex;align-items:center;gap:8px}
.sect:first-child{border-top:0}
.sect .r{margin-left:auto;font-weight:400;letter-spacing:0;text-transform:none}
.section-action{display:flex;align-items:stretch;border-bottom:1px solid var(--grid);background:var(--head)}
.section-action .sect{flex:1;border:0}
.section-action>.btn{align-self:center;margin-right:14px}
.kv{display:grid;grid-template-columns:140px 1fr;font-size:0.7813rem}
.kv dt,.kv dd{padding:7px 14px;border-bottom:1px solid var(--grid);min-height:31px;display:flex;align-items:center}
.kv dt{color:var(--ink-2);border-right:1px solid var(--grid)}
.kv dd{font-weight:500}
.phases{display:flex;border-bottom:1px solid var(--grid);overflow-x:auto;flex:none}
.ph{flex:1;min-width:122px;padding:9px 12px;border-right:1px solid var(--grid);text-align:left}
.ph:last-child{border-right:0}
.ph__no{font-size:0.6875rem;color:var(--ink-3)}
.ph__n{font-size:0.7813rem;font-weight:500;margin-top:1px;overflow:hidden;text-overflow:ellipsis}
.ph__d{font-size:0.6875rem;color:var(--ink-2);margin-top:2px}
.ph--done{background:var(--head)}
.ph--done .ph__n{color:var(--ink-2)}
.ph--now{background:var(--orange)}
.ph--now .ph__no,.ph--now .ph__d{color:var(--orange-deep)}
.ph--now .ph__n{font-weight:600}
.ph--todo .ph__n,.ph--todo .ph__d{color:var(--ink-3)}
/* A phase that leads somewhere. The strip is the one place in the project
   file that says where the job stands, and clicking "05 Bau" did nothing at
   all. As an <a> it would take the link colour — #945A00 on the orange
   --now field, which is exactly the pairing the design rules forbid — so the
   anchor keeps the surrounding ink and is marked as a control by its hover
   state, its pointer and the shared focus ring. x-hbs.phases renders it only
   where the item carries an href, so a strip without targets stays text. */
a.ph{display:block;color:inherit;text-decoration:none;cursor:pointer}
a.ph:hover{background:var(--sel)}
a.ph.ph--now:hover{background:#FFB733}
.task{display:flex;gap:10px;padding:10px 14px;border-bottom:1px solid var(--grid);width:100%;text-align:left;align-items:flex-start;color:inherit;text-decoration:none}
.task:hover{background:var(--sel)}
.task .ic{margin-top:1px;color:var(--ink-2)}
.task__m{flex:1;min-width:0}
.task__t{font-size:0.7813rem;font-weight:500}
.task__s{font-size:0.7188rem;color:var(--ink-2);margin-top:1px;white-space:normal}

/* ==========================================================================
   Kanban — columns in the same three-surface language
   ========================================================================== */
.kanban{flex:1;min-height:0;display:flex;overflow-x:auto;border-bottom:1px solid var(--grid)}
.kb-col{width:272px;flex:none;display:flex;flex-direction:column;border-right:1px solid var(--grid);min-height:0}
.kb-col__h{flex:none;display:flex;align-items:center;gap:8px;padding:9px 12px;background:var(--head);
  border-bottom:1px solid var(--grid-strong);font-size:0.75rem;font-weight:600}
.kb-col__h .dot{width:7px;height:7px;background:currentColor}
.kb-col__n{margin-left:auto;font-weight:400;color:var(--ink-2);font-size:0.7188rem}
.kb-col__sum{flex:none;padding:5px 12px;border-bottom:1px solid var(--grid);font-size:0.7188rem;color:var(--ink-2);background:var(--zebra)}
.kb-col__list{flex:1;overflow-y:auto;min-height:60px}
.kb-col--drop .kb-col__list{background:var(--sel)}
.kb-card{display:block;width:100%;text-align:left;background:#fff;border-bottom:1px solid var(--grid);
  padding:9px 12px;color:inherit;text-decoration:none;cursor:grab}
.kb-card:hover{background:var(--sel)}
.kb-card[draggable="true"]:active{cursor:grabbing}
.kb-card--ghost{opacity:.45}
.kb-card__top{display:flex;gap:8px;align-items:baseline}
.kb-card__id{font-size:0.6875rem;color:var(--ink-2)}
.kb-card__due{margin-left:auto;font-size:0.6875rem;color:var(--ink-2)}
.kb-card__due--late{color:var(--red);font-weight:600}
.kb-card__t{font-size:0.7813rem;font-weight:500;margin-top:2px}
.kb-card__s{font-size:0.7188rem;color:var(--ink-2);margin-top:2px}
.kb-card__foot{display:flex;align-items:center;gap:8px;margin-top:6px;font-size:0.6875rem;color:var(--ink-2)}
.kb-card__foot .state{font-size:0.6875rem}
/* The status control under a card (built by public/js/hbs.js). It is a
   sibling of the card, not a child: the card is an <a>, and a form control
   inside a link is interactive content inside a link. Drawn as the foot of
   the card it belongs to, so the pair reads as one object. */
.kb-move{padding:0 12px 8px;background:#fff;border-bottom:1px solid var(--grid)}
.kb-move__select{width:100%}

/* The board on a small screen: the columns stack.

   Five 272px columns in a 375px window showed 1.4 of them behind a sideways
   scroll — 27% of the board — and there was no @media rule in this file
   mentioning .kanban at all. Stacked, every column is a full-width section
   with its heading and its count, the page scrolls the way every other page
   does, and the status control under each card does the moving. */
@media (max-width:860px){
  .kanban{display:block;overflow-x:visible;min-height:0;flex:none}
  .kb-col{width:auto;flex:none;border-right:0;border-bottom:1px solid var(--grid-strong)}
  .kb-col:last-child{border-bottom:0}
  .kb-col__h{position:sticky;top:0;z-index:2;min-height:44px;font-size:0.8125rem}
  .kb-col__list{overflow:visible;min-height:0}
  .kb-card{padding:12px}
  .kb-move{padding:0 12px 10px}
  .kb-move__select{min-height:44px}
}
.avatar{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;
  background:var(--head);border:1px solid var(--grid);font-size:0.6875rem;font-weight:600;color:var(--ink-2);flex:none}

/* ==========================================================================
   Diagrams — flat, grid-lined, orange fills. No libraries, plain HTML/SVG.
   ========================================================================== */

/* Gantt (schedule): CSS grid, one row per item, month lines from component */
.gantt{flex:1;min-height:0;overflow:auto;border-bottom:1px solid var(--grid)}
.gantt-tools{display:flex;align-items:center;gap:6px;padding:6px 8px;border:1px solid var(--grid);border-bottom:0;background:var(--zebra)}
.gantt-tools>span{font-size:0.6875rem;font-weight:700;text-transform:uppercase;color:var(--muted);margin-right:3px}
.gantt-tools small{margin-left:auto;color:var(--muted)}
/* One floor, not two: the component also carried an inline min-width, and
   the larger of the pair won. 720px is a readable month view; below that the
   container scrolls rather than the columns collapsing. */
.gantt__inner{min-width:720px}
.gantt__head{display:grid;position:sticky;top:0;z-index:3;background:var(--head);border-bottom:1px solid var(--grid-strong)}
.gantt__head>div{font-size:0.6875rem;font-weight:600;padding:6px 8px;border-right:1px solid var(--grid);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.gantt__head>div span{margin-left:5px;font-weight:400;color:var(--ink-2)}
/* Scale caption: says what the horizontal axis actually spans. */
.gantt__scale{padding:5px 10px;border-bottom:1px solid var(--grid);background:var(--zebra);
  font-size:0.6875rem;color:var(--ink-2)}
/* Caption inside a wide bar; narrow bars keep the label beside them. */
.gantt__bar em{position:absolute;left:6px;top:50%;transform:translateY(-50%);
  font-style:normal;font-size:0.6875rem;color:var(--bar-ink,var(--orange-deep));white-space:nowrap;
  overflow:hidden;max-width:calc(100% - 12px);pointer-events:none}
/* The label follows the fill, which is still the activity's own colour. */
.gantt__bar--done em{color:var(--ink-2)}
/* Two lines, both cut off cleanly.

   This was a 32px flex row with the activity name and its description as
   siblings, so "Vorbereitung" and "8 days · Critical path · 1" sat side by
   side in 220px, overflowed, and printed across the rows above and below.
   The name gets its line, the description gets the one under it, and neither
   wraps — a Gantt is a chart, and rows of different heights would also pull
   the dependency arrows off the bars they point at. The full text stays
   available in the row's tooltip. */
/* Above the bars, not level with them. A bar that starts before the visible
   range keeps its caption at its own left edge, which is off-screen to the
   left — so the caption printed straight through the frozen name column.
   Both sat at z-index 2, and the bar comes later in the row, so the bar won. */
.gantt__label{position:sticky;left:0;z-index:4;background:#fff;border-right:1px solid var(--grid);
  padding:3px 10px;display:flex;flex-direction:column;justify-content:center;gap:1px;
  font-size:0.7813rem;height:var(--gantt-row);border-bottom:1px solid var(--grid);overflow:hidden}
.gantt__label>*{display:block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.gantt__label .sub{font-size:0.6875rem;line-height:1.3;color:var(--ink-2)}
.gantt__head .gantt__label{background:var(--head);z-index:5;height:auto;padding:6px 10px;border-bottom:0}
.gantt__row{display:grid;border-bottom:1px solid var(--grid);position:relative;height:var(--gantt-row)}
.gantt__body{position:relative}
.gantt__links{position:absolute;left:var(--gantt-label);top:0;width:calc(100% - var(--gantt-label));height:100%;z-index:1;pointer-events:none;overflow:visible}
.gantt__links path{fill:none;stroke:#6d7780;stroke-width:1.25;vector-effect:non-scaling-stroke}
.gantt__links marker path{fill:#6d7780;stroke:none}
.gantt__cell{border-right:1px solid var(--grid)}
/* The bar's colour comes from the activity, emitted as --bar/--bar-edge/
   --bar-ink by the gantt component (App\Support\ScheduleColorResolver).
   The orange fallback only applies to a bar rendered without one. */
.gantt__bar{position:absolute;top:12px;bottom:12px;background:var(--bar,var(--orange));border:1px solid var(--bar-edge,var(--orange-edge));min-width:4px;z-index:2;touch-action:none}
/* Progress used to be painted in --orange-deep, which is invisible on a blue
   bar and muddy on a green one. A neutral darkening reads on every hue. */
.gantt__bar i{display:block;height:100%;background:rgba(0,0,0,.30)}
/* Finished work keeps its colour, washed out. Painting it grey threw away
   the one thing the bar was carrying. */
.gantt__bar--done{background:color-mix(in srgb,var(--bar,var(--orange)) 20%,#fff);border-color:var(--bar-edge,var(--grid-strong))}
@supports not (background:color-mix(in srgb,red 20%,#fff)){.gantt__bar--done{background:var(--head);border-color:var(--grid-strong)}}
.gantt__bar span{position:absolute;left:calc(100% + 6px);top:50%;transform:translateY(-50%);font-size:0.6875rem;color:var(--ink-2);white-space:nowrap}
a.gantt__bar,a.gantt__milestone{cursor:pointer}
a.gantt__bar[data-gantt-item],a.gantt__milestone[data-gantt-item]{cursor:grab}
.gantt__bar.gantt__bar--dragging,.gantt__milestone.gantt__bar--dragging{cursor:grabbing;opacity:.78;box-shadow:0 0 0 2px rgba(244,140,0,.24)}
/* A phase spans its own work; it is not work of its own. Drawn as the slim
   bracket every plan uses for a summary — never as a bar, so nobody tries to
   drag a chapter whose dates come from the activities inside it. */
.gantt__span{position:absolute;top:15px;height:7px;background:var(--bar,var(--orange));
  border:1px solid var(--bar-edge,var(--orange-edge));min-width:4px;z-index:2}
.gantt__span i{display:block;height:100%;background:rgba(0,0,0,.30)}
.gantt__span::before,.gantt__span::after{content:"";position:absolute;top:100%;
  border-top:5px solid var(--bar-edge,var(--orange-edge));
  border-left:4px solid transparent;border-right:4px solid transparent}
.gantt__span::before{left:-1px}
.gantt__span::after{right:-1px}
.gantt__handle{position:absolute;top:-1px;bottom:-1px;width:10px;z-index:4;cursor:ew-resize}
.gantt__handle--start{left:-5px}.gantt__handle--end{right:-5px}
.gantt__handle::after{content:"";position:absolute;top:35%;bottom:35%;left:4px;border-left:2px solid rgba(0,0,0,.48)}
a.gantt__bar:hover{border-color:var(--orange-deep)}
/* Milestones have no duration: a marker, not a bar. */
.gantt__milestone{position:absolute;top:50%;width:11px;height:11px;margin-left:-5px;
  transform:translateY(-50%) rotate(45deg);background:var(--bar,var(--orange-deep));border:1px solid var(--bar-edge,var(--orange-deep));z-index:2}
.gantt__milestone--done{background:var(--head);border-color:var(--grid-strong)}
.gantt__milestone--critical{border-color:var(--red)}
.gantt__today{position:absolute;top:0;bottom:0;width:0;border-left:2px solid var(--red);z-index:1}
.gantt__group .gantt__label{font-weight:600;background:var(--head)}
.gantt__group{background:var(--head)}
.dependency-grid{display:grid;gap:4px;width:100%}
.dependency-grid__row{display:grid;grid-template-columns:minmax(180px,1fr) minmax(170px,240px) 80px;gap:6px;align-items:center;padding:4px 0;border-bottom:1px solid var(--grid)}
.dependency-grid__row .check{min-width:0}.dependency-grid__row .check span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
@media (max-width:720px){.dependency-grid__row{grid-template-columns:1fr 1fr}.dependency-grid__row .check{grid-column:1/-1}}

/* Horizontal bar chart rows (Auswertungen, budget deviation, …) */
.hbars{border-bottom:1px solid var(--grid)}
.hbar{display:grid;grid-template-columns:190px 1fr 110px;align-items:center;border-bottom:1px solid var(--grid);height:var(--row)}
.hbar__k{padding:0 10px;font-size:0.7813rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;border-right:1px solid var(--grid)}
.hbar__track{position:relative;height:100%;border-right:1px solid var(--grid)}
.hbar__fill{position:absolute;top:7px;bottom:7px;left:0;background:var(--orange);border:1px solid var(--orange-edge)}
.hbar__fill--b{background:var(--head);border-color:var(--grid-strong)}
.hbar__fill--red{background:var(--red);border-color:var(--red)}
.hbar__fill--green{background:var(--green);border-color:var(--green)}
.hbar__v{padding:0 10px;font-size:0.7813rem;text-align:right}

/* Vertical column chart (monthly series) — pure CSS flex columns */
.cols{display:flex;align-items:flex-end;gap:0;height:180px;border-bottom:1px solid var(--grid-strong);padding:0 var(--pad)}
.col{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:flex-end;height:100%;border-right:1px solid var(--grid);padding:0 8px;min-width:56px}
.col:last-child{border-right:0}
.col__stack{width:100%;max-width:44px;display:flex;flex-direction:column;justify-content:flex-end}
.col__seg{width:100%;background:var(--orange);border:1px solid var(--orange-edge);border-bottom:0}
.col__seg--b{background:var(--head);border-color:var(--grid-strong)}
.col__v{font-size:0.6875rem;color:var(--ink-2);margin-bottom:3px;white-space:nowrap}
.cols-x{display:flex;padding:0 var(--pad);border-bottom:1px solid var(--grid)}
.cols-x span{flex:1;min-width:56px;text-align:center;font-size:0.6875rem;color:var(--ink-2);padding:5px 0;border-right:1px solid var(--grid)}
.cols-x span:last-child{border-right:0}
.help-rail__tour{padding:10px var(--pad);border-top:1px solid var(--grid)}
.help-rail__tour .btn{width:100%;justify-content:center}
/* First-visit module introduction — a band in the flow, never a modal. */
.tour{border-bottom:1px solid var(--grid);background:var(--sel)}
.tour__head{display:flex;align-items:center;gap:12px;padding:8px var(--pad);border-bottom:1px solid var(--grid)}
.tour__head strong{display:flex;align-items:center;gap:6px;font-size:0.6875rem;letter-spacing:.06em;text-transform:uppercase;color:var(--orange-deep)}
.tour__head form{margin-left:auto}
/* Only a phone folds the prose away, so only a phone needs the control. */
.tour__steps{list-style:none;display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:0}
.tour__steps li{display:flex;gap:10px;padding:10px var(--pad);border-right:1px solid var(--grid)}
.tour__steps li:last-child{border-right:0}
.tour__no{flex:0 0 auto;width:20px;height:20px;display:grid;place-items:center;background:var(--orange);border:1px solid var(--orange-edge);font-size:0.6875rem;font-weight:600;color:var(--orange-deep)}
.tour__steps b{display:block;font-size:0.7813rem}
.tour__steps p{margin:2px 0 0;font-size:0.75rem;line-height:1.5;color:var(--ink-2)}
/* Three paragraphs at the top of the page, at every width.

   On the dashboard the band pushed the greeting, the six figures and the
   charts below the fold; on a phone it WAS the first screen. The prose is
   what makes it tall, and it is the part somebody reads once — so the strip
   is the three headings, and the sentences under them open on the control
   that was until now only offered below 860px. */
.tour__steps p{display:none}
.tour--open .tour__steps p{display:block}
/* Closed, the band is one line: the three headings, then the controls. It was
   two stacked rows — a label row and a row of three numbered cards — for 88px
   on every module, on a page whose whole job is to show a plan. Nobody reads a
   manual before starting (Paradox of the Active User), so the strip states
   what the screen is and gets out of the way; "Mehr" opens the sentences. */
@media(min-width:861px){
  .tour:not(.tour--open){display:flex;align-items:center;flex-wrap:wrap}
  .tour:not(.tour--open) .tour__head{order:2;border-bottom:0;flex:0 0 auto;margin-left:auto}
  .tour:not(.tour--open) .tour__steps{order:1;display:flex;flex:1 1 auto;min-width:0;flex-wrap:wrap}
  .tour:not(.tour--open) .tour__steps li{align-items:center;padding:7px 14px 7px 0;border-right:0}
  /* The badge shrinks, the digit does not: 11px is the floor for anything
     anybody has to read in daylight on a building site (DesignSystemTest). */
  .tour:not(.tour--open) .tour__no{width:18px;height:18px}
}
.tour__more{display:inline-flex;margin-left:auto}
.tour__head form{margin-left:0}
.tour--open .tour__more .ic{transform:rotate(180deg)}
@media(max-width:900px){.tour__steps li{border-right:0;border-bottom:1px solid var(--grid)}}

/* Charts (line + donut) — plain SVG, same three surfaces as everything else.
   No library, no gradients, no shadows; the grid lines are the same colour as
   every other line in the document. */
.chart{margin:0;border-bottom:1px solid var(--grid);background:var(--bg)}
.chart__title{padding:8px var(--pad) 0;font-size:0.6875rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-2)}
.chart__svg{display:block;width:100%;height:180px;padding:0 var(--pad);overflow:visible}
.chart__grid{stroke:var(--grid);stroke-width:1;vector-effect:non-scaling-stroke}
.chart__area{fill:var(--sel);stroke:none}
.chart__line{fill:none;stroke:var(--orange);stroke-width:2.5;vector-effect:non-scaling-stroke;stroke-linejoin:round}
.chart__line--b{stroke:var(--grid-strong);stroke-dasharray:6 4}
.chart__dot{fill:var(--bg);stroke:#C88300;stroke-width:2;vector-effect:non-scaling-stroke}
.chart--donut .donut{display:flex;flex-wrap:wrap;align-items:center;gap:var(--pad);padding:10px var(--pad) 14px}
.donut svg{width:150px;height:150px;flex:0 0 auto;transform:rotate(-90deg)}
.donut__track{fill:none;stroke:var(--head);stroke-width:20}
.donut__arc{fill:none;stroke-width:20}
.donut__arc--orange{stroke:var(--orange)}
.donut__arc--b{stroke:var(--grid-strong)}
.donut__arc--blue{stroke:var(--blue)}
.donut__arc--green{stroke:var(--green)}
.donut__arc--amber{stroke:var(--amber)}
.donut__arc--red{stroke:var(--red)}
.donut__centre{transform:rotate(90deg);transform-origin:80px 80px;font-size:1.25rem;font-weight:600;fill:var(--ink)}
.donut__centre-label{transform:rotate(90deg);transform-origin:80px 80px;font-size:0.6875rem;fill:var(--ink-2)}
.donut__legend{list-style:none;flex:1 1 200px;min-width:200px;display:grid;gap:2px}
.donut__legend li{display:flex;align-items:center;gap:8px;font-size:0.7813rem;padding:3px 0;border-bottom:1px solid var(--grid)}
.donut__legend li:last-child{border-bottom:0}
.donut__legend b{margin-left:auto;font-variant-numeric:tabular-nums}
.donut__key{width:10px;height:10px;flex:0 0 auto;border:1px solid var(--grid-strong)}
.donut__key--orange{background:var(--orange);border-color:var(--orange-edge)}
.donut__key--b{background:var(--grid-strong)}
.donut__key--blue{background:var(--blue);border-color:var(--blue)}
.donut__key--green{background:var(--green);border-color:var(--green)}
.donut__key--amber{background:var(--amber);border-color:var(--amber)}
.donut__key--red{background:var(--red);border-color:var(--red)}
.donut__empty{color:var(--ink-2)}
.chart-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));border-bottom:1px solid var(--grid)}
.chart-grid>.chart{border-right:1px solid var(--grid);border-bottom:0}
.chart-grid>.chart:last-child{border-right:0}
@media(max-width:900px){.chart-grid>.chart{border-right:0;border-bottom:1px solid var(--grid)}}
.legend{display:flex;gap:16px;padding:8px var(--pad);border-bottom:1px solid var(--grid);font-size:0.7188rem;color:var(--ink-2)}
.legend i{display:inline-block;width:9px;height:9px;margin-right:6px;vertical-align:-1px;background:var(--orange);border:1px solid var(--orange-edge)}
.legend i.b{background:var(--head);border-color:var(--grid-strong)}
.legend i.red{background:var(--red);border-color:var(--red)}

/* ==========================================================================
   Forms — plain rows on white, grid lines, labels left
   ========================================================================== */
/* A form is a column of label/field pairs, and a column of those is only
   readable up to about 760px — beyond that the eye loses the line between a
   label on the far left and its field on the far right.

   But the cap was on the form itself, so anything wider that lived inside one
   was cut off by it: the line items of an invoice are a 1084px table inside a
   760px box, and the columns past "Unit" — quantity, price, total — were
   simply not on the screen. The cap belongs on the rows, not on the form, so
   a table, a section head or a toolbar inside a form uses the width the page
   has. */
.form{max-width:none}
.form>.frow,
.form>.form-hint,
.form>.form__hint,
.form>.form-error,
.form>.check,
.form>p{max-width:760px}
.frow{display:grid;grid-template-columns:190px 1fr;border-bottom:1px solid var(--grid);min-height:44px}
.frow>label,.frow__l{padding:12px 14px;font-size:0.7813rem;color:var(--ink-2);border-right:1px solid var(--grid);display:flex;align-items:center}
.frow__f{padding:8px 14px;display:flex;align-items:center;gap:9px;flex-wrap:wrap}
.frow__f .field{width:100%;max-width:340px;height:28px}
.frow__f textarea.field{height:auto;min-height:72px;padding:6px 8px;width:100%;max-width:520px}
.frow__hint{font-size:0.7188rem;color:var(--ink-2);width:100%}
.frow--err .frow__l{color:var(--red)}
.ferr{font-size:0.7188rem;color:var(--red);width:100%}
/* Four classes that were written in views and never existed in this file. A
   class that is not defined does not warn: the browser renders the element as
   if the attribute were not there. So a validation message about a failed
   storage connection, a wrong database or an unreadable photo came out as an
   ordinary grey paragraph, indistinguishable from a hint — on exactly the
   screens where something has just gone wrong.
   They are aliases of the patterns they were always meant to be. */
.form-error{display:flex;align-items:flex-start;gap:6px;padding:7px var(--pad);font-size:0.7813rem;color:var(--red);background:#F9F0EF;border-left:3px solid var(--red)}
.muted{color:var(--ink-2)}
.fact{padding:12px 14px;display:flex;gap:8px;border-bottom:1px solid var(--grid)}
.check{display:inline-flex;align-items:center;gap:8px;font-size:0.7813rem;cursor:pointer}
/* Two tick boxes side by side in one cell ran into each other, so "Aufgabe
   anlegen" and "Serviceanfrage anlegen" read as one word. */
.check + .check{margin-left:16px}
/* A colour well beside the "let the app choose" box, on one line. */
.colorpick{display:flex;align-items:center;gap:12px;flex-wrap:wrap}
.colorpick input[type=color]{width:44px;height:32px;padding:0;border:1px solid var(--grid-strong);background:none;cursor:pointer}
.check input{width:16px;height:16px;accent-color:var(--green)}

/* Tabs (settings) */
/* Quick-action page: the same entries as the bar, laid out to be scanned. */
.qgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:6px;padding:8px var(--pad) 14px}
.qgrid__item{
  min-height:76px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:7px;
  padding:10px 6px;border:1px solid var(--grid);background:var(--bg);
  font-size:0.75rem;text-align:center;color:var(--ink);text-decoration:none;
}
.qgrid__item:hover{background:var(--sel);border-color:var(--grid-strong)}
.qgrid__item .ic{stroke:var(--orange-deep)}

.tabs{display:flex;border-bottom:1px solid var(--grid);background:var(--head);flex:none;overflow-x:auto}
/* Project workspace tabs sit directly under the page head. The row carries
   the bar plus the "Mehr" menu for the registers that do not fit on it, so
   the bar itself never has to scroll sideways and cut a name in half. */
.tabs-row{display:flex;align-items:stretch;border-top:1px solid var(--grid);
  border-bottom:1px solid var(--grid);background:var(--head)}
.tabs-row .tabs{flex:1 1 auto;border-top:0;border-bottom:0;min-width:0}
.tabs-row__more{flex:0 0 auto;display:flex;align-items:stretch;border-left:1px solid var(--grid)}
.tabs-row__more > button{padding:9px 14px;font-size:0.7813rem;color:var(--ink-2);white-space:nowrap}
.tabs-row__more > button:hover{background:#fff;color:var(--ink)}
.tabs--project{border-top:1px solid var(--grid)}
.tabs a{padding:9px 16px;font-size:0.7813rem;color:var(--ink-2);text-decoration:none;border-right:1px solid var(--grid);white-space:nowrap}
.tabs a:hover{background:#fff}
.tabs a.on{background:#fff;color:var(--ink);font-weight:600;box-shadow:inset 0 -3px 0 var(--orange)}

/* Wizard steps (storage / AI setup) */
.steps{display:flex;border-bottom:1px solid var(--grid);flex:none}
.step{flex:1;padding:9px 12px;border-right:1px solid var(--grid);font-size:0.75rem}
.step:last-child{border-right:0}
/* The step chips are links now, so they are targets: 31px is under the floor
   the rest of the application observes for anything tapped. */
a.step{display:block;color:inherit;text-decoration:none;min-height:44px}
a.step:hover{background:var(--sel)}
a.step.step--now:hover{background:#FFB733}
.step__no{color:var(--ink-3);font-size:0.6875rem}
.step--now{background:var(--orange)}
.step--now .step__no{color:var(--orange-deep)}
.step--done{background:var(--head);color:var(--ink-2)}

/* ==========================================================================
   AI chat — same grid language: white messages, grey assistant rows
   ========================================================================== */
.chat{flex:1;min-height:0;display:flex;flex-direction:column}
.chat__log{flex:1;overflow-y:auto;border-bottom:1px solid var(--grid)}
.chat__msg{display:flex;gap:10px;padding:12px var(--pad);border-bottom:1px solid var(--grid);align-items:flex-start}
.chat__msg--ai{background:var(--zebra)}
.chat__who{flex:none;width:26px;height:26px;display:flex;align-items:center;justify-content:center;
  background:var(--head);border:1px solid var(--grid);font-size:0.6875rem;font-weight:600;color:var(--ink-2)}
.chat__msg--ai .chat__who{background:var(--orange);color:var(--orange-deep);border-color:var(--orange-edge)}
.chat__body{flex:1;min-width:0;font-size:0.8125rem;white-space:pre-wrap;word-wrap:break-word}
.chat__meta{font-size:0.6875rem;color:var(--ink-3);margin-top:4px}
.chat__tool{font-size:0.7188rem;color:var(--ink-2);border:1px solid var(--grid);background:#fff;
  display:inline-flex;align-items:center;gap:6px;padding:3px 8px;margin-top:6px}
.chat__form{flex:none;display:flex;gap:8px;padding:10px var(--pad);border-top:1px solid var(--grid);background:var(--head)}
.chat__form .field{flex:1;height:30px}

/* Team, project and customer chat ---------------------------------------- */
.field--multi{height:auto!important;min-height:78px;padding:4px}
.chat-create__form{
  display:grid;grid-template-columns:repeat(4,minmax(150px,1fr));gap:8px 12px;
  padding:10px 12px;background:var(--zebra);border-top:1px solid var(--grid);align-items:end;
}
.chat-create__form>label{display:grid;gap:3px;font-size:0.6875rem;color:var(--ink-2)}
.chat-create__form>.check{display:flex;align-items:center;gap:7px;min-height:30px}
.chat-create__form>.btn{justify-self:start}
.chat-hub{
  flex:1;min-height:470px;display:grid;grid-template-columns:238px minmax(0,1fr);
  border-top:1px solid var(--grid);overflow:hidden;
}
.channel-list{min-width:0;border-right:1px solid var(--grid);background:var(--head);display:flex;flex-direction:column}
.channel-list>nav{flex:1;min-height:0;overflow-y:auto}
.channel-list__item{
  min-height:50px;padding:7px 10px;display:flex;align-items:center;gap:8px;
  border-bottom:1px solid var(--grid);text-decoration:none;color:var(--ink);
}
.channel-list__item:hover,.channel-list__item--active{background:#fff}
.channel-list__item--active{box-shadow:inset 3px 0 0 var(--orange-deep)}
.channel-list__item>span{min-width:0;flex:1}
.channel-list__item b,.channel-list__item small{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.channel-list__item b{font-size:0.7813rem}
.channel-list__item small{font-size:0.6875rem;color:var(--ink-2)}
.channel-list__item em{
  min-width:20px;height:20px;padding:0 5px;display:grid;place-items:center;
  background:var(--orange);border:1px solid var(--orange-edge);font-size:0.6875rem;font-style:normal;font-weight:600;
}
.direct-chat-form{padding:9px;border-top:1px solid var(--grid-strong);display:grid;gap:6px;background:#fff}
.direct-chat-form label{font-size:0.6875rem;color:var(--ink-2)}
.direct-chat-form .btn{justify-self:start}
.chat-contact-list{border-top:1px solid var(--grid);background:var(--head)}
.chat-contact-list>summary{padding:8px 10px;cursor:pointer;font-size:0.6875rem;font-weight:600}
.chat-contact-list>div{padding:6px 9px;display:flex;align-items:center;gap:7px;border-top:1px solid var(--grid);background:#fff}
.chat-contact-list>div>span{min-width:0;flex:1}
.chat-contact-list b,.chat-contact-list small{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.chat-contact-list b{font-size:0.7188rem}.chat-contact-list small{font-size:0.6875rem;color:var(--ink-2)}
.chat-contact-list .chat-contact-list__phone{color:var(--ink);font-variant-numeric:tabular-nums}
.chat-contact-list nav{display:flex;gap:4px}
.chat-contact-list nav a{width:25px;height:25px;display:grid;place-items:center;border:1px solid var(--grid);color:var(--ink);background:var(--head)}
.chat-contact-list nav a:hover{background:var(--orange)}
.channel-thread{min-width:0;display:flex;flex-direction:column;background:#fff}
/* Above the phone breakpoint the channel list is always on screen beside the
   thread, so the way back to it is not a control that needs to exist. */
.chat-back{display:none}
.channel-thread__head{
  min-height:58px;padding:9px 12px;display:flex;justify-content:space-between;align-items:center;
  gap:12px;border-bottom:1px solid var(--grid);background:var(--head);
}
.channel-thread__head h2{font-size:0.9375rem}
.channel-thread__head p{margin-top:2px;font-size:0.6875rem;color:var(--ink-2)}
.customer-chat-link{
  padding:8px 10px;display:grid;grid-template-columns:minmax(160px,240px) minmax(0,1fr) auto;
  align-items:end;gap:8px;border-bottom:1px solid var(--grid);background:var(--sel);
}
.customer-chat-link b,.customer-chat-link span{display:block}
.customer-chat-link span{font-size:0.6875rem;color:var(--ink-2)}
.chat-thread{overflow-y:auto}
.channel-thread__messages{flex:1;min-height:0}
/* A conversation, laid out the way everyone already reads one.

   Messages used to be full-width rows separated by rules, like a log file:
   to see who said what you read the name at the start of every line. Yours
   are on the right now and everyone else's on the left, which is the one
   convention every person with a phone already knows — the side does the
   work the name was doing, and the name only has to appear when the speaker
   changes.

   The rule the rest of this stylesheet follows — no radius, no shadow — is
   deliberately suspended here, for the same reason it is on the phone's
   bottom bar: a chat that does not look like a chat is not read as one. */
.chat-thread{overflow-y:auto;padding:14px 12px;background:var(--zebra);display:flex;flex-direction:column;gap:3px}
.channel-message{display:flex;gap:8px;align-items:flex-end;max-width:76%;margin-right:auto}
/* Mine: on the right, and the avatar is redundant — I know who I am. */
.channel-message--mine{margin-left:auto;margin-right:0;flex-direction:row-reverse}
.channel-message--mine .channel-message__avatar{display:none}
.channel-message__avatar{
  flex:none;width:30px;height:30px;display:grid;place-items:center;border-radius:50%;
  border:1px solid var(--grid);background:var(--head);font-size:0.6875rem;font-weight:600;
}
.channel-message--customer .channel-message__avatar{background:var(--orange);border-color:var(--orange-edge);color:var(--orange-deep)}
.channel-message__content{
  min-width:0;padding:7px 11px 6px;
  border:1px solid var(--grid);border-radius:14px 14px 14px 4px;
  background:var(--bg);box-shadow:0 1px 1px rgba(26,23,20,.06);
}
.channel-message--mine .channel-message__content{
  border-radius:14px 14px 4px 14px;
  background:var(--green-tint);border-color:var(--green-edge);
}
/* A customer is not a colleague, and on a channel they share with the site
   team that difference decides how you write. It stays marked. */
.channel-message--customer .channel-message__content{background:var(--amber-tint);border-color:var(--amber-edge)}
.channel-message__meta{display:flex;align-items:baseline;gap:8px;font-size:0.6875rem;color:var(--ink-2)}
.channel-message--mine .channel-message__meta b{display:none}
.channel-message__meta span{padding:1px 6px;border-radius:8px;background:var(--amber);color:#fff;font-size:0.6875rem}
.channel-message__meta time{margin-left:auto;color:var(--ink-3);white-space:nowrap}
.channel-message p{margin-top:2px;white-space:pre-wrap;overflow-wrap:anywhere;line-height:1.45}
@media (max-width:860px){
  .channel-message{max-width:88%}
  .chat-thread{padding:12px 10px}
}
.channel-compose{padding:9px 10px;display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px;border-top:1px solid var(--grid-strong);background:var(--head)}
.channel-compose textarea.field{height:54px;padding:6px 8px;resize:vertical}
.channel-compose .btn{align-self:end}
.guest-chat{width:min(840px,100%);min-height:100vh;margin:0 auto;border-left:1px solid var(--grid);border-right:1px solid var(--grid);display:flex;flex-direction:column;background:#fff}
.guest-chat__head{padding:14px 16px;border-bottom:1px solid var(--grid);background:var(--orange)}
.guest-chat__head>div{display:flex;justify-content:space-between;gap:12px;color:var(--orange-deep)}
.guest-chat__head h1{margin-top:14px;font-size:1.25rem}
.guest-chat__head p{color:var(--orange-deep)}
.guest-chat__thread{flex:1;min-height:300px;max-height:calc(100vh - 330px)}
.guest-chat__form{padding:10px 12px;display:grid;grid-template-columns:200px minmax(0,1fr) auto;align-items:end;gap:8px;border-top:1px solid var(--grid-strong);background:var(--head)}
.guest-chat__form label{display:grid;gap:3px;font-size:0.6875rem;color:var(--ink-2)}
.guest-chat__form textarea.field{height:64px;padding:6px 8px;resize:vertical}

/* Context assistant rail — always present on authenticated pages. */
/* height:100%, not 100vh. The rail is a grid item in a row that is already
   one viewport tall; asking for a second full viewport made it taller than
   its own row by the height of the horizontal scrollbar, which is what gave
   the document something to scroll. */
.agent-rail{
  min-width:0;height:100%;min-height:0;overflow:auto;
  border-left:1px solid var(--grid-strong);background:#fff;
  display:flex;flex-direction:column;
}
.agent-rail__head{
  min-height:58px;padding:10px 10px 9px 12px;border-bottom:1px solid var(--grid);
  background:var(--orange);display:flex;align-items:flex-start;gap:8px;
}
.agent-rail__head>div{min-width:0;flex:1}
.agent-rail__head strong{display:flex;align-items:center;gap:7px;font-size:0.8125rem}
.agent-rail__head span{display:block;margin-top:2px;color:var(--orange-deep);font-size:0.6875rem}
.agent-rail__close{margin-left:auto}
.agent-rail__context{padding:8px 12px;border-bottom:1px solid var(--grid);background:var(--head)}
.agent-rail__context span{display:block;color:var(--ink-2);font-size:0.6875rem}
.agent-rail__context b{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:0.75rem}
.agent-rail__log{flex:1;min-height:0;overflow-y:auto}
.agent-rail__message{display:flex;gap:8px;padding:9px 10px;border-bottom:1px solid var(--grid)}
.agent-rail__message>span{
  flex:none;width:24px;height:24px;display:flex;align-items:center;justify-content:center;
  border:1px solid var(--grid);background:var(--head);font-size:0.6875rem;font-weight:600;
}
.agent-rail__message--ai{background:var(--zebra)}
.agent-rail__message--ai>span{background:var(--orange);border-color:var(--orange-edge);color:var(--orange-deep)}
.agent-rail__message p{white-space:pre-wrap;overflow-wrap:anywhere;font-size:0.75rem}
.agent-rail__empty{padding:18px 14px;color:var(--ink-2);display:flex;gap:9px;align-items:flex-start;font-size:0.75rem}
.agent-rail__form{flex:none;padding:9px;border-top:1px solid var(--grid);background:var(--head);display:grid;gap:7px}
.agent-rail__form textarea.field{width:100%;height:64px;resize:vertical;padding:6px 8px}
.agent-rail__form .btn{justify-self:end}
.help-rail__head{background:var(--head)}
.help-rail__head span{color:var(--ink-2)}
.help-rail__body{flex:1;min-height:0;overflow-y:auto}
.help-rail__body section{padding:13px 14px;border-bottom:1px solid var(--grid)}
.help-rail__body section>span{display:block;margin-bottom:3px;color:var(--ink-2);font-size:0.6875rem;font-weight:600;letter-spacing:.07em;text-transform:uppercase}
.help-rail__body h2{font-size:0.875rem;margin-bottom:5px}
.help-rail__body p{font-size:0.7188rem;line-height:1.5;color:var(--ink-2)}
.help-legend{display:grid;gap:7px;margin-top:8px;list-style:none}
.help-legend li{display:flex;align-items:center;gap:8px;font-size:0.7188rem}
.help-dot{width:10px;height:10px;border:1px solid currentColor}
.help-dot--ok{background:#CDE8D5;color:#2F7D4A}.help-dot--info{background:#D9EAF8;color:#2A78B8}.help-dot--warn{background:#F9E6AE;color:#A36C00}.help-dot--risk{background:#F4D0CC;color:#B42318}
.help-rail__body .help-rail__privacy{display:flex;gap:9px;background:#F7F8FA}
.help-rail__privacy .ic{margin-top:2px;color:#2A78B8}
.help-rail__footer{display:flex;justify-content:space-between;gap:8px;padding:9px;border-top:1px solid var(--grid);background:var(--head)}

/* Shared protocol/history datatype */
.protocols{border-top:1px solid var(--grid);background:#fff}
.protocols__form{
  display:grid;grid-template-columns:minmax(150px,220px) minmax(180px,240px) 1fr;
  gap:8px 12px;padding:10px 12px;border-bottom:1px solid var(--grid);background:var(--zebra);
}
.protocols__form>div{display:grid;gap:3px;align-content:start}
.protocols__form label{font-size:0.6875rem;color:var(--ink-2)}
.protocols__form .field{width:100%;height:28px}
.protocols__form textarea.field{height:64px;padding:6px 8px;resize:vertical}
.protocols__wide{grid-column:span 3}
.protocols__actions{grid-column:span 3;justify-items:end}
.protocols__history{display:grid}
.protocol-entry{padding:10px 12px;border-bottom:1px solid var(--grid)}
.protocol-entry__meta{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:5px}
.protocol-entry__meta time,.protocol-entry__meta>span:last-child{font-size:0.6875rem;color:var(--ink-2)}
.protocol-entry__meta time{margin-left:auto}
.protocol-entry p{white-space:pre-wrap;margin:5px 0 8px;max-width:900px}
.protocol-entry--meeting{border-left:3px solid var(--blue)}
.protocol-entry--protocol{border-left:3px solid var(--green)}
.protocol-entry--photo{border-left:3px solid var(--orange)}
.protocol-entry--mail,.protocol-entry--chat{border-left:3px solid var(--amber)}
.custom-fields{border-top:1px solid var(--grid)}
.custom-set{border-bottom:1px solid var(--grid);padding:10px 12px}
.custom-set>strong{display:block}
.custom-set>span{font-size:0.6875rem;color:var(--ink-2)}
.custom-set>p{margin:4px 0 8px;color:var(--ink-2);font-size:0.75rem}
.metric-picker{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:0;border-bottom:1px solid var(--grid)}
.metric-option{display:grid;grid-template-columns:auto 1fr;gap:3px 9px;padding:13px;border-right:1px solid var(--grid);border-bottom:1px solid var(--grid);cursor:pointer}
.metric-option input{grid-row:span 2;accent-color:var(--blue)}
.metric-option span{font-size:0.6875rem;color:var(--ink-2)}
.onboarding-platform{display:flex;align-items:center;gap:12px;padding:7px 12px;border-bottom:1px solid var(--grid);background:var(--head);font-size:0.6875rem;color:var(--ink-2)}
.onboarding-platform span:first-child{display:flex;align-items:center;gap:5px;color:var(--green)}
.onboarding-platform b{color:var(--ink)}
.onboarding-platform span:last-child{margin-left:auto}
.onboarding-steps .step{min-width:0}
.onboarding-choices{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));border-bottom:1px solid var(--grid)}
.onboarding-guide{display:grid;gap:8px;padding:12px;font-size:0.75rem;line-height:1.55}
.onboarding-guide a{display:block;color:var(--ink);padding:7px 9px;border:1px solid var(--grid);background:var(--head);text-decoration:none}
.onboarding-guide a:hover{background:var(--orange)}
/* "You picked a country, here is everything that follows from it." --------- */
.derived{margin:0;padding:10px 12px;border-top:1px solid var(--grid);border-bottom:1px solid var(--grid);background:var(--head)}
.derived__lead{margin:0 0 8px;font-size:0.6875rem;color:var(--ink-2)}
.derived dl{display:grid;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));gap:8px;margin:0}
.derived dl>div{display:grid;gap:2px;padding:7px 9px;border:1px solid var(--grid);background:var(--paper)}
.derived dt{font-size:0.6875rem;color:var(--muted)}
.derived dd{margin:0;font-size:0.75rem;font-weight:600;color:var(--ink)}
.derived-override{border-bottom:1px solid var(--grid)}
.derived-override>summary{cursor:pointer;padding:9px 12px;font-size:0.7188rem;color:var(--ink-2);list-style:none}
.derived-override>summary::-webkit-details-marker{display:none}
.derived-override>summary::before{content:'▸ ';color:var(--muted)}
.derived-override[open]>summary::before{content:'▾ '}
.derived-override>summary:hover{background:var(--head)}
.derived-override .form__hint{padding:0 12px 6px;margin:0}

/* Employee master data and quick contact actions ------------------------- */
.employee-contact-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));border-bottom:1px solid var(--grid)}
.employee-contact-card{padding:10px;display:grid;gap:7px;border-right:1px solid var(--grid);border-top:1px solid var(--grid);background:#fff}
.employee-contact-card header{display:flex;align-items:center;gap:8px;padding-bottom:4px}
.employee-contact-card header .avatar{width:30px;height:30px}
.employee-contact-card header div{min-width:0}
.employee-contact-card header b,.employee-contact-card header small{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.employee-contact-card header small{color:var(--ink-2);font-size:0.6875rem}
.employee-contact-card>label{display:grid;gap:3px;color:var(--ink-2);font-size:0.6875rem}
.employee-contact-card>.btn{justify-self:start}
.employee-quick-actions{display:flex;align-items:center;gap:5px;flex-wrap:wrap}
.employee-quick-actions form{display:inline-flex}
.workforce-directory{display:grid;grid-template-columns:repeat(auto-fit,minmax(255px,1fr));border-bottom:1px solid var(--grid)}
.workforce-directory__card{min-width:0;padding:11px;display:grid;gap:9px;border-right:1px solid var(--grid);border-top:1px solid var(--grid);background:#fff}
.workforce-directory__card header{display:flex;align-items:center;gap:9px}
.workforce-directory__card header .avatar{width:32px;height:32px}
.workforce-directory__card header div{min-width:0}
.workforce-directory__card header b,.workforce-directory__card header small{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.workforce-directory__card header small{font-size:0.6875rem;color:var(--ink-2)}
.workforce-directory__facts{min-width:0;display:grid;gap:4px}
.workforce-directory__facts a,.workforce-directory__facts span{min-width:0;display:flex;align-items:center;gap:6px;color:var(--ink-2);font-size:0.7188rem;overflow-wrap:anywhere}
.workforce-directory__facts .ic{flex:none}

/* "Meine Aufgaben": the one page that is read on a phone, in a stairwell, with
   one hand and a glove on the other. Cards, not a table — a table on 375px is
   a horizontal scrollbar with data behind it. */
.task-list{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));border-bottom:1px solid var(--grid)}
.task-card{min-width:0;display:grid;gap:8px;padding:12px 14px;background:#fff;border-top:1px solid var(--grid);border-right:1px solid var(--grid)}
/* Late is a state, and a state may not be carried by colour alone (1.4.1). */
.task-card--late{border-left:3px solid var(--red)}
.task-card__title{font-size:0.8438rem;font-weight:600;line-height:1.3}
.task-card__title a{color:var(--ink);text-decoration:none}
.task-card__title a:hover{text-decoration:underline}
.task-card__facts{display:flex;flex-wrap:wrap;gap:4px 14px}
.task-card__facts span{display:flex;align-items:center;gap:6px;font-size:0.7188rem;color:var(--ink-2)}
.task-card__facts .ic{flex:none}
.task-card__text{font-size:0.7188rem;color:var(--ink-2);overflow-wrap:anywhere}
.task-card__sent-back{display:flex;gap:6px;align-items:flex-start;padding:7px 9px;background:var(--sel);border-left:3px solid var(--amber);font-size:0.7188rem}
.task-card__sent-back .ic{flex:none;margin-top:1px;color:var(--amber)}
.task-card__act{display:flex;flex-wrap:wrap;gap:6px;align-items:center}
@media(max-width:700px){
  .task-list{grid-template-columns:1fr}
  /* One thumb wide, and tall enough to hit while standing on a ladder. */
  .task-card__act .btn{flex:1 1 140px;min-height:44px}
}

/* Project process templates, AI review, checklists and photo inspection ---- */
.field--tall{min-height:120px!important;padding:7px 9px!important;resize:vertical}
/* Three peer choices. They used to be identical white cards that turned solid
   orange when picked, which said "selected" in the same colour the navigation
   uses for "you are here" and told you nothing about which of the three you
   were looking at. Each choice now owns a hue and keeps it whether it is
   picked or not; picking fills it with that hue's tint and thickens its edge.
   The accent is set per card with --choice, so the rule stays one rule. */
.process-choice-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));border-bottom:1px solid var(--grid)}
.process-choice{
  --choice:var(--blue);--choice-edge:var(--blue-edge);--choice-tint:var(--blue-tint);
  display:grid;grid-template-columns:auto 1fr;gap:9px;padding:14px;
  border-right:1px solid var(--grid);border-top:3px solid var(--choice);
  cursor:pointer;background:#fff;
}
.process-choice:nth-child(2){--choice:var(--teal);--choice-edge:var(--teal-edge);--choice-tint:var(--teal-tint)}
.process-choice:nth-child(3){--choice:var(--violet);--choice-edge:var(--violet-edge);--choice-tint:var(--violet-tint)}
.process-choice:last-child{border-right:0}
.process-choice input{margin-top:2px;accent-color:var(--choice)}
.process-choice span,.process-choice b,.process-choice small{display:block}
.process-choice b{color:var(--choice)}
.process-choice small{margin-top:3px;color:var(--ink-2);font-size:0.6875rem;line-height:1.4}
.process-choice:hover{background:var(--choice-tint)}
.process-choice--selected{background:var(--choice-tint);box-shadow:inset 0 0 0 1px var(--choice-edge)}
.process-choice--selected b{font-weight:700}
.wizard-standard,.ai-proposal-note,.permission-banner,.photo-evidence-callout{display:flex;align-items:center;gap:10px;padding:10px 12px;border-bottom:1px solid var(--grid);background:#F1F6F2}
.wizard-standard .ic,.ai-proposal-note .ic,.permission-banner .ic,.photo-evidence-callout .ic{flex:none}
.wizard-standard div,.ai-proposal-note div,.permission-banner div,.photo-evidence-callout div{min-width:0;display:grid;gap:2px}
.wizard-standard span,.ai-proposal-note span,.permission-banner span,.photo-evidence-callout span{font-size:0.7188rem;color:var(--ink-2)}
.wizard-standard--info,.ai-proposal-note{background:#EEF4FA}
.process-format-help{display:grid;gap:5px;padding:12px;border-bottom:1px solid var(--grid);background:var(--zebra)}
.process-format-help code{display:block;padding:7px 9px;border:1px solid var(--grid);background:#fff;overflow-wrap:anywhere}
.process-format-help span{font-size:0.7188rem;color:var(--ink-2)}
.form--compact .frow{grid-template-columns:minmax(130px,34%) 1fr}
.process-editor{display:grid}
.process-card{border-bottom:1px solid var(--grid);background:#fff}
.process-card>summary{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:10px 12px;cursor:pointer;list-style-position:inside}
.process-card>summary>span:first-child{display:grid;gap:2px}
.process-card>summary small{color:var(--ink-2);font-size:0.6875rem}
.process-card__meta{display:flex;gap:5px;align-items:center;flex-wrap:wrap}
.process-card>.form-actions{padding:8px 12px;border-top:1px solid var(--grid);justify-content:flex-end}
.permission-banner{background:#F1F6F2}
.permission-banner>.btn{margin-left:auto}
.permission-banner--bypass{background:#FFF4DC;border-color:#E6B44C}
.action-review-list{display:grid}
.action-review{border-bottom:1px solid var(--grid);background:#fff}
.action-review--pending{border-left:4px solid var(--orange-deep)}
.action-review>header{display:flex;justify-content:space-between;gap:16px;padding:13px}
.action-review h2{font-size:0.875rem;margin:3px 0}
.action-review header p{color:var(--ink-2);font-size:0.7188rem}
.action-review header>div>span{font-size:0.6875rem;text-transform:uppercase;letter-spacing:.06em;color:var(--ink-2)}
.action-review__facts{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));border-top:1px solid var(--grid);border-bottom:1px solid var(--grid)}
.action-review__facts>div{padding:8px 11px;border-right:1px solid var(--grid)}
.action-review__facts dt{font-size:0.6875rem;color:var(--ink-2);text-transform:uppercase}
.action-review__facts dd{margin-top:2px;overflow-wrap:anywhere}
.action-preview{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));background:var(--zebra)}
.action-preview>div{display:grid;gap:3px;padding:9px 11px;border-right:1px solid var(--grid);border-bottom:1px solid var(--grid)}
.action-preview span,.action-preview small{font-size:0.6875rem;color:var(--ink-2)}
.action-review>footer{display:flex;gap:7px;padding:10px 12px;justify-content:flex-end}
.action-review__reviewer{color:var(--ink-2);font-size:0.6875rem}
.agent-rail__actions{flex:none;border-bottom:1px solid var(--grid);background:#FFF8E8;max-height:250px;overflow:auto}
.agent-rail__actions>header{display:flex;justify-content:space-between;padding:7px 9px;border-bottom:1px solid var(--grid);font-size:0.6875rem}
.agent-rail__actions article{display:grid;gap:3px;padding:8px 9px;border-bottom:1px solid var(--grid)}
.agent-rail__actions article>span{font-size:0.6875rem;text-transform:uppercase;color:var(--ink-2)}
.agent-rail__actions article>small{font-size:0.6875rem;color:var(--ink-2)}
.agent-rail__actions article>div{display:flex;gap:5px;margin-top:3px}
.agent-rail__actions article .btn{height:24px;padding:0 7px;font-size:0.6875rem}
.photo-inspection-head{display:flex;justify-content:space-between;gap:10px;padding:10px 12px;border-bottom:1px solid var(--grid)}
.photo-inspection-head>div{display:grid;gap:2px}
.photo-inspection-head span{font-size:0.6875rem;color:var(--ink-2)}
.photo-canvas{position:relative;width:100%;line-height:0;background:#222;overflow:hidden}
.photo-canvas>img{display:block;width:100%;height:auto;max-height:72vh;object-fit:contain}
.photo-box{position:absolute;border:3px solid var(--box-color);background:color-mix(in srgb,var(--box-color) 13%,transparent);box-shadow:0 0 0 1px rgba(255,255,255,.8) inset;line-height:1.2}
.photo-box>span{position:absolute;left:-3px;top:-24px;max-width:240px;padding:3px 5px;background:var(--box-color);color:#fff;font-size:0.6875rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.inspection-summary{padding:10px 12px;border-bottom:1px solid var(--grid)}
.inspection-summary p{white-space:pre-wrap}
.inspection-summary span{display:block;margin-top:4px;font-size:0.6875rem;color:var(--ink-2)}
.finding-list{display:grid}
.finding-list article{display:grid;grid-template-columns:24px 1fr auto;gap:8px;align-items:start;padding:8px 11px;border-bottom:1px solid var(--grid);border-left:4px solid var(--finding-color)}
.finding-list article>span{width:22px;height:22px;display:flex;align-items:center;justify-content:center;background:var(--finding-color);color:#fff;font-size:0.6875rem}
.finding-list article div{display:grid;gap:2px}
.finding-list article small{font-size:0.6875rem;color:var(--ink-2)}
.inspection-review{display:flex;gap:7px;padding:10px 12px;border-bottom:1px solid var(--grid);justify-content:flex-end}
.box-coordinate-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:7px;padding:10px 12px;border-bottom:1px solid var(--grid)}
.box-coordinate-grid label{display:grid;gap:3px;font-size:0.6875rem;color:var(--ink-2)}
.inspection-history{display:grid}
.inspection-history a{display:grid;grid-template-columns:1fr auto;gap:2px 8px;padding:8px 11px;border-bottom:1px solid var(--grid);color:inherit;text-decoration:none}
.inspection-history a.on{background:var(--orange)}
.inspection-history small{grid-column:1/-1;color:var(--ink-2)}
.operational-checklist{display:grid;padding:8px 12px;border-bottom:1px solid var(--grid);background:#fff}
.operational-checklist label{display:flex;gap:8px;align-items:flex-start;padding:7px;border-bottom:1px solid var(--grid)}
.operational-checklist input{accent-color:var(--green)}
.operational-checklist .btn{justify-self:end;margin-top:8px}

/* ==========================================================================
   Auth screens — centered card made of the same grid language
   ========================================================================== */
.auth{min-height:100vh;display:flex;align-items:center;justify-content:center;background:var(--head);padding:20px}
.auth__box{width:400px;max-width:100%;background:#fff;border:1px solid var(--grid-strong)}
.auth__brand{background:var(--orange);padding:14px 18px;font-size:0.9375rem;font-weight:600;display:flex;gap:9px;align-items:center;border-bottom:1px solid #C88300}
.auth__body{padding:6px 0 0}
.auth__row{padding:10px 18px}
.auth__row label{display:block;font-size:0.75rem;color:var(--ink-2);margin-bottom:4px}
.auth__row .field{width:100%;height:30px}
.auth__title{font-size:0.9375rem;font-weight:600;margin:0 0 6px}
.auth__text{font-size:0.7813rem;color:var(--ink-2);margin:0 0 8px;line-height:1.5}
.auth__list{font-size:0.7813rem;margin:0;padding-left:18px;line-height:1.6}
/* Error pages: the status number is context, not the headline. */
.errpage__code{font-size:0.6875rem;letter-spacing:.09em;text-transform:uppercase;color:var(--ink-2);margin:0 0 4px}
.auth__act{padding:14px 18px;display:flex;align-items:center;gap:10px;border-top:1px solid var(--grid);margin-top:8px}
.auth__foot{padding:10px 18px;background:var(--zebra);border-top:1px solid var(--grid);font-size:0.7188rem;color:var(--ink-2);display:flex;gap:12px;flex-wrap:wrap}
.auth__support{margin-left:auto}
.auth__developer{flex-basis:100%;font-size:0.6875rem}
/* Imprint: the one auth-shell page that is read rather than filled in. */
.auth--doc{align-items:flex-start}
.auth__box--wide{width:620px}
.legal__section{padding:10px 18px}
.legal__section+.legal__section{border-top:1px solid var(--grid)}
.legal__section h2{font-size:0.8125rem;font-weight:600;margin:0 0 5px}
.legal__dl{display:grid;grid-template-columns:auto 1fr;gap:3px 14px;font-size:0.7813rem;color:var(--ink-2);line-height:1.5;margin:0}
.legal__dl dd{margin:0}
.auth__err{margin:10px 18px 0;padding:8px 10px;background:#F9F0EF;color:var(--red);font-size:0.75rem}
.auth__ok{margin:10px 18px 0;padding:8px 10px;background:#F1F6F2;color:var(--green);font-size:0.75rem}

/* ==========================================================================
   Print document — standalone letter view (invoices/print), no app frame.
   The screen shows a sheet of paper on the grey surface; @media print at
   the bottom of this file strips chrome so the dialog output is the letter.
   ========================================================================== */
.doc{background:var(--head);min-height:100vh}
.doc__page{max-width:760px;margin:24px auto;background:#fff;border:1px solid var(--grid);padding:44px 48px}
.doc__head{display:flex;justify-content:space-between;gap:24px;margin-bottom:38px}
.doc__sender{font-size:0.7813rem;line-height:1.7}
.doc__sender b{font-size:0.9375rem}
.doc__meta{font-size:0.7813rem;text-align:right;color:var(--ink-2);line-height:1.8}
.doc__meta b{color:var(--ink);font-weight:600}
.doc__addr{font-size:0.8125rem;line-height:1.6;margin-bottom:30px}
.doc__title{font-size:1rem;font-weight:600;letter-spacing:-.01em}
.doc__sub{font-size:0.7813rem;color:var(--ink-2);margin:3px 0 16px}
.doc .sheet th,.doc .sheet td{white-space:normal}
.doc__note{font-size:0.7813rem;margin-top:14px;line-height:1.6}
.doc__foot{margin-top:44px;padding-top:10px;border-top:1px solid var(--grid);
  font-size:0.6875rem;color:var(--ink-2);display:flex;justify-content:space-between;gap:12px}
/* Legal footer: one line per block, wraps instead of squeezing. */
.doc__foot--legal{margin-top:14px;display:block;border-top-width:2px}
.doc__foot--legal span{display:block;line-height:1.5}

/* ==========================================================================
   Responsive
   ========================================================================== */
@media (max-width:1100px){
  .split{grid-template-columns:minmax(0,1fr)}.split__r{border-left:0;border-top:1px solid var(--grid)}
  .chat-create__form{grid-template-columns:repeat(2,minmax(150px,1fr))}
}
/* The top bar holds one thing that changes — which job you are on — and a
   row of controls that never do.

   The words used to go at 1300px, which is not a small screen: on a 1280x800
   laptop — the commonest business screen there is — the right half of the bar
   was six identical grey squares and the account menu was an anonymous person
   icon. Slots are shed instead of words now: the mailto link is gone (it is
   the first line of the footer of every page) and the language moved into the
   account menu, which leaves room for the labels down to 1000px. The
   breadcrumb goes first, because the switcher beside it already says which
   job you are on and the page below already carries its own heading. */
@media (max-width:1200px){
  .top__crumb{display:none}
  .top__search-field{width:150px}
}
@media (max-width:1000px){
  .top__right .top__label{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}
  .top__right .txtbtn{padding-inline:8px}
}
@media (max-width:860px){
  .app{grid-template-columns:1fr}
  .app.side-panel-collapsed{grid-template-columns:1fr}
  .nav{position:fixed;top:0;left:0;bottom:0;width:224px;z-index:50;transform:translateX(-100%);transition:transform .12s}
  .nav[data-open="true"]{transform:none;box-shadow:0 0 0 100vmax rgba(26,23,20,.35)}
  .burger{display:inline-flex!important}
  .figs .fig{min-width:50%}
  .frow{grid-template-columns:1fr}
  .frow>label,.frow__l{border-right:0;padding-bottom:0}
  .agent-rail{position:fixed;right:0;top:0;bottom:0;width:min(360px,95vw);z-index:70;box-shadow:-1px 0 0 var(--grid-strong)}
  .app.side-panel-collapsed .agent-rail{transform:translateX(100%);visibility:hidden}
  .protocols__form{grid-template-columns:1fr}
  .protocols__wide,.protocols__actions{grid-column:span 1}
  .top{padding-inline:8px;gap:4px}
  /* No room for both. The switcher wins: on a phone "which job" is the
     question, and the page you are on already has a heading. */
  .top__crumb{display:none}
  /* The first-visit explainer is three paragraphs. On a desk it is a strip
     above the page; on a phone it was the whole first screen, so the module
     it explains was below the fold on every visit until somebody dismissed
     it. The headings stay, the prose folds away until tapped. */
  .tour__steps{grid-template-columns:1fr}
  .tour__steps li{padding:10px var(--pad);border-right:0;border-bottom:1px solid var(--grid)}
  .tour__steps li:last-child{border-bottom:0}

  /* Six 48px icons is 288px of a 375px bar; whatever is left is not a place
     to put the name of the job. Three of them are already somewhere better:
     "new" is the big button in the middle of the bottom bar, the assistant
     lives in the navigation sheet, and the language is a line in the account
     menu at every width. */
  .top__quick,.agent-toggle{display:none}
  .top__switch{flex:1 1 auto}
  /* The switcher carries the job and only the job at every width now, so the
     rule that used to hide the company name here has nothing left to hide. */
  .top__right{gap:1px}
  /* 44px, not the 30px that stood here: everything from this width down is a
     touch layout, and the 700px block that corrected it left the 700–860px
     band — an iPad in portrait — with the phone's stripped navigation and the
     desk's 30px controls. */
  .top__right .txtbtn{width:44px;height:44px;padding:0;justify-content:center}
  .top__right .top__label{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
  .top__search{display:none}
  .app-footer__developer{display:none}
  .app-footer{gap:8px;white-space:nowrap}
}

/* --------------------------------------------------------------------------
   Phone ergonomics.

   Everything above is a desk interface: 26px controls and 12.5px labels are
   right for a mouse on a 27" screen and wrong for a thumb in a stairwell.
   Below the shell breakpoint the same markup gets finger-sized geometry —
   nothing is hidden or re-ordered, only enlarged, so what someone learns on
   the desktop still applies on site.

   Minimum 48px on anything tappable (WCAG 2.5.5 asks 44; a wet glove asks
   more), and the base type goes from 13px to 15px.
   -------------------------------------------------------------------------- */
@media (max-width:860px){
  /* --row IS the height of a table row: the sticky group header positions
     itself at top:var(--row), directly under the sticky column header. Raising
     the real cell height for a thumb without raising the token left the group
     row sticking 10px too high, printed across the row above it — which is
     what "everything bugs when I scroll" looked like. */
  :root{--row:42px}
  body{font-size:0.9375rem}
  .ic{width:17px;height:17px}
  .ic--lg{width:21px;height:21px}

  /* Buttons, inputs and rows.

     44px, not 48. The first pass took the desk's 28px control straight to 48
     and it came out chunky: a row of three buttons filled the screen and a
     form looked like a phone dialler. 44 is what WCAG 2.5.5 asks for and what
     the navigation sheet already felt right at, and the type comes down half
     a step with it so a label is not shouting either. */
  /* Under its label, a field takes the line. The 340px cap keeps a text box
     a readable measure when it sits BESIDE a label; once it sits under one,
     the cap only leaves dead space to the right of everything. */
  .frow__f{padding:8px var(--pad) 12px}
  .frow__f .field,.frow__f textarea.field,.frow__f select,.frow__f .relation-picker{max-width:none;width:100%}
  .relation-picker__field{max-width:none}
  .frow__f>*{min-width:0}
  .btn,.txtbtn{min-height:44px;padding:0 14px;font-size:0.875rem;gap:8px}
  .btn--icon{min-width:44px;padding-inline:10px}
  .field{min-height:44px;padding:6px 11px;font-size:0.875rem}
  select.field{padding-right:8px}
  textarea.field{min-height:88px;padding:10px 11px}
  .menu__item{min-height:44px;padding:9px 14px;font-size:0.875rem}
  .check{min-height:44px;display:inline-flex;align-items:center;gap:10px}
  input[type="checkbox"],input[type="radio"]{width:22px;height:22px}
  .field--color{width:58px!important;height:44px}

  /* The top bar carries the identity and the global controls. The 44px square
     they need is set in the shell block above, so that a tablet gets it too. */
  .top{min-height:52px;padding-inline:10px;gap:6px}
  .top__right{gap:2px}
  .top__crumb{font-size:0.875rem}

  /* Tables in card mode: a row is a tap target for the link it contains. */
  .sheet td,.sheet th{min-height:42px;font-size:0.875rem}
  .sheet tbody tr td:first-child{min-height:48px;display:flex;align-items:center;font-size:0.9375rem}
  .sheet td::before{font-size:0.8125rem}

  /* Filter bars: one filter per line, its name above it.

     .bar is a wrapping flex row of loose labels and fields. On a desk they
     read left to right as pairs; wrapped onto a 375px screen the pairs come
     apart and you get "Phase [all] Site manager" on one line and "[all]
     Status [all]" on the next — every label sitting above the wrong field.
     Each pair becomes a block, and each block takes the width. */
  .bar,.filters{display:grid;grid-template-columns:1fr;gap:10px;align-items:stretch}
  .bar>label,.filters>label{display:grid;gap:4px;font-size:0.8125rem;font-weight:600;color:var(--ink-2)}
  /* A label followed by its own control, the way .bar is written. */
  .bar>label+.field,.bar>label+select,.bar>label+input{margin-top:-6px}
  .bar .field,.filters .field,.bar select,.bar input{width:100%}
  /* …except the select a relation picker has already replaced. It is
     visually hidden but still laid out, so width:100% gave it 375px starting
     at the bar's padding — 390px of document in a 375px window, and the whole
     page slid sideways under the thumb while it was being scrolled. */
  .bar select.relation-picker__value,.filters select.relation-picker__value{width:1px}
  .bar__r,.bar .bar__r{margin-left:0;flex-wrap:wrap}
  /* Direct children only. A relation picker injects its own refresh (and
     sometimes create) button INTO the bar; a descendant selector gave those
     width:100% too, and because they are flex:none that width became their
     basis — the button took the whole row and the field it belongs to
     collapsed to nothing. The schedule's project switcher and the task
     filters were unusable on a phone. */
  .bar>.btn,.filters>.btn{width:100%;justify-content:center}
  .bar .vtog,.filters .vtog{width:100%}
  .bar .vtog a,.bar .vtog button{flex:1;justify-content:center}

  /* Tabs and pills that were sized for a cursor. */
  .vtog a,.vtog button,.tabs a,.tabs button,.pager a,.pager span{min-height:44px;display:inline-flex;align-items:center;padding-inline:13px;font-size:0.875rem}

  /* Section heads and page titles get room to breathe. */
  .head__t{font-size:1.25rem}
  .sect{font-size:0.8125rem}
}
@media (max-width:700px){
  /* Lists on a phone: one card per row, label in front of every value.

     A spreadsheet is the right shape on a desk and the wrong one in a
     stairwell — nowrap cells across eight columns meant reading a name,
     pushing the table sideways, and no longer knowing whose amount you were
     looking at. public/js/hbs.js copies the column names onto the cells and
     keeps the ARIA roles, so the table stays a table for a screen reader. */
  .grid-wrap{overflow:visible;border-bottom:0}
  .sheet,.sheet thead,.sheet tbody,.sheet tfoot,.sheet tr,.sheet th,.sheet td{display:block}
  .sheet:has(tfoot){height:auto}
  /* The head is not deleted — it is the accessible name of every value. */
  .sheet thead{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}
  .sheet__spacer{display:none}
  .sheet tbody tr{margin:0 0 8px;border:1px solid var(--grid-strong);border-left-width:3px;border-left-color:var(--grid-strong);background:#fff}
  .sheet td,.sheet th{
    height:auto;min-height:34px;padding:6px 10px;white-space:normal;overflow-wrap:anywhere;
    border-right:0;border-bottom:1px solid var(--grid);
    display:grid;grid-template-columns:minmax(80px,34%) 1fr;gap:10px;align-items:start;
  }
  .sheet td::before{content:attr(data-label);color:var(--ink-2);font-size:0.7188rem;line-height:1.4}
  /* The first column identifies the record — it is the card's headline, not
     another label/value pair. */
  .sheet tbody tr td:first-child{display:block;padding:8px 10px;background:var(--head);font-weight:600}
  .sheet tbody tr td:first-child::before{display:none}
  /* A cell holding nothing but a dash says nothing. It keeps the columns
     lined up on a desk; on a card it is a line to scroll past. */
  .sheet td[data-blank]:not(:first-child){display:none}
  /* Action columns have no heading — the buttons take the full width. */
  .sheet td[data-label=""]::before{display:none}
  .sheet td[data-label=""],.sheet td:not([data-label]){grid-template-columns:1fr}
  .sheet tbody tr td:last-child{border-bottom:0}
  /* Sticky is meaningless without a scroll direction to stick against. */
  .sheet td.stick,.sheet th.stick,.sheet thead th.stick{position:static}
  /* The state marker moves from the first cell to the edge of the card. */
  .sheet tbody tr td:first-child{box-shadow:none!important}
  .sheet tbody tr.status-row--ok{border-left-color:var(--green)}
  .sheet tbody tr.status-row--info{border-left-color:var(--blue)}
  .sheet tbody tr.status-row--warn{border-left-color:var(--amber)}
  .sheet tbody tr.status-row--risk{border-left-color:var(--red)}
  .sheet tbody tr.status-row--idle{border-left-color:var(--ink-3)}
  .sheet tbody tr.status-row--custom{border-left-color:var(--status-color)}
  /* The same edge, for the ~170 lists that never set the class. The state is
     read off the row's own state cell by public/js/hbs.js — on the card the
     edge is what a reader scans, and it was grey on every one of them. */
  .sheet tbody tr[data-tone="ok"]{border-left-color:var(--green)}
  .sheet tbody tr[data-tone="info"]{border-left-color:var(--blue)}
  .sheet tbody tr[data-tone="warn"]{border-left-color:var(--amber)}
  .sheet tbody tr[data-tone="risk"]{border-left-color:var(--red)}
  .sheet tbody tr[data-tone="idle"]{border-left-color:var(--ink-3)}
  .sheet tfoot{display:block;border:1px solid var(--grid-strong);border-left-width:3px;background:var(--head)}
  .sheet tfoot td{position:static;border-top:0;border-bottom:1px solid var(--grid);background:transparent}
  .sheet tfoot td:last-child{border-bottom:0}
  /* Everything you tap is a thumb wide and a thumb tall. */
  .sheet td .btn,.sheet td .inline-form .btn{min-height:44px}
  .sheet td .inline-form{display:flex;flex-wrap:wrap;gap:6px}
  .sheet td .inline-form .field{min-height:44px;flex:1 1 120px}

  /* Sorting, once the column heads are folded away.

     The head is the accessible name of every value, so it stays in the
     document and out of sight — and the sort links went out of sight with it.
     public/js/hbs.js lifts them into this control and takes the hidden ones
     out of the tab order. It sits directly above the list, in the same shape
     as the filter bar, because on a phone sorting and filtering are the same
     question asked twice. */
  .listsort{display:grid;gap:4px;margin:0 var(--pad) 10px}
  .listsort label{font-size:0.8125rem;font-weight:600;color:var(--ink-2)}
  .listsort select{width:100%;min-height:44px}

  /* One card, one glance.

     An eleven-column table became an eleven-line card. The lines that
     identify and rank the record stay; the rest is one tap away on the card
     itself, so the list is four screens instead of thirteen and nothing has
     been thrown away. */
  .sheet tbody tr.is-folded td.td--fold{display:none}
  .td-fold-btn{
    display:inline-flex;align-items:center;min-height:44px;margin-top:4px;padding:0;
    border:0;background:none;color:var(--link);font-size:0.8125rem;font-weight:400;
    text-decoration:underline;text-underline-offset:2px;
  }
  .td-fold-btn::after{content:" ▾";text-decoration:none}
  .sheet tbody tr.is-unfolded .td-fold-btn::after{content:" ▴"}

  /* The month calendar on a phone: a list of the days that have something on
     them. Seven columns at 375px is 53 pixels per day — the grid used to keep
     its 780px minimum and push the page sideways instead, so reading Thursday
     meant losing sight of which week it was. */
  .calendar{display:block;min-width:0}
  .calendar__weekday{display:none}
  .calendar__day{
    display:grid;grid-template-columns:52px minmax(0,1fr);gap:10px;align-items:start;
    min-height:0;padding:9px 10px;border-right:0;
  }
  .calendar__day:not(:has(.work-event)){display:none}
  /* Today stays, empty or not — it is where you start reading. Two classes,
     so it outranks the rule above. */
  .calendar .calendar__day--today{display:grid;box-shadow:inset 3px 0 0 var(--orange-deep)}
  .calendar__day--outside:not(:has(.work-event)){display:none}
  .calendar__date{flex-direction:column;align-items:flex-start;gap:0;line-height:1.2}
  .calendar__dow{display:block;font-size:0.6875rem;font-weight:600;color:var(--ink-2);text-transform:uppercase}
  .calendar__events{display:grid;gap:5px}
  .work-event{white-space:normal}

  .license-overview{grid-template-columns:1fr 1fr}
  .chat-create__form{grid-template-columns:1fr}
  /* One panel at a time. Stacking the list above the thread put a 280px
     scrollbox inside the page scroll: you scrolled inside a box inside a page
     to pick a channel, then scrolled back down to read it. The thread is what
     you came for, so it is what you get; the list is one tap away. */
  .chat-hub{grid-template-columns:1fr;overflow:visible;min-height:0}
  .channel-list{max-height:none;overflow:visible;border-right:0;display:none}
  .chat-hub[data-panel="channels"] .channel-list{display:flex}
  .chat-hub[data-panel="channels"] .channel-thread{display:none}
  .channel-list__item{min-height:56px}
  .chat-back{display:inline-flex}
  .channel-thread__head{flex-wrap:wrap;gap:8px}
  /* The module menu is the main way around a module on a phone. */
  .tabs a{padding:13px 14px}
  /* And it has to say that it goes on. Fifteen registers are 1474px of which
     two and a half are on the screen, with no arrow, no scrollbar and no
     edge — so a Handwerker saw three registers and took them for all of them.
     public/js/hbs.js scrolls the current one into view; this fades the right
     edge so the strip visibly continues. */
  .tabs--overflow{
    -webkit-mask-image:linear-gradient(to right,#000 calc(100% - 28px),transparent);
    mask-image:linear-gradient(to right,#000 calc(100% - 28px),transparent);
  }
  .channel-thread{min-height:0}
  .customer-chat-link{grid-template-columns:1fr}
  .guest-chat__form{grid-template-columns:1fr}
  .guest-chat__thread{max-height:none}
  .process-choice-grid{grid-template-columns:1fr}.process-choice{border-right:0}
  .action-review__facts{grid-template-columns:1fr 1fr}
  .box-coordinate-grid{grid-template-columns:1fr 1fr}
}
.burger{display:none}

/* ==========================================================================
   Mobile bottom bar + quick-action sheet (phones only)

   Deliberate, scoped exception to the "no shadow / no radius" house rule:
   Leo asked for the translucent iOS-style bar. It applies ONLY to .mbar and
   .qsheet — every other surface stays flat. Both are display:none above the
   mobile breakpoint, so the desktop document look is untouched.
   ========================================================================== */
.mbar{display:none}

/* ==========================================================================
   Quick actions — one surface, two shapes.

   The same permission-filtered action list serves both the phone (bottom
   sheet above the bar) and the desktop ("Neu" in the top bar, Alt+N), so the
   two can never drift apart.
   ========================================================================== */
.qsheet{position:fixed;inset:0;z-index:80}
.qsheet[hidden]{display:none}
.qsheet__scrim{
  position:absolute;inset:0;width:100%;height:100%;
  background:rgba(26,23,20,.32);
  -webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);
}
.qsheet__panel{
  position:absolute;left:50%;top:64px;transform:translateX(-50%);
  width:min(560px,92vw);max-height:78vh;overflow-y:auto;
  padding:12px var(--pad) 16px;
  border:1px solid var(--grid-strong);
  background:rgba(255,255,255,.92);
  -webkit-backdrop-filter:blur(24px) saturate(180%);
  backdrop-filter:blur(24px) saturate(180%);
  box-shadow:0 8px 32px rgba(26,23,20,.18);
}
.qsheet__grip{display:none}
.qsheet__title{font-size:0.6875rem;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-2)}
.qsheet__state{display:flex;align-items:center;gap:6px;margin-top:6px;font-size:0.7188rem;color:var(--green)}
.qsheet__grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:6px;margin-top:10px}
.qsheet__item{
  min-height:74px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;
  padding:8px 4px;border:1px solid var(--grid);background:var(--bg);
  font-size:0.6875rem;line-height:1.15;text-align:center;color:var(--ink);text-decoration:none;
}
.qsheet__item:hover{background:var(--sel);border-color:var(--grid-strong)}
.qsheet__item .ic{stroke:var(--orange-deep)}
.qsheet__close{width:100%;margin-top:10px;justify-content:center}

/* 700px, not 860px. Everything in this block replaces the sidebar with the
   floating bar and the "More" sheet — which is right for a phone and wrong for
   a tablet: a 768px iPad in portrait lost the whole 37-entry menu, burger and
   all, and was left with four destinations plus a 43-row sheet. Between 700
   and 860px the shell block above still applies, so a tablet keeps the real
   navigation as a burger drawer with 44px controls; below 700px the phone
   takes over. */
@media (max-width:700px){
  /* The bar floats: it is a control that sits ON the page, not a strip glued
     to the bezel. Inset on all three sides, fully rounded, and lifted clear of
     the home indicator by the safe-area inset — on a phone with a gesture bar
     the old bottom:0 put the labels underneath it. */
  .mbar{
    display:flex;align-items:stretch;justify-content:space-around;gap:2px;
    position:fixed;left:10px;right:10px;z-index:45;
    bottom:calc(10px + env(safe-area-inset-bottom));
    padding:6px;
    border:1px solid var(--grid-strong);
    border-radius:22px;
    background:rgba(255,255,255,.82);
    -webkit-backdrop-filter:blur(20px) saturate(180%);
    backdrop-filter:blur(20px) saturate(180%);
    box-shadow:0 6px 24px rgba(26,23,20,.16);
  }
  .mbar__item{
    flex:1 1 0;min-width:0;min-height:56px;border-radius:16px;
    display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;
    font-size:0.75rem;line-height:1.1;color:var(--ink-2);text-decoration:none;text-align:center;
  }
  .mbar__item .ic{width:24px;height:24px}
  .mbar__item>span{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .mbar__item--on{color:var(--orange-deep);font-weight:600;background:var(--sel)}
  .mbar__item--on .ic{stroke-width:1.9}
  .mbar__action{
    flex:0 0 auto;width:56px;height:56px;align-self:center;
    display:flex;align-items:center;justify-content:center;
    border-radius:50%;background:var(--orange);color:var(--orange-deep);
    border:1px solid var(--orange-edge);
    box-shadow:0 2px 10px rgba(26,23,20,.22);
  }
  .mbar__action .ic{width:26px;height:26px;stroke-width:2}
  /* The bar floats above the scroll container — keep the last row reachable. */
  .page{padding-bottom:calc(92px + env(safe-area-inset-bottom))}
  /* The phone never gets the desktop sidebar. "More" opens the navigation
     sheet instead, so there is no off-canvas drawer to discover or dismiss. */
  .app:has(.mbar) .burger{display:none!important}
  .app:has(.mbar) .nav{display:none!important}

  /* On a phone the same panel becomes a bottom sheet above the bar. */
  .qsheet__panel{
    left:0;right:0;top:auto;bottom:0;transform:none;width:auto;max-height:82vh;
    padding:8px var(--pad) calc(14px + env(safe-area-inset-bottom));
    border:0;border-top:1px solid var(--grid-strong);border-radius:18px 18px 0 0;
    background:rgba(255,255,255,.86);
    box-shadow:0 -2px 24px rgba(26,23,20,.18);
    animation:qsheet-in .16s ease-out;
  }
  @keyframes qsheet-in{from{transform:translateY(12px);opacity:.6}to{transform:none;opacity:1}}
  @media (prefers-reduced-motion:reduce){.qsheet__panel{animation:none}}
  .qsheet__grip{display:block;width:44px;height:5px;margin:0 auto 10px;border-radius:3px;background:var(--grid-strong)}
  .qsheet__title{font-size:0.8125rem}
  .qsheet__grid{grid-template-columns:repeat(auto-fill,minmax(104px,1fr));gap:8px}
  .qsheet__item{min-height:84px;border-radius:14px;background:rgba(255,255,255,.62);font-size:0.8125rem;gap:7px}
  .qsheet__item .ic{width:24px;height:24px}
}
/* ==========================================================================
   Navigation sheet — the phone's "More".

   Same destinations as the sidebar, same order, rows a thumb can hit. Only
   ever rendered on a phone; above the breakpoint the sidebar is present and
   the sheet has no trigger.
   ========================================================================== */
.nsheet{position:fixed;inset:0;z-index:85}
.nsheet[hidden]{display:none}
.nsheet__scrim{
  position:absolute;inset:0;width:100%;height:100%;
  background:rgba(26,23,20,.32);
  -webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);
}
.nsheet__panel{
  position:absolute;left:0;right:0;bottom:0;
  display:flex;flex-direction:column;max-height:88vh;max-height:88dvh;
  padding:8px var(--pad) calc(14px + env(safe-area-inset-bottom));
  border-top:1px solid var(--grid-strong);border-radius:18px 18px 0 0;
  background:rgba(255,255,255,.94);
  -webkit-backdrop-filter:blur(24px) saturate(180%);
  backdrop-filter:blur(24px) saturate(180%);
  box-shadow:0 -2px 24px rgba(26,23,20,.18);
  animation:qsheet-in .16s ease-out;
}
@media (prefers-reduced-motion:reduce){.nsheet__panel{animation:none}}
.nsheet__grip{width:44px;height:5px;margin:0 auto 10px;border-radius:3px;background:var(--grid-strong)}
.nsheet__title{font-size:0.8125rem;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-2)}
.nsheet__body{flex:1;overflow-y:auto;-webkit-overflow-scrolling:touch;margin-top:6px}
/* The group header is the fold: it is a <summary>, so it has to be a target
   a thumb can hit and has to say which way it is pointing. */
.nsheet__group{
  list-style:none;display:flex;align-items:center;justify-content:space-between;gap:10px;
  margin-top:8px;min-height:48px;padding:6px 2px;
  font-size:0.75rem;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-3);
  cursor:pointer;
}
.nsheet__group::-webkit-details-marker{display:none}
.nsheet__group .ic{width:14px;height:14px;flex:none;transition:transform .12s}
.nsheet__section[open]>.nsheet__group .ic{transform:rotate(90deg)}
.nsheet__section{border-bottom:1px solid var(--grid)}
.nsheet__section:last-child{border-bottom:0}
/* Not a fold — the caption over the account row. */
.nsheet__group--plain{min-height:0;cursor:default;margin-top:0}
.nsheet__items{display:grid;gap:4px}
.nsheet__item{
  display:flex;align-items:center;gap:12px;width:100%;min-height:52px;
  padding:8px 12px;border-radius:12px;border:1px solid transparent;
  font-size:0.9375rem;text-align:left;color:var(--ink);text-decoration:none;background:none;
}
.nsheet__item .ic{stroke:var(--orange-deep);flex:none}
.nsheet__item--on{background:var(--sel);border-color:var(--orange-edge);font-weight:600}
.nsheet__item--on .ic{stroke:var(--ink)}
.nsheet__n{
  margin-left:auto;min-width:24px;padding:2px 7px;border-radius:11px;
  background:var(--head);border:1px solid var(--grid-strong);
  font-size:0.8125rem;font-weight:600;text-align:center;
}
/* The account row sits below the scrolling list, not inside it: "Abmelden"
   must not be a neighbour of "Tagesberichte". */
.nsheet__foot{flex:none;margin-top:8px;padding-top:8px;border-top:1px solid var(--grid-strong)}
.nsheet__items--row{grid-auto-flow:column;grid-auto-columns:minmax(0,1fr);align-items:stretch}
.nsheet__items--row form{display:flex}
.nsheet__item--sm{min-height:48px;justify-content:center;gap:7px;padding:8px;font-size:0.8125rem;text-align:center}
.nsheet__close{width:100%;margin-top:12px;justify-content:center;min-height:48px}

/* Devices without backdrop-filter get an opaque bar instead of a grey haze. */
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .mbar{background:var(--bg)}
  .qsheet__panel{background:var(--bg)}
  .qsheet__item{background:var(--zebra)}
  .nsheet__panel{background:var(--bg)}
}

/* ==========================================================================
   Command palette (Ctrl/Cmd+K) — one box for starting, going and finding.
   ========================================================================== */
.palette{position:fixed;inset:0;z-index:90}
.palette[hidden]{display:none}
.palette__scrim{position:absolute;inset:0;width:100%;height:100%;background:rgba(26,23,20,.34)}
.palette__panel{
  position:absolute;left:50%;top:12vh;transform:translateX(-50%);
  width:min(620px,94vw);display:flex;flex-direction:column;
  border:1px solid var(--grid-strong);background:var(--bg);
  box-shadow:0 12px 40px rgba(26,23,20,.22);
}
.palette__input{
  height:44px;padding:0 14px;border:0;border-bottom:1px solid var(--grid);
  font-size:0.9375rem;background:var(--bg);
}
/* The search field of the command palette is focused the moment the dialog
   opens, so it is the one place where the focus indicator matters most. It
   used to remove the outline and mark focus with a #FCFCFB background — a
   1.02:1 difference against white, which is to say none at all. The ring sits
   inside here because the field is flush with the dialog edge; a solid
   two-pixel inset border carries it. */
.palette__input:focus{outline:none;background:var(--zebra);box-shadow:inset 0 0 0 2px var(--ink)}
.palette__results{max-height:52vh;overflow-y:auto}
.palette__item{
  display:flex;align-items:center;gap:12px;padding:9px 14px;
  border-bottom:1px solid var(--grid);color:var(--ink);text-decoration:none;font-size:0.8125rem;
}
.palette__item em{margin-left:auto;font-style:normal;font-size:0.6875rem;color:var(--ink-2);white-space:nowrap}
.palette__item--on{background:var(--sel)}
.palette__foot{
  display:flex;gap:16px;padding:7px 14px;border-top:1px solid var(--grid);
  background:var(--head);font-size:0.6875rem;color:var(--ink-2);
}

/* Company and active project in one control at the top left.

   These were two separate things: a breadcrumb link that went home, and a
   chip that could only be cleared. Switching jobs meant finding a module
   that happened to have a project selector. One control now says who you are
   and which job you are on, and opens the list of the others. */
/* The switcher shrinks; the controls on the right do not.

   A max-width stops a box growing, it does not stop nowrap text spilling out
   of it — so overflow:hidden has to be here, and the button must not be
   allowed to exceed the box the flex row gave it. Sized in percent rather
   than vw: what matters is how much room is left in the bar, not how wide
   the window is, and at 1100px a 46vw switcher ran straight under "Support". */
.top__switch{flex:0 1 300px;min-width:0;max-width:300px;overflow:hidden}
.top__switch-btn{max-width:100%;width:auto;gap:4px;overflow:hidden;flex-wrap:nowrap;white-space:nowrap}
.top__right{flex:0 1 auto;min-width:0}
.top__switch-project{flex:1 1 auto;min-width:0;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.top__switch-all{color:var(--ink-2)}
/* The switcher on a screen the job does not narrow: still usable, visibly
   not in force, and it says so in words as well as in weight — colour is
   never the only carrier of meaning. */
.top__switch-btn--idle .top__switch-project{font-weight:400;color:var(--ink-2)}
.top__switch-note{color:var(--ink-2);font-size:0.6875rem;white-space:nowrap}

.top__switch-caret{flex:none;color:var(--ink-3)}
.menu__panel--wide{left:0;right:auto;width:min(300px,88vw);min-width:0;max-width:300px}
/* The list scrolls; the search box and the "open the list" row do not, so
   they stay reachable however many projects there are. */
/* One scroll region, not two. The panel was 579px of menu with a 437px
   scroll box inside it: the wheel scrolled the inner list while the panel
   itself already reached past the fold, so on a shorter screen the jobs at
   the bottom could not be reached at all. The list now holds about eight
   rows — the ones somebody is working on — and the search box above it plus
   "Alle Projekte anzeigen" reach the rest. */
.menu__scroll{max-height:min(38vh,270px);overflow-y:auto;overscroll-behavior:contain;
  border-top:1px solid var(--grid)}
.menu__filter{width:calc(100% - 16px);margin:8px}
/* A heading inside a menu — the language block in the account menu. Not a
   destination, so it is not a .menu__item: it must not look tappable. */
.menu__head{padding:8px 12px 3px;border-top:1px solid var(--grid);font-size:0.6875rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-3)}
.menu__item b{flex:none;color:var(--ink-2);font-weight:600}
.menu__item[hidden]{display:none}
@media (max-width:700px){.top__switch{flex:1 1 auto;max-width:none}}

@media print{
  .nav,.top,.bar,.statusbar,.head__act,.agent-rail,.mbar,.qsheet{display:none!important}
  .app{display:block;height:auto}
  .page{overflow:visible}
  /* Print document: the letter fills the paper, no screen chrome */
  .doc{background:#fff;min-height:0}
  .doc__page{max-width:none;margin:0;border:0;padding:0}
}

/* ==========================================================================
   Client portal — standalone guest page (signed link, no app frame).
   Used only by views/portal/show.blade.php:
   .portal-body (grey canvas) · .portal (centered white sheet) ·
   .portal__top (orange header: company + portal tag) · .portal__foot.
   Tables keep their natural height here (no viewport grid like the app).
   ========================================================================== */
.portal-body{background:var(--head)}
.portal{max-width:960px;margin:0 auto;min-height:100vh;display:flex;flex-direction:column;background:var(--bg);border-left:1px solid var(--grid);border-right:1px solid var(--grid)}
.portal__top{flex:none;display:flex;align-items:center;gap:9px;padding:13px var(--pad);background:var(--orange);border-bottom:1px solid #C88300;font-size:0.9375rem;font-weight:600}
.portal__top .ic{width:17px;height:17px}
.portal__top span{margin-left:auto;font-size:0.6875rem;font-weight:600;letter-spacing:.09em;text-transform:uppercase;color:var(--orange-deep)}
.portal .grid-wrap{flex:none}
/* Signed-in customer: identity strip and the auth/decision forms. */
.portal__bar{display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  padding:8px var(--pad);border-bottom:1px solid var(--grid);background:var(--head)}
.portal__who{display:inline-flex;align-items:center;gap:6px;margin-left:auto;
  font-size:0.75rem;color:var(--ink-2)}
.portal__auth{padding:10px var(--pad);border-bottom:1px solid var(--grid)}
.portal__auth .frow{max-width:520px}
.portal__hint{padding:0 var(--pad) 14px;font-size:0.7188rem;color:var(--ink-2)}
.portal__notice{padding:14px var(--pad)}
.portal__notice p{max-width:62ch;line-height:1.6}
/* Selection options: one tappable row per choice, price consequence inline. */
.selection-option{display:flex;gap:10px;align-items:flex-start;padding:10px 12px;margin-bottom:6px;
  border:1px solid var(--grid);background:var(--bg);cursor:pointer}
.selection-option:hover{background:var(--zebra);border-color:var(--grid-strong)}
.selection-option:has(input:checked){background:var(--sel);border-color:var(--orange-deep)}
.selection-option input{margin-top:3px}
.selection-option__body{display:flex;flex-direction:column;gap:3px;font-size:0.7813rem}
.selection-option__body span{color:var(--ink-2)}
.selection-option__price{font-size:0.75rem;color:var(--ink)}
.selection-option__price em{font-style:normal}
.portal__form{display:grid;grid-template-columns:minmax(180px,1fr) minmax(220px,2fr) auto;gap:10px;padding:10px var(--pad);border-bottom:1px solid var(--grid);background:var(--zebra);align-items:end}
.portal__form label{display:grid;gap:3px;color:var(--ink-2);font-size:0.6875rem}
.portal__form .field{width:100%;height:30px}
.portal__form textarea.field{height:70px;padding:6px 8px;resize:vertical}
.portal-chat article{padding:9px var(--pad);border-bottom:1px solid var(--grid)}
.portal-chat article>div{display:flex;gap:12px;align-items:center}
.portal-chat time{margin-left:auto;color:var(--ink-2);font-size:0.6875rem}
.portal-chat p{margin-top:4px;white-space:pre-wrap}
.portal__foot{margin-top:auto;padding:12px var(--pad);border-top:1px solid var(--grid);background:var(--head);font-size:0.7188rem;color:var(--ink-2);display:flex;justify-content:space-between;gap:12px;flex-wrap:wrap}
.portal__foot a{color:inherit}
.metric-bars{display:grid;gap:8px;padding:10px var(--pad);border-bottom:1px solid var(--grid);background:var(--zebra)}
.metric-bars__item{display:grid;grid-template-columns:minmax(120px,180px) minmax(140px,1fr) 130px;gap:10px;align-items:center;font-size:0.6875rem}
.metric-bars__item>div{height:10px;background:#E7E5E0;border:1px solid #D6D2CA;overflow:hidden}
.metric-bars__fill{display:block;width:var(--metric-width);height:100%;background:#2A78B8}
.metric-bars__fill--income{background:#2F7D4A}.metric-bars__fill--expense{background:#B42318}.metric-bars__fill--travel{background:#7A5AA6}.metric-bars__fill--purchase{background:#D47A00}
.metric-bars__item b{text-align:right;font-variant-numeric:tabular-nums}
@media (max-width:700px){.portal__form{grid-template-columns:1fr}.portal__form-wide{grid-column:1}.portal__form .btn{justify-self:start}}
@media (max-width:700px){.metric-bars__item{grid-template-columns:100px 1fr}.metric-bars__item b{grid-column:2;text-align:left}}
@media (max-width:700px){.onboarding-steps{overflow-x:auto}.onboarding-steps .step{min-width:110px}}
.custom-column-builder {
    border: 1px solid var(--line, #d9d7d2);
    background: var(--paper, #fff);
    padding: 14px;
}
.custom-column-builder__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}
.custom-column-row {
    display: grid;
    grid-template-columns: minmax(180px, 1.4fr) minmax(160px, .8fr) minmax(220px, 1.2fr) auto;
    gap: 8px;
    margin-top: 8px;
    align-items: start;
}
@media (max-width: 820px) {
    .custom-column-row {
        grid-template-columns: 1fr;
    }
}

/* ==========================================================================
   Checklist editor

   Checkpoints used to be typed into a textarea, one per line — or, in the
   project wizard, all on one line separated by semicolons. This is the same
   data edited as what it is: a list of things somebody will tick off.
   ========================================================================== */
.chklist{display:grid;gap:6px}
.chklist__items{list-style:none;display:grid;gap:4px;margin:0;padding:0}
.chklist__item{display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:8px}
/* Green, because this is the mark the person will make on site, and green is
   the one colour everyone reads as "done" without being told. */
.chklist__tick{
  display:inline-grid;place-items:center;width:22px;height:22px;flex:none;
  border:1px solid var(--green-edge);background:var(--green-tint);color:var(--green);
}
.chklist__tick .ic{width:14px;height:14px;stroke-width:2.2}
.chklist__text{width:100%}
.chklist__remove{color:var(--ink-2)}
.chklist__remove:hover{color:var(--red);border-color:var(--red-edge)}
.chklist__add{justify-self:start}
@media (max-width:860px){
  .chklist__item{gap:10px}
  .chklist__tick{width:30px;height:30px}
  .chklist__tick .ic{width:18px;height:18px}
}

/* ==========================================================================
   Feedback — the report button that sits on every page (x-hbs.feedback).

   It is a <details> element, so it opens and closes without JavaScript. The
   trigger is deliberately quiet: an outlined button in the bottom right that
   never competes with the page's own actions. It carries no orange — orange
   is the navigation, and this is content.

   Stacking: closed it sits at 44, one below the phone's floating bar (45), so
   the bar always wins if the two ever meet. Open it rises to 70 — above the
   bar it now overlaps, still below the quick-action sheet (80), the navigation
   sheet (85) and the command palette (90), so opening any of those covers the
   panel instead of appearing behind it. The panel is inside the element's own
   stacking context, which is why the value lives on .fbox and not on it.
   ========================================================================== */
/* Top right, under the bar. It used to sit bottom right, which is exactly
   where the assistant rail's send button and the chat composer are — so
   the one control you press to send a message had a second control
   parked on top of it. Reporting a problem is rare; sending a message is
   constant, and the constant one keeps the corner. */
.fbox{position:fixed;right:16px;top:52px;z-index:44}
.fbox[open]{z-index:70}
.fbox__trigger{
  display:inline-flex;align-items:center;gap:6px;
  min-height:36px;padding:6px 12px;
  border:1px solid var(--grid-strong);background:var(--bg);
  font-size:0.8125rem;color:var(--ink);cursor:pointer;
  list-style:none;
}
/* Safari still paints the disclosure marker without this. */
.fbox__trigger::-webkit-details-marker{display:none}
.fbox__trigger:hover{background:var(--head);border-color:var(--ink-2)}
/* The word beside the icon. It goes on a phone, where the button sits above
   the floating bar and every pixel of width belongs to the page. */
.fbox__trigger-label{white-space:nowrap}
@media (max-width:520px){.fbox__trigger-label{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}}
.fbox__trigger .ic{stroke:var(--blue)}
.fbox[open] .fbox__trigger{background:var(--blue-tint);border-color:var(--blue-edge)}

/* A closed <details> hides its content with a user-agent rule, and the
   author display below outranks it — so the panel stayed laid out, 15px off
   the right edge, and every page it sits on scrolled sideways by that much.
   Same trap as [hidden]; here the state lives on the parent. */
.fbox:not([open]) .fbox__panel{display:none}
.fbox__panel{
  position:absolute;right:0;top:calc(100% + 8px);
  width:min(380px,calc(100vw - 32px));
  padding:12px var(--pad) 14px;
  border:1px solid var(--grid-strong);background:var(--bg);
  box-shadow:0 8px 32px rgba(26,23,20,.18);
}
.fbox__head{display:flex;align-items:center;gap:8px;margin-bottom:8px}
.fbox__title{font-weight:600;font-size:0.9375rem}
.fbox__close{margin-left:auto}
.fbox__form{display:grid;gap:8px}
.fbox__legend{font-size:0.75rem;color:var(--ink-2)}
.fbox__kinds{display:flex;flex-wrap:wrap;gap:6px;border:0;padding:0}
.fbox__kinds .fbox__legend{flex:1 0 100%;margin-bottom:2px}
.fbox__kind{
  display:inline-flex;align-items:center;gap:6px;min-height:36px;padding:5px 10px;
  border:1px solid var(--control-line);background:var(--bg);
  font-size:0.8125rem;cursor:pointer;
}
/* The chosen kind is marked by fill AND by the radio itself — never by
   colour alone, and never in the brand orange. */
.fbox__kind:has(input:checked){background:var(--blue-tint);border-color:var(--blue-edge);font-weight:600}
.fbox__text{width:100%;min-height:96px;resize:vertical}
.fbox__page{font-size:0.7188rem;color:var(--ink-2);overflow-wrap:anywhere}
.fbox__send{justify-content:center}
.fbox__done,.fbox__err{display:flex;align-items:center;gap:8px;padding:10px 0 2px;font-size:0.8125rem}
.fbox__done{color:var(--green)}
.fbox__done .ic{stroke:var(--green)}
.fbox__err{color:var(--red)}
.fbox__err .ic{stroke:var(--red)}
.fbox__done[hidden],.fbox__err[hidden]{display:none}

@media (max-width:860px){
  /* Clear of the floating bottom bar and of the home indicator, so the two
     controls never sit on top of each other on a phone. */
  .fbox{
    right:12px;
    top:calc(48px + env(safe-area-inset-top));
    bottom:auto;
  }
  .fbox__trigger{min-height:44px;padding:8px 14px;border-radius:22px}
  /* On a narrow screen the panel is the width of the page, not of the
     button it hangs off. */
  .fbox__panel{
    position:fixed;left:10px;right:10px;bottom:calc(140px + env(safe-area-inset-bottom));
    width:auto;max-height:60vh;max-height:60dvh;overflow-y:auto;
  }
  .fbox__kind{min-height:44px}
  .fbox__send{min-height:48px}
}

@media print{.fbox{display:none!important}}

/* ==========================================================================
   Embedded page (?embed=1) and the dialog that hosts it.

   A create form opened from a picker is the same page as always, without the
   frame: no navigation, no top bar, no side rails, no footer. Adding a
   customer in the middle of the project wizard should not mean leaving the
   wizard, and it should not mean a second copy of the whole application in
   another window either.
   ========================================================================== */
body.embed{height:auto;overflow:auto;background:var(--bg)}
.embed__page{display:flex;flex-direction:column;min-height:100vh;min-height:100dvh}
/* The page head inside a dialog keeps its heading — it is a document of its
   own and a screen reader needs one — but the dialog bar above it says the
   same thing, so it is quieter here. Its buttons are the whole point and
   they stay in reach while the form scrolls. */
.embed__page .head{position:sticky;top:0;z-index:5;background:var(--head);padding-block:10px}
.embed__page .head h1{font-size:1rem}
.embed__page .head__sub{display:none}
/* "Back" and "Cancel" in a page head are links to the list you came from.
   Inside a dialog that list has nowhere to go but the frame itself, which
   would load the whole application into a 760px box. The dialog's own close
   button is the way out. */
.embed__page .head__act a{display:none}

.rdialog{position:fixed;inset:0;z-index:88;display:flex;align-items:center;justify-content:center}
.rdialog__scrim{position:absolute;inset:0;background:rgba(26,23,20,.42);border:0}
.rdialog__panel{
  position:relative;display:flex;flex-direction:column;
  width:min(760px,94vw);height:min(760px,88vh);
  border:1px solid var(--grid-strong);background:var(--bg);
  box-shadow:0 12px 40px rgba(26,23,20,.24);
}
.rdialog__bar{
  display:flex;align-items:center;gap:10px;flex:none;
  padding:8px 10px;border-bottom:1px solid var(--grid-strong);background:var(--head);
}
.rdialog__title{flex:1;font-size:0.8125rem}
.rdialog__frame{flex:1;width:100%;border:0;background:var(--bg)}
/* The "+" beside a picker is an ordinary control, not the most important
   thing on the screen — it used to be a solid orange block. */
.relation-picker__create{color:var(--ink)}
.relation-picker__create:hover{border-color:var(--blue-edge);background:var(--blue-tint)}

@media (max-width:860px){
  .rdialog__panel{width:100vw;height:100dvh;border:0}
  .rdialog__bar{min-height:56px}
}

/* A value the wizard derived rather than asked for, shown so the person is
   not surprised by it later, with the one place it can be changed. */
.wizard-derived{display:flex;flex-wrap:wrap;align-items:baseline;gap:6px;padding:4px 0;font-size:0.8125rem}
.wizard-derived a{margin-left:auto;font-size:0.7188rem}
/* ==========================================================================
   Client portal — navigation.

   The portal used to be one page per audience and needed no navigation, which
   is why there was none. Now that every section is its own page there has to
   be a way between them, and it has to work with a thumb: the customer is the
   one person guaranteed to open this on a phone, standing on a building site.

   Two levels. The upper one belongs to the account (overview, offers,
   decisions), the lower one to the project that is open (progress, dates,
   site, files, invoices, messages). Both scroll sideways instead of wrapping
   into an unreadable pile, and every target is at least 44px tall.

   The active item is blue, not orange. Orange is the brand and the frame of
   this portal — the header above is orange — so it cannot also mean "you are
   here" without the two meanings cancelling each other out.
   ========================================================================== */
.pnav{
  display:flex;align-items:stretch;gap:2px;
  padding:0 var(--pad);
  border-bottom:1px solid var(--grid);
  background:var(--head);
  overflow-x:auto;-webkit-overflow-scrolling:touch;
  scrollbar-width:thin;
}
.pnav__item{
  display:inline-flex;align-items:center;gap:7px;
  min-height:44px;padding:0 12px;
  border-bottom:3px solid transparent;
  color:var(--ink);text-decoration:none;white-space:nowrap;
  font-size:0.8125rem;font-weight:500;
}
.pnav__item .ic{flex:none;stroke:var(--ink-2)}
.pnav__item:hover{background:var(--zebra)}
.pnav__item.is-on{
  border-bottom-color:var(--blue-edge);
  background:var(--blue-tint);color:var(--blue);font-weight:600;
}
.pnav__item.is-on .ic{stroke:var(--blue)}
/* Inherits the global ring; an inward offset put it on the tab's own fill. */

/* The second level names the project its pages belong to. A customer with two
   houses under construction must not have to read an invoice to find out
   which one is on screen. */
.pnav--sub{background:var(--bg);border-bottom:1px solid var(--grid-strong)}
.pnav__here{
  display:inline-flex;align-items:center;min-height:44px;padding-right:12px;margin-right:6px;
  border-right:1px solid var(--grid);
  font-size:0.75rem;font-weight:600;color:var(--ink-2);white-space:nowrap;
}
.pnav__here a{color:inherit}

/* The frame is a flex column; the content between navigation and footer is
   the part that may grow. Without this the pages sit squashed at the top. */
.portal>main{flex:1 1 auto;min-width:0}
/* The skip link moves focus here programmatically; a ring drawn around the
   whole page for a click nobody made is noise. A keyboard user still gets
   one — :focus-visible is exactly the difference between the two. */
.portal>main:focus:not(:focus-visible){outline:none}

/* The other way in, under a sign-in form: a button, not a link buried in
   prose, because for a customer without a password it is the way in. */
.portal__alt{display:flex;gap:10px;flex-wrap:wrap;padding:0 var(--pad) 12px}
.portal__alt .btn{min-height:44px}

/* An access code is read from paper or from a screen and typed back in.
   Wide letter spacing and a large size are what make that possible. */
.field--code{
  font-family:ui-monospace,'SF Mono',Menlo,Consolas,monospace;
  font-size:1.125rem;letter-spacing:.14em;text-transform:uppercase;
  min-height:44px;
}

/* Everything a customer taps in this portal is a phone target. Sign out,
   upload, send — 44px, because the person holding the phone is standing on a
   building site and not looking for a 30px button. */
.portal__bar .btn,
.portal__form .btn,
.portal__notice .btn{min-height:44px}
.portal__form .field{min-height:44px}
.portal__form textarea.field{min-height:88px}

@media (max-width:700px){
  .pnav{padding:0 8px}
  .pnav__item{padding:0 10px;font-size:0.875rem}
  .pnav__here{padding-right:8px;margin-right:4px}
  .portal__bar{gap:8px}
  .portal__foot{flex-direction:column;gap:6px}
}

/* ==========================================================================
   The access code as the builder sees it, once.

   It is stored hashed, so the moment after it is generated is the only moment
   it is readable anywhere. That is worth a box of its own rather than a line
   in a flash message that scrolls past.
   ========================================================================== */
.codecard{
  margin:10px var(--pad);padding:12px 14px;
  border:1px solid var(--blue-edge);background:var(--blue-tint);
}
.codecard__head{
  display:flex;align-items:center;gap:8px;
  font-size:0.875rem;font-weight:600;color:var(--blue);
}
.codecard__head .ic{stroke:var(--blue)}
.codecard__row{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:8px}
.codecard__row .btn{min-height:44px}
.codecard__code{max-width:280px;background:var(--bg)}
.codecard__hint{margin-top:8px;max-width:70ch;font-size:0.75rem;line-height:1.5;color:var(--ink)}

/* Whether a code is out there, in the account table. */
.codestate{
  display:inline-block;padding:2px 7px;
  border:1px solid var(--green-edge);background:var(--green-tint);
  font-size:0.6875rem;font-weight:600;color:var(--green);white-space:nowrap;
}

/* ==========================================================================
   Estimating (/estimates and /estimates/{id})

   Nothing new in character — the estimate pages were carrying their own
   layout in style="" attributes and their secondary material in sections of
   the same weight as the main one. This is what those two habits cost, moved
   into the stylesheet where the rest of the system lives.
   ========================================================================== */

/* A sortable column heading is a link, and a link inside a table head has to
   look like the heading it replaces — not like a blue word. The estimate list
   carried this as style="color:inherit;text-decoration:none" on six anchors. */
.sheet thead th.sortable a{
  display:inline-flex;align-items:center;gap:4px;
  min-height:24px;color:inherit;text-decoration:none;
}
.sheet thead th.sortable a:hover{text-decoration:underline}

/* The enquiries that still need a price. They belong to the pipeline and are
   repeated on the estimate list only as a worklist, so they are folded away
   and clearly a second thing rather than a second main table. */
.est-worklist{margin:12px 12px 0}
.est-worklist>summary{display:flex;align-items:center;gap:8px;min-height:40px}
.est-worklist .bar{border-top:1px solid var(--grid)}
.est-worklist .grid-wrap{border-bottom:0}

/* Header data of the document. Eleven fields including two 5000-character
   text areas used to stand permanently between the figures and the positions;
   folded up they are one row until somebody wants them. */
.est-doc{margin:12px 12px 0}
.est-doc>summary{display:flex;align-items:center;gap:8px;min-height:40px}
.est-doc .kv{border-top:1px solid var(--grid)}

/* One position form for adding and for editing. While it is pointed at an
   existing line it has to say so without being read: the same form in the
   same place doing two different things is how a correction becomes a
   duplicate. */
.est-line-form{scroll-margin-top:12px}
.est-line-form[data-editing]{border-color:var(--blue-edge);border-left-width:3px}
.est-line-form[data-editing] .edit-card__head{background:var(--blue-tint)}
/* .btn carries display:inline-flex, which outranks the browser's [hidden]. */
.btn[hidden],.est-line-edit[hidden]{display:none}

/* What happens next: the state of the document, the offer made from it, the
   hand-over into the budget. A strip, not a card — none of it is a form the
   reader fills in. */
.est-next{
  flex-wrap:wrap;margin:0 12px 12px;
  border:1px solid var(--grid);border-top:1px solid var(--grid);
}
.est-next .inline-form{margin-left:0;flex-wrap:wrap;gap:6px}
.est-next__status{width:170px}

@media (max-width:860px){
  /* On a phone every one of these is a full-width row: a status select and a
     submit button sharing a line at 375px leaves neither of them tappable. */
  .est-next{display:grid;gap:8px}
  .est-next .inline-form{display:grid;grid-template-columns:1fr;gap:8px;width:100%}
  .est-next__status{width:100%}
  .est-next .btn,.est-next form .btn{width:100%;justify-content:center}
  .est-worklist,.est-doc{margin-left:0;margin-right:0}
  .est-line-form{margin-left:0;margin-right:0}
  /* The disclosure summary is the control that opens it — thumb-sized. */
  .est-worklist>summary,.est-doc>summary{min-height:48px;padding:12px}
}
/* ==========================================================================
   Work tasks — several people on one task, phases above them, and checkpoints
   that are actually asked.
   ========================================================================== */

/* Picking the people responsible. A <select multiple> is unusable with a thumb
   and invisible as a concept ("hold ctrl"); a checkbox list says plainly that
   more than one name is allowed and gives every name a 44 px target. */
/* The chosen people are tinted, so a glance answers "who is on this" without
   reading every checkbox. Blue, not orange: orange is the navigation. */
@media (max-width:900px){
}

/* Checkpoint progress, wherever the hand-in button is. Amber while something
   is still open, green when everything has been answered — the same two
   meanings the rest of the application gives those colours. */
.chk-count{
  display:inline-flex;align-items:center;min-height:22px;padding:2px 7px;
  font-variant-numeric:tabular-nums;font-size:0.7188rem;border:1px solid transparent;
}
.chk-count--open{background:var(--amber-tint);border-color:var(--amber-edge);color:var(--amber)}
.chk-count--done{background:var(--green-tint);border-color:var(--green-edge);color:var(--green)}

.task-card__checks{
  display:flex;align-items:center;gap:7px;padding:7px 9px;font-size:0.7188rem;
  background:var(--green-tint);border-left:3px solid var(--green);color:var(--ink);
}
.task-card__checks .ic{flex:none;color:var(--green)}
.task-card__checks--open{background:var(--amber-tint);border-left-color:var(--amber)}
.task-card__checks--open .ic{color:var(--amber)}

/* What the row tints mean, said on the page. The list has been colour-coded
   from the start and nothing ever named the colours, so the tint was decoration
   for anyone who had not been told. Each swatch carries its own label, so the
   colour never has to be understood on its own. */
.task-legend{
  display:flex;flex-wrap:wrap;align-items:center;gap:6px 10px;
  padding:8px var(--pad);border-bottom:1px solid var(--grid);
  background:var(--head);font-size:0.7188rem;
}
.task-legend__k{color:var(--ink-2);font-weight:600}
.task-legend__i{
  display:inline-flex;align-items:center;min-height:22px;padding:2px 8px;
  border:1px solid var(--grid-strong);background:var(--bg);color:var(--ink);
}
.task-legend__i--ok{background:var(--green-tint);border-color:var(--green-edge)}
.task-legend__i--info{background:var(--blue-tint);border-color:var(--blue-edge)}
.task-legend__i--warn{background:var(--amber-tint);border-color:var(--amber-edge)}
.task-legend__i--risk{background:var(--red-tint);border-color:var(--red-edge)}
.task-legend__i--idle{background:var(--head);border-color:var(--grid-strong);color:var(--ink-2)}

/* The reviewer's evidence. Approve and Reject used to sit above the whole
   thirteen-field planning form, with the checkpoints somewhere below it — a
   decision asked for without showing what was decided. */
.review-bar{display:grid;border:1px solid var(--grid-strong);border-left:3px solid var(--amber);background:var(--bg)}
.review-bar__checks{list-style:none;display:grid;margin:0;padding:8px var(--pad)}
.review-bar__check{
  display:flex;align-items:flex-start;gap:8px;padding:5px 0;font-size:0.7813rem;
  border-bottom:1px solid var(--line-soft);
}
.review-bar__check:last-child{border-bottom:0}
.review-bar__check .ic{flex:none;margin-top:2px;color:var(--green)}
.review-bar__check--open{color:var(--ink-2)}
.review-bar__check--open .ic{color:var(--amber)}
.review-bar__warn{
  padding:7px var(--pad);background:var(--amber-tint);
  border-top:1px solid var(--amber-edge);font-size:0.7188rem;color:var(--amber);
}
@media (max-width:900px){
  .review-bar__check{padding:9px 0}
}

/* A sentence under a group of fields explaining the group, as opposed to
   .frow__hint which belongs to one field. */
.form-hint{padding:6px 0 10px;font-size:0.7813rem;color:var(--ink-2)}
.form-hint a{margin-left:6px}

/* Two-row navigation: the kind of setting, then the pages of that kind.
   The group row reads as the heading it is; the page row is the ordinary
   tab strip used everywhere else in the application. */
.tabs--groups{background:var(--head);border-bottom:1px solid var(--grid-strong)}
.tabs--groups a{font-weight:600}
.tabs--sub{background:var(--bg)}
.tabs--sub a{font-size:0.7813rem}
/* ==========================================================================
   Documents — the folder structure on screen, and files that can be dropped
   where they belong.

   The module was a flat list with a folder filter, so "where does this go"
   had no answer anywhere in the interface. The tree on the left is the same
   in every project (App\Support\DocumentFolders), empty folders included,
   and every folder that can hold a file is also a drop target.
   ========================================================================== */
.doc-browse{
  position:relative;display:grid;grid-template-columns:minmax(230px,290px) minmax(0,1fr);
  flex:1 1 auto;min-height:0;border-top:1px solid var(--grid);
}

/* The structure -------------------------------------------------------- */
.doc-tree{
  display:flex;flex-direction:column;min-width:0;overflow-y:auto;
  border-right:1px solid var(--grid);background:var(--paper);
}
.doc-tree__head{padding:10px var(--pad);border-bottom:1px solid var(--grid);background:var(--head)}
.doc-tree__head b{font-size:0.6875rem;text-transform:uppercase;letter-spacing:.06em;color:var(--ink-2)}
.doc-tree__head p{margin-top:4px;font-size:0.6875rem;color:var(--ink-2);line-height:1.45}
.doc-tree__row{
  display:flex;align-items:center;gap:8px;min-height:34px;padding:5px 10px;
  border-bottom:1px solid var(--line-soft);color:inherit;text-decoration:none;
  font-size:0.7813rem;cursor:pointer;
}
.doc-tree__row:hover{background:var(--head)}
.doc-tree__row .ic{flex:none;color:var(--ink-2)}
.doc-tree__label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* The count is the honest answer to "is there anything in here" — and the
   word "leer" is there so an empty folder does not look like a broken one. */
.doc-tree__count{flex:none;font-size:0.6875rem;color:var(--ink-2);font-variant-numeric:tabular-nums}
.doc-tree__hint{padding:0 10px 8px 34px;margin-top:-1px;font-size:0.6875rem;color:var(--ink-2);line-height:1.45}

/* The open folder. Blue with a bar on the left and aria-current on the
   element — never the background alone, and never the orange: orange is the
   navigation, and "which folder am I in" is page content. */
.doc-tree__row--on{background:var(--blue-tint);box-shadow:inset 3px 0 0 var(--blue);font-weight:600}
.doc-tree__row--child{padding-left:26px}
.doc-tree__row--group{list-style:none;font-weight:600;background:var(--bg)}
.doc-tree__row--group::-webkit-details-marker{display:none}
/* The disclosure triangle is drawn here so it points at the phase number and
   turns when the group opens. */
.doc-tree__row--group::before{
  content:'';flex:none;width:0;height:0;margin-right:-2px;
  border-left:5px solid var(--ink-2);border-top:4px solid transparent;border-bottom:4px solid transparent;
  transition:transform .12s ease-out;
}
.doc-tree__group[open]>.doc-tree__row--group::before{transform:rotate(90deg)}
@media (prefers-reduced-motion:reduce){.doc-tree__row--group::before{transition:none}}
/* The phase number, in the phase colour the schedule and the calendar use for
   the same phase. It is a repetition of the label, never its replacement. */
.doc-tree__num{
  flex:none;display:inline-flex;align-items:center;justify-content:center;
  min-width:22px;height:18px;padding:0 5px;background:var(--stage-8);color:#fff;
  font-size:0.6875rem;font-weight:600;font-variant-numeric:tabular-nums;
}
.doc-tree__num[data-stage="1"]{background:var(--stage-1)}
.doc-tree__num[data-stage="2"]{background:var(--stage-2)}
.doc-tree__num[data-stage="3"]{background:var(--stage-3)}
.doc-tree__num[data-stage="4"]{background:var(--stage-4)}
.doc-tree__num[data-stage="5"]{background:var(--stage-5)}
.doc-tree__num[data-stage="6"]{background:var(--stage-6)}
.doc-tree__num[data-stage="7"]{background:var(--stage-7)}
.doc-tree__num[data-stage="8"]{background:var(--stage-8)}

/* The files of the open folder ----------------------------------------- */
.doc-files{display:flex;flex-direction:column;min-width:0;min-height:0}
.doc-crumb{
  display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  padding:8px var(--pad);border-bottom:1px solid var(--grid);background:var(--head);
}
.doc-crumb__list{display:flex;align-items:center;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none;font-size:0.7813rem}
.doc-crumb__list li+li::before{content:'›';margin-right:6px;color:var(--ink-3)}
.doc-crumb__list a{color:var(--link)}
.doc-crumb__list b{font-weight:600}

/* The drop target ------------------------------------------------------ */
.doc-drop{
  display:flex;align-items:center;gap:12px;flex-wrap:wrap;
  margin:10px var(--pad);padding:12px 14px;
  border:2px dashed var(--grid-strong);background:var(--paper);
}
.doc-drop__icon{flex:none;color:var(--ink-2)}
.doc-drop__text{display:grid;gap:2px;flex:1 1 220px;min-width:0;font-size:0.75rem}
.doc-drop__text b{font-size:0.8125rem}
.doc-drop__target{color:var(--ink)}
.doc-drop__hint{color:var(--ink-2)}
.doc-drop__act{display:flex;align-items:center;gap:7px;flex-wrap:wrap}
/* A <label> pointing at the file input: the input itself is unstyleable
   across browsers, and it must stay a real input for the keyboard and the
   screen reader. It is only moved out of sight when the label replaces it. */
.doc-drop__label{
  min-height:28px;padding:0 12px;border:1px solid var(--orange-edge);background:var(--orange);
  font-size:0.7813rem;font-weight:600;display:inline-flex;align-items:center;gap:7px;
  color:var(--ink);cursor:pointer;
}
.doc-drop__label:hover{background:#FFB733;border-color:var(--orange-deep)}
.doc-drop__input{font-size:0.7813rem;max-width:210px}
.doc-drop__input:focus-visible{outline:2px solid var(--orange-deep);outline-offset:2px}
.doc-drop--js .doc-drop__input{position:absolute;width:1px;height:1px;margin:-1px;padding:0;border:0;overflow:hidden;clip-path:inset(50%)}
/* The keyboard focus of a hidden input has to be visible on its label,
   otherwise tabbing through the page loses the cursor entirely. */
.doc-drop--js .doc-drop__input:focus-visible+.doc-drop__label,
.doc-drop--js .doc-drop__label:has(+.doc-drop__input:focus-visible){outline:2px solid var(--orange-deep);outline-offset:2px}

/* Something is being dragged over the page or over this box. Green means
   "release and it happens", and the dashed border becomes solid so the state
   does not rest on colour alone. */
.doc-drop--over,.doc-tree__row--over{background:var(--green-tint)}
.doc-drop--over{border-style:solid;border-color:var(--green)}
.doc-tree__row--over{box-shadow:inset 3px 0 0 var(--green),inset 0 0 0 1px var(--green-edge)}

/* Per file: which one, how far, and what happened -----------------------*/
.doc-uploads{margin:0 var(--pad) 10px;border:1px solid var(--line)}
.doc-uploads__head{
  display:flex;align-items:center;gap:10px;padding:7px 10px;
  border-bottom:1px solid var(--line-soft);background:var(--head);
}
.doc-uploads__head b{font-size:0.6875rem;text-transform:uppercase;letter-spacing:.06em;color:var(--ink-2)}
.doc-uploads__head .btn{margin-left:auto}
.doc-uploads__list{margin:0;padding:0;list-style:none;max-height:220px;overflow-y:auto}
.doc-upload{
  display:grid;grid-template-columns:minmax(0,1fr) auto;gap:2px 10px;
  padding:7px 10px;border-bottom:1px solid var(--line-soft);font-size:0.75rem;
}
.doc-upload:last-child{border-bottom:0}
.doc-upload__name{font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.doc-upload__size{color:var(--ink-2);font-variant-numeric:tabular-nums}
.doc-upload__msg{grid-column:1/-1;color:var(--ink-2);line-height:1.45}
.doc-upload__bar{grid-column:1/-1;width:100%;height:6px;background:var(--grid);overflow:hidden}
.doc-upload__bar span{display:block;height:100%;width:0;background:var(--blue);transition:width .15s linear}
@media (prefers-reduced-motion:reduce){.doc-upload__bar span{transition:none}}
/* The result is a word plus a colour, in that order. A failed upload names
   the file and the reason — "Fehler" for eight photos is not an answer. */
.doc-upload--ok{background:var(--green-tint)}
.doc-upload--ok .doc-upload__msg{color:var(--green)}
.doc-upload--fail{background:var(--red-tint);box-shadow:inset 3px 0 0 var(--red)}
.doc-upload--fail .doc-upload__msg{color:var(--red);font-weight:600}

/* The page-wide drop hint ---------------------------------------------- */
.doc-overlay{
  position:absolute;inset:0;z-index:30;display:flex;align-items:center;justify-content:center;
  background:rgba(46,107,62,.10);border:3px dashed var(--green);pointer-events:none;
}

.doc-overlay__box{
  display:flex;align-items:center;gap:10px;padding:14px 18px;
  background:var(--bg);border:1px solid var(--green);color:var(--green);
  font-size:0.875rem;font-weight:600;text-align:center;
}
.doc-overlay__box .ic{color:var(--green)}

/* A thumb, not a mouse pointer -----------------------------------------
   A 24px tree row is unusable on a phone: the structure becomes a single
   column above the files, and every row is 44px tall. */
@media (max-width:900px){
  .doc-browse{grid-template-columns:1fr}
  .doc-tree{
    border-right:0;border-bottom:1px solid var(--grid);
    max-height:46vh;overflow-y:auto;
  }
  .doc-tree__row{min-height:44px;padding:9px 12px;font-size:0.8125rem}
  .doc-tree__row--child{padding-left:30px}
  .doc-tree__count{font-size:0.75rem}
  .doc-drop{flex-direction:column;align-items:stretch;text-align:left}
  .doc-drop__act{flex-direction:column;align-items:stretch}
  .doc-drop__label,.doc-drop__act .btn{min-height:44px;justify-content:center}
  .doc-uploads__list{max-height:none}
  .doc-upload{padding:10px 12px;font-size:0.8125rem}
}
/* ==========================================================================
   Schedule — the overview is a chart, and the calendar agrees with it.
   ========================================================================== */

/* How much room a row label gets is a property of what the rows are. A trade
   is two words ("Estrich"); a project is a number and a name. The Gantt reads
   --gantt-label, so the page says it once instead of the component guessing.
   It shrinks with the viewport, because on a laptop the label taking 340 px
   is 340 px the timeline does not have. */
/* min-width:0 because the wrapper is a flex item of .view and the chart
   inside it carries a min-width of its own: without it a three-year plan
   pushes the whole page wider than the window instead of scrolling inside
   .gantt, and every other page element inherits that width. */
.sched-chart{--gantt-label:clamp(150px,20vw,260px);min-width:0}
.sched-chart--portfolio{--gantt-label:clamp(170px,26vw,340px)}

/* The row label is two lines, and every row is exactly as tall as the next.

   The label is a flex row 32px high, so the name and the subtitle sat beside
   each other and both wrapped. Once a subtitle says period, duration, who is
   responsible, whether it is critical and how many tasks hang off it, that
   is four wrapped lines spilling out of a 32px box: labels overlapped their
   neighbours, and the dependency arrows — an SVG that divides its height by
   the number of rows — pointed at the gaps between bars instead of at them.

   Name above, subtitle below, one line each, the row tall enough for both.
   What does not fit is cut with an ellipsis rather than pushing the row
   open, because equal rows are what makes the arrows land — the component
   puts the whole of both lines in the label's title, so nothing is lost. */
/* One token, not two. --row is the height of a table row everywhere in this
   stylesheet; overriding it here would also shrink any table inside the
   chart. --gantt-row is the Gantt's own. */
.sched-chart{--gantt-row:42px}
.sched-chart .gantt__label{display:block;overflow:hidden;padding-top:4px;line-height:1.25}
.sched-chart .gantt__label>*{
  display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
/* The head cell is one line and sizes itself; it must not inherit the row. */
.sched-chart .gantt__head .gantt__label{padding-top:6px}

/* A summary row is a chapter heading, not a bar: it stays legible when the
   activities under it are folded away, and its whole label is the control
   that folds them. */
.gantt__group .gantt__label a.rowlink{display:block;width:100%}
.gantt__group .gantt__label .sub{color:var(--ink-2)}
.gantt__group .gantt__row{background:var(--head)}

/* Calendar ---------------------------------------------------------------- */

.calendar-head__n{margin-left:auto;font-size:0.6875rem;color:var(--ink-2)}

/* Room for four entries plus the "+3 weitere" line. The old cell fitted two
   and cut the rest off without saying so. */
.calendar--sched .calendar__day{min-height:138px}
.calendar--sched .calendar__events{gap:2px}

/* An entry wears the colour of its activity — the same --bar the Gantt paints
   its bar with (App\Support\ScheduleColorResolver), so the month and the chart
   are two views of one plan and not two colour schemes. The colour is never
   the message: the trade, the project number, the progress and "kritischer
   Pfad" are all written out. */
.calendar-event--plan{
  display:grid;gap:0;padding:3px 5px;white-space:normal;overflow:hidden;
  border-left:3px solid var(--bar-edge,var(--grid-strong));
  background:color-mix(in srgb,var(--bar,var(--head)) 13%,#fff);
  /* Not underlined at rest. A full month is 230 entries, and 230 underlines
     is a grid of ruled lines with words between them; the tinted block is
     already what says "this is a thing you can open". The underline comes
     back under the pointer and on keyboard focus, where it is the answer to
     "which one am I on". */
  text-decoration:none;
}
.calendar-event--plan:hover,.calendar-event--plan:focus-visible{text-decoration:underline}
@supports not (background:color-mix(in srgb,red 13%,#fff)){
  .calendar-event--plan{background:var(--head)}
}
/* The site number leads the name. It is dimmer than the name because the
   name is what is being read; it is bold because it is what is being
   scanned for. */
.calendar-event__p{font-weight:700;letter-spacing:.02em;color:var(--ink-2)}
.calendar-event__n{
  font-weight:600;line-height:1.3;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.calendar-event__n b{font-weight:700}
.calendar-event__n i{font-style:normal;margin-right:3px;color:var(--bar-edge,var(--ink-2))}
/* 0.6875rem, not the 0.625rem this line started at: 11px is the floor the
   type scale enforces, and a calendar entry is read at arm's length on a
   site office monitor. */
.calendar-event__m{font-size:0.6875rem;color:var(--ink-2);white-space:nowrap}
/* The day an activity begins or ends. Those two are the reason a day is
   worth looking at, so they sort to the top of the cell and carry a thicker
   edge to say why they are there. The word — "Beginn" / "Ende" — is on the
   entry itself; the edge is only how you find it again on the second pass,
   never the only place the fact exists. */
.calendar-event--edge{border-left-width:6px}

/* The critical path is a ring in the Gantt, so it is a ring here too. */
.calendar-event--critical{box-shadow:inset 0 0 0 1px var(--red)}
.calendar-event--plan:hover{background:color-mix(in srgb,var(--bar,var(--head)) 24%,#fff)}

/* A day with more than fits says how many are left and opens on the spot.
   Cutting them off silently is how a Tuesday with six trades on it looked
   like a Tuesday with four. */
.cal-more{margin-top:1px}
.cal-more>summary{
  display:flex;align-items:center;min-height:24px;padding:2px 4px;
  font-size:0.6875rem;font-weight:600;color:var(--ink-2);cursor:pointer;
  border:1px dashed var(--grid-strong);background:var(--zebra);
}
.cal-more[open]>summary{margin-bottom:2px}
.cal-more>summary:hover{color:var(--ink)}
.cal-more>*+*{margin-top:2px}

@media (max-width:700px){
  /* The month on a phone: the days something happens on.

     The rule that turns the grid into a list hides every day with no
     .work-event on it — that is the workforce calendar's entry class, while a
     schedule day carries .calendar-event, so the plan first came out empty.
     The patch that fixed that swung the other way: a multi-day activity draws
     a continuation bar on EVERY day it runs, so all 42 days qualified and the
     month became a 5865px column in which "Innenausbau 30 %" stood thirty
     times underneath itself. You could not see when the screed starts or
     stops — only the same name over and over.

     A day is worth a row when something begins or ends on it
     (.calendar-event--edge, which also carries the words "Beginn"/"Ende"),
     when it is today, or when it is the first cell of the grid — that last
     one is what still shows an activity running from May to November in the
     middle of July. And the 138px cell height had to be reset here: it is
     written .calendar--sched .calendar__day (0,2,0) and beat the list rule's
     .calendar__day{min-height:0} (0,1,0), so every row stayed 138px tall. */
  .calendar--sched .calendar__day{display:grid;min-height:0}
  .calendar--sched .calendar__day:not(:has(.calendar-event--edge)):not(.calendar__day--today):not(.calendar__day--lead){display:none}
  /* An empty lead cell is a row that says nothing — it only earns its place
     while it is the last sight of something that is running. */
  .calendar--sched .calendar__day--lead:not(:has(.calendar-event)):not(.calendar__day--today){display:none}
  .calendar-event--plan{padding:7px 8px;font-size:0.8125rem}
  .calendar-event__n{white-space:normal}
  .calendar-event__m,.calendar-event__p{font-size:0.75rem}
  /* Thumb-sized, like everything else that is tapped. */
  .cal-more>summary{min-height:44px;padding:8px 10px;font-size:0.875rem}
  /* A wide label column on a 375px screen leaves no timeline at all: 132px of
     375 is more than a third of the window, and what was left of the chart was
     21% of an 18-month plan. 96px is two words, and the label column is
     sticky, so the row you are reading keeps its name while the timeline
     scrolls under it — the edge says where the names end. The whole of both
     lines is in the label's title attribute (x-hbs.gantt), so an abbreviated
     name is never the only copy. */
  .sched-chart,.sched-chart--portfolio{--gantt-label:96px;--gantt-row:48px}
  .gantt__label{box-shadow:1px 0 0 var(--grid-strong)}
}
/* ==========================================================================
   Forms — multiple choice as checkboxes, and saying where a list is kept.
   ========================================================================== */

/* A group of checkboxes instead of <select multiple>.
   The old control needed ctrl-click for a second entry, showed three rows of a
   twenty-entry list and was close to unusable with a thumb — on fields where
   picking several is the entire point. The boxes say plainly that more than
   one is allowed, and every entry gets a target a finger can hit.
   Ticks stay the green the stylesheet gives every checkbox; nothing here
   touches accent-color. */
.cgroup{display:grid;gap:8px;margin:0;padding:0;border:0;min-width:0}
.cgroup__bar{display:flex;flex-wrap:wrap;align-items:center;gap:8px}
.cgroup__filter{max-width:280px;flex:1 1 180px}
/* The count is the answer to "did that click do anything" on a list long
   enough to scroll. Tabular figures so it does not jitter while counting. */
.cgroup__count{
  display:inline-flex;align-items:center;min-height:22px;padding:2px 7px;
  font-size:0.7188rem;font-variant-numeric:tabular-nums;color:var(--ink-2);
  background:var(--head);border:1px solid var(--grid);
}
.cgroup__acts{display:inline-flex;gap:6px;margin-left:auto}
.cgroup__act{font-size:0.7188rem}
.cgroup__list{
  display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));
  /* min-content, not the default auto: with a capped height the browser
     divides that definite height across auto rows, so a two-line entry was
     cut to one row height and printed on top of the entry below it. */
  grid-auto-rows:min-content;
  gap:2px;max-height:280px;overflow-y:auto;
  border:1px solid var(--control-line);background:var(--bg);
}
/* The box, then the entry, then what it belongs to underneath it. Side by
   side they fought over the width: a defect called "Fensterbänke aufmessen"
   on "Sonnenhalde 14" wrapped into a two-word column and collided with the
   site name. Stacked, both stay readable however long either one is. */
.cgroup__one{
  display:grid;grid-template-columns:auto 1fr;align-items:center;gap:1px 9px;
  min-height:38px;padding:6px 10px;
  border-bottom:1px solid var(--line-soft);cursor:pointer;font-size:0.7813rem;
}
.cgroup__one:hover{background:var(--head)}
.cgroup__one input{grid-row:1;width:17px;height:17px}
.cgroup__label{grid-column:2;min-width:0;overflow-wrap:anywhere}
.cgroup__sub{grid-column:2;min-width:0;overflow-wrap:anywhere}
/* What is ticked is tinted, so a glance answers "what is on this" without
   reading every box. The inset bar carries the same meaning as the tint,
   because a 1.1:1 background is not a state a low-vision reader can see. */
.cgroup__one:has(input:checked){background:var(--green-tint);box-shadow:inset 3px 0 0 var(--green)}
/* The box keeps the application's own focus ring — a negative outline offset
   on the row would draw over the fill instead of outside it. The row lifts as
   well, so the eye finds the focused entry in a two-column grid; the ring, not
   the tint, is what carries the state. */
.cgroup__one:has(input:focus-visible){background:var(--blue-tint)}
.cgroup__empty{margin:0;padding:8px 10px;font-size:0.7188rem;color:var(--ink-2);border:1px solid var(--grid);background:var(--head)}
/* The filter, the count and the all/none buttons are chrome the script owns.
   They stay hidden until it runs, so a browser without JavaScript never shows
   a search box that does not search. The rule has to be here: a class that
   sets display would otherwise win over the hidden attribute. */
.cgroup [hidden]{display:none}
@media (max-width:900px){
  .cgroup__list{grid-template-columns:1fr;max-height:none}
  .cgroup__one{min-height:44px;padding:9px 12px}
  .cgroup__one input{width:20px;height:20px}
  .cgroup__acts{margin-left:0}
  .cgroup__act{min-height:44px}
  .cgroup__filter{flex:1 1 100%;max-width:none}
}

/* "This list belongs to your company, and it is edited there."
   A hint under a field, not a call to action: the size and grey of every
   other hint, and never orange — orange is the brand and the navigation. */
.list-hint{
  display:flex;align-items:flex-start;gap:6px;width:100%;
  margin-top:2px;font-size:0.7188rem;line-height:1.45;color:var(--ink-2);
}
.list-hint .ic{flex:none;width:12px;height:12px;margin-top:2px;color:var(--ink-3)}
.list-hint__link{color:var(--blue);text-decoration:underline}
.list-hint__link:hover{color:var(--ink)}
@media (max-width:700px){
  /* The link stays in the sentence. WCAG's target-size rules exempt a link
     inside a line of text for exactly this reason, and breaking it onto its
     own 44px line turned one quiet hint into two objects stacked under the
     field — which reads as something to deal with rather than something to
     glance at. The row gets the height instead. */
  .list-hint{min-height:36px;align-items:center;font-size:0.8125rem}
  .list-hint__link{padding-block:6px}
}

/* Settings › Master data & lists › Pick lists — the five company-owned
   domains that until now could be seeded but never edited. */
/* One section per domain, separated by the same hairline the rest of the
   application separates blocks with. */
.vlist{border-bottom:1px solid var(--grid)}
.vlist:last-of-type{border-bottom:0}
.vlist__intro{margin:0;padding:10px var(--pad);font-size:0.7813rem;color:var(--ink-2);border-bottom:1px solid var(--grid)}
.vlist__jump{
  display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;
  padding:8px var(--pad);border-bottom:1px solid var(--grid);
  background:var(--head);font-size:0.7188rem;
}
.vlist__jump-k{color:var(--ink-2);font-weight:600}
.vlist__jump a{color:var(--ink);min-height:22px;display:inline-flex;align-items:center}
.vlist__used{margin:0;padding:6px var(--pad);font-size:0.7188rem;color:var(--ink-2)}
.vlist__rows{list-style:none;margin:0;padding:0 var(--pad)}
.vlist__row{display:flex;flex-wrap:wrap;align-items:center;gap:6px;padding:5px 0;border-bottom:1px solid var(--line-soft)}
.vlist__row .field{flex:1 1 220px;min-width:0}
.vlist__add{display:flex;flex-wrap:wrap;gap:6px;padding:10px var(--pad) 16px}
.vlist__add .field{flex:1 1 220px;max-width:340px}
@media (max-width:700px){
  .vlist__jump a{min-height:44px}
  .vlist__row{padding:8px 0}
  .vlist__row .field,.vlist__add .field{flex:1 1 100%;max-width:none}
  .vlist__row .btn,.vlist__add .btn{min-height:44px}
}

/* ==========================================================================
   The plan of a project template — one nested list, not two side by side.

   Phases were drawn on the left and tasks on the right. The standard template
   has one task per phase, so that was the same six names printed twice with
   nothing on screen saying how they related. A task belongs to a phase, so it
   is drawn inside it, indented, with its own way in at the end.
   ========================================================================== */
.plan-tree{display:grid;gap:0}
.plan-phase{border-bottom:1px solid var(--grid-strong)}
.plan-phase__head>summary{
  display:flex;align-items:center;gap:12px;flex-wrap:wrap;
  padding:12px var(--pad);cursor:pointer;background:var(--head);list-style:none;
}
.plan-phase__head>summary::-webkit-details-marker{display:none}
/* The disclosure triangle, drawn rather than inherited, so it can turn. */
.plan-phase__head>summary::before{
  content:"";flex:none;width:0;height:0;
  border-left:6px solid var(--ink-2);border-top:4px solid transparent;border-bottom:4px solid transparent;
  transition:transform .12s;
}
.plan-phase__head[open]>summary::before{transform:rotate(90deg)}
.plan-phase__head>summary:hover{background:var(--sel)}
.plan-phase__title{display:grid;gap:1px;min-width:0;flex:1}
.plan-phase__title b{font-size:0.875rem}
.plan-phase__title small{font-size:0.7188rem;color:var(--ink-2)}
.plan-phase__meta{display:flex;gap:6px;flex-wrap:wrap}
.plan-phase__orphan-note{padding:10px var(--pad);font-size:0.7813rem;color:var(--ink-2);background:var(--amber-tint)}

/* Indented under their phase, with a rule showing what they hang off. */
.plan-tasks{display:grid;gap:0;padding-left:var(--pad);border-left:3px solid var(--grid-strong);margin-left:var(--pad)}
.plan-task{border-bottom:1px solid var(--grid)}
.plan-task:last-of-type{border-bottom:0}
.plan-task>summary{
  display:flex;align-items:center;gap:12px;flex-wrap:wrap;
  padding:9px var(--pad) 9px 0;cursor:pointer;list-style:none;
}
.plan-task>summary::-webkit-details-marker{display:none}
.plan-task>summary::before{
  content:"";flex:none;width:0;height:0;
  border-left:5px solid var(--ink-3);border-top:3.5px solid transparent;border-bottom:3.5px solid transparent;
  transition:transform .12s;
}
.plan-task[open]>summary::before{transform:rotate(90deg)}
.plan-task>summary:hover{background:var(--zebra)}
.plan-task__title{display:grid;gap:1px;min-width:0;flex:1}
.plan-task__title b{font-weight:600}
.plan-task__title small{font-size:0.7188rem;color:var(--ink-2)}
.plan-task__meta{display:flex;gap:6px;flex-wrap:wrap}

/* The way in, at the end of the list it adds to — because that is where you
   are looking when you decide you need one more. */
.plan-add>summary{
  display:flex;align-items:center;justify-content:center;gap:8px;
  min-height:48px;padding:10px var(--pad);cursor:pointer;list-style:none;
  border:2px dashed var(--grid-strong);color:var(--ink-2);
  font-size:0.875rem;font-weight:600;background:var(--bg);
}
.plan-add>summary::-webkit-details-marker{display:none}
.plan-add>summary:hover{border-color:var(--orange-edge);background:var(--sel);color:var(--ink)}
.plan-add[open]>summary{border-style:solid;border-color:var(--orange-edge);background:var(--sel);color:var(--ink)}
.plan-add>summary .ic{stroke-width:2}
.plan-add--task>summary{margin:6px 0 8px;min-height:44px;font-weight:400}
.plan-add--phase{margin-top:12px}
.plan-add--phase>summary{min-height:60px;font-size:0.9375rem}
.plan-add--phase>summary .ic{width:24px;height:24px}

@media (max-width:860px){
  .plan-tasks{margin-left:8px;padding-left:10px}
  /* The badges go under the name instead of beside it. Side by side they
     have about 90px on a 375px screen, so "Sicherheitsunterweisung" and
     "1 task" ended up printed through each other. */
  .plan-phase__head>summary,.plan-task>summary{
    padding-block:12px;display:grid;grid-template-columns:auto minmax(0,1fr);
    align-items:start;row-gap:6px;
  }
  .plan-phase__head>summary::before,.plan-task>summary::before{margin-top:6px}
  .plan-phase__meta,.plan-task__meta{grid-column:2}
  .plan-add--phase>summary{min-height:64px}
}
/* ==========================================================================
   Dauern schätzen — the duration proposals in the project wizard and on a
   project template.

   Two rules shaped everything below. First: a builder decides between nine
   days and twelve by looking, not by reading a table, so the comparison is
   two bars of different length with the numbers written next to them. Second:
   a proposal is not a change — so it never looks like the field it sits next
   to, and the only control that changes anything is a checkbox that says
   "Übernehmen".

   Colour follows the house palette: blue for what the company's own finished
   sites say, violet for what a machine guessed. Neither colour ever carries
   the message on its own — every bar, every dot and every badge has its words
   beside it.
   ========================================================================== */

/* The panel that offers the two sources ----------------------------------- */

.dest{
  margin:10px var(--pad);padding:12px 14px;
  border:1px solid var(--blue-edge);background:var(--blue-tint);
}
.dest__title{
  display:flex;align-items:center;gap:8px;
  font-size:0.875rem;font-weight:600;color:var(--blue);
}
.dest__title .ic{stroke:var(--blue)}
.dest__intro{margin-top:6px;max-width:74ch;font-size:0.75rem;line-height:1.5;color:var(--ink)}

/* The two ways to get a number, each with the sentence that says when it is
   the right one. They sit side by side on a desk and stack on a phone. */
.dest__run{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;margin-top:10px}
.dest__pick{display:flex;flex-direction:column;gap:4px;align-items:flex-start}
.dest__btn{min-height:44px;background:var(--bg)}
.dest__btn[disabled]{opacity:0.55;cursor:not-allowed}
.dest__pick-hint{font-size:0.6875rem;line-height:1.45;color:var(--ink-2)}
.dest__pick-hint a{color:var(--link)}

/* What came back, and what to do with it. */
.dest__apply{margin-top:12px;padding-top:10px;border-top:1px solid var(--blue-edge)}
.dest__result{display:flex;flex-wrap:wrap;gap:4px 10px;align-items:baseline;font-size:0.75rem}
.dest__result b{color:var(--blue)}
.dest__warn{
  display:flex;gap:8px;align-items:flex-start;margin-top:8px;padding:7px 9px;
  border:1px solid var(--violet-edge);background:var(--bg);
  font-size:0.6875rem;line-height:1.45;color:var(--ink);
}
.dest__warn .ic{stroke:var(--violet);flex:none}
.dest__acts{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}
.dest__acts .btn{min-height:44px}
.dest__hint{margin-top:8px;font-size:0.6875rem;color:var(--ink-2)}

/* One proposal, beside one duration --------------------------------------- */

/* The table cell holds the number that counts and the proposal under it. The
   width is deliberate: a proposal squeezed into 120 px becomes six wrapped
   lines, and the two bars stop being comparable at a glance. */
.wizard-duration{vertical-align:top}
.wizard-duration .field--table{max-width:110px}
/* A fixed width, because a table column sizes itself to its longest line: the
   sentence that says where the number comes from would otherwise stretch the
   cell across half the screen and push the two bars out of view. And a
   sentence wraps — a table cell is nowrap by default, which cut the evidence
   off mid-word at the edge of the box. */
.wizard-duration .dprop,.wizard-duration .dprop--none{width:250px;max-width:100%}
.wizard-duration .dprop,.wizard-duration .dprop--none,
.wizard-duration .dprop *{white-space:normal}
.wizard-duration .dprop__k,.wizard-duration .dprop__v{white-space:nowrap}

.dprop{
  margin-top:8px;padding:8px 9px;
  border:1px solid var(--grid-strong);border-left:3px solid var(--blue);
  background:var(--bg);
  font-size:0.6875rem;line-height:1.45;color:var(--ink);
}
.dprop--ai{border-left-color:var(--violet)}
.dprop__head{display:flex;flex-wrap:wrap;gap:4px 8px;align-items:baseline}
.dprop__badge{
  padding:1px 6px;border:1px solid var(--blue-edge);background:var(--blue-tint);
  font-size:0.6875rem;font-weight:600;color:var(--blue);
}
.dprop--ai .dprop__badge{border-color:var(--violet-edge);background:var(--violet-tint);color:var(--violet)}
.dprop__days{font-size:0.875rem;font-weight:600;font-variant-numeric:tabular-nums}

/* The comparison. Label, bar, number — twice, so the eye reads two lengths
   and the numbers are still there for the contract. */
.dprop__bars{
  display:grid;grid-template-columns:auto minmax(48px,1fr) auto;
  gap:4px 7px;align-items:center;margin-top:7px;
}
.dprop__k{color:var(--ink-2);white-space:nowrap}
.dprop__v{font-variant-numeric:tabular-nums;white-space:nowrap}
.dprop__track{
  position:relative;display:block;height:12px;
  border:1px solid var(--grid-strong);background:var(--head);
}
.dprop__fill{position:relative;display:block;height:100%}
.dprop__fill--now{background:var(--ink-3)}
.dprop__fill--new{background:var(--blue)}
.dprop--ai .dprop__fill--new{background:var(--violet)}

/* The band is where the finished jobs actually landed — "meist 8 bis 11" as
   a shape rather than a second sentence. It sits behind the bar, so a
   proposal at the edge of its own range is visible as such. */
.dprop__band{
  position:absolute;top:0;bottom:0;
  border-left:1px solid var(--blue-edge);border-right:1px solid var(--blue-edge);
  background:var(--blue-tint);
}
.dprop--ai .dprop__band{border-color:var(--violet-edge);background:var(--violet-tint)}

.dprop__delta{display:flex;align-items:center;gap:5px;margin-top:6px;font-weight:600}
.dprop__delta .ic{stroke:var(--ink-2);flex:none}
.dprop__basis,.dprop__planned{margin-top:5px;color:var(--ink-2)}

/* How much the number can be leaned on: three marks and the words for them.
   The marks alone would be colour and count carrying meaning; the words are
   what actually says it. */
.dprop__sure{display:flex;align-items:center;gap:6px;margin-top:6px;color:var(--ink-2)}
.dprop__dots{display:inline-flex;gap:3px;flex:none}
.dprop__dot{
  display:inline-block;width:8px;height:8px;
  border:1px solid var(--control-line);background:var(--bg);
}
.dprop__dot--on{background:var(--blue);border-color:var(--blue)}
.dprop--ai .dprop__dot--on{background:var(--violet);border-color:var(--violet)}

/* The only control here that changes anything. Full width and 44 px high, so
   it can be hit with a thumb in a glove on a phone. */
.dprop__take{
  display:flex;align-items:center;min-height:44px;margin-top:6px;padding:0 4px;
  border-top:1px solid var(--line-soft);font-weight:600;
}
.dprop__take input{width:20px;height:20px;accent-color:var(--blue)}
.dprop--ai .dprop__take input{accent-color:var(--violet)}
.dprop__take:hover{background:var(--sel)}
.dprop__take:focus-within{outline:2px solid var(--blue);outline-offset:1px}

/* No number is an answer too, and it looks like a note rather than a bar. */
.dprop--none{
  display:flex;gap:7px;align-items:flex-start;margin-top:8px;padding:7px 9px;
  border:1px solid var(--grid-strong);border-left:3px solid var(--ink-3);
  background:var(--paper);font-size:0.6875rem;line-height:1.45;color:var(--ink-2);
}
.dprop--none .ic{stroke:var(--ink-3);flex:none}

/* On a template the proposal is not in a table but under the task it belongs
   to, one column, full width of the list. */
.plan-tasks>.dprop,.plan-tasks>.dprop--none{margin:0 0 8px 0}

@media (max-width:700px){
  /* One source per line: two 44 px buttons side by side on a 375 px screen
     leave no room for the sentence that says which one to press. */
  .dest__run{grid-template-columns:1fr}
  .dest__btn{width:100%}
  .dest__acts .btn{flex:1 1 100%}
  .dprop__bars{grid-template-columns:auto minmax(40px,1fr) auto;gap:3px 5px}
  /* On a phone the table row is a card and every cell is a two-column grid:
     the label on the left, the value on the right. The proposal is the third
     item in that grid, so it landed in the label column — 120 px wide, with
     the two bars and the sentence squeezed into it. The duration cell gets
     one column instead, and the proposal the full width of the card. */
  .sheet td.wizard-duration{grid-template-columns:1fr}
  .sheet td.wizard-duration .dprop,.sheet td.wizard-duration .dprop--none{width:100%}
  .sheet td.wizard-duration .field--table{max-width:none}
}

/* ==========================================================================
   The upload area — one for the whole application.

   Eight forms used to show the browser's own control: an unstyleable grey
   button, the words "Keine Datei ausgewählt", no limit, no drop target, and
   no way to take a file back off once it was picked. This is the documents
   drop zone, generalised (resources/views/components/hbs/file-field.blade.php),
   and it is now the only upload control in the product.

   The native input is still there. It lies transparent on top of its own
   label, so the mouse hits one target, the keyboard reaches a real file input
   and the browser's own "required" bubble points at the button a person can
   see. It is never display:none — that would take it off the keyboard.
   ========================================================================== */
.ff{display:grid;gap:0}
/* Where the area is not inside a form row but sits in a page band of its own
   (the documents list), it keeps the page's own gutter. */
.ff-inset{display:block;margin:10px var(--pad)}

.ff__zone{
  display:flex;align-items:center;gap:12px;flex-wrap:wrap;
  padding:12px 14px;border:2px dashed var(--grid-strong);background:var(--paper);
}
.ff__icon{flex:none;color:var(--ink-2)}
.ff__text{display:grid;gap:2px;flex:1 1 220px;min-width:0;font-size:0.75rem}
.ff__title{font-size:0.8125rem}
.ff__sub{color:var(--ink)}
.ff__hint{color:var(--ink-2);line-height:1.45}
.ff__act{display:flex;align-items:center;gap:7px;flex-wrap:wrap}

/* Button and input are one object: the input covers the label exactly. */
.ff__pick{position:relative;display:inline-flex}
.ff__input{
  position:absolute;inset:0;width:100%;height:100%;
  opacity:0;cursor:pointer;font-size:0.7813rem;
}
.ff__btn{
  min-height:30px;padding:0 12px;border:1px solid var(--orange-edge);background:var(--orange);
  font-size:0.7813rem;font-weight:600;display:inline-flex;align-items:center;gap:7px;
  color:var(--ink);cursor:pointer;
}
/* The input is on top, so the label never sees a hover of its own. */
.ff__pick:hover .ff__btn{background:#FFB733;border-color:var(--orange-deep)}
/* Keyboard focus lives on the input and has to be drawn on the button,
   otherwise tabbing through the form loses the cursor completely. */
.ff__input:focus-visible+.ff__btn{outline:2px solid var(--orange-deep);outline-offset:2px}

/* Something is being dragged over the area. Green plus a solid border: the
   state never rests on colour alone. */
.ff__zone--over{background:var(--green-tint);border-style:solid;border-color:var(--green)}
/* A row that takes the file directly — a folder in the documents tree. */
.ff-target--over{background:var(--green-tint);box-shadow:inset 3px 0 0 var(--green),inset 0 0 0 1px var(--green-edge)}

/* Refused before it is sent: the error belongs to the area, not to a dialog. */
.ff--err .ff__zone{border-color:var(--red)}

/* What is already on file ---------------------------------------------- */
.ff__current{
  display:flex;align-items:center;gap:8px;flex-wrap:wrap;
  padding:7px 12px;border:1px solid var(--line);border-top:0;background:var(--head);
  font-size:0.75rem;
}
.ff__current-k{font-size:0.6875rem;text-transform:uppercase;letter-spacing:.06em;color:var(--ink-2)}
.ff__current-v{font-weight:600;color:var(--link);overflow:hidden;text-overflow:ellipsis}
.ff__current-hint{color:var(--ink-2)}
/* A picture answers "what is on file" faster than its file name does. */
.ff__current .avatar{width:44px;height:44px;flex:0 0 44px;font-size:0.875rem}
.ff__current img{max-height:56px;width:auto}

/* One line per file ----------------------------------------------------- */
.ff__list{margin:0;padding:0;list-style:none;border:1px solid var(--line);border-top:0}
.ff__item{
  display:grid;grid-template-columns:minmax(0,1fr) auto auto;gap:2px 10px;align-items:center;
  padding:7px 10px;border-bottom:1px solid var(--line-soft);font-size:0.75rem;
}
.ff__item:last-child{border-bottom:0}
.ff__name{font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ff__size{color:var(--ink-2);font-variant-numeric:tabular-nums}
.ff__msg{grid-column:1/-1;color:var(--ink-2);line-height:1.45}
.ff__bar{grid-column:1/-1;width:100%;height:6px;background:var(--grid);overflow:hidden}
.ff__bar span{display:block;height:100%;width:0;background:var(--blue);transition:width .15s linear}
@media (prefers-reduced-motion:reduce){.ff__bar span{transition:none}}

/* Taking a file back off before it is sent. An x with nothing but a colour
   would be a guess on a phone, so it carries its own accessible name. */
.ff__drop-one{
  min-width:26px;min-height:26px;display:inline-flex;align-items:center;justify-content:center;
  border:1px solid var(--line);background:var(--bg);color:var(--ink-2);cursor:pointer;
}
.ff__drop-one:hover{border-color:var(--red);color:var(--red)}
.ff__drop-one:focus-visible{outline:2px solid var(--blue);outline-offset:2px}

/* The answer is a word plus a colour, in that order. */
.ff__item--ok{background:var(--green-tint)}
.ff__item--ok .ff__msg{color:var(--green)}
.ff__item--bad{background:var(--red-tint);box-shadow:inset 3px 0 0 var(--red)}
.ff__item--bad .ff__msg{color:var(--red);font-weight:600}

.ff__done{
  display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  padding:7px 10px;border:1px solid var(--line);border-top:0;background:var(--head);
  font-size:0.75rem;
}
.ff__done .btn{margin-left:auto}

/* The page-wide hint ---------------------------------------------------- */
.ff-overlay{
  position:fixed;inset:0;z-index:60;display:flex;align-items:center;justify-content:center;
  background:rgba(46,107,62,.10);border:3px dashed var(--green);pointer-events:none;
}
.ff-overlay__box{
  display:flex;align-items:center;gap:10px;padding:14px 18px;
  background:var(--bg);border:1px solid var(--green);color:var(--green);
  font-size:0.875rem;font-weight:600;text-align:center;
}
.ff-overlay__box .ic{color:var(--green)}

/* A thumb, not a mouse pointer ------------------------------------------ */
@media (max-width:700px){
  .ff__zone{flex-direction:column;align-items:stretch;text-align:left}
  .ff__act{flex-direction:column;align-items:stretch}
  .ff__pick{display:flex}
  .ff__pick>.ff__btn{flex:1}
  .ff__btn,.ff__act .btn{min-height:44px;justify-content:center}
  .ff__item{padding:10px 12px;font-size:0.8125rem}
  .ff__drop-one{min-width:44px;min-height:44px}
}
/* ==========================================================================
   Actions are buttons, not forms parked on a page.

   Everything here exists because a form was taken off a page and put behind a
   button that opens it in a dialog. Three shapes need styling afterwards:
   the square of the qualification matrix, which is now a link; the "one more
   phase" panel, which was a <details> and is now a button; and the cards on
   the user and role screens, which used to be the form itself and now show
   what is set with a button beside it.
   ========================================================================== */

/* The qualification matrix -------------------------------------------------
   Every square is the way in — click Meier under Kranschein and the dialog
   opens with Meier and Kranschein already in it. So the whole cell has to be
   the target, not a word inside it, and it has to be big enough for a thumb
   on a phone (WCAG 2.5.5). The table cell keeps its padding at zero and the
   link takes it over, otherwise there is a dead border around every square. */
td.qmx{padding:0}
.qmx__cell{
  display:flex;flex-direction:column;justify-content:center;gap:2px;
  min-height:44px;padding:7px 14px;
  color:inherit;text-decoration:none;cursor:pointer;
}
.qmx__cell:hover{background:var(--sel)}
/* Focus must be visible on a keyboard, and inside a table an outline is the
   only ring that is not clipped by the neighbouring cell. */
/* The ring stays outside and near-black, like every other control. Drawn
   inside it sits on the cell's own fill, where it measured 1.4:1 on the tinted
   rows — which is the reason this stylesheet has one focus treatment and not
   one per component. --focus was never a token; the fallback was carrying it. */
.qmx__cell:focus-visible{background:var(--sel)}
.qmx__cell .sub{font-size:0.6875rem;color:var(--ink-2)}

/* Adding a phase or a task -------------------------------------------------
   The dashed panel at the end of the list used to be a <summary> that folded
   a form open in place. It is a button now and keeps exactly the same shape,
   because the shape is what says "the list ends here and this is how you
   make it longer". */
.plan-add>.btn{
  display:flex;align-items:center;justify-content:center;gap:8px;width:100%;
  min-height:48px;padding:10px var(--pad);
  border:2px dashed var(--grid-strong);color:var(--ink-2);
  font-size:0.875rem;font-weight:600;background:var(--bg);
}
.plan-add>.btn:hover{border-color:var(--orange-edge);background:var(--sel);color:var(--ink)}
.plan-add>.btn .ic{stroke-width:2}
.plan-add--task>.btn{margin:6px 0 8px;min-height:44px;font-weight:400}
.plan-add--phase>.btn{min-height:60px;font-size:0.9375rem}
.plan-add--phase>.btn .ic{width:24px;height:24px}

/* The colleague card -------------------------------------------------------
   It carried two open forms per person. It now reads as a record: what is
   set, then the buttons that change it. */
.employee-contact-card .kv{grid-template-columns:minmax(90px,40%) 1fr;font-size:0.75rem}
.employee-contact-card .kv dt,.employee-contact-card .kv dd{padding:5px 8px;min-height:28px}
.employee-contact-card footer.bar{display:flex;flex-wrap:wrap;gap:6px;padding:0}

/* A role, read before it is changed --------------------------------------- */
.role-card p.sub{margin:0;color:var(--ink-2);font-size:0.75rem}
.role-card header .color-swatch{width:14px;height:14px;flex:0 0 auto}

@media (max-width:860px){
  /* On a phone the matrix scrolls sideways; the squares must stay thumb-sized
     rather than shrink to fit more columns on the screen. */
  .qmx__cell{min-height:48px}
  .employee-contact-card footer.bar .btn{flex:1 1 100%;justify-content:center}
}

/* ==========================================================================
   The upload area — one for the whole application.

   Eight forms used to show the browser's own control: an unstyleable grey
   button, the words "Keine Datei ausgewählt", no limit, no drop target, and
   no way to take a file back off once it was picked. This is the documents
   drop zone, generalised (resources/views/components/hbs/file-field.blade.php),
   and it is now the only upload control in the product.

   The native input is still there. It lies transparent on top of its own
   label, so the mouse hits one target, the keyboard reaches a real file input
   and the browser's own "required" bubble points at the button a person can
   see. It is never display:none — that would take it off the keyboard.
   ========================================================================== */
.ff{display:grid;gap:0}
/* Where the area is not inside a form row but sits in a page band of its own
   (the documents list), it keeps the page's own gutter. */
.ff-inset{display:block;margin:10px var(--pad)}

.ff__zone{
  display:flex;align-items:center;gap:12px;flex-wrap:wrap;
  padding:12px 14px;border:2px dashed var(--grid-strong);background:var(--paper);
}
.ff__icon{flex:none;color:var(--ink-2)}
.ff__text{display:grid;gap:2px;flex:1 1 220px;min-width:0;font-size:0.75rem}
.ff__title{font-size:0.8125rem}
.ff__sub{color:var(--ink)}
.ff__hint{color:var(--ink-2);line-height:1.45}
.ff__act{display:flex;align-items:center;gap:7px;flex-wrap:wrap}

/* Button and input are one object: the input covers the label exactly. */
.ff__pick{position:relative;display:inline-flex}
.ff__input{
  position:absolute;inset:0;width:100%;height:100%;
  opacity:0;cursor:pointer;font-size:0.7813rem;
}
.ff__btn{
  min-height:30px;padding:0 12px;border:1px solid var(--orange-edge);background:var(--orange);
  font-size:0.7813rem;font-weight:600;display:inline-flex;align-items:center;gap:7px;
  color:var(--ink);cursor:pointer;
}
/* The input is on top, so the label never sees a hover of its own. */
.ff__pick:hover .ff__btn{background:#FFB733;border-color:var(--orange-deep)}
/* Keyboard focus lives on the input and has to be drawn on the button,
   otherwise tabbing through the form loses the cursor completely. */
.ff__input:focus-visible+.ff__btn{outline:2px solid var(--orange-deep);outline-offset:2px}

/* Something is being dragged over the area. Green plus a solid border: the
   state never rests on colour alone. */
.ff__zone--over{background:var(--green-tint);border-style:solid;border-color:var(--green)}
/* A row that takes the file directly — a folder in the documents tree. */
.ff-target--over{background:var(--green-tint);box-shadow:inset 3px 0 0 var(--green),inset 0 0 0 1px var(--green-edge)}

/* Refused before it is sent: the error belongs to the area, not to a dialog. */
.ff--err .ff__zone{border-color:var(--red)}

/* What is already on file ---------------------------------------------- */
.ff__current{
  display:flex;align-items:center;gap:8px;flex-wrap:wrap;
  padding:7px 12px;border:1px solid var(--line);border-top:0;background:var(--head);
  font-size:0.75rem;
}
.ff__current-k{font-size:0.6875rem;text-transform:uppercase;letter-spacing:.06em;color:var(--ink-2)}
.ff__current-v{font-weight:600;color:var(--link);overflow:hidden;text-overflow:ellipsis}
.ff__current-hint{color:var(--ink-2)}
/* A picture answers "what is on file" faster than its file name does. */
.ff__current .avatar{width:44px;height:44px;flex:0 0 44px;font-size:0.875rem}
.ff__current img{max-height:56px;width:auto}

/* One line per file ----------------------------------------------------- */
.ff__list{margin:0;padding:0;list-style:none;border:1px solid var(--line);border-top:0}
.ff__item{
  display:grid;grid-template-columns:minmax(0,1fr) auto auto;gap:2px 10px;align-items:center;
  padding:7px 10px;border-bottom:1px solid var(--line-soft);font-size:0.75rem;
}
.ff__item:last-child{border-bottom:0}
.ff__name{font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ff__size{color:var(--ink-2);font-variant-numeric:tabular-nums}
.ff__msg{grid-column:1/-1;color:var(--ink-2);line-height:1.45}
.ff__bar{grid-column:1/-1;width:100%;height:6px;background:var(--grid);overflow:hidden}
.ff__bar span{display:block;height:100%;width:0;background:var(--blue);transition:width .15s linear}
@media (prefers-reduced-motion:reduce){.ff__bar span{transition:none}}

/* Taking a file back off before it is sent. An x with nothing but a colour
   would be a guess on a phone, so it carries its own accessible name. */
.ff__drop-one{
  min-width:26px;min-height:26px;display:inline-flex;align-items:center;justify-content:center;
  border:1px solid var(--line);background:var(--bg);color:var(--ink-2);cursor:pointer;
}
.ff__drop-one:hover{border-color:var(--red);color:var(--red)}
.ff__drop-one:focus-visible{outline:2px solid var(--blue);outline-offset:2px}

/* The answer is a word plus a colour, in that order. */
.ff__item--ok{background:var(--green-tint)}
.ff__item--ok .ff__msg{color:var(--green)}
.ff__item--bad{background:var(--red-tint);box-shadow:inset 3px 0 0 var(--red)}
.ff__item--bad .ff__msg{color:var(--red);font-weight:600}

.ff__done{
  display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  padding:7px 10px;border:1px solid var(--line);border-top:0;background:var(--head);
  font-size:0.75rem;
}
.ff__done .btn{margin-left:auto}

/* The page-wide hint ---------------------------------------------------- */
.ff-overlay{
  position:fixed;inset:0;z-index:60;display:flex;align-items:center;justify-content:center;
  background:rgba(46,107,62,.10);border:3px dashed var(--green);pointer-events:none;
}
.ff-overlay__box{
  display:flex;align-items:center;gap:10px;padding:14px 18px;
  background:var(--bg);border:1px solid var(--green);color:var(--green);
  font-size:0.875rem;font-weight:600;text-align:center;
}
.ff-overlay__box .ic{color:var(--green)}

/* A thumb, not a mouse pointer ------------------------------------------ */
@media (max-width:700px){
  .ff__zone{flex-direction:column;align-items:stretch;text-align:left}
  .ff__act{flex-direction:column;align-items:stretch}
  .ff__pick{display:flex}
  .ff__pick>.ff__btn{flex:1}
  .ff__btn,.ff__act .btn{min-height:44px;justify-content:center}
  .ff__item{padding:10px 12px;font-size:0.8125rem}
  .ff__drop-one{min-width:44px;min-height:44px}
}
/* ==========================================================================
   Daily report — the figures that fill themselves in.
   Hours out of the day's clocked time, crew out of the clock-ins, weather off
   the site's coordinates. Everything in here is a proposal, so it has to LOOK
   like a proposal: filled in, plainly sourced, and obviously still typeable.
   ========================================================================== */

/* The block that holds the three pre-filled fields. It is set apart from the
   rest of the form because a foreman needs to see at a glance which figures
   arrived on their own and which they still have to write. */
.fillcard{
  margin:14px 0;padding:12px 14px 4px;
  background:#F8F8F6;border:1px solid var(--line);border-radius:10px;
}
.fillcard__head{
  display:flex;align-items:flex-start;gap:10px;flex-wrap:wrap;
  padding-bottom:10px;margin-bottom:8px;border-bottom:1px solid var(--line);
}
.fillcard__head > div{flex:1 1 240px;min-width:0;display:grid;gap:2px}
.fillcard__head b{font-size:0.875rem}
.fillcard__head .sub{font-size:0.75rem;line-height:1.45}
.fillcard__head .ic{flex:none;margin-top:2px;color:var(--muted)}
/* Fetching again is a repeat of what the page did on its own, not the main
   action — a plain button, never the green one that saves. */
.fillcard__again{flex:none;min-height:36px}
.fillcard .frow:last-of-type{border-bottom:0}

/* One pre-filled field: the control, the line saying where its value came
   from, and — only once somebody has typed over it — the hand-edited mark. */
.fillfield{display:grid;gap:5px;min-width:0}
/* A head count is two digits. It gets the width of two digits and a thumb,
   not the width of the weather line next to it. */
.fillfield__num{max-width:110px;min-width:76px}

/* Where the number came from, in one line: "aus 5 Zeiterfassungen".
   Deliberately quiet. It is a receipt, not a message. */
.fillfield__src{
  display:flex;align-items:center;gap:6px;margin:0;
  font-size:0.75rem;color:var(--muted);
}
.fillfield__src .ic{flex:none;width:13px;height:13px;opacity:0.8}

/* "von Hand geändert" — the promise that a second look-up will not undo the
   correction. Marked by a bar and an icon as well as by colour, because a
   report is read on a phone in daylight and colour alone says nothing to
   someone who cannot separate these two. */
.fillfield__mark{
  display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin:0;
  padding:3px 8px;border-left:3px solid var(--orange-deep);
  background:#FFF7E4;border-radius:0 6px 6px 0;
  font-size:0.75rem;color:var(--ink);
}
.fillfield__mark .ic{flex:none;width:13px;height:13px}
.fillfield__reset{min-height:30px;padding:2px 9px;font-size:0.7188rem}

/* The same mark on the saved report. */
.fillmark{
  display:inline-flex;align-items:center;margin-left:6px;padding:1px 7px;
  border:1px solid var(--line);border-left:3px solid var(--orange-deep);
  border-radius:0 6px 6px 0;font-size:0.7188rem;color:var(--muted);
}

/* Why it says 37,5 — who, when, how long. Closed by default: the sum is the
   answer, the breakdown is the proof, and proof is only wanted when the sum
   is doubted. */
.fillbreak{margin:6px 0 0}
.fillbreak > summary{
  display:inline-flex;align-items:center;gap:6px;
  min-height:36px;padding:6px 2px;cursor:pointer;
  font-size:0.75rem;color:var(--muted);
}
.fillbreak > summary:hover{color:var(--ink)}
.fillbreak__list{list-style:none;margin:2px 0 0;padding:0;display:grid;gap:1px}
.fillbreak__list li{
  display:grid;grid-template-columns:minmax(0,1fr) auto auto;align-items:center;
  gap:10px;padding:7px 9px;background:#fff;
  border:1px solid var(--line);border-radius:6px;font-size:0.75rem;
}
.fillbreak__who{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.fillbreak__when{color:var(--muted);font-variant-numeric:tabular-nums;white-space:nowrap}
.fillbreak__h{font-variant-numeric:tabular-nums;font-weight:600;white-space:nowrap}
.fillbreak__sum{
  margin:6px 0 0;font-size:0.75rem;font-weight:600;
  font-variant-numeric:tabular-nums;
}

/* Turning the weather host on, from the page that wants it. Same shape as the
   map's basemap consent so the two read as one decision, made twice. */
.fill-consent{
  margin-top:8px;padding:10px 12px;display:grid;gap:6px;
  background:#fff;border:1px solid var(--line);
  border-left:3px solid var(--blue);border-radius:0 8px 8px 0;
}
.fill-consent b{font-size:0.8125rem}
.fill-consent p{margin:0;font-size:0.75rem;color:var(--muted);line-height:1.5}
.fill-consent__acts{display:flex;flex-wrap:wrap;gap:8px;margin-top:2px}

/* On a phone the block is the first thing under the project and the date, so
   it gets the full width and thumb-sized controls. */
@media (max-width:767px){
  /* margin:12px -2px made this block 379px wide in a 375px window. The page
     then had a horizontal scroll of its own, so the daily-report form — the
     one a Polier fills in standing on site at five in the afternoon — drifted
     sideways under the thumb while it was being scrolled, and the field being
     typed into went off the edge. Nothing may be wider than the viewport. */
  .fillcard{margin:12px 0;padding:12px 12px 4px}
  .fillcard__again{width:100%;min-height:44px;justify-content:center}
  .fillfield__num{max-width:none}
  .fillfield__reset{min-height:44px;padding:6px 14px}
  .fillbreak > summary{min-height:44px}
  /* Name over period: three columns of 40 px each are unreadable, and the
     hours have to stay on the right where the eye adds them up. */
  .fillbreak__list li{grid-template-columns:minmax(0,1fr) auto;gap:2px 10px}
  .fillbreak__who{grid-column:1;grid-row:1}
  .fillbreak__when{grid-column:1;grid-row:2}
  .fillbreak__h{grid-column:2;grid-row:1/3;align-self:center}
  .fill-consent__acts .btn{width:100%;justify-content:center;min-height:44px}
}
/* ==========================================================================
   Phase → Vorgang → Aufgabe: the three levels of a plan, made visible.

   The schedule only ever showed the middle one. A bar said when a trade
   happens; nothing said which chapter it belongs to and nothing said that
   anybody has a job on it — although the tasks were in the database from the
   day the project was created. These rules are what the levels look like on
   the row label, in the table and inside the activity dialog.
   ========================================================================== */

/* The strip at the top of an activity dialog. Three fields, the middle one
   marked as where you are — a breadcrumb that goes nowhere on purpose: it
   explains a structure rather than offering a trip through it. */
.plan-levels{
  display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:1px;
  margin:0;padding:0;list-style:none;
  border:1px solid var(--grid);background:var(--grid);
}
.plan-levels__l{display:flex;flex-direction:column;gap:2px;padding:8px 10px;background:var(--bg);min-width:0}
.plan-levels__k{font-size:0.6875rem;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-2)}
.plan-levels__l b{font-size:0.8125rem;font-weight:600;overflow-wrap:anywhere}
/* Where you are is not a colour: the level is named in the label above it and
   the bar underneath repeats what the arrow could only imply. */
.plan-levels__l--here{background:var(--head);box-shadow:inset 0 -2px 0 var(--orange)}

/* The jobs on one activity. A list, not a table: three facts per row on a
   phone, and the status has to be the first thing read in either layout. */
.acttasks{margin:0;padding:0;list-style:none;border:1px solid var(--grid);border-bottom:0}
.acttasks__t{
  display:grid;grid-template-columns:150px minmax(0,1fr);align-items:start;gap:10px;
  padding:10px;border-bottom:1px solid var(--grid);
}
.acttasks__t:nth-child(2n){background:var(--head)}
.acttasks__s{display:flex;align-items:center;min-height:24px}
.acttasks__b{display:flex;flex-direction:column;gap:2px;min-width:0}
/* 44px of tappable height on a phone without 44px of empty space on a
   desktop: the padding grows with the row, the link keeps its own line. */
.acttasks__b a{display:inline-block;padding:2px 0;overflow-wrap:anywhere}
.acttasks__b .sub{overflow-wrap:anywhere}

/* The link in a section heading — "Alle Aufgaben des Projekts". It sits in
   the heading's right slot and must not shout louder than the heading. */
.sect__link{display:inline-flex;align-items:center;gap:4px;font-size:0.75rem;color:var(--ink)}
.sect__link .ic{width:14px;height:14px}

/* The plan table reads as chapters. A phase row is a heading with data in it,
   so it is heavier and darker than the activities that follow it, and those
   are indented — and the indent is never the only thing saying "this is
   inside that": the phase row states how many activities it holds.

   The selectors carry .sheet because the sticky first column paints its own
   white background (.sheet td.stick), which would otherwise leave the phase
   heading white for exactly the cell that names it. */
.sheet .planrow-phase>th,.sheet .planrow-phase>td{
  background:var(--head);font-weight:600;border-top:1px solid var(--grid-strong);
}
.sheet .planrow-phase>th{text-align:left}
.planrow-phase th .sub{display:block;font-weight:400}
/* The indent is a shadow, not a border or a pseudo-element: the cell is
   position:sticky and painting inside it is the one thing that cannot fight
   with that. */
.sheet .planrow-activity>.stick{padding-left:22px;box-shadow:inset 3px 0 0 var(--grid)}
/* Type, trade and critical path belong under the name, not after it. */
.planrow-activity>.stick .sub{display:block}

@media (max-width:700px){
  /* Three columns in 375px means three unreadable ones. The levels stack, and
     each keeps its label, so the strip still says what each line is. */
  .plan-levels{grid-template-columns:minmax(0,1fr)}
  .plan-levels__l--here{box-shadow:inset 2px 0 0 var(--orange)}
  /* Status above the title rather than beside it: a 150px column and a task
     name cannot both fit, and truncating the name loses the row's subject. */
  .acttasks__t{grid-template-columns:minmax(0,1fr);gap:4px;padding:12px 10px}
  .acttasks__b a{padding:6px 0}
}
/* ==========================================================================
   The clock, and the working-day / holiday calendar.

   Two screens that were hiding: clocking in was the map page with a flag set,
   and the working calendar was a tab plus a folded <details> at the bottom of
   the schedule.
   ========================================================================== */

/* The clock. --------------------------------------------------------------
   Somebody standing on a site in November holds a phone in one hand, with
   gloves half off, and wants to answer one question — is my time running —
   and press one button. So the state, the duration, the job and the button
   are the whole first screen, and everything else is under them. */
.clock{
  display:grid;justify-items:center;gap:10px;
  padding:26px 16px 20px;margin:0 auto 14px;max-width:44rem;
  border:2px solid var(--grid-strong);background:var(--head);
  text-align:center;
}
.clock--on{border-color:var(--green-edge);background:var(--green-tint)}

/* Running or not, said three ways at once — the word, the lamp and the
   colour — because colour alone is not allowed to carry it (1.4.1). */
.clock__state{
  display:flex;align-items:center;gap:9px;margin:0;
  font-size:0.8125rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase;
  color:var(--ink-2);
}
.clock--on .clock__state{color:var(--green)}
.clock__led{
  width:13px;height:13px;flex:none;
  border:2px solid var(--control-line);background:var(--bg);
}
.clock--on .clock__led{
  background:var(--green);border-color:var(--green-edge);
  animation:clock-beat 2s steps(1,end) infinite;
}
@keyframes clock-beat{50%{opacity:.3}}

.clock__time{
  margin:0;font-size:clamp(3.25rem,15vw,6rem);line-height:1;font-weight:600;
  font-variant-numeric:tabular-nums;letter-spacing:-.02em;color:var(--ink);
}
.clock:not(.clock--on) .clock__time{color:var(--ink-3)}

.clock__job{
  display:flex;align-items:center;justify-content:center;gap:8px;margin:2px 0 0;
  font-size:1.125rem;font-weight:600;line-height:1.3;
}
.clock__job .ic{flex:none}
.clock__since{margin:0;font-size:0.8125rem;color:var(--ink-2)}

.clock__form{display:grid;gap:10px;width:100%;max-width:26rem;margin-top:6px}
.clock__pick{font-size:0.75rem;font-weight:600;color:var(--ink-2);text-align:left}
.clock__select{width:100%;min-height:48px;font-size:1rem}
/* One button, thumb-sized: 72px tall and the full width of the card, so it
   can be hit without looking and without a second hand. */
.clock__btn{
  width:100%;min-height:72px;gap:14px;
  font-size:1.25rem;font-weight:600;letter-spacing:.01em;
}
/* The play triangle and the stop square, drawn rather than fetched: the icon
   set is a 1.5px line family and these two want to be solid at this size. */
.clock__glyph{display:block;flex:none}
.clock__glyph--start{
  width:0;height:0;
  border-top:11px solid transparent;border-bottom:11px solid transparent;
  border-left:18px solid currentColor;
}
.clock__glyph--stop{width:18px;height:18px;background:currentColor}

.clock__today{margin:6px 0 0;font-size:0.9375rem;color:var(--ink-2)}
.clock__today b{color:var(--ink);font-variant-numeric:tabular-nums}
/* The privacy notice is a line, not a banner: it used to be the first thing
   on the page, above the clock it was talking about. */
.clock__note{margin:0;font-size:0.75rem;color:var(--ink-2);max-width:34rem}
.clock__note:empty{display:none}

.clock-more{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:14px}
.clock-loc{
  display:flex;flex-wrap:wrap;align-items:center;gap:12px;
  padding:12px;margin-bottom:14px;border:1px solid var(--line);background:var(--bg);
}
.clock-loc p{margin:0;font-size:0.8125rem;color:var(--ink-2)}
.clock-loc .sub{flex-basis:100%}
.clock-loc__tick{font-size:0.875rem;min-height:44px}
.clock-loc__tick input{width:22px;height:22px}

.clock-roster{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:8px}
.clock-roster article{
  display:flex;align-items:center;gap:10px;
  padding:9px 11px;border:1px solid var(--line);background:var(--bg);
}
.clock-roster b{display:block;font-size:0.8125rem}
.clock-roster small{display:block;color:var(--ink-2);font-size:0.75rem}

@media (max-width:700px){
  .clock{padding:22px 12px 18px}
  .clock__form{max-width:none}
  .clock__select{min-height:52px}
  .clock__btn{min-height:76px}
  .clock-more .btn{flex:1 1 100%;min-height:44px}
  .clock-loc form,.clock-loc .btn{width:100%}
}

/* The working calendar. ---------------------------------------------------
   The working week is shown, not asked: seven boxes where the working ones
   are filled and carry their name, so the answer is readable in one glance
   and only changes behind a button. */
.workweek{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 12px}
.workweek__day{
  display:grid;justify-items:center;gap:2px;
  min-width:56px;padding:8px 10px;
  border:1px solid var(--line);background:var(--bg);color:var(--ink-3);
  font-size:0.75rem;
}
.workweek__day b{font-size:0.9375rem;font-weight:600}
.workweek__day--on{
  border-color:var(--green-edge);background:var(--green-tint);color:var(--green);
}
.workweek__day--on b{color:var(--ink)}

.wcal-head{
  display:flex;flex-wrap:wrap;align-items:center;gap:14px;
  padding:12px;margin-bottom:14px;border:1px solid var(--line);background:var(--head);
}
.wcal-head__facts{display:flex;flex-wrap:wrap;gap:18px;margin:0}
.wcal-head__facts>div{display:grid;gap:2px}
.wcal-head__facts dt{font-size:0.6875rem;text-transform:uppercase;letter-spacing:.04em;color:var(--ink-2)}
.wcal-head__facts dd{margin:0;font-size:0.9375rem;font-weight:600}
.wcal-head .btn{margin-left:auto}

/* A holiday and an extra working day are opposites and must never be told
   apart by a word alone at the end of a long row. */
.wcal-kind{
  display:inline-flex;align-items:center;gap:6px;
  padding:1px 8px;border:1px solid var(--line);font-size:0.75rem;white-space:nowrap;
}
.wcal-kind--free{border-color:var(--red-edge);background:var(--red-tint);color:var(--red)}
.wcal-kind--work{border-color:var(--green-edge);background:var(--green-tint);color:var(--green)}
.wcal-past{color:var(--ink-3)}

@media (max-width:700px){
  .wcal-head .btn{margin-left:0;width:100%;min-height:44px}
  /* Four columns, then three: seven boxes in a row do not fit a phone, and
     wrapping them by content leaves the fourth hanging over the edge. */
  .workweek{display:grid;grid-template-columns:repeat(4,minmax(0,1fr))}
  .workweek__day{min-width:0;padding:8px 4px}
}
/* ── Ihr Ablauf: the company writes its own process down ────────────────────
   Four screens, one question each. Everything here is built out of classes
   that already exist (.steps, .task, .metric-option, .chklist, .form-hint) —
   these rules only do the three things those could not: make a list of phases
   read as a numbered list rather than as a checklist, give the assistant a
   place to stand that is quieter than a button in the header, and let a
   question fill the screen it is the only thing on. */

/* The question is the whole screen. On a wizard step where nothing else
   competes for attention, the heading may be bigger than a page title. */
.view .head h1 + .head__sub,
.proc-list + .proc-later { margin-top: 0; }

/* A list of phases is a list, not a checklist -------------------------------
   The editor is shared with the checkpoint fields, where a tick in front of
   every row is exactly right. Here the rows are the chapters of a building
   job, so they are counted instead. */
/* The editor sits straight in the form here, not inside a .frow, because the
   section head above it is already its label and a 190 px label column beside
   a list of six short names is a column of nothing. It therefore has to carry
   its own padding — without it the remove button of every row hangs off the
   right edge of the page — and its own width: a phase is called "Rohbau", not
   a paragraph, and a 760 px field invites a sentence. */
.proc-list .chklist { padding: 10px 12px; max-width: 640px; }
.proc-list .chklist__items { counter-reset: proc-row; }
.proc-list .chklist__item { counter-increment: proc-row; }
.proc-list .chklist__tick { display: none; }
.proc-list .chklist__item::before {
  content: counter(proc-row);
  min-width: 26px;
  padding: 0 4px;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--ink-2);
  text-align: right;
  font-variant-numeric: tabular-nums;
}
/* 44 px of finger, on the row and on the button that removes it. */
.proc-list .chklist__text { min-height: 44px; }
.proc-list .chklist__remove { min-width: 44px; min-height: 44px; }
.proc-list .chklist__add { min-height: 44px; }

/* What normally sits in a phase of this name: words to read, not rows to
   delete. Set apart from the field above it, never coloured like a warning. */
.proc-suggest {
  border-left: 3px solid var(--teal-edge);
  background: var(--teal-tint);
  padding: 8px 12px;
  margin: 8px 0 0;
  color: var(--ink);
}
.proc-suggest b { font-weight: 600; }

/* The honest small print. Same size and colour as every other hint under a
   field — "you can change this later" is a fact, not a call to action. */
.proc-later { color: var(--ink-2); }

/* What an answer actually changes, next to the answer itself. */
.proc-effect {
  display: flex;
  align-items: baseline;
  gap: 8px;
  border-left: 3px solid var(--blue-edge);
  background: var(--blue-tint);
  padding: 9px 12px;
  margin: 0;
  color: var(--ink);
}
.proc-effect .ic { flex: none; color: var(--blue); }

/* The assistant ────────────────────────────────────────────────────────────
   Bottom of the screen, one line, one button. It is an offer: it must be
   visible when looked for and invisible when not. Violet, because it is the
   machine speaking and never the navigation. */
.proc-assist {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 18px;
  padding: 10px 12px;
  border: 1px solid var(--grid);
  border-left: 3px solid var(--violet-edge);
  background: var(--zebra);
}
.proc-assist__ic { flex: none; color: var(--violet); }
.proc-assist__text {
  flex: 1 1 240px;
  min-width: 0;
  font-size: 0.75rem;
  line-height: 1.5;
  color: var(--ink-2);
}
.proc-assist__act { flex: none; min-height: 44px; }
.proc-assist form { margin: 0; }

/* The review: the plan as one readable list ────────────────────────────── */
.proc-review {
  list-style: none;
  margin: 0;
  padding: 0;
  border-bottom: 1px solid var(--grid);
}
.proc-review__phase {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 2px 12px;
  align-items: baseline;
  padding: 10px 12px;
  border-top: 1px solid var(--grid);
  counter-increment: proc-phase;
}
.proc-review { counter-reset: proc-phase; }
.proc-review__phase > b::before {
  content: counter(proc-phase) '.';
  display: inline-block;
  min-width: 22px;
  color: var(--ink-3);
  font-weight: 600;
}
.proc-review__work {
  grid-column: 1;
  font-size: 0.75rem;
  line-height: 1.5;
  color: var(--ink-2);
  padding-left: 22px;
}
.proc-review__edit {
  grid-row: span 2;
  align-self: center;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  padding: 0 8px;
  font-size: 0.75rem;
  color: var(--link);
}

/* The sentence that decides whether somebody dares to press the button. */
.proc-promise {
  border-left: 3px solid var(--green-edge);
  background: var(--green-tint);
  padding: 10px 12px;
  margin: 0;
  color: var(--ink);
}

/* Phone ────────────────────────────────────────────────────────────────────
   One question per screen is already the right shape for a phone; all that
   changes is that the three ways in and the two navigation buttons stop
   sitting beside each other. */
@media (max-width: 700px) {
  .proc-review__phase { grid-template-columns: minmax(0, 1fr); }
  .proc-review__edit { grid-row: auto; justify-self: start; padding-left: 22px; }
  /* The button drops to its own full-width line; the icon and the sentence
     stay together on the first one. Turning the strip into a column instead
     would make flex-basis mean height and blow the sentence up into a
     240 px-tall box with the button stranded at the bottom of it. */
  .proc-assist__text { flex: 1 1 160px; }
  .proc-assist__act { flex: 1 0 100%; justify-content: center; }
  .proc-list .chklist__item::before { min-width: 20px; }
}
/* ==========================================================================
   Wer ist wo — the crew overview (/workforce).

   NOT a fragment for hbs.css. This sheet ships as public/css/workforce-overview.css
   and the page loads it with @push('head'), exactly the way the sites & team
   map loads public/css/workforce-map.css. Merging a copy of it into hbs.css
   would define every one of these rules twice.

   The real file is:  public/css/workforce-overview.css
   The page loading it: resources/views/workforce/overview.blade.php
   ========================================================================== */
/* Daily reports: the list a site manager reads in the evening. ------------- */

/* The count on the "still open" tab. It is the reason to press the tab, so it
   sits on it — not in a sentence further down the page. Red is the meaning
   here; the number beside it says the same thing without the colour. */
.tabs .tabcount {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 20px;
    height: 18px;
    margin-left: 6px;
    padding: 0 5px;
    border: 1px solid var(--red-edge);
    background: var(--red-tint);
    color: var(--red);
    font-size: 0.6875rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

/* The sites that owe a report. Not a table: four fields, one of them a
   button, and it has to stay readable when the phone is 375 px wide — which a
   table with a horizontal scrollbar does not. */
.openlist {
    margin: 8px 0 0;
    padding: 0;
    list-style: none;
    border-top: 1px solid var(--line-soft);
}

.openlist__row {
    display: grid;
    grid-template-columns: minmax(0, 2fr) minmax(0, 1fr) minmax(0, 1fr) auto;
    gap: 8px 16px;
    align-items: center;
    padding: 10px 4px;
    border-bottom: 1px solid var(--line-soft);
}

.openlist__row:hover { background: var(--zebra); }

.openlist__site,
.openlist__who,
.openlist__last {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.openlist__site .strong { text-decoration: none; }
.openlist__site .sub,
.openlist__last .sub { font-size: 0.7188rem; color: var(--ink-2); }

/* On a wide screen the column heads live in the layout; on a phone the row
   stacks and each field has to say what it is again. */
.openlist__k {
    font-size: 0.6875rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--ink-3);
}

.openlist__act { justify-self: end; min-height: 36px; }

/* The day bar: which day, and the three days anybody actually asks for. Two
   groups rather than six controls in a row, because the filter bar stacks its
   children on a phone and six full-width bars is a page of buttons before the
   list even starts. */
.daynav,
.dayjump {
    display: flex;
    gap: 6px;
    align-items: center;
    min-width: 0;
}

.daynav .field { flex: 1 1 9rem; min-width: 8rem; }
.daynav .btn--icon { min-width: 34px; justify-content: center; }

/* One report per site and day, said where the choice is made. Amber, because
   this is not a mistake somebody made — it is the answer to what they were
   trying to do, and the way to it stands right beside it. */
.reportexists {
    display: flex;
    flex-wrap: wrap;
    gap: 8px 12px;
    align-items: center;
    margin: 0 0 12px;
    padding: 10px 12px;
    border: 1px solid var(--amber-edge);
    border-left-width: 4px;
    background: var(--amber-tint);
    font-size: 0.8125rem;
}

.reportexists .ic { flex: none; color: var(--amber); }
.reportexists span { flex: 1 1 14rem; min-width: 0; }
.reportexists .btn { flex: none; }

/* Why a row wants a second look. Each mark carries its own words, so the row
   reads the same in grayscale — the colour only makes it faster (1.4.1). */
.mark {
    display: inline-flex;
    align-items: center;
    margin: 1px 4px 1px 0;
    padding: 2px 7px;
    border: 1px solid var(--line-soft);
    background: var(--zebra);
    color: var(--ink-2);
    font-size: 0.6875rem;
    white-space: nowrap;
}

.mark--red { border-color: var(--red-edge); background: var(--red-tint); color: var(--red); }
.mark--amber { border-color: var(--amber-edge); background: var(--amber-tint); color: var(--amber); }
.mark--violet { border-color: var(--violet-edge); background: var(--violet-tint); color: var(--violet); }
.mark--blue { border-color: var(--blue-edge); background: var(--blue-tint); color: var(--blue); }
.mark--green { border-color: var(--green-edge); background: var(--green-tint); color: var(--green); }

/* The pictures already on a report being corrected: proof that they are still
   there, small enough not to push the form off the screen. */
.private-photo-grid--sm {
    grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
    margin-bottom: 10px;
}

/* Phone. -------------------------------------------------------------------
   The day strip and the open list both stack; every tap target stays at the
   44 px a thumb needs on a scaffold. */
@media (max-width: 700px) {
    .openlist__row {
        grid-template-columns: 1fr;
        gap: 6px;
        padding: 12px 4px;
    }

    .openlist__who,
    .openlist__last {
        flex-direction: row;
        gap: 8px;
        align-items: baseline;
    }

    .openlist__act {
        justify-self: stretch;
        justify-content: center;
        min-height: 44px;
    }

    .daynav,
    .dayjump { width: 100%; }
    .daynav .btn--icon,
    .dayjump .btn { min-height: 44px; }
    /* Three equal thirds: a thumb finds "Heute" without reading. */
    .dayjump .btn { flex: 1 1 0; justify-content: center; }
    .daynav .btn--icon { flex: 0 0 44px; }

    .tabs .tabcount { min-width: 22px; height: 20px; }

    .reportexists .btn { flex: 1 1 100%; justify-content: center; min-height: 44px; }
}

/* ==========================================================================
   Package time+country · /workforce/time-tracking
   Appended section — nothing above this line is touched.

   Two additions to the clock screen, both of them things that used to be on
   another page: the location question, asked at the button where it means
   something, and the person's own booked hours, which lived in the wage
   office's table of everybody.

   Used outdoors, on a phone, one-handed, in gloves. Every target is at least
   44 px, nothing depends on hover, and the whole block reflows to one column
   long before a phone runs out of width.
   ========================================================================== */

/* The location question, directly under the stamp button. --------------- */
.clock__where{
  display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:6px;
  margin:0;font-size:0.8125rem;color:var(--ink-2);
}
.clock__where .ic{flex:none}
.clock__where span{color:var(--ink-3)}

.clock__loc{
  display:grid;justify-items:center;gap:8px;width:100%;max-width:26rem;
  padding:10px 12px;
  border:1px solid var(--line);background:var(--bg);
}
/* Thumb-sized, and the label is part of the target: the tick used to be a
   13 px box in a settings block two screens further down. */
.clock__tick{
  display:flex;align-items:center;gap:10px;
  min-height:44px;font-size:0.9375rem;font-weight:600;text-align:left;
}
.clock__tick input{width:24px;height:24px;flex:none}
.clock__loc .clock__note{text-align:center}
.clock__loc .btn{min-height:44px}

/* His own hours, under the clock. --------------------------------------- */
.mytime{margin:0 0 14px}
/* Two numbers people actually ask for — today, and the week — before any
   list. Tabular figures so the column does not jitter. */
.mytime__sums{
  display:flex;flex-wrap:wrap;gap:8px;margin:0 0 10px;
}
.mytime__sums span{
  display:flex;flex-wrap:wrap;align-items:baseline;gap:6px;
  flex:1 1 12rem;padding:9px 12px;
  border:1px solid var(--line);background:var(--head);
}
.mytime__sums small{flex:1 1 100%;font-size:0.75rem;color:var(--ink-2)}
.mytime__sums b{font-size:1.375rem;font-variant-numeric:tabular-nums;color:var(--ink)}

.mytime__day{margin:0 0 10px}
.mytime__date{
  display:flex;align-items:baseline;justify-content:space-between;gap:10px;
  margin:0 0 4px;padding:0 2px;
  font-size:0.8125rem;font-weight:600;letter-spacing:.04em;text-transform:uppercase;
  color:var(--ink-2);
}
.mytime__daysum{font-variant-numeric:tabular-nums;text-transform:none;letter-spacing:0}

.mytime__row{
  display:flex;flex-wrap:wrap;align-items:center;gap:10px;
  padding:10px 12px;margin-top:-1px;
  border:1px solid var(--line);background:var(--bg);
}
/* The running booking is marked three ways — the word "Läuft" in its chip,
   the edge and the tint — because colour may not carry it alone. */
.mytime__row--on{border-color:var(--green-edge);background:var(--green-tint)}
.mytime__what{flex:1 1 14rem;min-width:0;display:grid;gap:2px}
.mytime__what b{font-size:0.9375rem;line-height:1.3}
.mytime__what small{font-size:0.8125rem;color:var(--ink-2)}
.mytime__where{display:flex;align-items:center;gap:5px;color:var(--ink-3)}
.mytime__where .ic{flex:none}
.mytime__why{color:var(--red)}
.mytime__h{
  flex:none;min-width:4.5rem;text-align:right;
  font-size:1rem;font-weight:600;font-variant-numeric:tabular-nums;
}
.mytime__edit{flex:none;min-width:44px;min-height:44px}

@media (max-width:700px){
  .clock__loc{max-width:none}
  /* On a phone the row becomes two lines: what and how long on top, the
     state and the correction button underneath, both full-thumb height. */
  .mytime__row{padding:12px}
  .mytime__what{flex:1 1 100%}
  .mytime__h{min-width:0;margin-left:auto;text-align:left}
  .mytime__edit{margin-left:auto}
  .mytime__sums span{flex:1 1 100%}
}

/* ==========================================================================
   Assistant chat — package "assistant"
   Appended section. Owned by the assistant package; nothing above is edited.

   What was wrong: the rail was a form with a receipt underneath it. The whole
   aside scrolled (overflow:auto), so a long answer pushed the box you type in
   out of the window; 108px of stacked header bars stood between the top edge
   and the first message; and the send button sat on its own line below the
   text, so "write, then look somewhere else to send" was the shape of every
   question. This section makes it behave like the chat apps everyone already
   uses (Jakob's Law): fixed frame, one scrolling list, send beside the caret.
   ========================================================================== */

/* The rail is furniture, not page content. It never scrolls as a whole — only
   .agent-rail__log does — which is what keeps the composer and the newest
   message on screen no matter how long the conversation gets. */
.agent-rail{overflow:hidden}

/* One header instead of two. Title, close and "which page am I looking at"
   used to cost 108px before the first word of the conversation; the second
   bar only ever repeated the page heading three centimetres to its left. */
.agent-rail__head{min-height:0;padding:11px 12px;align-items:center}
.agent-rail__head span{margin-top:3px}
/* 44px of thumb, without letting the button set the height of the bar. */
.agent-rail__close{min-width:44px;min-height:44px;justify-content:center;margin:-7px -8px -7px 0}

/* The conversation ------------------------------------------------------- */
.agent-rail__log{padding:10px;background:var(--zebra);display:flex;flex-direction:column;gap:9px;overscroll-behavior:contain}
.agent-rail__empty{padding:14px 4px}

/* One turn: who said it, then what they said. The name is text, never just a
   side of the rail — WCAG 2.2 AA, colour and position carry no meaning alone. */
.chatline{display:flex;flex-direction:column;gap:3px;max-width:88%}
.chatline--me{align-self:flex-end;align-items:flex-end}
.chatline--ai{align-self:flex-start;align-items:flex-start}
.chatline__who{font-size:0.6875rem;font-weight:600;color:var(--ink-2)}
.chatline__text{
  border:1px solid var(--grid-strong);background:#fff;padding:8px 10px;
  font-size:0.75rem;line-height:1.55;white-space:pre-wrap;overflow-wrap:anywhere;
}
/* Three surfaces, D-23: orange for what I said, white for the answer, light
   grey for the list they sit on. No radius, no shadow, no gradient. */
.chatline--me .chatline__text{background:var(--orange);border-color:var(--orange-edge)}
.chatline__tools{display:flex;flex-wrap:wrap;gap:4px}
.chatline__tools span{
  display:inline-flex;align-items:center;gap:5px;padding:2px 6px;
  border:1px solid var(--grid);background:#fff;font-size:0.6875rem;color:var(--ink-2);
}
.chatline__links{display:flex;flex-wrap:wrap;gap:5px}
.chatline__links a{
  display:inline-flex;align-items:center;min-height:44px;padding:6px 9px;
  border:1px solid var(--grid-strong);background:#fff;color:var(--link);
  font-size:0.75rem;font-weight:600;text-decoration:none;
}
.chatline__links a:hover{text-decoration:underline}
.chatline__links a:focus-visible{outline:3px solid Highlight;outline-offset:2px}
.chatline__retry{margin-top:6px;min-height:44px}
.chatline--streaming .chatline__text::after{content:'\202F';display:inline-block;width:0.55em;border-bottom:2px solid currentColor;animation:assistant-caret 900ms steps(1,end) infinite}
@keyframes assistant-caret{50%{opacity:0}}
@media (prefers-reduced-motion:reduce){.chatline--streaming .chatline__text::after{animation:none}}

/* The composer ----------------------------------------------------------- */
.agent-rail__composer{flex:none;padding:9px;border-top:1px solid var(--grid);background:var(--head)}
/* align-items:flex-end, not center: as the field grows the send control stays
   next to the line being written, which is the whole point of the complaint. */
.composer{display:flex;align-items:flex-end;gap:7px}
.composer__field{
  flex:1;min-width:0;min-height:44px;max-height:40vh;height:44px;
  padding:11px 9px;line-height:1.45;resize:none;overflow-y:auto;
}
.composer__send{flex:none;width:44px;height:44px;padding:0;justify-content:center}
.composer__send .ic{margin:0}
.composer__hint{margin-top:6px;font-size:0.6875rem;color:var(--ink-2)}
.composer__extra{display:flex;flex-wrap:wrap;gap:5px;margin-top:6px}
.composer__extra .btn{min-height:32px}

/* The full page at /assistant is the same conversation, wider. */
.chat__log{padding:14px var(--pad);background:var(--zebra);display:flex;flex-direction:column;gap:11px}
.chat__log .chatline{max-width:min(560px,80%)}
.chat__log .chatline__text{font-size:0.8125rem}
.chat__composer{flex:none;padding:10px var(--pad);border-top:1px solid var(--grid);background:var(--head)}

@media (max-width:860px){
  /* top:0/bottom:0 measures the large viewport, so on a phone with the URL bar
     showing, the composer sat below the fold exactly when the keyboard was
     open. dvh tracks what is actually visible. */
  .agent-rail{height:100dvh}
  .chatline{max-width:92%}
  /* Every textarea on a phone gets a two-line floor, which is right for a note
     field and wrong for a composer: 88px of empty box pushed the newest
     message off a 375px screen before a word was typed. One line that grows,
     still 44px of thumb. Selector matches the general rule's weight and wins
     on order — see textarea.field in the mobile block above. */
  textarea.composer__field{min-height:44px;height:44px;padding:11px 9px;font-size:0.8125rem}
}

/* ==========================================================================
   Chat — a screen that holds still                        (package chat+mail)

   Appended section. It only adds to and overrides rules written elsewhere in
   this file; nothing above is edited.

   What was wrong: /chat was a normal document. Title, subline, button row,
   then a 470px box with the conversation in it, inside a page that scrolled.
   On a laptop that put the composer below the fold — writing a message began
   by scrolling to find the field, and every answer pushed it further down.
   Scrolling the messages meant scrolling a box inside a page, so the two
   scrollbars fought each other and nothing stayed where you left it.

   What it is now: the frame is fixed to the window and exactly two things
   scroll, each on its own — the list of conversations and the messages. The
   newest message and the field you type in are always on the screen. That is
   Jakob's Law: every messenger anyone has ever used is built this way.
   ========================================================================== */

/* The page stops scrolling on /chat, and hands its height to the chat.
   :has() rather than a class on <main>, because the frame (layout.blade.php)
   belongs to every page and this is the only page that needs it. */
.page:has(.chat-hub){overflow:hidden}
.view:has(.chat-hub){flex:1 1 auto;min-height:0;overflow:hidden}

/* min-height:0 replaces the 470px floor: with a fixed frame a floor is what
   pushes the composer off the bottom of a short window. grid-template-rows
   pins the single row to the container — an implicit auto row would size
   itself to the longest channel list instead. */
.chat-hub{
  flex:1 1 auto;min-height:0;
  grid-template-columns:264px minmax(0,1fr);grid-template-rows:minmax(0,1fr);
}

.channel-list{min-height:0;overflow:hidden}
.channel-list__head{
  flex:none;min-height:48px;display:flex;align-items:center;gap:8px;
  padding:7px 10px;border-bottom:1px solid var(--grid-strong);background:#fff;
}
.channel-list__head h2{flex:1;min-width:0;font-size:0.8125rem}
/* The one scrolling thing on the left. overscroll-behavior stops the page
   behind it from taking over the moment the list reaches its end. */
.channel-list>nav{flex:1 1 0;min-height:0;overflow-y:auto;overscroll-behavior:contain}
.direct-chat-form{flex:none}

.channel-thread{min-height:0;overflow:hidden}
.channel-thread__head{flex:none;align-items:center}
.channel-thread__title{min-width:0;flex:1}
/* The name of a conversation can be longer than the column it sits in —
   "Kundenchat · Aufstockung Panoramaweg" on a 390px phone. It shortens, it
   does not push the delete button off the screen. */
.channel-thread__head h2{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.channel-thread__head p{display:flex;align-items:center;gap:7px;flex-wrap:wrap}
/* The other scrolling thing. flex:1 1 0 with min-height:0 means the messages
   are what gives way when the window gets short — the header and the composer
   are flex:none and can never be squeezed off the screen. */
.channel-thread__messages{flex:1 1 0;min-height:0;overflow-y:auto;overscroll-behavior:contain}
.channel-compose{flex:none}

/* A conversation of three messages belongs at the bottom, next to the field
   you answer in, not floating at the top of an empty pane. */
.chat-thread>.channel-message:first-of-type{margin-top:auto}
.chat-thread>.empty{margin:auto 0}

/* Customer access: one line, not two stacked panels with their own headings.
   It is a link you copy once — it does not deserve a third of the header. */
.chat-share{
  flex:none;display:grid;grid-template-columns:auto minmax(0,1fr) auto;
  align-items:center;gap:8px;padding:6px 10px;
  border-bottom:1px solid var(--grid);background:var(--sel);
}
.chat-share label{font-size:0.6875rem;color:var(--ink-2);white-space:nowrap}
.chat-older{
  flex:none;padding:5px 12px;border-bottom:1px solid var(--grid);
  background:var(--head);font-size:0.6875rem;color:var(--ink-2);
}

@media (max-width:700px){
  /* On a phone the route decides which of the two panels is the screen:
     /chat is the list, /chat/{channel} is the conversation. It used to be a
     button toggling a data attribute on both panels at once, which is how the
     list came to be drawn over the messages — and why the browser's own back
     button did nothing useful once you were inside a conversation.

     One panel is display:none, so the two can never occupy the same space. */
  .chat-hub{display:flex;flex-direction:column;overflow:hidden;min-height:0}
  .chat-hub[data-chat-view="list"] .channel-list{display:flex;flex:1 1 auto;min-height:0}
  .chat-hub[data-chat-view="list"] .channel-thread{display:none}
  .chat-hub[data-chat-view="thread"] .channel-list{display:none}
  .chat-hub[data-chat-view="thread"] .channel-thread{display:flex;flex:1 1 auto;min-height:0}
  .chat-back{display:inline-flex;min-height:44px}
  .channel-list__head{min-height:52px}
  .chat-share{grid-template-columns:minmax(0,1fr) auto}
  .chat-share label{grid-column:1/-1}
}

/* ==========================================================================
   Money package (change orders · invoices · payments) — appended section.

   One rule, and only one. The screens in this area carry a standing caveat
   (<x-hbs.notice>) saying that their figures have not been checked by anyone
   qualified. That sentence is for the person working in the application and
   for nobody else: a printed invoice must never leave the house carrying a
   line that says its own numbers are unverified. The letter view (body.doc)
   is the one page in this application that is printed on purpose, so the
   caveat is dropped there — and there only, so no other page loses a notice
   it may legitimately want on paper.
   ========================================================================== */
/* A standing caveat (<x-hbs.notice>) is read once and acted on every time. At
   the 0.75rem of the plain .notice it is the size of a hint under a field,
   which is what people skim past; at reading size it is a sentence. Only the
   component carries this class, so the notices written by hand elsewhere in
   the application keep the size they were designed at. */
.notice--standing{font-size:0.8125rem;line-height:1.55;padding:11px 13px;gap:10px}

/* On screen the letter page is a sheet of paper on grey; the caveat above it
   lines up with that sheet instead of running the full width of the window. */
.doc>.notice{max-width:760px;margin:10px auto}
@media print{.doc .notice{display:none!important}}

/* ==========================================================================
   Visual sweep — the assistant and the chat, looked at    (package assistant)
   at 1512px and at 375px.

   Appended section. It only adds rules; nothing above is edited.

   Three things a person saw and nobody had measured:

   1. On a phone /assistant is the only assistant there is — the rail cannot be
      opened below 700px at all. Its composer sat underneath the floating
      bottom bar: the field was a grey sliver behind the blur and the send
      button was completely covered by "Mehr". The one control the whole
      screen exists for could not be tapped.
   2. The report button ("Rückmeldung") is fixed to the top right of the
      window. So is the assistant rail. On every page with the rail open the
      button was parked on top of the first line of the conversation.
   3. The chat's send button sat at the bottom of a fixed two-line well while
      the caret was on the first line, and Enter made a new line instead of
      sending. That is the complaint the rail was already rebuilt for; /chat
      had not had it applied.
   ========================================================================== */

/* 1 — /assistant gets the same fixed frame /chat has. The page stops
   scrolling and hands its height to the conversation, so the composer is
   furniture at the bottom of the window instead of the last thing in a
   document. On a phone .page already reserves the height of the floating bar
   in its padding, which is what puts the composer above it. */
.page:has(.chat){overflow:hidden}
.view:has(.chat){flex:1 1 auto;min-height:0;overflow:hidden}
/* min-height:0 on the log, flex:none on the composer: when the window gets
   short it is the history that gives way, never the field you type in. */
.chat__log{min-height:0}

/* 2 — step aside for the rail. Both rails carry .agent-rail and the open one
   is the one without [hidden]; below 861px the rail is a full-height overlay
   above the button, so it only matters where the rail is a column. 340px is
   the rail column in .app, plus the button's own 16px margin. */
@media (min-width:861px){
  .app:not(.side-panel-collapsed):has(.agent-rail:not([hidden])) .fbox{right:356px}
}

/* 3 — the chat writes like the assistant writes. Same .composer row, so the
   send control is beside the line being written and grows down with it; the
   grid that put the button on its own line is replaced by that row. */
.channel-compose{display:block}
/* Beats ".channel-compose textarea.field" (same weight, later in the file)
   and the phone rule that gives every textarea a two-line floor — 88px of
   empty box in a composer pushes the newest message off a 375px screen
   before a word is typed. assistant-chat.js grows it from here. */
.channel-compose textarea.composer__field{
  height:44px;min-height:44px;max-height:40vh;
  padding:11px 9px;line-height:1.45;resize:none;overflow-y:auto;
}

/* 4 — one product, one chat. The rail's turns are flat rectangles on the
   three surfaces (D-23); the same bubbles on /chat carried a 14px radius and
   a drop shadow, so the two conversations in this application looked like two
   different applications. The tinted fills stay — they are what tells a
   colleague from a customer, and each of them also says so in words. */
.channel-message__content{border-radius:0;box-shadow:none}
.channel-message--mine .channel-message__content{border-radius:0}

/* ==========================================================================
   Visual sweep — Projekt anlegen, Einrichtung, E-Mail
   (screens: /projects/create 1–3, /setup, /setup/process/*, /mail)

   Three things a person could see and nobody could measure away. Every rule
   here is new; none of them changes a declaration written above.
   ========================================================================== */

/* 1 — A hint that stands on the page, not inside a box, was 14 px to the left
   of everything else on the screen. .form-hint and .form__hint carry no
   horizontal padding, which is right inside a padded card and wrong when the
   paragraph is a direct child of the page or of a split column: "Alle Felder
   sind freiwillig …" under the wizard's questions, "Noch nicht beantwortet …"
   on the setup checklist and "Unten steht der Ablauf, den wir mitliefern …"
   above the phase list all started flush against the edge of the surface
   while the section band above them started at 14 px. Same rhythm as .sect
   and .frow, so the column of text on the screen has one left edge. */
.view>.form-hint,.view>.form__hint,
.split__l>.form-hint,.split__l>.form__hint,
.split__r>.form-hint,.split__r>.form__hint{padding-left:14px;padding-right:14px}
/* The phase list carries its own 12 px (see .proc-list .chklist above), so
   the sentence under it lines up with the rows, not with the page frame. */
.proc-list>.form-hint{padding-left:12px;padding-right:12px}

/* 2 — /projects/create scrolled sideways on a phone. The relation picker
   keeps the real <select> in the page for the form post and hides it the
   accessible way (1 px, clipped); the phone rule that makes every field in a
   .frow__f full width caught that select too, so a 375 px screen carried a
   375 px element starting 15 px in and the whole page could be dragged to the
   right. The same patch already exists for .bar and .filters (line ~1665) —
   the form rows were simply never in it. Kunde and Bauleitung on step 1 of
   the wizard are where it shows. */
@media (max-width:700px){
  .frow__f select.relation-picker__value{width:1px}
}

/* 3 — On a phone a row of the review table is a card and every cell is a
   two-column grid: label left, value right. A cell holding two controls put
   the second one into the label column, which is 120 px wide — "Serviceanfrage
   anlegen" came out broken across three lines in the middle of the word, and
   the photo prompt beside it was a 240 px box in a 375 px card. The duration
   cell above already solves this by taking one column instead; these two do
   the same. */
@media (max-width:700px){
  .sheet td.wizard-flags{grid-template-columns:1fr}
  /* The 16 px that separates two ticks standing side by side is an indent
     once they stand under each other, and the two answers stopped sharing a
     left edge. */
  .sheet td.wizard-flags .check+.check{margin-left:0}
}
/* On a wide screen the same cell put the tick "Ja" and the prompt field on
   one line: the field is width:100% of the cell, so starting it after the
   tick pushed 45 px of it out over the right-hand edge of the table. One
   control per line — the cell is a column of two answers, not a sentence. */
.sheet td.wizard-flags .field--table{display:block;width:100%;max-width:none;margin-top:5px}

/* ==========================================================================
   Zeiterfassung, Mitarbeiter, Schichten — package "workforce"
   Appended section. Nothing above this line is edited.

   Three things a builder saw and did not trust:

   1 — "Meine Zeiten": the day heading sat 11 px to the left of the bookings
       it belongs to, and the day's total ended 2 px from the edge of the
       screen — on a phone the full stop after "Std." was against the bezel.
       The rows below use 12 px of padding inside a 1 px border, so the
       heading takes the same 13 px and the column of text has one left edge
       and one right edge (Proximity / Uniform Connectedness).

   2 — On a phone the second line of a booking had two items with
       margin-left:auto on it. Two auto margins share the free space
       between them, so "0,13 Std." floated in the middle of an empty line
       with 90 px of nothing on either side and read like a layout that had
       given up. Only the correction button is pushed to the right now; the
       hours and the state start where the project name above them starts.

   3 — On a phone a table row is a card and ::before carries the column name.
       On a status cell that pseudo-element already had width:12px from the
       desktop rule that draws the ✓ / ! glyph in front of the state — so the
       word "Status" was rendered one letter per line, five lines tall, on
       /workforce/shifts, /workforce/sick and every other list with a state
       column. The glyph is not drawn on a card anyway (the label wins the
       content), so the width and the bold weight only have to go away.
   ========================================================================== */

.mytime__date{padding:0 13px}

@media (max-width:700px){
  .mytime__h{margin-left:0}
  .sheet td.status-cell::before{width:auto;margin-right:0;font-weight:400}
}

/* ==========================================================================
   Visual sweep — Dashboard, /projects, Projektakte (2026-08-16)

   Six things a person sees before he reads anything. None of them is a
   matter of taste: an amount printed across its own chart, a number that
   falls out of its row, two words run into one — Aesthetic-Usability, and
   every one of them costs trust in the figures next to it.
   ========================================================================== */

/* 1 — `.sheet th,.sheet td{text-align:left}` has a specificity of (0,1,1) and
       quietly beat `.num` (0,1,0). Every figure in every table has been
       left-aligned ever since: in the DIN-276 budget "0,00" started under the
       first digit of "39.800,00", and not one money column lined up at the
       decimal point. Tabular figures are a D-23 rule and the reason a column
       of amounts can be read at all. Only on the desk — on a phone a row is a
       card, where the value stands next to its own label. */
@media (min-width:701px){
  .sheet th.num,.sheet td.num{text-align:right}
}

/* 2 — <x-hbs.progress> is a bar followed by its percentage. On a phone a cell
       is a two-column grid (label | value), so the bar and the number became
       two grid items: the bar sat beside "Fortschritt" and "96 %" dropped to
       the next line into the label column, reading as a value belonging to
       nothing. Such a cell lays its own children out in one row instead. */
@media (max-width:700px){
  .sheet tbody td:has(> .miniprog){display:flex;gap:10px;align-items:baseline}
  .sheet tbody td:has(> .miniprog)::before{flex:0 0 34%;min-width:80px}
}

/* 3 — "P-2024-0255Alle Werte (5)". The fold button is appended straight after
       the headline of the card, so the number of a building site and the name
       of a control were printed as one word. */
@media (max-width:700px){
  .td-fold-btn{margin-left:10px}
}

/* 4 — The axis under a chart reserves 56px per column. Twelve months are
       688px, so the dashboard — the page everyone lands on — scrolled
       sideways on every phone. The columns share what there is instead — and
       the type stays at 11px, because a month nobody can read is not a fix:
       "Sep" is 20px of the 28px a 375px screen has per month. */
@media (max-width:700px){
  .cols-x span{min-width:0;padding-inline:1px}
}

/* 5 — The link in a section heading ("Terminplan öffnen", "Alle Aufgaben")
       was 17px tall. Everything else in this stylesheet grew to a thumb for a
       phone; these were missed, and they are the way out of half the sections
       of the Projektakte (WCAG 2.5.8). It also broke mid-phrase — "Budget /
       öffnen" over two lines with the arrow beside neither of them — while
       the heading next to it had room to wrap. The heading wraps now. */
@media (max-width:900px){
  .sect__link{min-height:44px;flex:none;white-space:nowrap}
}

/* 6 — The donut prints its total in the middle of the ring. In the 160-unit
       viewBox the hole is 100 units across and "109.604,00 €" is 130 at
       1.25rem, so the largest number on the start page was drawn straight
       through its own chart on both sides. Thirteen characters — "109.604,00 €"
       and everything shorter — now stand inside the hole. Longer than that
       and the component has to shrink the type itself; see docs. */
.donut__centre{font-size:0.875rem}

/* ==========================================================================
   The decision card — package "assistant"

   A change the assistant prepared, shown where it was asked for. It used to
   live only on /assistant/actions: the answer said "waiting for approval",
   and approving it meant leaving the conversation, finding another page in
   another group of the navigation, and reading the same sentence again.
   Everything a person needs to decide — what it is, how heavy it is, yes and
   no — is now attached to the answer that proposed it (Doherty: the loop ends
   where it started). No radius, no shadow: it is the same paper as the rest.
   ========================================================================== */
.aicard{
  align-self:stretch;border:1px solid var(--grid-strong);border-left-width:4px;
  background:#fff;padding:9px 11px;display:flex;flex-direction:column;gap:6px;
}
/* Weight is carried by the left edge and by the word beside it, never by the
   edge alone — the same rule the state chips follow (WCAG 1.4.1). */
/* The same three inks the status cells and calendar events already use, so
   "heavy" looks the same wherever the application says it. */
.aicard--low{border-left-color:#2e7d46}
.aicard--medium{border-left-color:#b77900}
.aicard--high{border-left-color:#b6372d}
.aicard__head{display:flex;flex-wrap:wrap;align-items:baseline;gap:7px;font-size:0.8125rem}
.aicard__risk{
  font-size:0.6875rem;font-weight:600;color:var(--ink-2);
  border:1px solid var(--grid);padding:1px 5px;white-space:nowrap;
}
.aicard__sum{font-size:0.75rem;line-height:1.5;color:var(--ink-2);overflow-wrap:anywhere}
.aicard__note{font-size:0.6875rem;color:var(--ink-2)}
/* Two thumbs side by side, 44px each, and they wrap before they shrink. */
.aicard__acts{display:flex;flex-wrap:wrap;gap:7px}
.aicard__acts .btn{min-height:44px}
