/* ============================================================================
   Quercia viva — forms
   ============================================================================
   Everything that makes a page you type into: the field itself, the single
   column it lives in, the boxes a choice is drawn as, the message when it is
   wrong, and the bar that saves it.

   The rules are written down in docs/brand/forms.md; the Symfony form theme in
   templates/form/quercia_layout.html.twig is what makes them the default
   rendering rather than something a template has to remember. Change one and
   the other two have to agree.

   Split out of components.css, which had grown to 1865 lines and held four
   unrelated input recipes.
   ============================================================================ */

:root {
  /* Masked, not drawn: the mask takes the alpha and the colour comes from
     `currentColor`, so one file serves the error message in both themes. */
  --qc-icon-alert: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='M12 8v4'/%3E%3Cpath d='M12 16h.01'/%3E%3C/svg%3E");
}

/* ================================================================== *
 * §6 — Inputs / forms
 * ================================================================== */
.form-control,
.form-select,
.input-group-text {
  background-color: var(--qc-field-bg);
  border-color: var(--qc-field-border);
  border-radius: var(--qc-radius-input);
  color: var(--qc-text-primary);
}
.form-control::placeholder { color: var(--qc-text-tertiary); }
.form-label { color: var(--qc-text-secondary); font-weight: 600; }

/* Mandatory-field marker: Tabler draws the asterisk after any `.required`
   label (Symfony adds that class to required fields) — retint it through the
   token layer so it stays contrast-safe in both themes. */
.required::after { color: var(--qc-error-text); font-weight: 700; }

/* ...but a group is mandatory once, not once per option. Symfony marks every
   child of a required expanded choice as required too, so a six-option radio
   group came out with six asterisks — one on the legend and one beside each
   name, where it reads as though each option were separately compulsory. The
   legend keeps its marker; the options drop theirs.

   Scoped to a fieldset on purpose: that is what the row of a compound field is
   rendered as, and it is the case where a legend is there to carry the marker.
   A lone required checkbox — terms, consent — is not in one, and keeps its
   asterisk because there is nothing else to hold it. */
fieldset .form-check-label.required::after { content: none; }

/* ------------------------------------------------------------------ *
 * Forms — one column, and a measure to read it by
 * ------------------------------------------------------------------ */

/* A form page, and how wide it is allowed to get.
 *
 * The column is capped at the measure; the page around it was not, so a form
 * card stretched to the window and left the fields hugging its left edge with
 * half a monitor of nothing beside them. The settings hub never had the problem
 * because the rail eats the difference. A page with no rail needs to say it
 * itself — wide enough for a canvas that has to be wide (the addresses, a line
 * editor), never wide enough to strand the column. */
.qc-formpage { max-width: 64rem; }

/* A single column, because a two-column form makes the eye choose between
   reading down and reading across, and the fields on the right are the ones
   people skip. The measure matters as much as the count: past ~40rem a label
   and its input drift apart horizontally and stop reading as a pair, which is
   what a full-width form on a wide monitor does.
 *
   The measure belongs to what is read, not to the container. Capping .qc-form
   itself also capped the bar that closes it, so the save button stopped at the
   edge of the column instead of the edge of the card — on a page whose card is
   wider than 42rem that reads as a button someone forgot to align. The cap
   moves to the children; the bar and the danger zone opt out. */
.qc-form { --qc-form-measure: 42rem; max-width: none; }
.qc-form > * { max-width: var(--qc-form-measure); }
.qc-form > .qc-form__actions,
.qc-form > .qc-form__danger { max-width: none; }

.qc-form__group + .qc-form__group {
  margin-top: 2rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--qc-divider);
}
/* A group that is not a sibling of the others — it sits outside the column
   because it holds a canvas — still needs the rule that separates it. */
.qc-form__group--ruled {
  margin-top: 2rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--qc-divider);
}
.qc-form__grouptitle {
  font-family: var(--qc-font-display);
  font-size: var(--qc-fs-xl);
  font-weight: 700;
  margin: 0 0 .125rem;
}
/* A title followed straight by a field — "Quando" over "Data" — gets the room
   a lede would have given it; with the title's own .125rem the label below
   read as a second line of the heading. */
.qc-form__grouptitle + :not(.qc-form__grouplede) { margin-top: 1rem; }
/* A row after a wrapper (the day block, then "Prioritario") is spaced as a row
   after a row: `.qc-form__row + .qc-form__row` cannot see across the wrapper. */
.qc-form__group > :not(.qc-form__grouptitle, .qc-form__grouplede) + .qc-form__row { margin-top: 1rem; }
.qc-form__grouplede {
  color: var(--qc-text-secondary);
  font-size: var(--qc-fs-md);
  margin: 0 0 1rem;
}
.qc-form__aside {
  margin: 1rem 0 0;
  color: var(--qc-text-secondary);
  font-size: var(--qc-fs-md);
}

/* A document a step points at rather than holds — the job's estimate on its
   sheet: what it is, its figure, and the way in. */
.qc-estimate-summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem 1rem;
  padding: .875rem 1rem;
  border: 1px solid var(--qc-border);
  border-radius: var(--qc-radius-card);
  background: color-mix(in srgb, var(--qc-field-bg) 55%, var(--qc-card-bg));
  margin-bottom: 1rem;
}
.qc-estimate-summary__ident { display: flex; flex-direction: column; flex: 1 1 12rem; min-width: 0; }
.qc-estimate-summary__title { font-weight: 600; color: var(--qc-text-primary); }
.qc-estimate-summary__meta { font-size: var(--qc-fs-sm); color: var(--qc-text-secondary); }
.qc-estimate-summary__total { font-size: var(--qc-fs-2xl); font-weight: 700; color: var(--qc-text-primary); }
/* The agreement wizard's Periodo stage: what the agreed number makes of the
   management — three figures, then the windows drawn as a strip of segments
   (widths in proportion, alternating tints), the two ends of the period under
   it. A read-out, not a control: nothing in it is pressable. */
.qc-cadence {
  margin: .25rem 0 1.25rem;
  padding: .875rem 1rem 1rem;
  border: 1px solid color-mix(in srgb, var(--qc-primary) 22%, var(--qc-border));
  border-radius: var(--qc-radius-card);
  background: color-mix(in srgb, var(--qc-primary) 5%, var(--qc-card-bg));
}
.qc-cadence[hidden] { display: none; }
.qc-cadence__head { display: flex; align-items: center; gap: .5rem; margin-bottom: .75rem; }
.qc-cadence__icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.75rem; height: 1.75rem; border-radius: 50%;
  background: var(--qc-layer-wash);
  color: var(--qc-primary);
}
.qc-cadence__title { font-weight: 600; font-size: var(--qc-fs-md); color: var(--qc-text-primary); }
.qc-cadence__facts {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: .75rem;
  margin: 0 0 .875rem;
}
.qc-cadence__fact { min-width: 0; }
.qc-cadence__fact dt {
  font-size: var(--qc-fs-xs);
  font-weight: 500;
  color: var(--qc-text-secondary);
  margin-bottom: .125rem;
}
.qc-cadence__fact dd {
  margin: 0;
  font-family: var(--qc-font-display);
  font-size: var(--qc-fs-2xl);
  font-weight: 600;
  line-height: 1.2;
  color: var(--qc-text-primary);
  white-space: nowrap;
}
.qc-cadence__strip {
  display: flex;
  gap: 2px;
  height: .625rem;
  border-radius: var(--qc-radius-pill);
  overflow: hidden;
}
.qc-cadence__seg {
  flex: 1 1 0;
  min-width: 2px;
  background: color-mix(in srgb, var(--qc-primary) 55%, transparent);
  transition: flex-grow var(--qc-dur-standard) var(--qc-ease-standard);
}
.qc-cadence__seg:nth-child(even) { background: color-mix(in srgb, var(--qc-primary) 30%, transparent); }
.qc-cadence__seg:first-child { background: var(--qc-primary); }
.qc-cadence__ends {
  display: flex;
  justify-content: space-between;
  margin-top: .375rem;
  font-size: var(--qc-fs-xs);
  color: var(--qc-text-secondary);
}
@media (max-width: 575.98px) {
  .qc-cadence__facts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .qc-cadence__fact:last-child { grid-column: 1 / -1; }
}
@media (prefers-reduced-motion: reduce) {
  .qc-cadence__seg { transition: none; }
}

/* Toggle pills (qc_variant: 'chips'): a tick box that reads as a tag. */
.qc-chips { display: flex; flex-wrap: wrap; gap: .5rem; }
.qc-chip { cursor: pointer; margin: 0; }
.qc-chip__face {
  display: inline-flex;
  align-items: center;
  gap: .375rem;
  padding: .375rem .75rem;
  border: 1px solid var(--qc-border);
  border-radius: var(--qc-radius-pill);
  background: var(--qc-card-bg);
  font-size: var(--qc-fs-md);
  color: var(--qc-text-secondary);
  transition: background-color var(--qc-dur-fast) var(--qc-ease-standard), border-color var(--qc-dur-fast) var(--qc-ease-standard), color var(--qc-dur-fast) var(--qc-ease-standard);
}
.qc-chip__check { display: none; }
.qc-chip:hover .qc-chip__face { border-color: color-mix(in srgb, var(--qc-primary) 40%, var(--qc-border)); }
.qc-chip__input:checked + .qc-chip__face {
  border-color: color-mix(in srgb, var(--qc-primary) 55%, var(--qc-border));
  background: color-mix(in srgb, var(--qc-primary) 12%, var(--qc-card-bg));
  color: var(--qc-text-primary);
}
.qc-chip__input:checked + .qc-chip__face .qc-chip__check { display: inline; color: var(--qc-primary); }
.qc-chip__input:focus-visible + .qc-chip__face { outline: 2px solid var(--qc-primary); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .qc-chip__face { transition: none; } }

/* The rules page's preview (commission/generate.html.twig): the windows N
   jobs would get, one line each — number, dates, length. Read, not edited. */
.qc-rulespreview { list-style: none; margin: 0; padding: 0; max-height: 22rem; overflow-y: auto; }
.qc-rulespreview__row {
  display: grid;
  grid-template-columns: 2rem 1fr;
  gap: .75rem;
  align-items: center;
  padding: .375rem 0;
  border-top: 1px solid var(--qc-divider);
}
.qc-rulespreview__row:first-child { border-top: 0; }
.qc-rulespreview__strip { margin: 1.25rem 0 .75rem; }
.qc-rulespreview__error {
  margin: .75rem 0 0;
  padding: .625rem .875rem;
  border-radius: var(--qc-radius-sm);
  background: color-mix(in srgb, var(--qc-error) 10%, var(--qc-card-bg));
  color: var(--qc-error-text);
  font-size: var(--qc-fs-md);
}
.qc-rulespreview__error[hidden] { display: none; }
.qc-rulespreview__strip:empty { display: none; }
.qc-rulespreview__n { color: var(--qc-text-secondary); font-weight: 600; font-size: var(--qc-fs-sm); }
/* Modifica finestre (commission/windows.html.twig): the period as a timeline
   whose bars are dragged (.qc-timeline--edit), then one card per job — its
   number and services on the left, its window on the right — with the error
   of an overlap under the card it concerns. A done job is drawn, not edited;
   a card whose window was just moved is marked until the save. */
.qc-windows__timeline {
  margin: .5rem 0 1.25rem;
  padding: 1.75rem 1rem .75rem;
  border: 1px solid var(--qc-border);
  border-radius: var(--qc-radius-card);
  background: color-mix(in srgb, var(--qc-primary) 3%, var(--qc-card-bg));
}
.qc-windows__hint { margin: .625rem 0 0; font-size: var(--qc-fs-xs); color: var(--qc-text-secondary); }

.qc-timeline--edit { --qc-tl-h: 2.25rem; }
.qc-timeline--edit .qc-timeline__track { border-radius: var(--qc-radius-sm); }
.qc-timeline--edit .qc-timeline__seg {
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--qc-radius-sm);
  box-shadow: 0 0 0 2px var(--qc-card-bg);
  color: var(--qc-text-primary);
  user-select: none;
  touch-action: none;
}
.qc-timeline--edit .qc-timeline__seg.is-done {
  background: repeating-linear-gradient(135deg,
    color-mix(in srgb, var(--qc-status-green) 55%, transparent) 0 6px,
    color-mix(in srgb, var(--qc-status-green) 30%, transparent) 6px 12px);
  cursor: not-allowed;
}
.qc-timeline--edit .qc-timeline__seg.is-movable { cursor: grab; }
.qc-timeline--edit .qc-timeline__seg.is-movable:hover { box-shadow: 0 0 0 2px var(--qc-card-bg), 0 0 0 4px color-mix(in srgb, var(--qc-primary) 45%, transparent); }
.qc-timeline--edit .qc-timeline__seg.is-dragging { cursor: grabbing; z-index: 3; box-shadow: 0 0 0 2px var(--qc-card-bg), var(--qc-shadow-md); transition: none; }
.qc-timeline--edit.is-dragging { cursor: grabbing; }
.qc-timeline--edit .qc-timeline__seg.is-overlap {
  background: color-mix(in srgb, var(--qc-error) 55%, transparent);
  box-shadow: 0 0 0 2px var(--qc-card-bg), 0 0 0 4px var(--qc-error);
}
.qc-timeline__n { font-size: var(--qc-fs-xs); font-weight: 700; pointer-events: none; }
.qc-timeline__handle {
  position: absolute;
  top: 0;
  bottom: 0;
  /* A quarter of the bar at most, so a narrow bar keeps a middle to grab. */
  width: min(.625rem, 25%);
  cursor: ew-resize;
}
.qc-timeline__handle::after {
  content: "";
  position: absolute;
  top: 25%;
  bottom: 25%;
  left: 50%;
  width: 2px;
  margin-left: -1px;
  border-radius: 1px;
  background: color-mix(in srgb, var(--qc-text-primary) 45%, transparent);
  opacity: 0;
  transition: opacity var(--qc-dur-fast) var(--qc-ease-standard);
}
.qc-timeline__seg:hover .qc-timeline__handle::after,
.qc-timeline__seg.is-dragging .qc-timeline__handle::after { opacity: 1; }
.qc-timeline__handle.is-start { left: 0; }
.qc-timeline__handle.is-end { right: 0; }
.qc-timeline__tip {
  position: absolute;
  bottom: calc(100% + .375rem);
  left: 50%;
  transform: translateX(-50%);
  padding: .125rem .5rem;
  border-radius: var(--qc-radius-sm);
  background: var(--qc-text-primary);
  color: var(--qc-card-bg);
  font-size: var(--qc-fs-xs);
  font-weight: 600;
  white-space: nowrap;
  pointer-events: none;
}
.qc-timeline--edit .qc-timeline__todaylabel { bottom: calc(100% + .125rem); }

.qc-windows { list-style: none; margin: 0 0 1rem; padding: 0; display: grid; gap: .5rem; }
.qc-windows__row {
  display: grid;
  grid-template-columns: minmax(12rem, 1fr) auto;
  gap: .25rem 1.5rem;
  align-items: center;
  padding: .625rem .875rem;
  border: 1px solid var(--qc-border);
  border-radius: var(--qc-radius-card);
  background: var(--qc-card-bg);
  transition: border-color var(--qc-dur-fast) var(--qc-ease-standard), background-color var(--qc-dur-fast) var(--qc-ease-standard);
}
.qc-windows__row.is-done { background: color-mix(in srgb, var(--qc-text-secondary) 5%, var(--qc-card-bg)); }
.qc-windows__row.is-touched {
  border-color: color-mix(in srgb, var(--qc-primary) 55%, var(--qc-border));
  background: color-mix(in srgb, var(--qc-primary) 5%, var(--qc-card-bg));
}
.qc-windows__row.has-error { border-color: var(--qc-error-text); }
.qc-windows__row.has-error .qc-windows__num { color: var(--qc-error-text); border-color: var(--qc-error-text); }
.qc-windows__row > .qc-form__error { grid-column: 1 / -1; }
.qc-windows__ident { display: flex; gap: .75rem; align-items: center; min-width: 0; }
.qc-windows__num {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.75rem; height: 1.75rem; border-radius: 50%;
  border: 1px solid var(--qc-border);
  font-size: var(--qc-fs-sm); font-weight: 600; color: var(--qc-text-secondary);
}
.qc-windows__row.is-touched .qc-windows__num { background: var(--qc-primary); border-color: var(--qc-primary); color: var(--qc-on-primary); }
.qc-windows__what { display: flex; flex-direction: column; min-width: 0; }
.qc-windows__what { gap: .3rem; }
.qc-windows__types { font-weight: 600; color: var(--qc-text-primary); }
/* What the job is doing: its status, the verdict on its window, its day and
   who goes — pills first, words after, wrapping under themselves. */
.qc-windows__meta {
  display: flex; flex-wrap: wrap; align-items: center; gap: .3rem .5rem;
  font-size: var(--qc-fs-sm); color: var(--qc-text-secondary);
}
.qc-windows__fact { display: inline-flex; align-items: center; gap: .25rem; white-space: nowrap; }
.qc-windows__fact .ti { color: var(--qc-text-tertiary); }
.qc-windows__dates {
  display: grid;
  grid-template-columns: repeat(2, minmax(9rem, 11rem));
  gap: .5rem 1rem;
  margin: 0;
}
.qc-windows__dates > * { margin: 0 !important; }
.qc-windows__dates .form-label { font-size: var(--qc-fs-xs); margin-bottom: .125rem; }
.qc-windows__fixed { color: var(--qc-text-secondary); font-size: var(--qc-fs-md); }
/* The save carries a dot once something moved: the page holds changes. */
.btn.is-dirty { position: relative; }
.btn.is-dirty::after {
  content: "";
  position: absolute;
  top: -.25rem;
  right: -.25rem;
  width: .625rem;
  height: .625rem;
  border-radius: 50%;
  background: var(--qc-status-orange);
  box-shadow: 0 0 0 2px var(--qc-card-bg);
}
@media (max-width: 767.98px) {
  .qc-windows__row { grid-template-columns: 1fr; }
}
@media (prefers-reduced-motion: reduce) {
  .qc-windows__row, .qc-timeline__handle::after { transition: none; }
}

/* The theme writes `mb-3 qc-form__row` on every row. `mb-3` is what spaces a
   row on the pages still laid out in Bootstrap columns, where a row is an only
   child with no sibling to space against; inside the column the rhythm below
   takes over, so the Bootstrap margin is handed back. */
.qc-form .qc-form__row { margin-bottom: 0; }
.qc-form__row + .qc-form__row { margin-top: 1rem; }
/* A column of checkboxes reads as one list, not as a stack of separate fields,
   so it closes up. */
.qc-form__row--tight + .qc-form__row--tight { margin-top: .375rem; }

/* An input as wide as what goes into it. A three-character abbreviation in a
   42rem field tells the reader to write an essay; the width is the first hint
   anybody gets about what is expected. */
.qc-form__row--tiny  :is(.form-control, .form-select, .ts-wrapper) { max-width: 7rem; }
.qc-form__row--short :is(.form-control, .form-select, .ts-wrapper) { max-width: 16rem; }

/* And where the input type already says how much goes in, no class is needed:
   thirteen of the settings are counts and thresholds, and each of them was a
   42rem box holding the number 12. */
.qc-form input[type="number"] { max-width: 8rem; }
.qc-form input[type="time"] { max-width: 8rem; }
.qc-form input[type="date"] { max-width: 11rem; }

/* One option per row, drawn as a card: the same shape the modules picker uses,
   because they are two tabs of the same form. The <label> is the card, so the
   whole row is the hit area, and the radio inside it stays a real radio. */
.qc-form__pick { display: flex; flex-direction: column; gap: .5rem; }

.qc-form__option {
  display: flex;
  align-items: center;
  gap: .75rem;
  margin: 0;
  padding: .75rem 1rem;
  /* A choice is a control, outlined like a field (--qc-field-border): on the
     dark card the hairline left the unchosen options without an edge. */
  border: 1px solid var(--qc-field-border);
  border-radius: var(--qc-radius-card);
  background: var(--qc-surface-light);
  cursor: pointer;
  transition: border-color var(--qc-dur-standard) var(--qc-ease-standard),
              background-color var(--qc-dur-standard) var(--qc-ease-standard);
}
.qc-form__option:hover { border-color: var(--qc-primary); }
.qc-form__option:has(input:checked) {
  border-color: var(--qc-primary);
  background: color-mix(in srgb, var(--qc-primary) 8%, var(--qc-surface-light));
}
/* The focus ring belongs to the card, not to the radio buried on its right. */
.qc-form__option:has(input:focus-visible) {
  outline: 2px solid var(--qc-primary);
  outline-offset: 2px;
}
.qc-form__optionicon {
  display: flex;
  font-size: 1.25rem;
  color: var(--qc-text-secondary);
}
.qc-form__option:has(input:checked) .qc-form__optionicon { color: var(--qc-primary); }
/* An option the sheet cannot take right now (the invitation with no e-mail):
   no hover, no pointer, its words in the tertiary ink — never opacity. */
.qc-form__option.is-disabled { cursor: not-allowed; background: transparent; border-style: dashed; }
.qc-form__option.is-disabled:hover { border-color: var(--qc-border); }
.qc-form__option.is-disabled .qc-form__optionname,
.qc-form__option.is-disabled .qc-form__optionlede,
.qc-form__option.is-disabled .qc-form__optionicon { color: var(--qc-text-tertiary); }
.qc-form__optionname { display: block; font-weight: 600; }
.qc-form__optionlede { display: block; font-size: var(--qc-fs-md); color: var(--qc-text-secondary); }

/* The tile: up to four options side by side, two on a phone (qc_variant
   'tile'). An icon over a word, and no taller than that: the first question of
   a form should not take half the screen before the fields it decides. */
.qc-form__tiles {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: .5rem;
}
@media (max-width: 575.98px) {
  .qc-form__tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.qc-form__option--tile {
  flex-direction: column;
  justify-content: center;
  gap: .25rem;
  padding: .625rem .5rem;
  text-align: center;
  line-height: 1.2;
}
.qc-form__option--tile .qc-form__optionicon { font-size: 1.375rem; }
.qc-form__option--tile .qc-form__optionname { font-size: var(--qc-fs-md); }
.qc-form__option--tile .qc-form__optionlede { font-size: var(--qc-fs-xs); line-height: 1.3; }

/* A customer's phone rows (customer/_phones.html.twig): the number wide, what
   it is beside it, the remove button level with the inputs, the rows closed up
   into one list. */
.qc-phone + .qc-phone { margin-top: .5rem; }
/* Doubled class: `.qc-form__pair` is declared further down and would win.
   The button is pushed down by a label's height (1.25rem line + .5rem gap)
   and is an input tall, so it sits on the inputs' centre line whether or not
   a row carries an error under it. */
.qc-form__pair.qc-phone > .qc-phone__remove {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  margin-top: 1.75rem;
  height: 2.5rem;
}
/* The labels are said once, over the first row: the rows under it keep them
   for a screen reader and drop them from sight, and so drop the push-down. */
.qc-phone + .qc-phone .form-label {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.qc-form__pair.qc-phone + .qc-phone > .qc-phone__remove { margin-top: 0; }
.qc-phone__add { margin-top: .75rem; }

@media (prefers-reduced-motion: reduce) {
  .qc-form__option { transition: none; }
}

/* ------------------------------------------------------------------ *
 * Hints
 * ------------------------------------------------------------------
 * `.form-hint` was emitted by thirteen templates and styled by none of
 * them, so it fell through to whatever the Tabler CDN happened to say.
 * Symfony's bootstrap_5 theme spells the same thing `.form-text`. Both
 * are drawn here: the house theme emits `.form-hint`, and a template
 * that still writes the other one does not silently lose its styling.
 * ------------------------------------------------------------------ */
.form-hint,
.form-text {
  display: block;
  margin-top: .35rem;
  margin-bottom: 0;
  font-size: var(--qc-fs-md);
  line-height: 1.4;
  color: var(--qc-text-tertiary);
}

/* ------------------------------------------------------------------ *
 * Validation
 * ------------------------------------------------------------------
 * Nothing in the project styled `.is-invalid`, so an invalid field came
 * out in Bootstrap's own red (#d63939) — not a Quercia colour, and as
 * text it does not clear AA on the warm-white canvas. The vivid
 * --qc-error is right for a border or a fill; --qc-error-text is the
 * darkened variant the token layer keeps for anything readable.
 *
 * Keyed off `.is-invalid` and never off `:invalid`, because `:invalid`
 * is true for a required field nobody has reached yet — which is how a
 * form ends up shouting at somebody who has not done anything wrong.
 * ------------------------------------------------------------------ */
.form-control.is-invalid,
.form-select.is-invalid,
textarea.is-invalid,
.ts-wrapper.is-invalid .ts-control {
  border-color: var(--qc-error) !important;
  background-color: color-mix(in srgb, var(--qc-error) 8%, var(--qc-field-bg)) !important;
  /* Bootstrap paints a warning glyph inside the field. The message below
     carries the icon instead, where it sits beside the words it explains. */
  background-image: none !important;
  padding-right: .75rem;
}
.form-control.is-invalid:focus,
.form-select.is-invalid:focus,
textarea.is-invalid:focus,
.ts-wrapper.is-invalid.focus .ts-control {
  border-color: var(--qc-error) !important;
  box-shadow: 0 0 0 .2rem color-mix(in srgb, var(--qc-error) 30%, transparent) !important;
}

/* The message, under the field it belongs to — never only at the top of
   the page. The icon is not decoration: colour is not a signal every
   reader receives, so the state is always said twice. */
.qc-form__error {
  display: flex;
  align-items: flex-start;
  gap: .35rem;
  margin-top: .35rem;
  font-size: var(--qc-fs-md);
  line-height: 1.4;
  color: var(--qc-error-text);
}
.qc-form__error::before {
  content: "";
  flex: none;
  width: 1rem;
  height: 1rem;
  margin-top: .1rem;
  background-color: currentColor;
  -webkit-mask: var(--qc-icon-alert) center / contain no-repeat;
          mask: var(--qc-icon-alert) center / contain no-repeat;
}

/* Bootstrap's own class, still emitted by work_schedule_controller.js.
   Re-tinted only — its display stays Bootstrap's business, so an empty
   one cannot appear. */
.invalid-feedback { color: var(--qc-error-text); font-size: var(--qc-fs-md); }

/* ------------------------------------------------------------------ *
 * Checkboxes, switches, and controls nobody can change
 * ------------------------------------------------------------------ */
.form-check-input {
  background-color: var(--qc-field-bg);
  /* Not the field's hairline: a tick box is nothing but its edge. */
  border-color: var(--qc-control-border);
}
.form-check-input:checked {
  background-color: var(--qc-primary);
  border-color: var(--qc-primary);
}
/* Tabler draws the tick, the radio's dot and the switch's knob as white SVGs.
   On the dark theme's mint that is 1.7:1 — a ticked box read as an empty green
   one. Same drawings in on-primary (#04120A; a data URI cannot read a token). */
[data-bs-theme="dark"] .form-check-input:checked[type="checkbox"] {
  --tblr-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' width='16' height='16'%3e%3cpath fill='none' stroke='%2304120A' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8.5l2.5 2.5l5.5 -5.5'/%3e%3c/svg%3e");
}
[data-bs-theme="dark"] .form-check-input:checked[type="radio"] {
  --tblr-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3ccircle r='3' fill='%2304120A' cx='8' cy='8' /%3e%3c/svg%3e");
}
[data-bs-theme="dark"] .form-switch .form-check-input:checked {
  --tblr-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2304120A'/%3e%3c/svg%3e");
}
.form-check-label { color: var(--qc-text-primary); }

/* Dim the field, never the text — quercia-viva §9.7. `opacity: 1` is there
   to undo Bootstrap, which dims the whole control including its label. */
.form-check-input:disabled {
  opacity: 1;
  background-color: var(--qc-surface-light);
  border-color: var(--qc-border);
}
.form-check-input:disabled ~ .form-check-label { color: var(--qc-text-tertiary); }
/* A field nobody can change had the fill of one that can, and nothing else to
   tell them apart. A dashed edge and the tertiary ink: it is there to be read. */
.form-control:disabled,
.form-control[readonly],
.form-select:disabled {
  opacity: 1;
  background-color: var(--qc-surface-well);
  border-style: dashed;
  color: var(--qc-text-tertiary);
}
.input-group-text { color: var(--qc-text-secondary); }

/* The "what is this?" beside a label, opening the field's help drawer: the
   icon and nothing around it. Doubled with `.btn` because the button recipe
   (components.css) gives every .btn a 40px minimum, a radius and a shadow,
   which drew a box around a 14px glyph. */
.btn.qc-form__helpbtn,
.qc-form__helpbtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 1.1rem;
  height: 1.1rem;
  min-width: 0;
  min-height: 0;
  padding: 0;
  border: 0;
  border-radius: 999px;
  box-shadow: none;
  font-size: var(--qc-fs-md);
  line-height: 1;
  color: var(--qc-text-tertiary);
  background: none;
}
.btn.qc-form__helpbtn:hover,
.btn.qc-form__helpbtn:active { color: var(--qc-primary); background: none; box-shadow: none; transform: none; }

/* ------------------------------------------------------------------ *
 * The one sanctioned two-column row
 * ------------------------------------------------------------------
 * Two inputs that are a single datum — a start and an end, a quantity
 * and its unit. The test is not "both are short"; it is "reading one
 * without the other is meaningless".
 * ------------------------------------------------------------------ */
.qc-form__pair {
  display: flex;
  flex-wrap: wrap;
  gap: .75rem;
  align-items: flex-start;
}
.qc-form__pair > * { flex: 1 1 12rem; min-width: 0; }
/* The halves sit side by side, so the rule that spaces one row under the
   previous must not push the second half down (it did, by 1rem). */
.qc-form__pair > .qc-form__row + .qc-form__row { margin-top: 0; }
/* The halves of one datum are rarely the same size: a street is not a house
   number, a town is not a postcode. */
.qc-form__pair > .is-wide { flex-grow: 3; }
.qc-form__pair > .is-narrow { flex: 0 1 8rem; }
@media (max-width: 575.98px) {
  .qc-form__pair > *,
  .qc-form__pair > .is-narrow { flex: 1 1 100%; }
}

/* ------------------------------------------------------------------ *
 * The bar that closes the form
 * ------------------------------------------------------------------
 * It closes the *form*, not the column: it spans the card it sits in, so
 * the primary lands on the same edge as everything else on the page. A bar
 * capped at the reading measure puts the save button in the middle of a
 * wide card, which reads as an alignment somebody missed.
 *
 * `.qc-settings__actions` was the settings pages' private name for the
 * same thing and is gone; both templates that used it now say
 * `.qc-form__actions`.
 * ------------------------------------------------------------------ */
.qc-form__actions {
  display: flex;
  align-items: center;
  gap: .75rem;
  margin-top: 1.25rem;
  padding-top: 1rem;
  border-top: 1px solid var(--qc-divider);
}
/* The primary sits right, and whatever else is in the bar stays left of it.
   The selector is deliberately a child selector: a bar whose buttons somebody
   wraps in a `.btn-list` stops matching and loses the alignment silently, so
   the buttons are the bar's own children everywhere. */
.qc-form__actions > [type="submit"],
.qc-form__actions > .btn-primary { margin-left: auto; }

/* When the bar already sits in something that separates it — a card-footer,
   a modal footer — it brings no rule and no gap of its own. */
.qc-form__actions--bare {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}

/* Facts a form does not decide.
 *
 * The username somebody signs in with, the rung they stand on, the sigla the
 * planner writes on a job: an administrator's decisions, shown on the account
 * page beside the four fields that are the person's own. A disabled input would
 * have said the same thing and invited them to try anyway; a definition list
 * says it is a fact and stops. */
.qc-facts { margin: 0; }
.qc-facts__row {
  display: flex;
  gap: 1rem;
  align-items: baseline;
  padding: .5rem 0;
}
.qc-facts__row + .qc-facts__row { border-top: 1px solid var(--qc-divider); }
.qc-facts dt {
  flex: 0 0 12rem;
  font-weight: 600;
  font-size: var(--qc-fs-sm);
  color: var(--qc-text-secondary);
}
.qc-facts dd { margin: 0; color: var(--qc-text-primary); }
@media (max-width: 575.98px) {
  .qc-facts__row { flex-direction: column; gap: .125rem; }
  .qc-facts dt { flex: none; }
}

/* The attachment bar: what is already pinned to a note, and one tile to add
 * more.
 *
 * The tiles were sized by an inline `style` on every one of them — 80px twice,
 * a dashed border with an `!important` to beat Bootstrap's, and a remove button
 * eighteen pixels square built out of `position-absolute top-0 end-0 p-0` and
 * three more declarations. A hex never leaked in only because nothing here was
 * coloured; the sizes still belonged in a stylesheet. */
.qc-attachments {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  margin-top: .25rem;
}
.qc-attachment {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 5rem;
  height: 5rem;
  padding: .25rem;
  overflow: hidden;
  cursor: pointer;
  border: 1px solid var(--qc-border);
  border-radius: var(--qc-radius-btn);
  background: var(--qc-field-bg);
}
.qc-attachment img {
  max-width: 100%;
  max-height: 100%;
  object-fit: cover;
  border-radius: calc(var(--qc-radius-btn) - .25rem);
}
.qc-attachment--add {
  border-style: dashed;
  color: var(--qc-text-tertiary);
}
.qc-attachment--add:hover {
  border-color: var(--qc-primary);
  color: var(--qc-primary);
}
/* The × is small because it is on top of the thing it removes. It gets the
   error tint rather than a filled red button, which at 18px was a red dot with
   no readable glyph in it. */
.qc-attachment__remove {
  position: absolute;
  top: .125rem;
  right: .125rem;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.25rem;
  height: 1.25rem;
  padding: 0;
  font-size: var(--qc-fs-xs);
  line-height: 1;
  border: 0;
  border-radius: 999px;
  color: var(--qc-error-text);
  background: color-mix(in srgb, var(--qc-error) 22%, var(--qc-card-bg));
}
.qc-attachment__remove:hover {
  color: var(--qc-on-primary);
  background: var(--qc-error);
}

/* The tile opens the attachment preview through a link that covers it
   (components/_attachment.html.twig). The × stays a sibling of that link —
   a button inside an <a> is not valid HTML — and sits above its cover. */
.qc-attachment__open {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: .25rem;
  color: inherit;
  border-radius: inherit;
}
.qc-attachment__open:focus-visible {
  outline: 2px solid var(--qc-primary);
  outline-offset: 2px;
}
.qc-attachment__remove { z-index: 1; }

/* A tile whose file is still going up. The ring is the brand's loading mark at
   the only size that fits inside an 80px tile. */
.qc-attachment--pending { cursor: default; }
.qc-attachment__pending {
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 999px;
  border: 2px solid color-mix(in srgb, var(--qc-primary) 25%, transparent);
  border-top-color: var(--qc-primary);
  animation: qc-attachment-spin 900ms linear infinite;
}
@keyframes qc-attachment-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .qc-attachment__pending { animation: none; border-top-color: var(--qc-primary); }
}

/* A delete never shares a row with a save. It goes to the bottom, in its
   own zone, and it asks through QcDialog before it happens. */
.qc-form__danger {
  margin-top: 2rem;
  padding: 1rem 1.25rem;
  border: 1px solid color-mix(in srgb, var(--qc-error) 35%, transparent);
  border-radius: var(--qc-radius-card);
  background: color-mix(in srgb, var(--qc-error) 6%, var(--qc-card-bg));
}
/* The same box for a repair that is not destructive — the customer's
   recalculation, beside its delete: neutral border, primary-ink heading. */
.qc-form__danger--neutral {
  border-color: var(--qc-border);
  background: var(--qc-surface-light);
}
.qc-form__danger--neutral h3 { color: var(--qc-text-primary); }
.qc-form__danger--neutral + .qc-form__danger { margin-top: 1rem; }
.qc-form__danger h3 {
  margin: 0 0 .125rem;
  font-family: var(--qc-font-display);
  font-size: var(--qc-fs-xl);
  font-weight: 700;
  color: var(--qc-error-text);
}
.qc-form__danger p {
  margin: 0 0 .75rem;
  font-size: var(--qc-fs-md);
  color: var(--qc-text-secondary);
}

/* Bootstrap makes a compound field's label a <legend class="col-form-label">,
   which floats and takes the full width. Inside the column it is a field label
   like any other. */
.qc-form fieldset > legend {
  float: none;
  width: auto;
  padding: 0;
  font-size: var(--qc-fs-lg);
}
/* ...and it reads like one: the size, weight, colour and gap of `.form-label`.
   With none of them the legend came out larger, paler-weighted and sitting on
   the first option (a tick list under "Chi ne fa parte" had no room at all). */
.qc-form fieldset.qc-form__row > legend.col-form-label {
  margin-bottom: .5rem;
  font-size: var(--qc-fs-md);
  font-weight: 600;
  line-height: 1.25rem;
  color: var(--qc-text-secondary);
}

/* Segmented control (all-day vs time-range) */
.qc-seg {
    display: inline-flex; padding: 3px; gap: 3px; margin-top: .25rem;
    background: var(--qc-surface-light); border: 1px solid var(--qc-field-border);
    border-radius: var(--qc-radius-pill);
}
.qc-seg__btn {
    appearance: none; border: 0; background: transparent; cursor: pointer;
    color: var(--qc-text-secondary); font: 600 var(--qc-fs-md)/1 var(--qc-font-body);
    padding: .45rem .95rem; border-radius: var(--qc-radius-pill);
    transition: background-color var(--qc-dur-fast) var(--qc-ease-fast),
                color var(--qc-dur-fast) var(--qc-ease-fast),
                box-shadow var(--qc-dur-fast) var(--qc-ease-fast);
}
.qc-seg__btn:hover { color: var(--qc-text-primary); }
/* The chosen segment, once, for the three ways a segmented control is written
   — buttons a script drives, radios, and the plan board's two links. A wash
   of primary with the label in primary's ink: lifted onto the card's surface
   it stood 1.13:1 off its track, and which of two segments was chosen had to
   be read off a shadow. */
.qc-seg__btn.is-active,
.qc-seg__seg input:checked + .qc-seg__face,
.qc-planview.is-on {
  background: var(--qc-layer-wash);
  color: var(--qc-primary-ink);
}
.qc-seg__btn:focus-visible { outline: 2px solid var(--qc-primary); outline-offset: 2px; }
/* A segment that navigates instead of submitting. The site pivot's axis is
   drawn server-side, so choosing one is a link — and a link left to itself
   arrives underlined and baseline-aligned inside the pill. */
a.qc-seg__btn { display: inline-flex; align-items: center; text-decoration: none; }

/* ---- When: the day and its hours, one datum (templates/work/_schedule.html.twig)
   One box round the date, the "to be set / a time range" switch and the hours,
   so the three read as one answer rather than as four rows among other dates.
   A hairline and the sunken surface, no shadow: a group inside the card, not a
   card inside the card. */
.qc-sched {
    max-width: var(--qc-form-measure, 42rem);
    padding: 1rem 1.125rem;
    border: 1px solid var(--qc-border);
    border-radius: var(--qc-radius-card);
    background: color-mix(in srgb, var(--qc-field-bg) 55%, var(--qc-card-bg));
}
.qc-sched .qc-form__row { margin-bottom: 0; }
.qc-sched__when { display: flex; flex-wrap: wrap; align-items: flex-start; gap: .75rem 1.25rem; }
/* The two labels on one line: the date's is a <label class="form-label">, the
   switch's sits in a flex row with its `?`, so both take the label's margin. */
.qc-sched__mode .qc-seg { margin-top: 0; }
.qc-sched__hint { margin: .5rem 0 0; color: var(--qc-text-secondary); font-size: var(--qc-fs-sm); }
.qc-sched__hint[hidden] { display: none; }
.qc-sched__day { flex: 0 1 12rem; min-width: 10rem; }
.qc-sched__day .qc-form__row { margin-bottom: 0; }
.qc-sched__range { margin-top: 1rem; padding-top: 1rem; border-top: 1px dashed var(--qc-border); }
.qc-sched__range[hidden] { display: none; }
.qc-sched__times {
  display: grid;
  grid-template-columns: auto auto auto;
  grid-template-areas: "lstart . lend" "start dash end";
  justify-content: start;
  column-gap: .75rem;
}
.qc-sched__lbl-start { grid-area: lstart; }
.qc-sched__lbl-end { grid-area: lend; }
.qc-sched__box-start { grid-area: start; }
.qc-sched__box-end { grid-area: end; }
.qc-sched__times .ts-wrapper,
.qc-sched__times .form-control { width: 7rem; max-width: 7rem; }
.qc-sched__arrow { grid-area: dash; align-self: center; color: var(--qc-text-tertiary); }
/* A time typed or picked (assets/qc_time_select.js): the job's hours and the
   report's lengths alike. One line: Tom Select draws the chosen item and then
   its input, and in a 7rem box the input fell to a second line. The item is
   drawn as the field's text, not as a pill — `!important` against
   components.css's chip recipe, which sets every property of `.ts-control
   .item` that way — and the input only takes room while typing. */
.ts-wrapper.qc-timesel .ts-control { flex-wrap: nowrap; overflow: hidden; }
.ts-wrapper.qc-timesel .ts-control > .item {
  background: none !important; border: 0 !important; padding: 0 !important; margin: 0;
  color: var(--qc-text-primary) !important; font-size: inherit; font-weight: 400;
  font-variant-numeric: tabular-nums;
}
.ts-wrapper.qc-timesel .ts-control > input { flex: 1 1 0; min-width: 0; width: 1ch !important; }
.ts-wrapper.qc-timesel.has-items:not(.focus) .ts-control > input,
.ts-wrapper.qc-timesel.has-items.input-hidden .ts-control > input { position: absolute; opacity: 0; }
.qc-sched__chips { display: flex; flex-wrap: wrap; align-items: center; gap: .375rem; margin-top: .875rem; }
.qc-sched__chipslabel { color: var(--qc-text-secondary); font-size: var(--qc-fs-sm); font-weight: 600; margin-right: .25rem; }
/* A length none of the chips says, drawn like a pressed chip but not one. */
.qc-sched__len {
  padding: .3rem .75rem;
  border: 1px dashed var(--qc-primary);
  border-radius: var(--qc-radius-pill);
  color: var(--qc-primary);
  font-size: var(--qc-fs-sm); font-weight: 600;
}
.qc-sched__len[hidden] { display: none; }
.qc-sched__chip {
    appearance: none; cursor: pointer;
    padding: .3rem .75rem;
    border: 1px solid var(--qc-field-border);
    border-radius: var(--qc-radius-pill);
    background: var(--qc-card-bg);
    color: var(--qc-text-secondary);
    font: 600 var(--qc-fs-sm)/1.2 var(--qc-font-body);
    transition: background-color var(--qc-dur-fast) var(--qc-ease-fast),
                border-color var(--qc-dur-fast) var(--qc-ease-fast),
                color var(--qc-dur-fast) var(--qc-ease-fast);
}
.qc-sched__chip:hover { color: var(--qc-text-primary); border-color: var(--qc-primary); }
.qc-sched__chip.is-active {
    color: var(--qc-primary);
    border-color: var(--qc-primary);
    background: color-mix(in srgb, var(--qc-primary) 10%, var(--qc-card-bg));
}
.qc-sched__chip:focus-visible { outline: 2px solid var(--qc-primary); outline-offset: 2px; }
/* The list of hours: the hour, and on the end's list how long it would last. */
/* The list is wider than the 7rem field it drops from, or "1h 30m" wraps under
   every hour; it is parented to <body>, hence its own class. */
.ts-dropdown.qc-sched__dropdown { min-width: 11rem; }
.qc-sched__opt { display: flex; justify-content: space-between; gap: 1rem; white-space: nowrap; font-variant-numeric: tabular-nums; }
.qc-sched__optlen { color: var(--qc-text-tertiary); font-size: var(--qc-fs-sm); }
@media (max-width: 575.98px) {
    .qc-sched { padding: .875rem; }
    .qc-sched__day { flex-basis: 100%; }
}

@media (prefers-reduced-motion: reduce) {
    .qc-seg__btn,
    .qc-sched__chip { transition: none; }
}

/* ------------------------------------------------------------------ *
 * Segmented control, on real radios
 * ------------------------------------------------------------------
 * `.qc-seg` above is button-based and driven by JavaScript, so it can
 * only carry role="group" — which tells assistive technology that these
 * things belong together and nothing at all about which one is chosen.
 * A choice field renders as this instead: the same pill, built from the
 * radios Symfony already emits, so the arrow keys, the selected state
 * and the screen reader come for free. New segmented controls use this;
 * the button-based one survives only in templates/work/edit.html.twig.
 * ------------------------------------------------------------------ */
.qc-seg__seg {
  position: relative;
  display: inline-flex;
  margin: 0;
}
/* Out of sight, not removed: it keeps focus and arrow-key behaviour. */
.qc-seg__seg input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  margin: 0;
  pointer-events: none;
}
.qc-seg__face {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: var(--qc-text-secondary);
  font: 600 var(--qc-fs-md)/1 var(--qc-font-body);
  padding: .45rem .95rem;
  border-radius: var(--qc-radius-pill);
  transition: background-color var(--qc-dur-fast) var(--qc-ease-fast),
              color var(--qc-dur-fast) var(--qc-ease-fast),
              box-shadow var(--qc-dur-fast) var(--qc-ease-fast);
}
.qc-seg__seg:hover .qc-seg__face { color: var(--qc-text-primary); }
/* The ring goes on the face, which is what the eye is actually looking at. */
.qc-seg__seg input:focus-visible + .qc-seg__face {
  outline: 2px solid var(--qc-primary);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .qc-seg__face { transition: none; }
}

/* ---------------------------------------------------------------------------
   Swatch pickers — the status colour and the status icon
   ---------------------------------------------------------------------------
   Both choices are visual, so the control shows the result rather than its name
   (see system/settings/_swatches.html.twig). The radio is moved out of sight,
   never hidden: it keeps its place in the tab order and the arrow keys keep
   working, which `display: none` would take away.
*/
.qc-swatches {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
}

.qc-swatch {
  position: relative;
  display: inline-flex;
  cursor: pointer;
  border-radius: var(--qc-radius-btn);
}

.qc-swatch__input {
  position: absolute;
  inset: 0;
  opacity: 0;
  margin: 0;
  cursor: pointer;
}

.qc-swatch__face {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: var(--qc-radius-btn);
  border: 1px solid var(--qc-border);
  background: var(--qc-surface);
  color: var(--qc-text-secondary);
  transition: box-shadow var(--qc-dur-fast) var(--qc-ease-fast),
              border-color var(--qc-dur-fast) var(--qc-ease-fast);
}

/* The colour swatch is the badge: the tint at the strength a chip wears it. */
.qc-swatches--color .qc-swatch__face {
  background: var(--qc-swatch);
  border-color: color-mix(in srgb, var(--qc-swatch) 60%, transparent);
}
/* "No colour", on an optional colour: the neutral chip, with a mark that says
   it is the absence of one rather than a pale grey. */
.qc-swatches--color .qc-swatch--none .qc-swatch__face {
  background: var(--qc-surface);
  border: 1px dashed var(--qc-border);
  color: var(--qc-text-tertiary);
}

.qc-swatch:hover .qc-swatch__face {
  border-color: var(--qc-text-tertiary);
}

/* The ring goes outside the swatch so the colour underneath stays the colour
   being chosen — an inset ring would eat into the sample. */
.qc-swatch__input:checked + .qc-swatch__face {
  border-color: var(--qc-primary);
  box-shadow: 0 0 0 2px var(--qc-primary);
}

.qc-swatches--icon .qc-swatch__input:checked + .qc-swatch__face {
  color: var(--qc-primary);
}

.qc-swatch__input:focus-visible + .qc-swatch__face {
  outline: 2px solid var(--qc-primary);
  outline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
  .qc-swatch__face { transition: none; }
}

/* ------------------------------------------------------------------ */
/* Tick boxes in families — the permissions matrix (forms.md §4, §7)   */
/* ------------------------------------------------------------------ */

/* A long list of tick boxes in families. One fieldset per family, each
   item a label that carries its word and the line under it, closed up to
   the list rhythm. Not cards: forty-nine cards is a wall. */
.qc-form__checklist { display: flex; flex-direction: column; gap: 1.25rem; }
.qc-form__checkgroup { margin: 0; padding: 0; border: 0; min-width: 0; }
.qc-form__checkgrouptitle {
  float: none; width: auto; padding: 0; margin: 0 0 .25rem;
  font-size: var(--qc-fs-sm); font-weight: 600; color: var(--qc-text-secondary);
}
.qc-form__check {
  display: grid; grid-template-columns: auto 1fr; column-gap: .625rem; align-items: start;
  margin: 0; padding: .4rem 0; cursor: pointer;
}
.qc-form__check + .qc-form__check { border-top: 1px solid var(--qc-divider); }
.qc-form__check .form-check-input { margin-top: .2rem; }
.qc-form__checkname { display: block; font-weight: 500; }
.qc-form__checklede { display: block; font-size: var(--qc-fs-sm); color: var(--qc-text-secondary); }
/* Locked: the box says it cannot change, the note above the group says why. */
.qc-form__check:has(input:disabled) { cursor: default; }
.qc-form__check:has(input:disabled) .qc-form__checkname { color: var(--qc-text-secondary); }
/* The ring belongs to the row, not to the box. */
.qc-form__check:has(input:focus-visible) {
  outline: 2px solid var(--qc-primary); outline-offset: 2px; border-radius: var(--qc-radius-input, .5rem);
}

/* A swatch drawn as a webfont glyph sits like the SVG ones do. */
.qc-swatch__face > .ti { font-size: 1.25rem; line-height: 1; }

/* ---- A form in steps (templates/components/_step_form.html.twig) --------- *
 * The rail of steps on the left, the open step on the right, the bar of
 * actions under it — every stepped page (forms.md §1.6). It replaced a
 * stepper across the top: steps with their hints did not fit a line, the
 * active dot's glow was clipped by the stepper's own scroll box, and the
 * step's name was said twice.
 *
 * Laid out off the component's own width (a container query), not the
 * window's: the job page is as wide as the screen and a new person is 60rem,
 * and the rail needs room for itself *and* a materials table beside it.
 * Tokens only; no backdrop-filter. */
.qc-stepform {
  container: qc-stepform / inline-size;
  border-radius: var(--qc-radius-card);
}
/* A card clips its corners with overflow: hidden, which makes it a scroll box
   and leaves the sticky rail and bar with nothing to stick to. clip rounds the
   same corners and is not one. */
.card:has(.qc-stepform) { overflow: clip; }
/* The card of a stepped page whose steps are one column of fields: the rail
   plus the 42rem column and its gutters, and no more. Stretched to the window,
   the column sat on the left of a card twice its width, with the bar's save
   a screen away from the last field. A step holding a line editor (the job's
   materials sheet, the document wizards' lines) is what may still need the
   whole width, and that page leaves the class off. */
.qc-stepcard { max-width: 60rem; }
/* Under a frame that stays on screen from every step (the job page's hero,
   its errors): a hairline, and no rounding where the two meet. */
* + .qc-stepform {
  border-top: 1px solid var(--qc-divider);
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}
.qc-stepform__grid {
  display: grid;
  grid-template-columns: 15.5rem minmax(0, 1fr);
  min-height: 32rem;
  border-radius: inherit;
  /* clip, not hidden: hidden would make this a scroll box and the sticky rail
     and bar below would stick to nothing. */
  overflow: clip;
}
.qc-stepform__rail {
  padding: 1.75rem 1.25rem;
  background: color-mix(in srgb, var(--qc-primary) 5%, var(--qc-surface-light));
  border-right: 1px solid var(--qc-divider);
}
/* A long step (the job page) scrolls; its steps stay in reach beside it. */
.qc-stepform__rail > * { position: sticky; top: 1rem; }
.qc-stepform__rail > .qc-stepform__railtitle { position: static; }
.qc-stepform__railtitle {
  margin: 0 0 1.25rem .25rem;
  font-family: var(--qc-font-display);
  font-weight: 800;
  font-size: var(--qc-fs-xl);
  color: var(--qc-text-primary);
}
.qc-stepform__steps { list-style: none; margin: 0; padding: 0; }
/* The thread between the dots. */
.qc-stepform__steps li { position: relative; }
.qc-stepform__steps li + li::before {
  content: "";
  position: absolute;
  left: 1.3rem;
  top: -.625rem;
  height: 1.25rem;
  width: 2px;
  border-radius: 2px;
  background: var(--qc-divider);
}
.qc-stepform__step {
  display: flex;
  align-items: center;
  gap: .75rem;
  width: 100%;
  margin: .625rem 0;
  padding: .375rem .5rem .375rem .375rem;
  border: 0;
  border-radius: 14px;
  background: transparent;
  text-align: left;
  color: var(--qc-text-secondary);
  font: inherit;
  cursor: pointer;
  transition: background var(--qc-dur-fast) var(--qc-ease-fast);
}
.qc-stepform__step:hover { background: var(--qc-layer-hover); }
.qc-stepform__step:focus-visible { outline: 2px solid var(--qc-primary); outline-offset: 2px; }
.qc-stepform__dot {
  position: relative;
  flex: none;
  display: grid;
  place-items: center;
  width: 2.1rem;
  height: 2.1rem;
  border-radius: 999px;
  border: 1.5px solid var(--qc-border);
  background: var(--qc-surface);
  color: var(--qc-text-secondary);
  font-size: var(--qc-fs-lg);
  transition: background var(--qc-dur-standard) var(--qc-ease-standard),
              border-color var(--qc-dur-standard) var(--qc-ease-standard),
              box-shadow var(--qc-dur-standard) var(--qc-ease-standard);
}
.qc-stepform__donemark { display: none; }
.qc-stepform__label { display: grid; justify-items: start; min-width: 0; }
.qc-stepform__name { font-weight: 600; color: var(--qc-text-primary); }
.qc-stepform__hint { font-size: var(--qc-fs-sm); color: var(--qc-text-tertiary); }
.qc-stepform__step.is-active { background: var(--qc-surface); box-shadow: var(--qc-shadow-sm); }
.qc-stepform__step.is-active .qc-stepform__dot {
  background: var(--qc-primary);
  border-color: var(--qc-primary);
  color: var(--qc-on-primary);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--qc-primary) 18%, transparent);
}
.qc-stepform__step.is-active .qc-stepform__name { color: var(--qc-primary); }
/* Done: set by the controller (qc_steps.js), and said in words to a reader
   that cannot see the tick. */
.qc-stepform__step.is-done .qc-stepform__dot {
  border-color: color-mix(in srgb, var(--qc-primary) 55%, var(--qc-border));
  color: var(--qc-primary);
}
.qc-stepform__step.is-done .qc-stepform__doticon { display: none; }
.qc-stepform__step.is-done .qc-stepform__donemark { display: block; }
.qc-stepform__state--done { display: none; }
.qc-stepform__step.is-done .qc-stepform__state--done { display: block; }
/* A field to fix in this step — before the server (the step controller's own
   check) or after it (a refusal). Red on the dot and the name, over done and
   over open alike, and said in words; never colour alone. */
.qc-stepform__state--error { display: none; }
.qc-stepform__step.has-error .qc-stepform__state--error { display: block; }
.qc-stepform__step.has-error .qc-stepform__state--done { display: none; }
.qc-stepform__step.has-error .qc-stepform__dot {
  border-color: var(--qc-error);
  color: var(--qc-error-text);
  background: color-mix(in srgb, var(--qc-error) 12%, var(--qc-surface));
}
.qc-stepform__step.has-error .qc-stepform__doticon { display: none; }
.qc-stepform__step.has-error .qc-stepform__donemark { display: none; }
.qc-stepform__step.has-error .qc-stepform__dot::after {
  content: "!";
  font-weight: 800;
  font-size: var(--qc-fs-lg);
  line-height: 1;
}
.qc-stepform__step.has-error .qc-stepform__name { color: var(--qc-error-text); }
.qc-stepform__step.is-active.has-error .qc-stepform__dot {
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--qc-error) 22%, transparent);
}
/* The thread is walked up to the open step. */
.qc-stepform__steps li:has(~ li .is-active) + li::before,
.qc-stepform__steps li:has(.is-active)::before { background: color-mix(in srgb, var(--qc-primary) 55%, var(--qc-divider)); }

/* The flag a step carries: something owed to the reader before they open it
   (the job's materials sheet: still open, still short of a figure). The
   state's own pill (.qc-stepform__flag > .qc-stpill, drawn by
   work_wizard_controller): words first, the colour only confirms them. */
.qc-stepform__flag { display: flex; margin-top: .25rem; }
.qc-stepform__flag:empty { display: none; }

/* With the words folded away (the row of dots) the dot keeps the signal —
   never on its own: opening the step says in words what it pointed at. */
.qc-stepform__pip {
  position: absolute; top: -2px; right: -2px;
  width: .7rem; height: .7rem; border-radius: 50%;
  background: var(--qc-warning);
  border: 2px solid var(--qc-card-bg);
  display: none;
}

.qc-stepform__main { display: flex; flex-direction: column; min-width: 0; }
.qc-stepform__body {
  /* What a step holds (the document wizards' lines) is laid out off the
     width it is given, which the rail beside it has already narrowed. */
  container: qc-steppanel / inline-size;
  flex: 1;
  padding: 1.75rem 2rem 1.25rem;
}
.qc-stepform__panel { display: none; }
.qc-stepform__panel.is-active { display: block; animation: qcStepIn var(--qc-dur-reveal) var(--qc-ease-standard); }
@keyframes qcStepIn { from { opacity: 0; transform: translateX(10px); } to { opacity: 1; transform: none; } }
.qc-stepform__head { margin-bottom: 1.5rem; }
.qc-stepform__eyebrow {
  margin: 0 0 .25rem;
  font-size: var(--qc-fs-xs);
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--qc-primary);
}
.qc-stepform__title {
  margin: 0;
  font-family: var(--qc-font-display);
  font-weight: 800;
  font-size: var(--qc-fs-3xl);
  letter-spacing: -.01em;
  color: var(--qc-text-primary);
}
/* Focused by script when the step opens; the ring would only say "here",
   which the heading already does. */
.qc-stepform__title:focus { outline: none; }
.qc-stepform__lede { margin: .375rem 0 0; color: var(--qc-text-secondary); max-width: 38rem; }
/* The bar stays on screen down a long step: saving is never a scroll away. */
.qc-stepform__nav {
  position: sticky;
  bottom: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: .75rem;
  padding: .875rem 2rem;
  border-top: 1px solid var(--qc-divider);
  background: var(--qc-card-bg);
}
.qc-stepform__navstart,
.qc-stepform__navend { display: flex; align-items: center; flex-wrap: wrap; gap: .5rem; min-width: 0; }
.qc-stepform__navend { margin-left: auto; justify-content: flex-end; }
/* The save line leads the buttons it is about. */
.qc-stepform__navend .qc-savestate-wrap { margin-right: .5rem; }
.qc-stepform__navstart:empty { display: none; }

/* Narrow: the rail folds into a row of dots over the step, the open one
   keeping its name; the eyebrow in the page still says "2 of 4". */
@container qc-stepform (max-width: 52rem) {
  .qc-stepform__grid { grid-template-columns: minmax(0, 1fr); min-height: 0; }
  .qc-stepform__rail { padding: .75rem 1rem; border-right: 0; border-bottom: 1px solid var(--qc-divider); }
  .qc-stepform__rail > * { position: static; }
  .qc-stepform__railtitle { display: none; }
  .qc-stepform__steps { display: flex; justify-content: space-between; align-items: center; gap: .25rem; }
  .qc-stepform__steps li + li::before { display: none; }
  .qc-stepform__step { width: auto; margin: 0; padding: .25rem; }
  .qc-stepform__step:not(.is-active) .qc-stepform__label { display: none; }
  .qc-stepform__hint, .qc-stepform__flag { display: none; }
  .qc-stepform__step.has-warn .qc-stepform__pip { display: block; }
  .qc-stepform__body { padding: 1.25rem 1rem .75rem; }
  .qc-stepform__nav { padding: .75rem 1rem; }
  .qc-stepform__title { font-size: var(--qc-fs-2xl); }
}
@media (prefers-reduced-motion: reduce) {
  .qc-stepform__step, .qc-stepform__dot { transition: none; }
  .qc-stepform__panel.is-active { animation: none; }
}

/* ---- The new person (templates/user/new.html.twig) ----------------------- */
/* Which seat of the licence this person will take, said as the profile
   changes; past a ceiling in the warning colour — said, not refused. */
.qc-usernew__seat {
  display: flex;
  align-items: center;
  gap: .5rem;
  margin: 1.25rem 0 0;
  padding: .625rem .875rem;
  color: var(--qc-text-secondary);
  font-size: var(--qc-fs-md);
}
.qc-usernew__noemail {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .375rem .5rem;
  margin: .75rem 0 0;
  font-size: var(--qc-fs-md);
  color: var(--qc-text-secondary);
}
.qc-usernew__noemail[hidden] { display: none; }
.qc-usernew__seat.is-over { color: var(--qc-warning-text); }

/* The page after a person is created (user/ready): a check and a rise, no
   rings — it is a content page, not a brand moment. */
.qc-userready__head { display: flex; align-items: center; gap: 1rem; margin-bottom: 1.5rem; }
.qc-userready__check {
  display: grid;
  place-items: center;
  flex: none;
  width: 3.25rem;
  height: 3.25rem;
  border-radius: 999px;
  background: var(--qc-layer-wash);
  color: var(--qc-primary);
  font-size: var(--qc-fs-3xl);
  animation: qcUserReadyPop var(--qc-dur-emphasized) var(--qc-ease-emphasized) both;
}
.qc-userready__title { margin: 0; font-family: var(--qc-font-display); font-weight: 800; font-size: var(--qc-fs-2xl); }
.qc-userready__lede { margin: .25rem 0 0; color: var(--qc-text-secondary); }
.qc-userready__block {
  padding-top: 1.25rem;
  margin-top: 1.25rem;
  border-top: 1px solid var(--qc-divider);
  animation: qcUserReadyRise var(--qc-dur-reveal) var(--qc-ease-reveal) both;
  animation-delay: calc(var(--i, 0) * 90ms);
}
.qc-userready__password { display: flex; align-items: center; gap: .75rem; flex-wrap: wrap; }
.qc-userready__password code {
  padding: .25rem .75rem;
  border-radius: var(--qc-radius-input);
  background: var(--qc-surface-light);
  color: var(--qc-text-primary);
  font-size: var(--qc-fs-xl);
  letter-spacing: .04em;
}
@keyframes qcUserReadyPop { from { opacity: 0; transform: scale(.6); } to { opacity: 1; transform: none; } }
@keyframes qcUserReadyRise { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  .qc-userready__check, .qc-userready__block { animation: none; }
}

/* A few lines saying what a group is for, before its controls: the user
   sheet's modules pane. Quieter than the lede's paragraph would be at that
   length — an icon a line, so each is read as a separate fact. */
.qc-form__explain {
  list-style: none;
  margin: -.25rem 0 1.25rem;
  padding: .75rem .875rem;
  display: grid;
  gap: .5rem;
  font-size: var(--qc-fs-md);
  color: var(--qc-text-secondary);
}
.qc-form__explain li { display: flex; gap: .5rem; align-items: flex-start; }
.qc-form__explain .ti { flex: none; margin-top: .125rem; color: var(--qc-primary); }

/* Why a control cannot be changed, said beside it rather than left to a
   greyed-out card: the last owner's profile. */
.qc-form__notice {
  display: flex;
  gap: .5rem;
  align-items: flex-start;
  margin: -.25rem 0 1rem;
  padding: .625rem .875rem;
  color: var(--qc-warning-text);
  font-size: var(--qc-fs-md);
}
.qc-form__notice .ti { flex: none; margin-top: .125rem; }

/* ---- A file dropped or picked (the importers) -------------------------------
   A <label> round a visually hidden file input: a click opens the picker, the
   input keeps the focus, and the box shows where it is. */
.qc-dropzone {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .375rem;
  padding: 2rem 1rem;
  border: 2px dashed var(--qc-border);
  border-radius: var(--qc-radius-card);
  background: var(--qc-surface-light);
  color: var(--qc-text-secondary);
  text-align: center;
  cursor: pointer;
  transition: border-color var(--qc-dur-fast) var(--qc-ease-fast), background-color var(--qc-dur-fast) var(--qc-ease-fast);
}
.qc-dropzone:hover,
.qc-dropzone.is-over {
  border-color: var(--qc-primary);
  background: color-mix(in srgb, var(--qc-primary) 6%, var(--qc-surface-light));
}
.qc-dropzone:has(input:focus-visible) { outline: 2px solid var(--qc-primary); outline-offset: 2px; }
.qc-dropzone__icon { font-size: 2rem; color: var(--qc-primary); }
.qc-dropzone__file { font-weight: 600; color: var(--qc-text-primary); }
@media (prefers-reduced-motion: reduce) {
  .qc-dropzone { transition: none; }
}

/* ---- The company logo (system/settings/_logo_field) ------------------------
   A well with the two places the logo goes — the head of a sheet and the app's
   chip — over a bar with the button and the formats. The plates are paper in
   both themes (`--qc-paper` under data-bs-theme="light"): a logo drawn for
   white is judged on white. The whole well takes a dropped file. */
.qc-logofield {
  border: 1px solid var(--qc-border);
  border-radius: var(--qc-radius-card);
  background: var(--qc-surface-well);
  transition: border-color var(--qc-dur-fast) var(--qc-ease-fast), background-color var(--qc-dur-fast) var(--qc-ease-fast);
}
.qc-logofield.is-over {
  border-color: var(--qc-primary);
  background: var(--qc-layer-wash);
}
.qc-logofield__stage {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 1rem 1.5rem;
  padding: 1.25rem 1.25rem 1rem;
}
.qc-logofield__plate {
  display: flex;
  flex-direction: column;
  gap: .5rem;
  margin: 0;
}
.qc-logofield__plate--sheet { flex: 1 1 14rem; max-width: 20rem; }
.qc-logofield__plate figcaption {
  font-size: var(--qc-fs-xs);
  color: var(--qc-text-tertiary);
}

/* The head of a sheet: the logo top left as the PDF sets it, two rules for the
   address under it. */
.qc-logofield__sheet {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: .5rem;
  min-height: 6.5rem;
  padding: 1rem 1.125rem;
  border: 1px solid var(--qc-border);
  border-radius: var(--qc-radius-input);
  background: var(--qc-paper);
  color: var(--qc-text-primary);
}
.qc-logofield__sheetlogo {
  max-width: 10rem;
  max-height: 2.75rem;
  object-fit: contain;
}
.qc-logofield__words {
  font-weight: 700;
  font-size: var(--qc-fs-lg);
  line-height: 1.25;
  overflow-wrap: anywhere;
}
/* No name yet: the field's label stands in, in the hint's ink. */
.qc-logofield__words:empty::before {
  content: attr(data-placeholder);
  font-weight: 400;
  color: var(--qc-text-tertiary);
}
.qc-logofield__lines { display: flex; flex-direction: column; gap: .3rem; width: 100%; }
.qc-logofield__lines i {
  display: block;
  height: .3rem;
  border-radius: var(--qc-radius-pill);
  background: var(--qc-divider);
}
.qc-logofield__lines i:first-child { width: 55%; }
.qc-logofield__lines i:last-child { width: 38%; }

/* The app's chip, as the account sheet draws it. */
.qc-logofield__chip {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 3.25rem;
  min-width: 7rem;
  padding: 0 1.125rem;
  border: 1px solid var(--qc-border);
  border-radius: var(--qc-radius-btn);
  background: var(--qc-paper);
}
.qc-logofield__chip img {
  max-width: 9rem;
  max-height: 1.75rem;
  object-fit: contain;
}

.qc-logofield__bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem .75rem;
  padding: .75rem 1.25rem;
  border-top: 1px solid var(--qc-divider);
}
.qc-logofield__pick { cursor: pointer; margin: 0; }
.qc-logofield__pick:has(input:focus-visible) { outline: 2px solid var(--qc-primary); outline-offset: 2px; }
.qc-logofield__hint { font-size: var(--qc-fs-sm); color: var(--qc-text-tertiary); }
.qc-logofield__file {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  min-width: 0;
  font-size: var(--qc-fs-sm);
}
.qc-logofield__filename {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 16rem;
  font-weight: 600;
  color: var(--qc-text-primary);
}
.qc-logofield__pending { color: var(--qc-text-tertiary); white-space: nowrap; }
@media (prefers-reduced-motion: reduce) {
  .qc-logofield { transition: none; }
}

/* ---- The rich-text field (Quill) --------------------------------------------
   Quill arrives with the snow theme's own paper: a white toolbar, a #ccc
   hairline and #444 icons. On a card in the light theme the box sank into the
   page, and in the dark theme it was a white slab with black glyphs. It is a
   field like any other, so it takes the field's recipe: the toolbar is the
   card, the body is the sunken field, one border round both, and the focus
   ring of `.form-control:focus` when the caret is inside.

   `html` in front of every selector: the snow sheet is imported by the
   Stimulus bundle and lands after this file, with the same specificity. */
html .ql-toolbar.ql-snow,
html .ql-container.ql-snow {
  border-color: var(--qc-field-border);
  font-family: var(--qc-font-body);
}
html .ql-toolbar.ql-snow {
  background: var(--qc-card-bg);
  border-radius: var(--qc-radius-input) var(--qc-radius-input) 0 0;
}
html .ql-container.ql-snow {
  background: var(--qc-field-bg);
  color: var(--qc-text-primary);
  border-radius: 0 0 var(--qc-radius-input) var(--qc-radius-input);
  font-size: var(--qc-fs-lg);
}
html .ql-snow .ql-editor.ql-blank::before { color: var(--qc-text-tertiary); font-style: normal; }
html .ql-snow .ql-stroke { stroke: var(--qc-text-secondary); }
html .ql-snow .ql-fill,
html .ql-snow .ql-stroke.ql-fill { fill: var(--qc-text-secondary); }
html .ql-snow .ql-picker { color: var(--qc-text-secondary); }
html .ql-snow.ql-toolbar button:hover .ql-stroke,
html .ql-snow.ql-toolbar button:focus .ql-stroke,
html .ql-snow.ql-toolbar button.ql-active .ql-stroke,
html .ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke,
html .ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke { stroke: var(--qc-primary); }
html .ql-snow.ql-toolbar button:hover .ql-fill,
html .ql-snow.ql-toolbar button:focus .ql-fill,
html .ql-snow.ql-toolbar button.ql-active .ql-fill { fill: var(--qc-primary); }
html .ql-snow.ql-toolbar button:hover,
html .ql-snow.ql-toolbar button.ql-active,
html .ql-snow.ql-toolbar .ql-picker-label:hover,
html .ql-snow.ql-toolbar .ql-picker-label.ql-active,
html .ql-snow.ql-toolbar .ql-picker-item:hover,
html .ql-snow.ql-toolbar .ql-picker-item.ql-selected { color: var(--qc-primary); }
html .ql-snow .ql-picker-options {
  background: var(--qc-surface-overlay);
  border-color: var(--qc-field-border);
  border-radius: var(--qc-radius-input);
  box-shadow: var(--qc-shadow-sm);
}
html .ql-snow .ql-picker.ql-expanded .ql-picker-label { border-color: var(--qc-field-border); }
/* The caret inside the body rings the whole box, toolbar included: it is one field. */
html .ql-toolbar.ql-snow:has(+ .ql-container:focus-within),
html .ql-container.ql-snow:focus-within { border-color: var(--qc-primary); }
html .ql-container.ql-snow:focus-within { box-shadow: 0 .2rem 0 .1rem rgba(var(--qc-primary-rgb), .18); }
html .ql-toolbar.ql-snow:has(+ .ql-container:focus-within) { box-shadow: 0 -.1rem 0 .1rem rgba(var(--qc-primary-rgb), .18); }
.is-disabled .ql-container.ql-snow,
.ql-container.ql-disabled { color: var(--qc-text-secondary); }

/* Room to write, and a handle to make more. Was quilleditor.css, linked by the
   estimate and invoice pages only, so the job's editors were three lines tall. */
.quill-expandable .ql-container.ql-snow {
  resize: vertical;
  overflow: hidden;
  min-height: 150px;
  padding-bottom: 16px;
}
.quill-expandable .ql-editor { overflow-y: auto; height: 100%; min-height: 100%; cursor: text; }
/* Above its own editor body, below the step bar (`.qc-stepform__nav`, 2): at
   100 the toolbar slid over the sticky bar of actions while scrolling. */
.quill-expandable .ql-toolbar.ql-snow { position: relative; z-index: 1; overflow: visible; }
.quill-expandable #emoji-palette,
.quill-expandable .ql-table-select-container,
.quill-expandable .ql-table-menus-container,
.quill-expandable .ql-tooltip { z-index: 9999; }
.quill-expandable .ql-tooltip { left: 0 !important; max-width: 100%; white-space: normal; }
.quill-expandable .ql-tooltip input[type="text"] { width: 170px; }
#editor-resizer .toolbar { display: none !important; }

/* Under a settings field that is not at its shipped value: what that value is,
   and the way back (system/settings/_default.html.twig). Quiet — it is a fact
   about the field, not a warning. */
.qc-form__default {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .5rem .75rem;
  margin-top: .5rem;
  font-size: var(--qc-fs-sm);
  color: var(--qc-text-secondary);
}

/* Deciding an extra (commission/extra.html.twig): each way's fields under the
   choice, drawn by conditional-area. */
.qc-extradecide__way > * + * { margin-top: 1rem; }
.qc-extradecide__sub { margin: .5rem 0 0; font-size: var(--qc-fs-md); font-weight: 600; }

/* ------------------------------------------------------------------
 * The report fields (system/settings/submission_fields.html.twig).
 *
 * A list that reads at a glance: one line a field — the handle, its name over
 * its short form and who fills it in, its kind as a pill, where it ends up —
 * and under the open one its form, with the report's way of asking it beside.
 * submission_fields_controller.js keeps one row open at a time.
 * ------------------------------------------------------------------ */
.qc-sfields { padding: 1rem 1.25rem 1.25rem; }
.qc-sfields__list { list-style: none; margin: 0; padding: 0; }
.qc-sfields__empty { margin: .5rem 0 0; font-size: var(--qc-fs-md); }
.qc-sfields__foot { padding-top: .75rem; }
.qc-sfields__unsaved {
  display: inline-flex;
  align-items: center;
  gap: .375rem;
  font-size: var(--qc-fs-sm);
  color: var(--qc-text-secondary);
}
.qc-sfields__dot { width: .5rem; height: .5rem; border-radius: 50%; background: var(--qc-status-yellow); }

.qc-sfield + .qc-sfield { border-top: 1px solid var(--qc-divider); }
.qc-sfield__line {
  display: grid;
  grid-template-columns: auto minmax(0, 1.4fr) 11rem minmax(0, 2fr) auto;
  gap: .875rem;
  align-items: center;
  padding: .625rem .5rem;
  border-radius: var(--qc-radius-input);
  cursor: pointer;
  transition: background-color var(--qc-dur-fast) var(--qc-ease-fast);
}
.qc-sfield__line:hover { background: var(--qc-layer-hover); }
.qc-sfield.is-open > .qc-sfield__line { background: var(--qc-layer-selected); }
.qc-sfield.sortable-ghost { background: var(--qc-layer-wash); }

.qc-sfield__name { display: grid; min-width: 0; }
.qc-sfield__title {
  font-weight: 600;
  font-size: var(--qc-fs-md);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.qc-sfield__meta {
  display: flex;
  align-items: center;
  gap: .5rem;
  font-size: var(--qc-fs-xs);
  color: var(--qc-text-tertiary);
}
.qc-sfield__meta i { margin-right: .25rem; }
.qc-sfield__abbr {
  padding: 0 .3125rem;
  border-radius: var(--qc-radius-sm);
  background: var(--qc-surface-well);
  color: var(--qc-text-secondary);
}
.qc-sfield__kind { min-width: 0; }
.qc-sfield__places { display: flex; flex-wrap: wrap; gap: .25rem; min-width: 0; }
.qc-sfield__place {
  display: inline-flex;
  align-items: center;
  gap: .25rem;
  padding: .0625rem .5rem;
  border: 1px solid var(--qc-border);
  border-radius: var(--qc-radius-pill);
  font-size: var(--qc-fs-xs);
  color: var(--qc-text-secondary);
  white-space: nowrap;
}
.qc-sfield__place--none { border-style: dashed; color: var(--qc-text-tertiary); }
.qc-sfield__toggle { justify-self: end; }
.qc-sfield__toggle i { margin-right: .25rem; }

@media (max-width: 767.98px) {
  .qc-sfield__line { grid-template-columns: auto minmax(0, 1fr) auto; }
  .qc-sfield__kind, .qc-sfield__places { grid-column: 2; }
  .qc-sfield__toggle { grid-row: 1; grid-column: 3; }
}

.qc-sfield__editor {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 17rem;
  gap: 1.5rem;
  padding: 1rem .5rem 1.25rem 2.75rem;
}
@media (max-width: 991.98px) {
  .qc-sfield__editor { grid-template-columns: minmax(0, 1fr); padding-left: .5rem; }
}
/* Six kinds: two rows of three, not four and two. */
.qc-sfield .qc-form__tiles { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 575.98px) {
  .qc-sfield .qc-form__tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.qc-sfield__where { margin: 0; padding: 0; border: 0; }
.qc-sfield__where > .qc-form__row { margin-top: .375rem; }
.qc-sfield__where > .qc-sfield__child { padding-left: 1.75rem; }
.qc-sfield__where > .is-unavailable .form-check-label { color: var(--qc-text-tertiary); }

.qc-sfield__acts { display: flex; justify-content: space-between; gap: .5rem; flex-wrap: wrap; }
.qc-sfield__acts .btn i { margin-right: .25rem; }

.qc-sfield__preview { display: grid; gap: .5rem; align-content: start; }
.qc-sfield__previewlabel {
  font-size: var(--qc-fs-xs);
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--qc-text-tertiary);
}
.qc-sfield__preview > div {
  display: grid;
  gap: .5rem;
  padding: .875rem;
  border: 1px solid var(--qc-border);
  border-radius: var(--qc-radius-card);
  background: var(--qc-surface-well);
}
.qc-sfield__ask {
  display: grid;
  gap: .375rem;
  padding: .75rem;
  border-radius: var(--qc-radius-input);
  background: var(--qc-card-bg);
}
.qc-sfield__ask .form-label { margin: 0; overflow-wrap: anywhere; }
.qc-sfield__hidden {
  display: flex;
  gap: .375rem;
  align-items: center;
  margin: 0;
  font-size: var(--qc-fs-xs);
  color: var(--qc-text-tertiary);
}
.qc-sfield__options { display: flex; flex-wrap: wrap; gap: .375rem; }
.qc-sfield__options > span {
  padding: .375rem .75rem;
  border: 1px solid var(--qc-field-border);
  border-radius: var(--qc-radius-input);
  font-size: var(--qc-fs-sm);
}
.qc-sfield__options > span:first-child {
  border-color: var(--qc-primary);
  background: var(--qc-layer-selected);
  color: var(--qc-primary);
}
/* The rows above set their own display; `hidden` must still win. */
.qc-sfield [hidden] { display: none !important; }
/* The short form's line under it reads on two lines, not four. */
.qc-sfield .qc-form__pair > .is-narrow { flex-basis: 13rem; }
