@charset "UTF-8";
:root {
  /* Primary Colors (For important actions and primary display) */
  --color-primary-900: #020A1B;
  --color-primary-800: #061844;
  --color-primary-500: #2153CD;
  --color-primary-300: #90A9E6;
  --color-primary-200: #C7D4F3;
  --color-primary-100: #E9EEFA;
  --color-primary-50: #F4F6FC;
  /* Success / Secondary Colors (Positive feedback, secondary actions) */
  --color-success-900: #005E3E;
  --color-success-700: #008D5D;
  --color-success-500: #00BC7C;
  --color-success-300: #80DEBE;
  --color-success-200: #CEEFCE;
  --color-success-100: #E5F8F2;
  --color-success-50: #F2FCF8;
  /* Warning Colors (Warning feedback or status) */
  --color-warning-900: #805502;
  --color-warning-700: #BF7F03;
  --color-warning-500: #FFA904;
  --color-warning-300: #FFD481;
  --color-warning-200: #FFEAC0;
  --color-warning-100: #FFF6E6;
  --color-warning-50: #FFFBF2;
  /* Error Colors (Negative feedback or status) */
  --color-error-900: #68170C;
  --color-error-700: #9B2312;
  --color-error-500: #CF2E18;
  --color-error-300: #E7978B;
  --color-error-200: #F3CBC5;
  --color-error-100: #FAEAE8;
  --color-error-50: #FDF4F3;
  /* Gray Colors (Backgrounds, icons, division lines) */
  --color-gray-900: #000000;
  --color-gray-700: #4A4A4A;
  --color-gray-500: #777777;
  --color-gray-300: #E0E4E8;
  --color-gray-200: #F0F2F3;
  --color-gray-100: #F9FAFA;
  --color-gray-50: #FCFCFD;
  /* Dark Text Colors */
  --text-dark-1: #020A1B;
  --text-dark-2: #353B49;
  --text-dark-3: #676C76;
  --text-dark-4: #9A9DA4;
  --text-dark-5: #CCCED1;
  /* White Text Colors */
  --text-white-1: #FFFFFF;
  --text-white-2: #FAFAFA;
  --text-white-3: #EEEEEE;
  --text-white-4: #DDDDDD;
  --text-white-5: #BBBBBB;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  font-family: sans-serif;
  line-height: 1.15;
  -webkit-text-size-adjust: 100%;
  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}

article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
  display: block;
}

body {
  margin: 0;
  font-family: "Manrope", sans-serif;
  font-size: 0.875rem;
  font-weight: 400;
  line-height: 1.4285714;
  color: #212529;
  text-align: left;
  background-color: #f8fafc;
}

[tabindex="-1"]:focus:not(:focus-visible) {
  outline: 0 !important;
}

hr {
  box-sizing: content-box;
  height: 0;
  overflow: visible;
}

h1, h2, h3, h4, h5, h6 {
  margin-top: 0;
  margin-bottom: 0.5rem;
}

p {
  margin-top: 0;
  margin-bottom: 1rem;
}

abbr[title],
abbr[data-original-title] {
  text-decoration: underline;
  -webkit-text-decoration: underline dotted;
          text-decoration: underline dotted;
  cursor: help;
  border-bottom: 0;
  -webkit-text-decoration-skip-ink: none;
          text-decoration-skip-ink: none;
}

address {
  margin-bottom: 1rem;
  font-style: normal;
  line-height: inherit;
}

ol,
ul,
dl {
  margin-top: 0;
  margin-bottom: 1rem;
}

ol ol,
ul ul,
ol ul,
ul ol {
  margin-bottom: 0;
}

dt {
  font-weight: 700;
}

dd {
  margin-bottom: 0.5rem;
  margin-left: 0;
}

blockquote {
  margin: 0 0 1rem;
}

b,
strong {
  font-weight: bolder;
}

small {
  font-size: 80%;
}

sub,
sup {
  position: relative;
  font-size: 75%;
  line-height: 0;
  vertical-align: baseline;
}

sub {
  bottom: -0.25em;
}

sup {
  top: -0.5em;
}

a {
  color: #3490dc;
  text-decoration: none;
  background-color: transparent;
}
a:hover {
  color: #1d68a7;
  text-decoration: underline;
}

a:not([href]):not([class]) {
  color: inherit;
  text-decoration: none;
}
a:not([href]):not([class]):hover {
  color: inherit;
  text-decoration: none;
}

pre,
code,
kbd,
samp {
  font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
  font-size: 1em;
}

pre {
  margin-top: 0;
  margin-bottom: 1rem;
  overflow: auto;
  -ms-overflow-style: scrollbar;
}

figure {
  margin: 0 0 1rem;
}

img {
  vertical-align: middle;
  border-style: none;
}

svg {
  overflow: hidden;
  vertical-align: middle;
}

table {
  border-collapse: collapse;
}

caption {
  padding-top: 0.75rem;
  padding-bottom: 0.75rem;
  color: #6c757d;
  text-align: left;
  caption-side: bottom;
}

th {
  text-align: inherit;
  text-align: -webkit-match-parent;
}

label {
  display: inline-block;
  margin-bottom: 0.5rem;
}

button {
  border-radius: 0;
}

button:focus:not(:focus-visible) {
  outline: 0;
}

input,
button,
select,
optgroup,
textarea {
  margin: 0;
  font-family: inherit;
  font-size: inherit;
  line-height: inherit;
}

button,
input {
  overflow: visible;
}

button,
select {
  text-transform: none;
}

[role=button] {
  cursor: pointer;
}

select {
  word-wrap: normal;
}

button,
[type=button],
[type=reset],
[type=submit] {
  -webkit-appearance: button;
}

button:not(:disabled),
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
  cursor: pointer;
}

button::-moz-focus-inner,
[type=button]::-moz-focus-inner,
[type=reset]::-moz-focus-inner,
[type=submit]::-moz-focus-inner {
  padding: 0;
  border-style: none;
}

input[type=radio],
input[type=checkbox] {
  box-sizing: border-box;
  padding: 0;
}

textarea {
  overflow: auto;
  resize: vertical;
}

fieldset {
  min-width: 0;
  padding: 0;
  margin: 0;
  border: 0;
}

legend {
  display: block;
  width: 100%;
  max-width: 100%;
  padding: 0;
  margin-bottom: 0.5rem;
  font-size: 1.5rem;
  line-height: inherit;
  color: inherit;
  white-space: normal;
}

progress {
  vertical-align: baseline;
}

[type=number]::-webkit-inner-spin-button,
[type=number]::-webkit-outer-spin-button {
  height: auto;
}

[type=search] {
  outline-offset: -2px;
  -webkit-appearance: none;
}

[type=search]::-webkit-search-decoration {
  -webkit-appearance: none;
}

::-webkit-file-upload-button {
  font: inherit;
  -webkit-appearance: button;
}

output {
  display: inline-block;
}

summary {
  display: list-item;
  cursor: pointer;
}

template {
  display: none;
}

[hidden] {
  display: none !important;
}

h1, h2, h3, h4, h5, h6,
.h1, .h2, .h3, .h4, .h5, .h6 {
  margin-bottom: 0.5rem;
  font-weight: 500;
  line-height: 1.2;
}

h1, .h1 {
  font-size: 2.1875rem;
}

h2, .h2 {
  font-size: 1.75rem;
}

h3, .h3 {
  font-size: 1.53125rem;
}

h4, .h4 {
  font-size: 1.3125rem;
}

h5, .h5 {
  font-size: 1.09375rem;
}

h6, .h6 {
  font-size: 0.875rem;
}

.lead {
  font-size: 1.09375rem;
  font-weight: 300;
}

.display-1 {
  font-size: 6rem;
  font-weight: 300;
  line-height: 1.2;
}

.display-2 {
  font-size: 5.5rem;
  font-weight: 300;
  line-height: 1.2;
}

.display-3 {
  font-size: 4.5rem;
  font-weight: 300;
  line-height: 1.2;
}

.display-4 {
  font-size: 3.5rem;
  font-weight: 300;
  line-height: 1.2;
}

hr {
  margin-top: 1rem;
  margin-bottom: 1rem;
  border: 0;
  border-top: 1px solid rgba(0, 0, 0, 0.1);
}

small,
.small {
  font-size: 0.875em;
  font-weight: 400;
}

mark,
.mark {
  padding: 0.2em;
  background-color: #fcf8e3;
}

.list-unstyled {
  padding-left: 0;
  list-style: none;
}

.list-inline {
  padding-left: 0;
  list-style: none;
}

.list-inline-item {
  display: inline-block;
}
.list-inline-item:not(:last-child) {
  margin-right: 0.5rem;
}

.initialism {
  font-size: 90%;
  text-transform: uppercase;
}

.blockquote {
  margin-bottom: 1rem;
  font-size: 1.09375rem;
}

.blockquote-footer {
  display: block;
  font-size: 0.875em;
  color: #6c757d;
}
.blockquote-footer::before {
  content: "— ";
}

.navbar-laravel {
  background-color: #fff;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.04);
}

.v-application,
.v-overlay-container {
  font-family: "Manrope", sans-serif !important;
}
.v-application [class*=text-]:not(:where(.text-h1, .text-h2, .text-h3, .text-h4, .text-h5, .text-h6, .text-subtitle-1, .text-subtitle-2, .text-body-1, .text-body-2, .text-button, .text-caption, .text-overline)),
.v-overlay-container [class*=text-]:not(:where(.text-h1, .text-h2, .text-h3, .text-h4, .text-h5, .text-h6, .text-subtitle-1, .text-subtitle-2, .text-body-1, .text-body-2, .text-button, .text-caption, .text-overline)) {
  color: #36405a;
  font-family: "Manrope", sans-serif !important;
}
.v-application .text-none,
.v-application .text-transform-none,
.v-application .text-transform-unset,
.v-application .text-center,
.v-application .text-justify,
.v-application .text-capitalize,
.v-application .text-truncate,
.v-application .text-wrap,
.v-application .text-ellipsis,
.v-application .text-p,
.v-application .text-cap,
.v-application .text-area,
.v-application .text-input,
.v-overlay-container .text-none,
.v-overlay-container .text-transform-none,
.v-overlay-container .text-transform-unset,
.v-overlay-container .text-center,
.v-overlay-container .text-justify,
.v-overlay-container .text-capitalize,
.v-overlay-container .text-truncate,
.v-overlay-container .text-wrap,
.v-overlay-container .text-ellipsis,
.v-overlay-container .text-p,
.v-overlay-container .text-cap,
.v-overlay-container .text-area,
.v-overlay-container .text-input {
  color: #36405a;
}

.transparent-skeleton > div {
  background-color: transparent !important;
}

/*
 * The app's anchor colour.
 *
 * `EvidenceRequirement.vue` carried a bare `a { color: #2153CD !important }`
 * in an unscoped block, and a containment cycle scoped it to `.evidence-row a`
 * as a leak. It was not one. The Vue 2 build has no code splitting — every
 * route is a static import into one 22MB `app.js` — so every component's
 * unscoped style is injected at boot, on every screen, and that rule painted
 * every anchor in the app `#2153CD` from the first paint: probed on a fresh
 * incognito `/login` there, an `<a>` reads `rgb(33, 83, 205)`. This build
 * lazy-loads routes, so containing the rule left every anchor outside the
 * compliance screen on Reboot's `#3490dc` — the cookies consent's "Privacy
 * Policy" link is where it was reported.
 *
 * Stated as the Vue 2 build stated it, `!important` included: an app rule that
 * coloured a link differently at lower weight lost there too. Any element
 * Vuetify 3 renders as `<a>` where Vuetify 2 rendered a `<div>` is excluded by
 * name below as it is measured.
 */
a {
  color: #2153CD !important;
}

.img-fluid {
  max-width: 100%;
  height: auto;
}

.list-group {
  display: flex;
  flex-direction: column;
  padding-left: 0;
  margin-bottom: 0;
  border-radius: 0.25rem;
}

.table .thead-dark th {
  color: #fff;
  background-color: #343a40;
  border-color: #454d55;
}

.table-borderless th,
.table-borderless td,
.table-borderless thead th {
  border: 0;
}

.border-bottom-0 {
  border-bottom: 0 !important;
}

.border-success {
  border-color: #28a745 !important;
}

.border-danger {
  border-color: #dc3545 !important;
}

.align-top {
  vertical-align: top !important;
}

/*
 * Vuetify's own classes, styled in one place.
 *
 * These lived in component stylesheets — `.v-btn` in EvidencePage, the dialog
 * block in seven separate dialog components. Those blocks are unscoped, so the
 * rules already applied to the whole app; they just did it from whenever that
 * chunk happened to load. Chunk order is not a design decision.
 *
 * Each selector that competes with Vuetify repeats its own class once.
 * Vuetify's stylesheet is injected at runtime, after this file, so at equal
 * specificity Vuetify wins; `.v-btn.v-btn` is 0,2,0 against its 0,1,0 and
 * settles it without `!important`. Anchoring on `.v-application` would not
 * work — Vuetify teleports overlays into `.v-overlay-container`, a direct
 * child of <body>.
 */
.v-btn.v-btn {
  text-transform: none;
}

/*
 * Tables keep the Vue 2 build's proportions.
 *
 * Vuetify 2 drew a data table at 48px a row, header included. Vuetify 3 raised
 * both: `--v-table-header-height: 56px` and `--v-table-row-height: 52px` at the
 * default density (VTable.css:93-96), and no density token gives 48 —
 * `comfortable` is 44 and `compact` is 36 — so the variables are named directly
 * rather than translated through a prop.
 *
 * The header's colour moved too: Vuetify 2 drew an unsorted heading at 60%
 * black and the sorted one at 87%. Vuetify 3 draws both at the table's own text
 * colour, so every heading came out as dark as the sorted one.
 */
.v-table.v-table--density-default {
  --v-table-header-height: 48px;
  --v-table-row-height: 48px;
}

/*
 * A table's text is 12px, as the Vue 2 build draws it. Vuetify 3's `.v-table`
 * sets `0.875rem`, so on /table both the cells and the headings came out 14px
 * against the Vue 2 build's 12px — the whole table two pixels large.
 *
 * Set on the table rather than on the cells on purpose. Vuetify gives `th`
 * `font-size: inherit` (VTable.css:84-89), so the size flows down from here to
 * both, and a screen that sizes its own cells still wins by saying so.
 *
 * The heading's weight needs a selector of its own, below.
 */
.v-table.v-table {
  font-size: 12px;
}

/*
 * A heading is 700, as the Vue 2 build drew it — without flattening the screens
 * that say otherwise.
 *
 * Vuetify sets `font-weight: 500` on
 * `.v-table > .v-table__wrapper > table > thead > tr > th`, which is (0,2,4).
 * An earlier revision recorded this as unreachable, on the reasoning that
 * beating it needs (0,3,4) and that would also outrank the components with
 * their own heading weight.
 *
 * That was wrong, and the mistake was assuming a stronger selector has to mean
 * *more classes*. Specificity compares classes first and elements only as a
 * tiebreak, so adding two element names — the two `div`s Vuetify leaves
 * implicit — gives (0,2,6): above Vuetify's (0,2,4), still below any rule with
 * a third class.
 *
 * Twelve component rules set a heading weight. Sorted against Vuetify's
 * (0,2,4), they fall into three groups:
 *
 *   still win, on specificity
 *     (0,4,2)  .workflow-container … .business-process-container / .archived-…
 *     (0,3,2)  .training-code-container .code-list-container …   (two rules)
 *     (0,3,2)  .workflow-container .all-task-container …
 *   still win, on !important
 *     (0,2,2)  .audit-container .content-table thead th
 *     (0,2,2)  .notification-page-container .content-table thead th
 *   already lost to Vuetify before this rule existed
 *     (0,2,3)  .table-comp-container .jss > thead > tr > th
 *
 * So this takes nothing from any rule that still had it.
 *
 * A fourth group has since been emptied, and the reasoning that put it here was
 * wrong. `.platform-permission-container`, `.doc-setting-container` and
 * `.preset-container` were listed at (0,2,2) as having already lost to Vuetify —
 * true, but the Vue 2 build draws all three at **600**, so letting this rule
 * override them diverged from parity rather than restoring it. Each was measured
 * against the Vue 2 build and raised in its own component to (0,3,2)/(0,3,3) by
 * nesting under a class already in the DOM: the permission page in one cycle,
 * doc-setting and preset in another, after `/settings/version` reported 700
 * against the Vue 2 build's 600.
 *
 * The list is scraped from every `\3c style>` block in `resources/js`, not from
 * the routes that happened to be open. An earlier revision of this comment
 * named five rules and called them all of them — they were simply the ones
 * whose chunks had loaded.
 */
div.v-table > div.v-table__wrapper > table > thead > tr > th {
  font-weight: 700;
}

.v-data-table__th.v-data-table__th {
  color: rgba(0, 0, 0, 0.6);
}

/*
 * A heading is grey, as the Vue 2 build drew it — including the ones the app
 * writes by hand.
 *
 * Vuetify 2 coloured a table heading itself:
 * `.theme--light.v-data-table > .v-data-table__wrapper > table > thead > tr >
 * th { color: rgba(0, 0, 0, .6) }`, (0,3,4). Vuetify 3's VTable has **no**
 * `thead th` colour rule at all — a heading simply inherits `.v-table`'s
 * on-surface. Nothing replaced it, and that let `app.scss`'s
 * `[class*='text-']` rule through: 147 `<th>` in this app carry `text-left`,
 * `text-center` or `text-right` for alignment, and each of them came out
 * `#36405a`.
 *
 * Measured on `/settings/subscription` and `/settings/pricing`, where a rule
 * dump over every sheet shows the whole story: on the Vue 2 build the only
 * colour rule matching `th.text-left` is Vuetify's own, at `rgba(0, 0, 0, .6)`;
 * here the only one matching is `.v-application [class*='text-']`, at
 * `rgb(54, 64, 90)`. On the pricing table the heading also carries the
 * "Billing Frequency" `<h3>` inside it, which inherited the navy with it.
 *
 * (0,2,6) for the same reason the weight rule above is: it clears
 * `[class*='text-']`'s (0,2,0) — specificity compares classes first, so the
 * two implicit `div`s are enough — while every screen that states a heading
 * colour of its own does so with three classes (`TrainingCode.vue`'s
 * `.code-list-container`, `Settings.vue`'s `.doc-setting-container`,
 * `PresetSetting.vue`'s `.preset-container`, all (0,3,2)) and still wins.
 * `.text-black`, which `DocNumberFormat.vue` puts on eight headings, is
 * Vuetify's own utility and carries `!important`, so those stay black.
 */
div.v-table > div.v-table__wrapper > table > thead > tr > th {
  color: rgba(0, 0, 0, 0.6);
}

/*
 * A sorted heading stays darker, above the rule just added — hence the third
 * class: (0,3,0) beats (0,2,6) on class count, and still loses to the (0,3,2)
 * screen rules, which is where it sat before.
 */
.v-data-table__th--sorted.v-data-table__th--sorted.v-data-table__th--sorted {
  color: rgba(0, 0, 0, 0.87);
}

/*
 * A data-table cell is navy, as the Vue 2 build drew it.
 *
 * The same `app.scss` rule, from the other side. Vuetify 2 gave every cell it
 * generated an alignment class — `text-start` by default — and
 * `[class*='text-']` painted it `#36405a`; that is where this app's tables get
 * their body colour, not from any rule of their own. Vuetify 3 renames the
 * class `v-data-table-column--align-start`, the app rule stops matching, and
 * the cell falls back to `.v-table`'s on-surface `rgba(0, 0, 0, .87)`.
 * Measured on `/training-code`'s report table and again on
 * `/settings/company-information`'s departments table.
 *
 * Keying on the renamed classes restores exactly the cells that had it: a
 * `<td>` the app writes by hand carries no alignment class on either build and
 * stays `rgba(0, 0, 0, .87)` — `/compliance/index` and the team-member table
 * read that on both. `td` on purpose, because Vuetify 3 puts `v-data-table__td`
 * on the header cells too, and those are grey above.
 *
 * Matched by prefix, not by the three names, because `align` is not always an
 * alignment: `CompanyInformation.vue` passes `align: 'd-none d-lg-table-cell'`
 * to hide a column below `lg`, and Vuetify interpolates whatever it is given —
 * `text-d-none d-lg-table-cell` on the Vue 2 build, where the app rule matched
 * it like any other, and `v-data-table-column--align-d-none d-lg-table-cell`
 * here. Listing `start`/`center`/`end` left that column at `rgba(0, 0, 0, .87)`
 * beside its navy neighbours. A substring match is also what it is translating:
 * `[class*='text-']` is one itself.
 *
 * (0,2,1): a class heavier than nothing, a hair below the (0,2,2) screen rules
 * that already colour their own cells.
 */
td.v-data-table__td[class*=v-data-table-column--align-] {
  color: #36405a;
}

/*
 * An input at the default density keeps the Vue 2 build's height.
 *
 * 140 of the app's 722 input call sites pass no `dense`. Measured on two of
 * their screens — the profile form and the audit-trail filters — each was 16px
 * taller here. Measured on the profile form, where five fields stack:
 *
 *                 Vue 2   before   after
 *   control        32px     48px    32px
 *   details        22px     22px    22px
 *   total          54px     70px    54px
 *
 * The reserved message line was already right; only the control was wrong, and
 * `--v-input-control-height` alone does not move it. VField.css:140 reads
 *
 *   min-height: max(var(--v-input-control-height, 56px),
 *                   1.5rem + var(--v-field-input-padding-top)
 *                          + var(--v-field-input-padding-bottom));
 *
 * and at this density the second term is 24 + 20 + 4 = 48, so it wins whatever
 * the token says. Both terms have to come down together.
 *
 * The variant is named because Vuetify's own rule names it —
 * `.v-input--density-default .v-field--variant-underlined` sets the 48px
 * (VField.css:99-103) at the same specificity, and would otherwise win on
 * order. Naming it also makes the target exact: 32px with 4px of padding is
 * precisely what Vuetify 3 calls a *compact* underlined field
 * (VField.css:110-114). The Vue 2 build's ordinary input is Vuetify 3's
 * compact one.
 *
 * Scoped to fields with no floating label — `--no-label` and `--single-line`,
 * which is what a placeholder-only field renders as. A field with a label needs
 * that top padding to have somewhere to put it, and shrinking it would print
 * the label over the value.
 *
 * `density-compact` is untouched: the other 582 call sites are what `dense`
 * maps to and already land within 2px of the Vue 2 build.
 */
.v-input--density-default.v-input--plain-underlined {
  --v-input-padding-top: 4px;
}

.v-input--density-default .v-field--no-label.v-field--variant-underlined,
.v-input--density-default .v-field--single-line.v-field--variant-underlined {
  --v-input-control-height: 32px;
  --v-input-padding-top: 4px;
  --v-field-input-padding-top: 4px;
  --v-field-input-padding-bottom: 4px;
}

/*
 * A selection control reserves no room for a message it does not have.
 *
 * Vuetify 3 gives every input a 22px `.v-input__details` unless the call site
 * says `hide-details`. A dense switch on the Vue 2 build is 24px, with no
 * details block at all, so the block is collapsed here.
 *
 * 28 call sites are affected: the 7 switches and 21 checkboxes that pass no
 * `hide-details`. The other 109 already opt out.
 *
 * `min-height: 0` rather than `display: none`, so a control that really does
 * have a validation message still grows to show it. Note that the
 * `.v-messages` inside keeps its own 14px floor — this collapses the padding
 * around it, not the line itself.
 *
 * The reasoning first written here was wider than the measurement: it said
 * Vuetify 2 reserved no message space for *selection controls*, generalised
 * from the switch. Checkboxes that pass no `hide-details` do reserve it — the
 * docgen list's are 50px on the Vue 2 build, 24 of row plus 14 of message line
 * plus margins. Keeping this rule leaves them 8px over; dropping it would leave
 * them 16px over, so it stays, and the claim is narrowed to what was measured.
 */
.v-checkbox .v-input__details,
.v-switch .v-input__details,
.v-radio-group .v-input__details {
  min-height: 0;
  padding-top: 0;
}

/*
 * A validation message sits where the Vue 2 build put it.
 *
 * Vuetify 3 gives every `.v-input__details` a 22px box with `padding-top: 6px`
 * and `align-items: flex-end` (VInput.css:50), and `.v-text-field` insets it by
 * 16px (VTextField.css:36) — except under `.v-input--plain-underlined`, which
 * zeroes the inset again (line 39). The Vue 2 build's enclosed fields used a 14px box
 * with no padding above it, 12px of inset and 8px of clearance below. On
 * /login the message lands at x 1500 / y 149.91 there and x 1504 / y 157.91
 * here: 8px low and 4px right. The field's total height already agrees — 14
 * plus 8 of margin is the same 22 — so only the message inside it moved.
 *
 * Scoped to the enclosed variants on purpose. Read off the Vue 2 build by
 * constructing each field shape in the live page, its underlined default took
 * `padding: 0` and no margin where its outlined/solo/filled fields took
 * `0 12px` and `margin-bottom: 8px`. One blanket rule would be right for the
 * app's 524 enclosed call sites and wrong for its 212 underlined ones.
 *
 * The variant class lives on `.v-field`, not on `.v-input`, so the selector has
 * to reach down for it — there is no flat hook for "enclosed". `:has()` takes
 * the specificity of its most specific argument, which puts this at 0,4,0
 * against Vuetify's 0,2,0 and settles it without `!important`.
 *
 * The underlined field's own 22-against-14 was left alone here at first, on
 * the reasoning that Vuetify 2 also gave it `padding-top: 12px; margin-top:
 * 4px` on `.v-text-field` itself, which Vuetify 3 does not, so closing the 8px
 * at the bottom would only half-fix it. The two halves are independent: on
 * `/activate-account` (and 65 of the app's 103 underlined fields with a
 * message line) the screen zeroes the top with `pt-0 mt-0`, and the 8px at
 * the bottom was the whole of the difference — the message 44px below the
 * field's top against the Vue 2 build's 36, the card 6px taller. The rule
 * after this one closes the bottom; the 12+4 above the field is still not
 * restated, and is recorded as its own pass.
 */
.v-input:has(> .v-input__control > .v-field--variant-outlined,
> .v-input__control > .v-field--variant-filled,
> .v-input__control > .v-field--variant-solo,
> .v-input__control > .v-field--variant-solo-filled,
> .v-input__control > .v-field--variant-solo-inverted) > .v-input__details {
  align-items: stretch;
  margin-bottom: 8px;
  min-height: 14px;
  padding-top: 0;
  padding-inline: 12px;
}

/*
 * The underlined details box, Vuetify 2's: 14px, `padding: 0`, no margin
 * (`.v-text-field__details` without the `--enclosed` rule, read off the stable
 * bundle). Same key and weight as the enclosed rule above.
 */
.v-input:has(> .v-input__control > .v-field--variant-underlined) > .v-input__details {
  align-items: stretch;
  min-height: 14px;
  padding-top: 0;
}

/*
 * A field showing a message keeps its distance from it.
 *
 * The other half of the rule above, found on the checkout's promo code field:
 * type a code that does not exist and "Code Invalid" prints hard against the
 * box. Measured there —
 *
 *   field bottom to message top | Vue 2 4px | this build 0
 *   whole input                 | Vue 2 66  | this build 62
 *
 * Vuetify 2 put that space under the field itself: `.v-input__slot` carried
 * `margin-bottom: 8px`, `4px` under `dense` and `0` under `hide-details`
 * (vuetify.css:20740-20757). Vuetify 3 declares none, so its details box butts
 * against the field. The 8 below the message that the rule above restores was
 * only ever the second of the two gaps.
 *
 * Three choices, each measured rather than picked:
 *
 *   - **on `.v-field`**, not on `.v-input__details`. Seven login screens set
 *     `.v-field { margin-bottom: 4px !important }` themselves — it is how
 *     `/login` already looks right — and a parity margin on any other element
 *     would *add* to theirs, taking that field to 70 against the Vue 2 build's
 *     66. Same element, same property, same 4px: the screen's `!important`
 *     wins and nothing moves.
 *   - **keyed on `:has(> .v-input__details)`**, which is Vuetify 3's own way of
 *     saying the field is showing something: `hide-details` renders no details
 *     element, and that is exactly the case Vuetify 2 zeroed. Confirmed on the
 *     Vue 2 build by clearing the promo code — the margin drops to 0 and the
 *     field returns to 40.
 *   - **every variant.** Vuetify 2's `.v-input__slot { margin-bottom: 8px }`
 *     was not scoped to the enclosed ones; this rule was, while the
 *     underlined details box was still deferred. Now that it is Vuetify 2's
 *     too, the underlined field takes the same clearance (the login screens'
 *     own `.v-field { margin-bottom: 4px !important }` still wins on theirs).
 */
.v-input:has(> .v-input__details):has(> .v-input__control > .v-field--variant-outlined,
> .v-input__control > .v-field--variant-filled,
> .v-input__control > .v-field--variant-solo,
> .v-input__control > .v-field--variant-solo-filled,
> .v-input__control > .v-field--variant-solo-inverted,
> .v-input__control > .v-field--variant-underlined) > .v-input__control > .v-field {
  margin-bottom: 8px;
}

.v-input--density-compact:has(> .v-input__details):has(> .v-input__control > .v-field--variant-outlined,
> .v-input__control > .v-field--variant-filled,
> .v-input__control > .v-field--variant-solo,
> .v-input__control > .v-field--variant-solo-filled,
> .v-input__control > .v-field--variant-solo-inverted,
> .v-input__control > .v-field--variant-underlined) > .v-input__control > .v-field {
  margin-bottom: 4px;
}

/*
 * A field aligns its own text, whatever the column around it is doing.
 *
 * Vuetify 2 declared `text-align: left` on `.v-input` — verified by putting a
 * bare `.v-input` inside a centred host on the Vue 2 build, where it computes
 * `left` while `.v-text-field` and `.v-select` alone inherit `center`, so the
 * declaration is on `.v-input` and nothing else. Vuetify 3 declares none, and
 * the login form's `text-center` column (`Login.vue:76`) five levels up
 * inherits straight through: "E-mail must be valid" was painting centred at
 * x 1621.41 where the Vue 2 build starts it flush at x 1500.
 *
 * One class, not two. Vuetify 3 has no competing declaration, and every rule in
 * the app that deliberately centres a field's text — the OTP dialog,
 * `.centered-input`, the approval dialog — targets the `input` element itself
 * and so already outranks this, exactly as it had to on the Vue 2 build.
 *
 * `left` rather than `start` because that is the value Vuetify 2 shipped; the
 * app renders LTR only.
 */
.v-input {
  text-align: left;
}

/*
 * A placeholder is the colour it was written to be, not 38% of it.
 *
 * Vuetify 3 dims every field's placeholder — `opacity: var(--v-disabled-opacity)`
 * on `.v-field__input input::placeholder` (VField.css:163). Vuetify 2 dimmed
 * none. The app names its own #bbb and both builds compute exactly that, so the
 * colour already agreed; the login placeholders were simply being painted at 38%
 * of it, which over white reads as roughly #e8e8e8.
 *
 * The colour is named as well, and the first version of this rule was wrong to
 * leave it out. Vuetify 2's placeholder was `rgba(0, 0, 0, 0.38)`; Vuetify 3's
 * is `currentColor`, so lifting the opacity alone left every placeholder the
 * app does *not* colour sitting at full-strength body text — the signature
 * dialog's "Write here…" came out #36405a against the Vue 2 build's 38% black.
 * Where the app does name a colour, as /login does with #bbb, its own rule
 * still outranks this one and the opacity is what it needed.
 */
.v-field .v-field__input input::-moz-placeholder, .v-field input.v-field__input::-moz-placeholder, .v-field textarea.v-field__input::-moz-placeholder {
  color: rgba(0, 0, 0, 0.38);
  opacity: 1;
}
.v-field .v-field__input input::placeholder,
.v-field input.v-field__input::placeholder,
.v-field textarea.v-field__input::placeholder {
  color: rgba(0, 0, 0, 0.38);
  opacity: 1;
}

/*
 * A field insets its contents the way the Vue 2 build inset them.
 *
 * This is the same 12-against-16 as the validation message one rule above, on
 * the line directly over it: Vuetify 3 holds the inset in
 * `--v-field-padding-start` / `-end`, defaulted to 16px on `.v-field`
 * (VField.css:14-15), where every enclosed Vuetify 2 field used 12px. On
 * /login it puts the placeholder's first glyph at 1504 against 1500.
 *
 * Vuetify 3 also adds a 6px gap either side of an inner icon
 * (`.v-field--prepended { --v-field-padding-start: 6px }`, VField.css:120-125).
 * Vuetify 2 added none: an inner icon took exactly its own 24px and the text
 * began immediately after it. That rule outranks the variant selectors above,
 * so it is answered at its own weight rather than by widening theirs.
 *
 * Read off the Vue 2 build by constructing each field shape in the live page
 * and measuring from the field box's edge — not from a changelog:
 *
 *              | text left | gap to right edge
 *   enclosed   |    12     |   12
 *   prepended  |    36     |   12      (12 + the icon's 24 + nothing)
 *   appended   |    12     |   36
 *   underlined |     0     |    0
 *   und. prep. |    24     |    0      (0 + 24 + nothing)
 *
 * Simulated against real Vuetify 3 before writing: all five land on those
 * numbers exactly, the floating label follows the text from 16 to 12, and no
 * field changes height. The underlined default is already 0 and stays there —
 * only its prepended form moves, from 30 to 24.
 *
 * Each selector repeats a class: this file loads before Vuetify's, so
 * `.v-field--variant-outlined` alone would tie with `.v-field` and lose, and
 * the icon rules need one more again to outrank Vuetify's own `.v-field.v-field--prepended`.
 */
.v-field.v-field--variant-outlined,
.v-field.v-field--variant-filled,
.v-field.v-field--variant-solo,
.v-field.v-field--variant-solo-filled,
.v-field.v-field--variant-solo-inverted {
  --v-field-padding-start: 12px;
  --v-field-padding-end: 12px;
}

.v-field.v-field--prepended.v-field--prepended {
  --v-field-padding-start: 0;
}

.v-field.v-field--appended.v-field--appended {
  --v-field-padding-end: 0;
}

/*
 * A busy spinner has no track behind it, as the Vue 2 build's had none.
 *
 * Vuetify 3's VProgressCircular always renders two circles — an underlay at
 * `rgba(0, 0, 0, 0.12)` and the coloured overlay that sweeps. Vuetify 2's
 * indeterminate spinner rendered *only* the overlay: read off the live Vue 2
 * page, its markup is `svg > circle.v-progress-circular__overlay` and the
 * underlay element is not in the DOM at all. So every loading spinner in this
 * build gained a grey ring the Vue 2 build never drew — on `/reset-password`,
 * a 64px track around a white arc on a grey scrim.
 *
 * Scoped to `--indeterminate`, which is what 134 of the app's 136 call sites
 * pass. The other two bind `:model-value` — a real progress ring, where the
 * track is the point and where there is no Vue 2 rendering to measure against,
 * so they are left alone rather than guessed at.
 *
 * `display: none` because the Vue 2 build renders no element; the box, the
 * stroke width and the colour already agree.
 */
.v-progress-circular--indeterminate .v-progress-circular__underlay {
  display: none;
}

/*
 * A list row that was written as an icon beside a title still reads as a row.
 *
 * Vuetify 2's `.v-list-item` was a flex row, so `<v-list-item><v-icon/>
 * <v-list-item-title/></v-list-item>` put the two side by side. Vuetify 3 made
 * it a grid of `prepend / content / append` and wraps loose children in
 * `.v-list-item__content`, which is a *block*: the icon is inline-flex, the
 * title is a block, and the title drops onto its own line.
 *
 * Measured on the dashboard's Library fly-out, first row: the Vue 2 build draws
 * the icon at (112, 144) and the title at (136, 144.8) 306 wide; this build put
 * the title at (112, 156.3), under the icon.
 *
 * 67 of the app's 274 `v-list-item` call sites are written that way — an icon
 * and a title as loose children, no `prepend` slot and no content element. The
 * rule is keyed on that shape rather than on the row: a content box holding a
 * title *and a subtitle* is a legitimate Vuetify 3 stack and must keep it.
 *
 * The title takes `flex: 1 1 auto` because Vuetify 2's filled the row — 306px
 * against the 17.6 a flex item shrinks to — and the app's `text-ellipsis`
 * truncation depends on that width.
 */
.v-list-item__content:has(> .v-icon) {
  align-items: center;
  display: flex;
}

/*
 * A list title and subtitle fill their row and track no letters.
 *
 * Read off the installed builds by constructing a bare element in each live
 * page rather than from a changelog:
 *
 *                      | Vuetify 2   | Vuetify 3
 *   title    flex      | 1 1 100%    | 0 1 auto
 *            l-spacing | normal      | 0.15px  (0.009375em)
 *   subtitle flex      | 1 1 100%    | 0 1 auto
 *            l-spacing | normal      | 0.25px
 *
 * The flex half only bites where the row is a flex container — which, since
 * Vuetify 3 wraps loose children in `.v-list-item__content`, is wherever the
 * rules above or the app's own put one back. On the expanded sidebar the
 * Academy children's labels measured 66.5px wide against the Vue 2 build's 160,
 * because the title shrank to its text instead of taking the row. In a block
 * container `flex` is inert, so this is safe to state once for the class.
 *
 * `1 1 100%` and not `1 1 auto`: it is the value Vuetify 2 shipped, and the two
 * differ as soon as a row holds more than one growable child.
 */
.v-list-item-title.v-list-item-title,
.v-list-item-subtitle.v-list-item-subtitle {
  flex: 1 1 100%;
  letter-spacing: normal;
}

/*
 * …and their weight is the row's, not Vuetify's.
 *
 * Vuetify 2 declared neither a `font-weight` nor an `opacity` on a list's title
 * or subtitle: both inherited, which is how a list that says
 * `class="font-weight-600"` once at the top gets a whole panel of 600 text.
 * Vuetify 3 states `font-weight: 400` on each (VListItem.css:310-316, 286-292)
 * and fades the subtitle with `opacity: var(--v-medium-emphasis-opacity)`
 * (:267-276), and a rule on the element beats anything inherited.
 *
 * Reported as "many styles differ" on the library's file-detail sidebar, whose
 * `<v-list class="font-weight-600">` wraps the whole panel. Measured there
 * against the Vue 2 build, every label and value: "Document Name" 12px/600 →
 * 12px/400, "test (2)" 14px/600 opacity 1 → 14px/400 opacity 0.6 — fourteen
 * rows of it, and the value rows visibly paler as well as lighter.
 *
 * `inherit` rather than a number, because that is what Vuetify 2 left behind: a
 * list with no opinion still lands on the app's 400, and one with an opinion
 * keeps it. Stated before the dense-list rule further down, which names
 * Vuetify 2's own `font-weight: 500` for a compact title and still wins on
 * source order.
 */
.v-list-item-title.v-list-item-title,
.v-list-item-subtitle.v-list-item-subtitle {
  font-weight: inherit;
}

.v-list-item-subtitle.v-list-item-subtitle {
  opacity: 1;
}

/*
 * …and it does not clip what the Vue 2 markup hung outside it.
 *
 * The same wrapper as the rule above, a second consequence. Vuetify 3's
 * `.v-list-item__content` carries `overflow: hidden`; Vuetify 2 had no such box
 * at all, so a child could sit left of the row's padding and still be drawn.
 * The sidebar does exactly that: `Header.vue`'s `.lib-menu` takes
 * `margin-left: -14px` to hang each expand arrow outside the label column.
 *
 * Measured on the expanded sidebar, 11 arrows — Library, Academy and every
 * folder in the tree — sit at x 18 or 20 against a content box starting at 32
 * or 34, so all eleven were cut away. Their geometry never changed: the boxes
 * are identical on both builds, only the paint was missing, which is why the
 * guard asserts that the arrow is the element at its own centre rather than
 * asserting a position.
 *
 * Safe to relax because `.v-list-item-title` truncates itself — measured
 * `overflow: hidden; text-overflow: ellipsis; white-space: nowrap` — so the
 * content box's clipping is not what the app's ellipsis depends on. Keyed, like
 * the rule above, on a box holding something that is not one of Vuetify 3's own
 * title/subtitle children: that is the shape Vue 2 markup produces, and a box
 * holding only a title and a subtitle keeps Vuetify's clipping untouched.
 */
.v-list-item__content:has(> :not(.v-list-item-title):not(.v-list-item-subtitle)) {
  overflow: visible;
}

/*
 * An icon paints itself, as Vuetify 2's did.
 *
 * Vuetify 2's `.v-icon` declared `color: rgba(0, 0, 0, 0.54)`. Vuetify 3's
 * declares no colour at all (VIcon.css:1-17) and inherits, so every icon the
 * call site does not colour picks up whatever the surrounding text is — in this
 * app usually #36405a. Counted on the dashboard: 10 icons came out navy and
 * four at 0.87 where the Vue 2 build painted twelve at 0.54. The Academy
 * fly-out's row icons are the ones that were reported.
 *
 * One class, deliberately — the same weight Vuetify 2 used. Everything that
 * outranked it there still outranks it here: the app bar's white icons, the
 * sidebar's `.active i { color: #1890FF !important }`, a `color` prop, an
 * inline style. Adding weight would fix nothing and would start overriding
 * rules the Vue 2 build let through.
 *
 * Vuetify 3's disabled icons stay on `opacity: 0.38` rather than Vuetify 2's
 * 0.38 *colour*; they paint the same and are left alone.
 */
.v-icon {
  color: rgba(0, 0, 0, 0.54);
}

/*
 * …and a field does not fade them on top of that.
 *
 * Vuetify 3 washes an input's own icons out:
 * `.v-field__prepend-inner > .v-icon, .v-field__append-inner > .v-icon,
 * .v-field__clearable > .v-icon { opacity: var(--v-medium-emphasis-opacity) }`
 * (VField.css:231-234), which is 0.6. Vuetify 2 had no such rule — an icon was
 * the colour it was given, and the rule above gives every icon Vuetify 2's own
 * `rgba(0, 0, 0, 0.54)`. Stacked, that lands at roughly 0.32.
 *
 * Counted on `/library`: eight field icons on the Vue 2 build, seven at 0.54
 * and the search bar's magnifier at `var(--text-dark-3)`, every one of them at full
 * strength; the same eight here, the same colours, all at 0.6. The select
 * arrows are the ones on every screen.
 *
 * The class is repeated because this file loads before Vuetify's: at equal
 * weight the later sheet would win.
 *
 * Vuetify 3 already returns to 1 for a disabled, errored or focused field, so
 * those cases are unchanged.
 */
.v-field__prepend-inner > .v-icon.v-icon,
.v-field__append-inner > .v-icon.v-icon,
.v-field__clearable > .v-icon.v-icon {
  opacity: 1;
}

/*
 * …but an icon inside a button still inherits the button's colour.
 *
 * The companion to the rule above, and the half its first version was missing.
 * Vuetify 2 shipped `.v-btn > .v-btn__content .v-icon { color: inherit }`, so
 * the 0.54 default stopped at a button's edge. Vuetify 3 has no such rule, so
 * once `.v-icon` carried a colour of its own, every icon in a button took it —
 * measured on the profile page, the avatar's edit pencil sits in a white-on-
 * scrim overlay button and came out `rgba(0, 0, 0, 0.54)` against the Vue 2
 * build's white, which is to say invisible.
 */
.v-btn > .v-btn__content .v-icon {
  color: inherit;
}

/*
 * A prepended widget sits where Vuetify 2 put it.
 *
 * Vuetify 2's `.v-input__prepend-outer` took `margin-top: 4px`,
 * `margin-right: 9px`, no padding, and inherited `line-height: 1`. Vuetify 3's
 * `.v-input__prepend` is padded 8px at the top, sits 16px away, aligns to
 * flex-start and inherits 1.5. On the profile page that pushed the phone
 * field's country selector out of line with the number beside it:
 *
 *                          | Vue 2 build | before
 *   prepend / dropdown y   | 328         | 332
 *   dropdown height        | 28          | 33.2
 *   "+62" y / height       | 335 / 14    | 339 / 19.2
 *   number input x / width | 382.5 / 312 | 389.5 / 305
 *
 * The line-height is the half that is easy to miss: it is what grew the tel
 * widget's own boxes by 50%, and no amount of margin would have straightened
 * that.
 *
 * Both classes are doubled to reach (0,4,0). Vuetify claims this element from
 * two directions — `.v-input--horizontal .v-input__prepend` at two classes, and
 * `.v-input--density-default.v-input--plain-underlined .v-input__prepend` at
 * three, which is the one that reinstated the 8px padding once the field left
 * its disabled state. Three classes only tie with that, and Vuetify's sheet is
 * injected after this file.
 */
.v-input.v-input .v-input__prepend.v-input__prepend {
  align-items: flex-start;
  line-height: 1;
  margin-top: 4px;
  margin-inline-end: 9px;
  padding-top: 0;
}

/*
 * A card in a dialog that the markup laid out as a row is a row.
 *
 * Both Vuetify versions leave `.v-card` itself `display: block`, so
 * `class="d-flex"` alone makes it a flex row — that part did not change, and a
 * first draft of this rule claimed it had. What Vuetify 3 added is a dialog
 * rule: `.v-dialog > .v-overlay__content > .v-card` and its `> form > .v-card`
 * sibling both set `flex-direction: column` at (0,3,0) and (0,3,1). A `d-flex`
 * card inside a dialog therefore stacks, and `.d-flex` only sets `display`.
 *
 * The signature-limit dialog is one of them: its warning icon sat above the
 * heading instead of beside it, at (40, 52) from the dialog's corner against
 * the Vue 2 build's (54, 32). 59 call sites put `d-flex` on a card without
 * `flex-column`; the ones inside dialogs are the ones this reaches.
 *
 * Deliberately not `!important`: `.flex-column` is, so a call site that asks
 * for a column keeps getting one no matter how specific this rule is.
 */
.v-dialog > .v-overlay__content > .v-card.d-flex,
.v-dialog > .v-overlay__content > form > .v-card.d-flex {
  flex-direction: row;
}

/*
 * A checkbox is the size the Vue 2 build drew it — the tick, not a floor.
 *
 * An earlier cycle read this as a control-height problem: `/evidence/detail/121`
 * stacks 67 checkboxes at 44px on the Vue 2 build and drew them at 56 here, so
 * `--v-input-control-height` was pinned to 44 and the row came right. The tick
 * was left alone, explicitly — "the tick itself is untouched".
 *
 * That was the wrong half. Measured again, on two screens instead of one:
 *
 *                          Vue 2                    with the 44px floor
 *   /evidence/detail/121   44 row, 40 control       44 row, 44 control
 *   /library/evidence-…    36 row, 24 control       58 row, 44 control
 *   both screens           24x24 tick, 8px gap      40x40 tick, no gap
 *
 * The tick was the defect all along. Vuetify 2 drew a 24x24 tick at *every*
 * density with 8px to its label; Vuetify 3 draws `--v-selection-control-size`,
 * 40px at the default density, and dropped the gap. The floor then hid the
 * error on one screen and created a new one on the other — the second screen's
 * checkbox has a short label, so the Vue 2 build's row is the tick's own 24px
 * and a 44px floor stands it 20px too tall, 65 times down one list.
 *
 * So the floor goes and the tick comes back. With it, `/evidence/detail/121` is
 * unchanged at 44 — its height comes from its own wrapping label, not from the
 * floor, which is why lifting the floor moves it by nothing — and
 * `/library/evidence-submission` falls from 58 to 38 against the Vue 2 build's
 * 36.
 *
 * The 20px glyph is Vuetify 2's `v-icon--dense`, which it gave to a selection
 * control in a dense input; the fill is its
 * `.v-input--selection-controls__input .v-icon { width: 100% }`, without which
 * the icon cycle's `width: auto` base shrinks the box to the glyph.
 *
 * The floor becomes the tick's own 24px rather than going away. Vuetify writes
 * `min-height: var(--v-input-control-height)` on the selection control itself
 * (VCheckbox.css:4-6) and the token is a density default — 56px, 48, 40
 * (VInput.css:11-24) — so deleting the declaration does not remove the floor, it
 * hands it back to Vuetify: measured, both screens end up worse than they
 * started, at 70 and 56. 24px says what is actually true, that a checkbox
 * control cannot be shorter than its tick, and lets everything above that come
 * from the content the way Vuetify 2 did. Measured identical to `0px` on both
 * screens, and stated instead of it.
 *
 * Same rules as `.v-radio` below, on the class Vuetify 3 puts the selection
 * control on for a checkbox (`.v-checkbox-btn`). Switches keep their own block:
 * they are measured, closed, and a different shape.
 */
.v-checkbox.v-checkbox {
  --v-input-control-height: 24px;
}

.v-checkbox-btn.v-checkbox-btn {
  --v-selection-control-size: 24px;
}

.v-checkbox .v-selection-control__wrapper {
  margin-right: 8px;
}

.v-checkbox .v-selection-control__input > .v-icon {
  width: 100%;
  height: 100%;
}

.v-checkbox-btn.v-selection-control--density-compact .v-icon {
  font-size: 20px;
}

.v-checkbox.v-checkbox .v-label {
  height: auto;
}

/*
 * The same, one density down. `dense` maps to `density-compact`, and Vuetify 3
 * gives a compact underlined field a 32px floor where the Vue 2 build had none
 * at all — its dense field was purely content-sized, 26px for a 12px line.
 * Measured on /workflow's three filter fields: 26px there, 32px here.
 *
 * The floor is `max(--v-input-control-height, 1.5rem + the field's padding)`,
 * so 26 needs the padding at zero as well — 1.5rem is 24, and the text is
 * centred by the field rather than by that padding.
 *
 * Selects were excluded here for one revision, on the reasoning that a dense
 * select is taller than a dense text field. Measured, that is a per-screen
 * difference rather than a per-component one — /workflow's dense selects are
 * 26px on the Vue 2 build and /audit-trail's are 42px — and the exclusion left
 * more total drift than it removed. It is gone.
 */
.v-input--density-compact .v-field--no-label.v-field--variant-underlined,
.v-input--density-compact .v-field--single-line.v-field--variant-underlined {
  --v-input-control-height: 26px;
  --v-input-padding-top: 0px;
  --v-field-input-padding-top: 0px;
  --v-field-input-padding-bottom: 0px;
}

/*
 * `d-flex` on a list item still lays out what the item contains.
 *
 * Vuetify 2's `VListItem` put the default slot's children directly inside the
 * item, so a call site could write `d-flex` on the item and lay them out in a
 * row. Eleven do — the user menu, the library header's three menus, the academy
 * category list, and two menus that space their children apart.
 *
 * Vuetify 3 wraps the slot in `.v-list-item__content`. The item is still the
 * flex container, but its flex children are now Vuetify's own `__overlay`,
 * `__underlay` and `__content` — the call site's elements are one level deeper,
 * inside a block. So they stack: the academy's "Food Safety" and "(3)" went
 * from one 18px line to two, and the count dropped under the name.
 *
 * The layout is handed down to the wrapper rather than restated. `inherit`
 * takes each value from the item itself, so `justify-space-between` and
 * `align-center` keep working at the call sites that wrote them, and a future
 * one needs no addition here.
 */
.v-list-item.d-flex > .v-list-item__content,
.v-list-item.d-inline-flex > .v-list-item__content {
  display: flex;
  flex: 1 1 auto;
  flex-direction: inherit;
  align-items: inherit;
  justify-content: inherit;
  gap: inherit;
}

/*
 * A disabled button stops wearing its enabled colour.
 *
 * Vuetify 2 gave `color` to the button as a *class*, and greyed a disabled one
 * with `!important` — so the colour lost, whatever it was. Vuetify 3 writes
 * `color` as an **inline style**, and dropped the `!important`. Inline beats
 * every rule, so the enabled colour now survives being disabled: the
 * reinitialise button on a workflow keeps `background-color: #fff` and
 * `color: #000`, and Vuetify 3's disabled overlay — which lays `currentColor`
 * at 46% and is calibrated for its own `rgba(on-surface, .26)`, giving exactly
 * Vuetify 2's 12% grey — instead multiplies full black and paints the button
 * four times too dark.
 *
 * Demonstrated rather than assumed: applying this branch's inline style to the
 * same disabled button on the Vue 2 build changes nothing at all.
 *
 * 275 buttons across 96 files carry a colour and can be disabled, 37 of them
 * with an inline text colour written at the call site. Fixing them one by one
 * would mean 275 edits that a 276th call site would silently miss.
 *
 * `!important` is not a shortcut here — it is the only thing that outranks an
 * inline style, and it is what Vuetify 2 itself used.
 *
 * The background is Vuetify 2's own `rgba(0, 0, 0, 0.12)`, read off the Vue 2
 * build by constructing the classes there. This rule first used Vuetify 3's
 * surface colour instead, reasoning that its disabled overlay "lays
 * `currentColor` at 46% ... giving exactly Vuetify 2's 12% grey". Measured
 * afterwards that is false: with the button's colour still white, the overlay
 * computed `rgb(255, 255, 255)` at 46%, so it lightened. Surface white under a
 * white overlay is white, and the profile screen's disabled Confirm button was
 * reported as simply not being there — it was there, on a white card.
 *
 * That reading expired the moment this rule set the colour to
 * `rgba(0,0,0,.26)`: the overlay takes `currentColor`, so it now paints black
 * and *darkens*. The rule below removes it. Leaving the old sentence standing is
 * what let that hide for two cycles — it read as though the overlay had already
 * been accounted for.
 *
 * (0,4,0), Vuetify 2's own weight: its rule was
 * `.theme--light.v-btn.v-btn--disabled.v-btn--has-bg`. This one first sat at
 * (0,2,0) `!important`, which ties with a screen's `.reset__container
 * .submit-btn { background-color: … !important }` and loses on order — on
 * `/activate-account` a mistyped email left the disabled button bright green
 * with faint text, and four login screens carry that same rule. At Vuetify 2's
 * weight a screen rule that lost to it there loses here, and one that tied
 * there (`PublicLoginDialog`'s `.v-theme--light.v-btn.submit-btn.v-btn--disabled`,
 * (0,4,0), later in order) still wins.
 */
.v-theme--light.v-btn.v-btn--disabled.v-btn--variant-elevated,
.v-theme--light.v-btn.v-btn--disabled.v-btn--variant-flat {
  color: rgba(var(--v-theme-on-surface), 0.26) !important;
  background-color: rgba(0, 0, 0, 0.12) !important;
}

/*
 * …and nothing is laid over that background, because Vuetify 2 laid nothing.
 *
 * Vuetify 3 renders a `<span class="v-btn__overlay">` inside every button and
 * raises it to `opacity: 0.4615` for a disabled elevated or flat one, filled
 * with `currentColor` — which the rule above sets to `rgba(0,0,0,.26)`. The Vue
 * 2 build has no such element:
 *
 *                | background      | overlay              | composited
 *   Vue 2 build  | rgba(0,0,0,.12) | none                 | 0.12
 *   before this  | rgba(0,0,0,.12) | rgba(0,0,0,.26) @.46 | 0.226
 *
 * Nearly twice the ink. Measured on `/settings/version?tab=visibility`, whose
 * Delete and Edit setting buttons matched the Vue 2 build on **every computed
 * property** — colour, background, opacity, size — and still drew darker. The
 * extra element is what the comparison missed, which is the lesson worth keeping:
 * equal computed values are not equal paint when the markup differs.
 *
 * (0,4,0) is required rather than tidy. Vuetify states this on
 * `.v-btn--disabled.v-btn--variant-elevated .v-btn__overlay` at (0,3,0) and its
 * stylesheet is injected after this file, so the same rule at (0,2,0) left the
 * overlay untouched at 0.4615 — measured, not assumed.
 *
 * Only elevated and flat are affected: every disabled text button on
 * `?tab=general` already computes `opacity: 0` here, because those are the only
 * two variants Vuetify raises it for. And a disabled button carries
 * `pointer-events: none`, so no hover or focus state can want the overlay back.
 */
.v-btn.v-btn--disabled .v-btn__overlay.v-btn__overlay {
  opacity: 0;
}

/*
 * …and every other variant is dimmed by colour too, not by opacity.
 *
 * The rule above covers the two variants Vuetify 3 gives a background. For the
 * rest it says `.v-btn--disabled { opacity: 0.26 }` and leaves the colour alone,
 * where Vuetify 2 said
 * `.theme--light.v-btn.v-btn--disabled { color: rgba(0,0,0,.26) !important }`
 * and left the opacity alone — the colour-versus-opacity split again.
 *
 * Measured on `/settings/version?tab=general`, which renders twenty disabled
 * text buttons in its status table:
 *
 *                 | Vue 2 build      | this build
 *   button colour | rgba(0,0,0,.26)  | rgba(0,0,0,.87)
 *   opacity       | 1                | 0.26
 *
 * For near-black text the two land close in strength, which is why this survived
 * so long. The difference shows on anything *coloured*: the Vue 2 build turns it
 * grey, this build keeps the hue at 26%. The same screen's
 * `.doc-setting-container .edit-btn i { color: #2153CD }` is exactly that case.
 *
 * (0,3,0) is Vuetify 2's own weight — the theme class sits on the button in
 * Vuetify 3 just as it did in Vuetify 2 — so everything that outranked that rule
 * on the Vue 2 build still outranks this one: `PublicLoginDialog`'s
 * `.v-theme--light.v-btn.submit-btn.v-btn--disabled` at (0,4,0), and the nine
 * components that colour the label through `.v-btn--disabled span { … !important }`.
 *
 * `!important` is what Vuetify 2 used, and the cycle above established why it is
 * unavoidable here: Vuetify 3 writes a button's colour as an inline style, which
 * nothing weaker outranks. `opacity` carries none, so a screen can still dim.
 */
.v-btn.v-btn--disabled.v-theme--light {
  color: rgba(0, 0, 0, 0.26) !important;
  opacity: 1;
}

/*
 * …except pagination, which Vuetify 2 dimmed its own way.
 *
 * Vuetify 2's navigation arrow was not a `.v-btn`, so the disabled-button colour
 * rule never reached it: it had `.v-pagination__navigation--disabled { opacity: .6 }`
 * and kept the default `rgba(0,0,0,.54)` icon. Vuetify 3 renders it as a button,
 * so it takes the generic `opacity: .26` instead:
 *
 *                  | Vue 2 build | this build
 *   opacity        | 0.6         | 0.26
 *   icon colour    | .54         | .26
 *   effective ink  | 0.324       | 0.068
 *
 * Nearly five times fainter — the disabled arrow is close to invisible. The same
 * lesson as the pagination-colour cycle: a Vuetify 2 button rule that never
 * reached pagination must not start reaching it now that the markup changed.
 *
 * The opacity is (0,3,0), tying with the rule above and therefore placed after
 * it. The colour has to go on `.v-btn__content` rather than the button, and that
 * is not tidiness: **a screen's own rule reaches the arrow now too.**
 * `Settings.vue` writes `.doc-setting-container .v-btn--disabled span
 * { color: rgba(0,0,0,.26) !important }` (0,2,1) for its Edit and Delete
 * buttons, and on the Vue 2 build that never matched an arrow — there the arrow
 * was `.v-pagination__navigation--disabled`, not a button with a span. Setting
 * the colour on the button leaves that rule to win on the label, which measured
 * .26 where the Vue 2 build draws .54. On the content at (0,4,0) it restores the
 * boundary Vuetify 2 had, and a screen's rule still decides everywhere else —
 * verified on the same page's Edit buttons, which stay .26.
 */
.v-pagination .v-btn.v-btn--disabled {
  opacity: 0.6;
}

.v-pagination .v-btn.v-btn--disabled .v-btn__content {
  color: rgba(0, 0, 0, 0.54) !important;
}

/*
 * A disabled field keeps its value legible.
 *
 * Vuetify 2 dimmed a disabled input through `color`, so a rule naming the input
 * could keep the value black while the label and icons faded — which is what
 * the Vue 2 build does. Vuetify 3 dims the whole `.v-field` with
 * `opacity: var(--v-disabled-opacity)` (VField.css:22-25), and nothing inside
 * an opacity layer can escape it.
 *
 * That matters because this app uses `disabled` as a read-only *display* mode:
 * the profile shows your name and phone number until you press Edit. At 38%
 * they were barely readable where the Vue 2 build draws them black.
 *
 * The dimming is put back where Vuetify 2 had it — on the label and the icons —
 * so a disabled field still reads as disabled.
 */
.v-field--disabled.v-field--disabled {
  opacity: 1;
}

.v-field--disabled.v-field--disabled .v-label,
.v-field--disabled.v-field--disabled .v-icon {
  opacity: var(--v-disabled-opacity);
}

/*
 * An input's font size comes from the input, as it did under Vuetify 2.
 *
 * Vuetify 2 declared `font-size: 16px` on `.v-input` and nothing below it, so
 * the size flowed down to the slot and the native control, and a typography
 * class written on the input — `caption`, which this app uses on 15 of the 19
 * fields on the compliance audit plan alone — simply overrode it and took the
 * whole field with it.
 *
 * Vuetify 3 moved the declaration down to `.v-field` (VField.css:2-6). A class
 * on the input no longer reaches the control, because inheritance loses to any
 * declaration on a descendant, whatever its specificity. Every `text-caption`
 * field went from 12px to 16px: the Location textarea on that page is `rows=5`,
 * so it grew 85px → 126px and the table with it, 766px → 906px.
 *
 * Putting the declaration back on `.v-input` restores both halves. `:where()`
 * carries no specificity, so a typography class beats it exactly as `.caption`
 * beat Vuetify 2's own rule; and a field with no such class still computes
 * 16px, which is what the Vue 2 build shows for the app-bar search box.
 */
:where(.v-input) {
  font-size: 16px;
}

.v-field.v-field {
  font-size: inherit;
}

/*
 * A list row measures the way Vue 2 measured it.
 *
 * Two Vuetify 2 declarations went missing, and both add height to the same
 * rows:
 *
 * 1. `.v-list-item { padding: 0 16px }` became `padding: 4px 16px`
 *    (VListItem.css:1-11), and the density variants add block padding of their
 *    own on top (`--one-line` 4px, `--two-line` 12px, `--three-line` 16px).
 *    Vuetify 2 put nothing at all on the block axis: its variants set
 *    `min-height` and only `min-height`.
 * 2. Every item carried a ghost
 *    `.v-list-item::after { content: ''; min-height: inherit; font-size: 0 }`,
 *    which made `min-height` a floor on the *content* box. Vuetify 3 dropped
 *    it — `::after` is the focus ring now (VListItem.css:96-110), absolutely
 *    positioned and so unable to carry a floor — leaving `min-height` an
 *    ordinary border-box floor.
 *
 * Together: a Vue 2 row is `call-site padding + max(floor, content)`; a Vue 3
 * row is `max(floor, 8px + content)`. The 8px is invisible wherever the floor
 * absorbs it — a 24px option in a dropdown is 48px on both builds — and shows
 * on exactly the rows that outgrow their floor or whose component lowered it.
 * Measured against `127.0.0.120`: the workflow preset screen's library rows
 * 36px -> 44px and its two cards 132px -> 140px, `/library`'s document cards
 * 74px -> 86px, and on the academy course page — where a `py-5` at the call
 * site had already replaced the 4px, so only the ghost was missing — the video
 * rows 88px -> 65px and the section panel 240px -> 192px.
 *
 * Restoring both reproduces Vuetify 2's arithmetic rather than approximating
 * it, which is why it is safe to apply to every row: zeroing the block padding
 * and re-flooring the content box is a no-op precisely when the floor was
 * already winning, and that is every row the two builds already agreed on.
 *
 * `padding-block` is not `!important`, so a `py-*`/`pa-*` utility at the call
 * site still decides the row's padding — as it did on the Vue 2 build. It does
 * need three classes to outrank `.v-list-item--density-default.v-list-item--one-line`,
 * which is where Vuetify 3 puts its 4px.
 *
 * The ghost goes on `::before`, which Vuetify 3 leaves unused on this
 * component, and is placed in the `content` grid area: `.v-list-item` is a
 * grid now (`"prepend content append"`), so an auto-placed pseudo-element
 * would open a second row and add its height to the item instead of stacking
 * with the content.
 */
.v-list-item {
  --parity-list-floor: 48px;
}
.v-list-item::before {
  content: "";
  grid-area: content;
  /*
   * `inherit`, not the variable, so a component that sets its own
   * `min-height` on the row still decides the floor — which is what the ghost
   * did on the Vue 2 build.
   */
  min-height: inherit;
  font-size: 0;
}

.v-list-item--density-compact {
  /* Vuetify 2's `.v-list--dense .v-list-item`. */
  --parity-list-floor: 40px;
}

/*
 * Two classes, so a component's own `min-height` on the row still wins on
 * document order the way it did before; three for the padding, to clear
 * Vuetify's density rule.
 */
.v-list-item.v-list-item {
  min-height: var(--parity-list-floor);
}

.v-list-item.v-list-item.v-list-item {
  padding-block: 0;
}

/*
 * Vuetify 2's `.v-list-item__title` was `line-height: 1.2`; Vuetify 3's
 * `.v-list-item-title` is `1.5` (VListItem.css:310-316). At the 12px the app
 * writes its list titles in that is 3.6px a row, which is what remained of
 * `/library`'s document cards after the padding above: 78px against stable's
 * 74px.
 *
 * Two classes because Vuetify's own rule is one and lands after this file in
 * the cascade — measured, not assumed: at a single class the cards stayed at
 * 78px.
 */
.v-list-item-title.v-list-item-title {
  line-height: 1.2;
}

/*
 * Card text keeps the Vue 2 build's leading.
 *
 * Vuetify 2's `.v-card__text` set `line-height: 1.375rem` — an absolute 22px
 * that the whole subtree inherits whatever its own font size. Vuetify 3 writes
 * `.v-card .v-card-text { line-height: 1.425 }` (VCard.css:258-260), a factor,
 * which at the 14px the card sets is 19.95px. Every line of text inside a card
 * is 2.05px shorter than the Vue 2 build draws it; on the workflow preset
 * screen that was the last 2px of the 33px its container card had lost.
 *
 * The letter-spacing is the other half of the same declaration, and it is a
 * *width* difference that turns into a height one. Vuetify 2 spaced card text
 * `0.0071428571em`, Vuetify 3 spaces it `0.0178571429em` — 0.1px against 0.25px
 * a character at the 14px a card sets — and `letter-spacing` inherits, so it
 * reaches every descendant. On `/academy` the course title
 * "HACCP for Food Safety (Foundational course)" measures 339.97px on the Vue 2
 * build and 348.52px here; in its 211px box that is two lines against three, so
 * the card stood 210px tall against 196 and the section card holding it 287
 * against 273.
 *
 * Three classes: Vuetify declares the spacing in its `.v-card-text` block
 * (0,1,0) and the leading in `.v-card .v-card-text` (0,2,0), and its stylesheet
 * lands after this file in the cascade, so two would not be enough.
 */
.v-card .v-card-text.v-card-text {
  line-height: 1.375rem;
  letter-spacing: 0.0071428571em;
}

/*
 * A card title lays its children out in a row, as Vuetify 2's did.
 *
 * Reported on the workflow detail: a task card's avatar sat above "To be
 * Tasked with" instead of beside it. Measured on both builds:
 *
 *   .v-card-title  | Vue 2 flex, wrap, center, 32px | this build block, nowrap, 34px
 *   avatar / title | 813,144 and 906,152 — one row  | 813,144 and 813,163 — stacked
 *
 * Vuetify 2's `.v-card__title` was `display: flex; flex-wrap: wrap;
 * align-items: center; line-height: 2rem; word-break: break-all`
 * (vuetify.css:22636-22645). Vuetify 3's `.v-card-title` is `display: block;
 * white-space: nowrap; overflow: hidden; text-overflow: ellipsis` at
 * `line-height: 1.6` (VCard.css:189-207) — a single-line label. Ten titles in
 * this app were written as the flex row: an avatar, a spacer and a caption, or
 * a heading and a close button, relying on the row to place them.
 *
 * Every declaration here is one that measured differently; the padding did
 * not on a screen (this app sets its own on most titles) and is left alone.
 * Three classes because Vuetify states the leading in `.v-card .v-card-title`
 * (0,2,0) and its sheet lands after this file. Simulated at `app.css`'s real
 * position, the task card's five children land on the Vue 2 build's
 * coordinates to the pixel.
 */
.v-card .v-card-title.v-card-title {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  line-height: 2rem;
  white-space: normal;
  word-break: break-all;
  overflow: visible;
}

/*
 * A vertical divider takes no space above or below it.
 *
 * `v-divider` renders an `<hr>`, so Bootstrap's `hr { margin: 1rem 0 }` reaches
 * it. On the Vue 2 build Vuetify's own `.v-divider--vertical { margin: 0 -1px }`
 * shorthand zeroed the block axis back out; Vuetify 3 writes `margin-left: -1px`
 * and leaves the block axis alone, so the 1rem lands. `/compliance/index` grew
 * 10px the moment the Bootstrap rule came back.
 *
 * `margin-block`, not the shorthand, so a `mt-*` utility at the call site still
 * decides the top margin — which is what the Vue 2 build did: that divider
 * carries `mt-1` and measures 4px above, 0 below, on both builds now.
 */
.v-divider--vertical {
  margin-block: 0;
}

/*
 * An inset vertical divider takes its 8px from the top only.
 *
 * The rule Vuetify 2 wrote against the rule Vuetify 3 writes:
 *
 *   .v-divider--vertical.v-divider--inset {   .v-divider--inset.v-divider--vertical {
 *     margin-top: 8px;                          margin-top: 8px;
 *     min-height: 0;                            margin-bottom: 8px;   // added
 *     max-height: calc(100% - 16px);            max-height: calc(100% - 16px);
 *   }                                        }
 *
 * One declaration, and it costs the line 8px of the row it stretches in.
 * `/workflow`'s UI toggle — the app's only inset divider — draws 20px between
 * its two buttons against the Vue 2 build's 28, in a 36px flex row on both.
 *
 * `max-height` is the same on both builds and applies to neither: the flex
 * parent's height is not definite, so the percentage resolves to `none` and the
 * margins alone decide. 36 - 8 is 28; 36 - 8 - 8 is 20.
 *
 * `min-height` is deliberately not restored. Vuetify 2's `0` existed only to
 * undo its own `min-height: 100%` on the base vertical rule, which Vuetify 3
 * does not have — this build computes `auto` where that one computes `0px`, and
 * no divider in the app measures differently for it.
 *
 * Three classes: Vuetify's rule is two and its stylesheet is injected after this
 * file, so an equal-specificity rule here would lose. `margin-top` is left to
 * Vuetify, which happens to write the value Vuetify 2 wrote.
 */
.v-divider--vertical.v-divider--vertical.v-divider--inset {
  margin-bottom: 0;
}

/*
 * Pagination is the size and the shape the Vue 2 build drew.
 *
 * The control grew an element between the two majors:
 *
 *   Vue 2                          Vuetify 3
 *   nav.pagination (the app's)     nav.v-pagination.pagination
 *   ul.v-pagination — the row      ul.v-pagination__list — the row
 *   button.v-pagination__item      li.v-pagination__item > button.v-btn
 *
 * So the page number is a `v-btn` now, and everything Vuetify 2 put on
 * `.v-pagination__item` — `height: 34px`, `min-width: 34px`, `border-radius: 4px`,
 * a white background and an elevation-1 shadow — has no counterpart:
 * `.v-btn--icon.v-btn--density-default` sizes it `calc(var(--v-btn-height) + 12px)`
 * instead, which is 48px. Measured on `/audit-trail`, the row went 43.6px ->
 * 57.6px and the current page lost its filled `rgb(25, 118, 210)` pill.
 *
 * The size goes through `--v-btn-height` rather than `width`/`height` on
 * purpose. The token is declared on `.v-btn--size-default` (0,1,0), so a rule
 * at `.v-pagination__item .v-btn` (0,2,0) wins outright; `width` and `height`
 * are declared on `.v-btn--icon.v-btn--density-default` (0,2,0), which would
 * tie and then win on document order, since Vuetify's stylesheet lands after
 * this file.
 *
 * `margin: .3rem` is not restated: Vuetify 3 already carries it on
 * `.v-pagination__item`, the same value Vuetify 2 had, and neither is the 4px
 * `border-radius` — `.v-btn--rounded.v-btn--icon` already draws it, measured
 * before this rule existed. Restating either would only raise the specificity
 * a screen's own rule has to clear.
 */
.v-pagination__item .v-btn {
  /* 22 + 12 = the Vue 2 build's 34px. */
  --v-btn-height: 22px;
  min-width: 34px;
  width: auto;
  background: #ffffff;
  /* Vuetify 2's elevation-1, read off the Vue 2 build. */
  box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
  /*
   * The screens' own rules are written on `.v-pagination__item`, which is the
   * `<li>` now. `color` still reaches the button on its own; type does not,
   * because `.v-btn--size-default` sets `font-size: var(--v-btn-size)` on the
   * button itself. Same forwarding idiom as `.v-list-item__content` above.
   */
  font-size: inherit;
  font-weight: inherit;
  line-height: inherit;
}

.v-pagination__item--is-active .v-btn {
  background: #1976d2;
  /* Vuetify 2's elevation-3. `App.vue:1200` already forces the white text. */
  box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.2), 0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12);
}

.v-pagination__prev .v-btn,
.v-pagination__next .v-btn,
.v-pagination__first .v-btn,
.v-pagination__last .v-btn {
  /* 20 + 12 = the Vue 2 build's 32px navigation button. */
  --v-btn-height: 20px;
  background: #ffffff;
  box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
}

/*
 * …and the arrows keep the room Vuetify 2 gave them.
 *
 * The block above notes that `margin: .3rem` is not restated because Vuetify 3
 * already carries it. That is true of the numbers and not of the arrows:
 * Vuetify 2 set `margin: 0.3rem 10px` on `.v-pagination__navigation`, ten pixels
 * either side, while Vuetify 3 gives every child the same flat `0.3rem`
 * (`.v-pagination__item, .v-pagination__first, .v-pagination__prev, …`).
 *
 * Measured on `/audit-trail` and `/settings/version`: 4.8px sides against the
 * Vue 2 build's 10px, which pulls the arrows in against the numbers and takes
 * 20.8px off the row — 154.4 against 175.2 on `/settings/version`.
 *
 * (0,2,0), because Vuetify states it at (0,1,0) and its stylesheet is injected
 * after this file: an equal-weight rule would lose the tie.
 */
.v-pagination .v-pagination__prev,
.v-pagination .v-pagination__next,
.v-pagination .v-pagination__first,
.v-pagination .v-pagination__last {
  margin: 0.3rem 10px;
}

/*
 * A small chip is the size the Vue 2 build drew.
 *
 * Vuetify 2 sized chips with two declarations: a shared `padding: 0 12px` on
 * `.v-chip`, and a height per size — 16px x-small, 24px small, 32px default.
 * Vuetify 3 moved both into the size rule and changed the small one twice over
 * (VChip.css:66-69): `--v-chip-height: 26px` and `padding: 0 10px`. Its default
 * size is unchanged at 32px and 12px, which is why 59 of the app's 71 chips
 * already matched and the 12 small ones did not.
 *
 * Measured on `/dashboard`, where every task row carries two: chips 24px -> 26px
 * and 78/74 wide -> 74/70, which stood all nine task cards at 96px against the
 * Vue 2 build's 94.
 *
 * Three classes because Vuetify's own rule is two. Only `small` is restated:
 * the app writes no x-small, large or x-large chip anywhere, and Vuetify 2's
 * numbers for those (54px and 66px) are far enough from Vuetify 3's that
 * restoring them unmeasured would be a guess, not a parity fix.
 */
.v-chip.v-chip--size-small.v-chip--size-small {
  --v-chip-height: 24px;
  padding: 0 12px;
}

/*
 * A table header's sort icon flows with the label instead of taking width off it.
 *
 * Vuetify 2 put the label and the sort icon straight into the `<th>` as inline
 * content, so they wrapped together and the label could use the full cell.
 * Vuetify 3 wraps them in `.v-data-table-header__content`, a flex row
 * (VDataTable.css:143-145) — which makes the icon a flex item that reserves its
 * 18px whether or not it is visible, and it is invisible until hover
 * (`opacity: 0`).
 *
 * On `/dashboard`'s document widget that cost the "Days before Review" column
 * 8px of label: 70px wide over two lines on the Vue 2 build, 62px over three
 * here, standing the header row at 54.5px against 48. The narrower label also
 * redistributed the table's columns — the first went 178px -> 165 — which
 * wrapped a body row that had fit, 48px -> 55. The widget measured 546px
 * against 533.
 *
 * `display: block` is the Vue 2 layout, not an approximation of it: Vuetify 3
 * aligns a column through `text-align` on the `<th>` itself, and only uses the
 * flex row to decide which side the icon sits on. Inline content obeys that
 * `text-align`, and Vuetify 2 always put the icon after the label anyway.
 *
 * Two classes: Vuetify's own rule is one, and its stylesheet lands after this
 * file.
 */
.v-data-table-header__content.v-data-table-header__content {
  display: block;
}

/*
 * The app's default leading used to be restated here as the Vue 2 build's
 * `line-height: 1.5` (Vuetify 2's `.v-application` rule, which Vuetify 3
 * does not ship), on both `.v-application` and `.v-overlay-container` —
 * the second because Vuetify 3 teleports every overlay out of the app root
 * and Bootstrap's `body` leading reached it instead. Both are superseded:
 * `_typography.scss` sets the brand's Body style (14px / 20px) on both roots,
 * and the brand supersedes the Vue 2 number.
 */
/*
 * A pagination list carries no margin of its own.
 *
 * `v-pagination` renders a `<ul>`, so Bootstrap Reboot's
 * `ol, ul, dl { margin-bottom: 1rem }` reaches it. Vuetify 2 zeroed it back out
 * with `margin: 0` on `.v-pagination`; Vuetify 3's `.v-pagination__list` sets
 * `display`, `list-style-type`, `justify-content` and `width` — and no margin.
 *
 * The 16px lands below the control, inside whatever wraps it:
 * `/audit-trail`'s `.pagination-container` measured 59.6px around a 43.6px row.
 * Same shape as the `v-divider` finding — a Bootstrap element rule that
 * Vuetify 2 happened to cancel and Vuetify 3 does not.
 *
 * `margin-block`, so a `my-*` at the call site still decides.
 */
.v-pagination__list {
  margin-block: 0;
  /*
   * And no indent either. `.v-application ul` below restores the 24px Vuetify 2
   * put on lists inside the app, and Vuetify 2 zeroed it again on its own list
   * components — `.v-pagination { padding-left: 0 }` is in the Vue 2 bundle.
   * Vuetify 3 has no such rule because nothing was adding one. Measured: with
   * the indent alone this reads 24px on `/audit-trail` and `/settings/version`
   * where the Vue 2 build reads 0.
   *
   * `.v-application` in front because the indent rule is (0,1,1) and one class
   * here would lose to it.
   */
}

.v-application .v-pagination__list {
  padding-left: 0;
}

/*
 * An underlined field sits 4px below whatever is above it.
 *
 * Vuetify 2's `.v-text-field { padding-top: 12px; margin-top: 4px }` applied to
 * every field and was cancelled by
 * `.v-text-field--enclosed { margin: 0; padding: 0 }` — so the 4px belonged to
 * the *non-enclosed* field, the plain underlined one. Vuetify 3 has no
 * equivalent at any variant.
 *
 * `--plain-underlined` is the class Vuetify 3 puts on the input root for that
 * variant, and it is the one the wrapper produces for a Vuetify 2 field with no
 * `outlined`, `solo` or `filled` (`propCompat.js:58-75` never returns `plain`).
 * So the selector names exactly the fields Vuetify 2 gave the margin to.
 *
 * Measured on `/workflow`, whose filter row holds three of them: 62px against
 * the Vue 2 build's 66, which was the whole of that screen's 4px. Everywhere
 * else the app writes `mt-0` and wins on `!important` — as it did on the Vue 2
 * build, which is why `/audit-trail`'s four underlined fields have no margin on
 * either build.
 */
.v-input--plain-underlined {
  margin-top: 4px;
}

/*
 * An icon is the size, and the shape, the Vue 2 build drew it.
 *
 * Vuetify 2 sized an icon with a flat base and four rules over it, all four of
 * them about a button. Read out of the Vue 2 build's own stylesheet:
 *
 *   .v-icon.v-icon                          { font-size: 24px }
 *   .v-btn--icon.v-size--x-small  .v-icon,
 *   .v-btn--fab.v-size--x-small   .v-icon   { 18px, and an 18x18 box }
 *                       small,  default     { 24 }
 *                       large               { 28 }
 *                       x-large             { 32 }
 *   .v-btn__content .v-icon.v-icon--left,
 *   .v-btn__content .v-icon.v-icon--right   { 18px, and an 18x18 box }
 *
 * Vuetify 3 has none of them. Its size is `.v-icon--size-<n>` as a multiple of
 * the surrounding text, so an icon takes its size from its context: `1.5em` of
 * an `x-small` button's 10px is 15px, `1.5em` of a pagination button's 14px is
 * 21px. Measured against the Vue 2 build, that was 137 glyphs at 15 against 18
 * on `/evidence/detail/73`, the `/settings/version` pagination arrows at 21
 * against 24, and every `--start` icon in a button square to its own font
 * instead of 18.
 *
 * The base is Vuetify 2's selector character for character, so every app rule
 * that outranked it then outranks it now; the four below carry Vuetify 2's
 * specificities for the same reason, (0,3,0) and (0,4,0), and both clear
 * Vuetify's own `.v-icon--size-*` at (0,1,0) even though this file loads first.
 *
 * `.v-btn .v-icon` no longer needs an exclusion. What the four rules leave is a
 * bare icon in a button that is not an icon button, and Vuetify 2 gave that the
 * 24px base too.
 */
.v-icon.v-icon {
  font-size: 24px;
  /*
   * Vuetify 2 declared no box at all — `.v-icon` was an `inline-flex` and
   * nothing more, so a glyph took its natural width. Vuetify 3 writes
   * `height: 1em; width: 1em; min-width: 1em` (VIcon.css:5-16), which squares
   * every Font Awesome glyph: `fa-caret-down` in the company activator is 18px
   * wide here against the Vue 2 build's 11.25, and `/library`'s 13 file-type
   * icons are 60 against 70. That is the whole of the activator's 199px against
   * 192 — 18 - 11.25 plus 18 - 17.44 on the icon beside it.
   *
   * `min-width: auto`, not `0`. `auto` is the initial value — min-content for a
   * flex item, 0 for anything else — which is the property being undeclared,
   * and undeclared is what Vuetify 2 had. `0` is not the same thing and is
   * measurably worse: three of `/library`'s 35 file icons shrink to zero width
   * inside their flex row.
   */
  width: auto;
  min-width: auto;
  height: auto;
}

/*
 * Vuetify 2's icon-button map, transposed onto Vuetify 3's names.
 *
 * `v-size--*` became `v-btn--size-*`, and Vuetify 3's `.v-btn--icon` covers
 * both of Vuetify 2's `icon` and `fab` — which is already how `AppBtn` maps
 * them, so one selector family stands in for Vuetify 2's two.
 *
 * `min-width` and `min-height` beside `width` and `height` are not belt and
 * braces. Vuetify 2's `VIcon` wrote the `size` prop out as an inline
 * `font-size` and nothing else, so the rule's `height: 24px` still shaped an
 * explicitly-sized glyph; Vuetify 3's writes `fontSize`, `height` *and* `width`
 * inline (VIcon.js:68-70), and a stylesheet can only outrank an inline `width`
 * through `min-width`. `/dashboard`'s two `small` icon buttons are the case:
 * the Vue 2 build draws a 16px glyph inside a 24x24 box.
 */
.v-btn--icon.v-btn--size-x-small .v-icon {
  font-size: 18px;
  width: 18px;
  min-width: 18px;
  height: 18px;
  min-height: 18px;
}

.v-btn--icon.v-btn--size-small .v-icon,
.v-btn--icon.v-btn--size-default .v-icon {
  font-size: 24px;
  width: 24px;
  min-width: 24px;
  height: 24px;
  min-height: 24px;
}

.v-btn--icon.v-btn--size-large .v-icon {
  font-size: 28px;
  width: 28px;
  min-width: 28px;
  height: 28px;
  min-height: 28px;
}

.v-btn--icon.v-btn--size-x-large .v-icon {
  font-size: 32px;
  width: 32px;
  min-width: 32px;
  height: 32px;
  min-height: 32px;
}

/*
 * Vuetify 2's `.v-btn__content .v-icon--left/--right`, under Vuetify 3's names.
 * `AppIcon` already translates the call sites' `left`/`right` into
 * `start`/`end`; this is the size those two carried.
 */
.v-btn__content .v-icon.v-icon--start,
.v-btn__content .v-icon.v-icon--end {
  font-size: 18px;
  width: 18px;
  min-width: 18px;
  height: 18px;
  min-height: 18px;
}

/*
 * An icon fills the avatar it sits in.
 *
 * Vuetify 2's `.v-avatar .v-icon` set `height: inherit; width: inherit`
 * (and `border-radius: inherit`); Vuetify 3's VAvatar.css keeps the equivalent
 * for `.v-img` and dropped it for the icon. Measured on `/dashboard`, whose
 * carbon banner puts a `fa-leaf` in a `size="32"` avatar: 32x32 on the Vue 2
 * build, 24x24 here.
 *
 * It has to come after the base above, which it ties with at two classes, and
 * it is the reason that base is not the whole story: undeclaring the box let
 * the leaf take its natural 27x24, which is its own width but still not the
 * avatar's.
 */
.v-avatar .v-icon {
  border-radius: inherit;
  width: inherit;
  height: inherit;
}

/*
 * …and so does a raw image, which is the other half of the same Vuetify 2
 * rule: `.v-avatar img, .v-avatar svg, .v-avatar .v-icon, .v-avatar .v-image
 * { border-radius: inherit; display: inline-flex; height: inherit; width:
 * inherit }`. Vuetify 3 keeps the sizing for `.v-img` alone (VAvatar.css:104),
 * so an `<img>` written straight into an avatar — `AvatarGroup.vue` and twelve
 * other sites — draws at its natural size and is clipped to the circle.
 * Measured on the workflow canvas: a user's 82×64 picture cropped to an 18px
 * disc, where the Vue 2 build drew the whole picture at 14×14 inside its 2px
 * of padding.
 */
.v-avatar img,
.v-avatar svg {
  border-radius: inherit;
  display: inline-flex;
  width: inherit;
  height: inherit;
}

/*
 * A block button fills its box whatever `min-width` it was also given.
 *
 * Vuetify 2's `.v-btn--block { min-width: 100% !important }` beat the inline
 * `min-width` the prop writes; Vuetify 3's rule is the same without the
 * `!important` (VBtn.css:211-215), so the prop wins. The business process's
 * edit / archive / duplicate buttons pass both `block` and `min-width="36px"`
 * and came out 48px wide against the Vue 2 build's 137.5 — each filling a
 * third of the row. Eight `block` buttons in the app pass a `min-width`.
 * `max-width: none` is the rest of the Vuetify 2 declaration.
 */
.v-btn--block {
  min-width: 100% !important;
  max-width: none;
}

/*
 * A `<div class="v-btn">` lays its child out the way Vuetify 2's did.
 *
 * Twenty-eight sites wrap a disabled button in a `div` wearing `.v-btn` so a
 * tooltip has a live element to hang from. Vuetify 2's `.v-btn` made that
 * div an `inline-flex` box centring its content; Vuetify 3's `.v-btn` is an
 * `inline-grid` with `max-content auto max-content` columns (VBtn.css), so a
 * `block` button inside it is sized to a `max-content` column instead of the
 * row — the business process's duplicate button, 48px inside a 137.5px div
 * even with the rule above, because its `100%` resolved against that column.
 * Vuetify 3 renders no `div.v-btn` of its own (VBtn renders a button or an
 * anchor), so the tag is what tells the hand-written ones apart.
 */
div.v-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/*
 * A sort icon is 18px, which is the one size Vuetify 2 set inline rather than
 * through a rule — so the base above would make it 24. Pinned at the literal
 * value the Vue 2 build draws rather than handed back to
 * `--v-icon-size-multiplier`, which happens to yield 18 in a table header today
 * and would follow the header's type the moment that changed.
 */
.v-data-table-header__sort-icon.v-data-table-header__sort-icon {
  font-size: 18px;
}

/*
 * A switch is the size the Vue 2 build drew it.
 *
 * Vuetify 3 rebuilt the switch around `.v-selection-control` and resized every
 * part of it: an inset track is 32px tall and 52px wide (VSwitch.css:49-54),
 * the thumb is 24px, and the row is floored by `--v-selection-control-size`,
 * which is 28px at compact density (VSelectionControl.css:40-42). The Vue 2
 * build draws a 24px row around a 44x22 track with an 18px thumb.
 *
 * Measured on `/settings/version`, where three of them sit in the author
 * permission cards: 40px each against 24, which was 48px of that page's 1683
 * against the Vue 2 build's 1635.
 *
 * This is the case the checkbox rule above deferred by name — "radios and
 * switches are selection controls too and were not measured". All 23 switches
 * in the app pass `inset` and `density="compact"`, so there is one shape to
 * match rather than a family. Radios are still unmeasured and still untouched.
 *
 * Two of the four specificities carry the fix and neither is arbitrary:
 *
 *   - `--v-selection-control-size` is declared on `.v-selection-control` here
 *     because that is where Vuetify declares it, and an element's own
 *     declaration beats an inherited one. Put on `.v-switch` it is ignored and
 *     the row stops at 28px — measured, not assumed.
 *   - the track and thumb rules repeat `--inset` because Vuetify's own are
 *     `.v-switch--inset .v-switch__track` and `.v-switch--inset .v-switch__thumb`,
 *     two classes each, landing after this file. Two classes here lose on
 *     document order — measured: the row reached 24px while the track stayed
 *     52x32 and the thumb 24x24.
 *
 * `transform: none` because Vuetify 3 shrinks an *unchecked* inset thumb to
 * two thirds (`VSwitch.css:81-90`) and restores it only when the control is
 * dirty. The Vue 2 build draws 18px in both states — measured on this page,
 * whose third switch is off and still 18x18 — so an 18px thumb would otherwise
 * read 12px when off.
 *
 * The control comes out 44px wide against the Vue 2 build's 56. These switches
 * sit in a `justify-space-between` row, so that moves nothing — recorded rather
 * than chased.
 */
.v-switch.v-switch {
  --v-input-control-height: 24px;
}

.v-switch .v-selection-control {
  --v-selection-control-size: 24px;
}

.v-switch--inset.v-switch--inset .v-switch__track {
  height: 22px;
  min-width: 44px;
}

.v-switch--inset.v-switch--inset .v-switch__thumb {
  height: 18px;
  width: 18px;
  transform: none;
}

/*
 * A radio is the size and the shape the Vue 2 build drew it.
 *
 * The last selection control, and the one the checkbox and switch cycles both
 * deferred by name — "radios are still unmeasured and still untouched". They
 * were unmeasured for a reason: 111 call sites and not one of them renders on
 * any of the sixteen sweep routes, or on any route at rest, on either build.
 * The measurement here is `/form-builder/form/edit/242` — a form with two radio
 * fields — with its fifth section tab open, on both builds.
 *
 * Vuetify 2 sized a radio with three rules that Vuetify 3 has no equivalent of:
 *
 *   .v-input--selection-controls__input { height: 24px; width: 24px }
 *   .v-application--is-ltr .v-input--selection-controls__input { margin-right: 8px }
 *   .v-input--radio-group--row .v-radio { margin-right: 16px }
 *
 * Vuetify 3 replaced the first with `--v-selection-control-size`, which is 40px
 * at the default density and 28px at compact (VSelectionControl.css:36-42) —
 * where Vuetify 2's tick was 24px at *both* — and dropped the other two
 * outright, so the label butts against the tick and the radios in a row have no
 * gap between them. Measured: a 28x28 tick in a 28px row against a 24x24 tick in
 * a 24px one, with 0px where the Vue 2 build puts 8 and 16.
 *
 * The icon inside is 20px, not the 24px base. Vuetify 2 gave a selection
 * control in a dense input the `v-icon--dense` class and sized it
 * `font-size: 20px`; Vuetify 3 has no dense icon and draws the default 24. It
 * fills its 24px tick either way, because Vuetify 2's
 * `.v-input--selection-controls__input .v-icon { width: 100% }` said so — which
 * the icon cycle's `width: auto` base would otherwise shrink to the glyph.
 *
 * Scoped to `.v-radio` throughout, deliberately. Vuetify 2's tick rules were
 * written for *every* selection control, checkboxes and switches included, but
 * those two were measured and closed in their own cycles and are right as they
 * stand — widening these rules would move them off the numbers they were pinned
 * to.
 *
 * `.v-radio.v-radio` for the token because Vuetify declares it on the same
 * element (`.v-selection-control--density-compact`), and `.v-radio.v-radio` for
 * the label because Vuetify's `.v-selection-control .v-label` is two classes and
 * lands after this file. The other three compete with nothing.
 *
 * The 20px glyph keys off the *control's* own density class rather than the
 * group's `.v-input--density-compact`. Both are present when a group sets the
 * density, which is every call site the app has; a radio given `density`
 * directly carries only the first, and a descendant selector would miss it.
 *
 * The row direction is not here — it is a renamed prop, not a rule. See the
 * three `:inline` call sites.
 */
.v-radio.v-radio {
  --v-selection-control-size: 24px;
}

.v-radio .v-selection-control__wrapper {
  margin-right: 8px;
}

.v-radio .v-selection-control__input > .v-icon {
  width: 100%;
  height: 100%;
}

.v-radio.v-selection-control--density-compact .v-icon {
  font-size: 20px;
}

.v-selection-control-group--inline .v-radio {
  margin-right: 16px;
}

.v-radio.v-radio .v-label {
  height: auto;
}

/*
 * A bullet list keeps the indent Vuetify 2 gave it.
 *
 * A bare `<ul>` appended to `<body>` reads `padding-left: 0` on *both* builds,
 * so this is not a reset that went missing: Vuetify 2 put the indent back
 * inside the app with `.v-application ul, .v-application ol { padding-left:
 * 24px }`, and Vuetify 3 ships nothing equivalent.
 *
 * Every rich-text list the app renders has been drawing its bullets flush with
 * the text, which is a content defect before it is a height one. It shows as a
 * height on the academy course page, where the last item of "Who this course is
 * for" has 749px of room here against 725 on the Vue 2 build and so fits on one
 * line where the Vue 2 build wraps it onto two: the page measured 1458 against
 * 1477.
 *
 * Vuetify 3's own `<ul>` components are unaffected — `.v-breadcrumbs` sets its
 * own padding at higher specificity, and `.v-pagination__list` is zeroed above,
 * the way Vuetify 2 zeroed `.v-pagination`.
 */
.v-application ul,
.v-application ol {
  padding-left: 24px;
}

/*
 * A breadcrumb carries no margin of its own.
 *
 * `.v-breadcrumbs` is a `<ul>`, so Bootstrap Reboot's
 * `ol, ul, dl { margin-bottom: 1rem }` reaches it: `0px/16px` here against
 * `0px/0px` on the Vue 2 build, where Vuetify 2's own rule cancelled it.
 *
 * Fourth instance of the same shape, after `hr`, the vertical divider and the
 * pagination list: a `v-*` component that renders a bare element inherits
 * Reboot, and Vuetify 2's resets were quietly doing that cancelling.
 */
.v-breadcrumbs {
  margin-block: 0;
}

/*
 * A divider is a line you can see.
 *
 * Vuetify 2 drew one with `border-color: rgba(0, 0, 0, 0.12)` at full opacity.
 * Vuetify 3 takes the colour from `currentColor` and applies
 * `opacity: var(--v-border-opacity)` — 0.12 — instead. `v-divider` renders an
 * `<hr>`, so the colour it inherits here is Bootstrap's `rgba(0, 0, 0, 0.1)`,
 * and 0.1 multiplied by an opacity of 0.12 is an effective alpha of **0.012**: a
 * line that is technically drawn and practically invisible.
 *
 * Reported on the account menu, where three of them separate the sections, and
 * true of every divider in the app.
 *
 * Two classes: Vuetify's own rule is one and its stylesheet lands after this
 * file.
 */
.v-divider.v-divider {
  border-color: rgba(0, 0, 0, 0.12);
  opacity: 1;
}

/*
 * A list subheader keeps the Vue 2 build's leading.
 *
 * Vuetify 2's `.v-subheader` set no `line-height` at all and inherited
 * `.v-application`'s 1.5, which at its 14px is 21px. Vuetify 3 pins
 * `line-height: 1.375rem` — 22px. One pixel a subheader, and the last two of the
 * account menu's card once its dividers and its subheader height are right.
 *
 * Two classes, because Vuetify's own rule is one and lands after this file —
 * measured, not assumed: at one class the subheader stayed 22px through a full
 * rebuild.
 */
.v-list-subheader.v-list-subheader {
  line-height: 1.5;
}

/*
 * A breadcrumb item takes no horizontal padding, as the Vue 2 build drew it.
 *
 * Vuetify 2's `.v-breadcrumbs__item` set none at all; Vuetify 3's
 * `.v-breadcrumbs-item` adds `padding: 0 4px` (VBreadcrumbs.css:30-35). That is
 * 8px an item, and the library preview's three-crumb trail measured 193px wide
 * against the Vue 2 build's 169 — the dividers between them are already
 * identical at `0 4px` on both.
 *
 * Two classes, because Vuetify's own rule is one and its stylesheet lands after
 * this file.
 */
.v-breadcrumbs-item.v-breadcrumbs-item {
  padding: 0;
}

/*
 * A loading overlay centres its spinner, as the Vue 2 build drew it.
 *
 * Vuetify 2's `.v-overlay` was a centring flex box — `align-items: center;
 * justify-content: center` — with `.v-overlay__content` in normal flow.
 * Vuetify 3 rebuilt the component as the positioning primitive behind VDialog,
 * VMenu and VTooltip, which place their own content: `.v-overlay` keeps
 * `display: flex` and drops both alignment properties, and
 * `.v-overlay__content` becomes `position: absolute` (VOverlay.css:26-40).
 * With no insets that lands the spinner at 0,0 — on the library preview, in the
 * top-left corner of the viewport with half of it behind the app bar, against
 * the Vue 2 build's 928,393.5.
 *
 * Both declarations are needed and neither works alone: an absolutely
 * positioned child is out of flow, so the alignment moves nothing until the
 * content is `relative` again.
 *
 * `color` is Vuetify 2's `.theme--dark.v-overlay`, which every one of these
 * overlays got — its `dark` defaulted to true — and is what made the spinner
 * white. Vuetify 3 has no such rule, so the spinner inherited the app's body
 * colour and came out near-black on the scrim.
 *
 * Keyed on `.app-overlay`, which AppOverlay adds, so that the dialogs, menus,
 * tooltips and snackbars sharing these classes are left alone — they position
 * their content through `locationStrategy` and must stay absolute.
 *
 * Two classes, because Vuetify's own rules are one and its stylesheet lands
 * after this file.
 */
.v-overlay.app-overlay {
  align-items: center;
  justify-content: center;
  color: #fff;
}

.v-overlay.app-overlay > .v-overlay__content {
  position: relative;
}

/*
 * A column is a containing block, as the Vue 2 build's was.
 *
 * Vuetify 2's grid opened its column rule with `position: relative` — the same
 * declaration Bootstrap's does, and for the same reason: a column is the box an
 * absolutely positioned child is meant to hang off. Vuetify 3's rule sets only
 * `width` and `padding` (VGrid.css:29-38), so every `.v-col` stopped being a
 * containing block and its absolutely positioned children started resolving
 * against whatever ancestor happens to be positioned further up.
 *
 * Found through the profile avatar, whose `absolute` overlay is positioned by
 * the app's own `top: 12px; left: 12px`: against the column it lands on the
 * 100x100 avatar at 132,234, which is where the Vue 2 build draws it, and
 * against the page it landed 24px up and to the left, off the picture entirely.
 * Nothing else on the sixteen routes moves.
 *
 * Both selectors, because Vuetify writes the sized columns as `v-col-md-1` and
 * the unsized one as `v-col`; an element usually carries both but not always.
 * One class each is enough — Vuetify declares no `position` here to beat.
 */
.v-col,
[class*=v-col-] {
  position: relative;
}

/*
 * A list row's content box keeps the Vue 2 build's padding and leading.
 *
 * Vuetify 2's `.v-list-item__content` carried `padding: 12px 0`, and its
 * `.v-list-item__subtitle` took `line-height: 1.2` from the item
 * (export-pdf.css:52078-52086, 52139-52142). Vuetify 3's content wrapper
 * carries no padding at all and pins the subtitle at `line-height: 1rem`
 * (VListItem.css:219-224, 286-292) — 16px whatever the font size, against the
 * 16.8 the app's 14px subtitle had. The file-detail sidebar's rows came out 48px
 * against the Vue 2 build's 61.2, twelve of them, about 160px of panel.
 *
 * The padding is keyed on `.app-list-item-content` and not on Vuetify's own
 * class, because Vuetify 3 wraps *every* row in a `.v-list-item__content` —
 * the nav drawer's included, and those had no such element on the Vue 2 build.
 * Measured: on Vuetify's class the sidebar's rows reach 54.4 and the drawer's
 * correct 48 becomes 53. `AppListItemContent` renders this class at exactly the
 * call sites whose Vue 2 markup wrote `<v-list-item-content>`, so the padding
 * lands where it landed before and nowhere else.
 *
 * The subtitle rule is the pair of the title's above, which an earlier cycle
 * fixed for the same reason and left this one behind.
 */
.app-list-item-content {
  padding: 12px 0;
}

/*
 * …and 8px in a dense list, which is Vuetify 2's `.v-list--dense .v-list-item
 * .v-list-item__content { padding: 8px 0 }`. The rule above was measured on
 * the file-detail sidebar, whose lists are not dense, and left every compact
 * list 8px too tall a row: the workflow canvas's plus menu draws its items
 * 40px against the Vue 2 build's 32 (its own `min-height: 30px` floor, 8 + a
 * 16px title + 8). Both selectors because Vuetify 2 named both: the density
 * can sit on the list or on a single item.
 */
.v-list--density-compact .app-list-item-content,
.v-list-item--density-compact .app-list-item-content {
  padding: 8px 0;
}

/*
 * A dense list's subheader is inset 8px, as Vuetify 2 drew it.
 *
 * Vuetify 2: `.v-list--dense .v-subheader { padding: 0 8px }`. Vuetify 3 keeps
 * its 16px whatever the density — and states the start side as
 * `padding-inline-start: calc(16px + var(--indent-padding)) !important` at
 * (0,2,0), after this file (VList.css:86-87). Hence three classes and
 * `!important`, and `--indent-padding` kept in the sum so an `inset` subheader
 * still indents. Measured on the plus menu: "General Task" at 16px from the
 * card edge against the Vue 2 build's 8.
 *
 * The second rule is the one utility the app writes on a dense subheader —
 * `px-0` on the library preview lists — which Vuetify 2's rule, not being
 * `!important`, let win. Vuetify 3's is, so it never did here; this puts the
 * utility back on top.
 */
.v-list--density-compact .v-list-subheader.v-list-subheader {
  padding-inline: calc(8px + var(--indent-padding)) 8px !important;
}

.v-list--density-compact .v-list-subheader.v-list-subheader.px-0 {
  padding-inline: 0 !important;
}

.v-list-item-subtitle.v-list-item-subtitle {
  line-height: 1.2;
}

/*
 * A button's label sits where the Vue 2 build put it.
 *
 * `app.scss` gives `.primary-button`, `.secondary-button` and `.gray-button`
 * `padding: 10px 22px 12px 16px` — asymmetric, and taller than the 36px button
 * can hold once its 19px label is inside. Vuetify 2's `.v-btn` was an
 * `inline-flex` with `align-items: center`, so an over-tall label was centred
 * across the padding box and overhung it symmetrically. Vuetify 3's is an
 * `inline-grid`: the single content row is sized to the label and placed
 * straight after `padding-top`, which drops the label from 71.5 to 74 — the
 * text sitting visibly low in the box on every button the app pads that way.
 *
 * `align-content` centres that row in the content box, which is what flex was
 * doing. Measured on the library preview, all four such buttons land on the
 * Vue 2 build's numbers — 71.5, 267.5, 337 — and Reject, which takes Vuetify's
 * own symmetric `0 16px`, stays at 338 on both. No button changes width or
 * height and no icon moves; only the four the app pads asymmetrically shift.
 *
 * One class, not two: Vuetify declares `align-items` and `justify-content` on
 * `.v-btn` and no `align-content`, so there is nothing here to outrank —
 * verified at a single class rather than assumed.
 */
.v-btn {
  align-content: center;
}

/*
 * Field text is a colour at full strength, not a colour at 87% of itself.
 *
 * Reported as a white veil over the signature dialog's textarea: type into
 * "Signature limit has been reached" on /settings/general?tab=profile-settings
 * and the text comes out washed out against the Vue 2 build's near-black.
 * Measured on both, with the same "asd" typed in:
 *
 *   textarea      | Vue 2 build        | this build
 *   color         | rgba(0, 0, 0, .87) | rgb(54, 64, 90)
 *   opacity       | 1                  | 0.87
 *
 * Two halves of one change. Vuetify 2 expressed high emphasis as a *colour* at
 * full opacity — `.theme--light.v-input input, .theme--light.v-input textarea
 * { color: rgba(0, 0, 0, .87) }`. Vuetify 3 expresses it as opacity instead:
 * `.v-field__input { opacity: var(--v-high-emphasis-opacity) }`, the variable
 * measured at 0.87 on both `.v-application` and `.v-overlay-container`.
 *
 * Dropping the Vuetify 2 colour rule did not merely leave the text black. It
 * uncovered `app.scss`'s own `[class*='text-']` typography rule, which has
 * always matched here — `.v-text-field` contains the substring, and this
 * textarea carries `.text-area` besides — and which Vuetify 2's rule outranked
 * at (0,2,1) against (0,2,0). With it gone the app rule wins and paints field
 * text #36405a, which the 0.87 then lightens to roughly rgb(83, 90, 111): the
 * veil. The two effects compound, so both halves are restored.
 *
 * The colour is deliberately re-declared at (0,2,1) — Vuetify 2's own weight,
 * not a class more. `GeneralSetting.vue` carries `.general-container .v-input
 * input { color: rgb(0, 0, 0) }` at exactly the same (0,2,1), and on the Vue 2
 * build that tie is broken by order: component styles are injected at runtime,
 * after the stylesheet, so the page rule wins and the profile form's inputs are
 * pure black. Adding a class here would take those inputs to 87% black and
 * break a page that currently matches. Same weight, same tie, same loser.
 *
 * The opacity is lifted on the `.v-field__input` box rather than on the
 * controls, because that box is also VSelect's selection container and Vuetify
 * 2 dimmed neither. Two rules had to be left alone to do it, both verified
 * against the installed build rather than assumed:
 *
 *   - a disabled field signals itself through its label and icons, which an
 *     earlier cycle moved the dimming onto when it lifted
 *     `.v-field--disabled` to opacity 1 so a read-only value stays legible.
 *     Those rules sit on the label and the icon, not on the box, so this rule
 *     neither reaches them nor is reached by them.
 *   - `.v-select--selected .v-field .v-field__input > input { opacity: 0 }`
 *     hides a select's inner input behind its selection chip. The selector here
 *     stops at the box and never names `> input`, so the input stays hidden —
 *     naming it would print the value twice.
 *
 * A note for the next person measuring this: `getComputedStyle` returned 1 for
 * this textarea twice while the rule dump plainly said 0.87. Both readings
 * followed a stylesheet being added or removed in a tab whose
 * `visibilityState` was "hidden", where style recalculation is deferred. A
 * forced reflow and a throwaway probe element carrying the same class agree on
 * 0.87 five times running; that is the number.
 */
.v-field .v-field__input {
  opacity: 1;
}

.v-field input.v-field__input,
.v-field textarea.v-field__input {
  color: rgba(0, 0, 0, 0.87);
}

/*
 * A select's value is the colour Vuetify 2 painted it, not the colour above it.
 *
 * Reported on `/audit-trail`: choose a value in a filter select and the text
 * that appears reads grey. Measured on both builds with "Action" set to Logout:
 *
 *   filled selection | Vue 2 rgba(0,0,0,.87) | this build rgb(187,187,187)
 *   plain select, no page rule above it
 *                    | Vue 2 rgba(0,0,0,.87) | this build rgb(54,64,90)
 *
 * One defect with two faces, and the second is app-wide. Vuetify 2 gave the
 * value box its own colour — `.theme--light.v-select .v-select__selections
 * { color: rgba(0,0,0,.87) }`, read from that build's installed
 * `vuetify.css:19493` — so nothing above it could reach the value. Vuetify 3
 * ships no colour for the selection at all: `VSelect.css` styles
 * `.v-select__selection` and `.v-select__selection-text` for layout only, and
 * `VField.css:38-42` colours just the two enclosed variants, `solo` and
 * `solo-filled`. Every other variant inherits, and inherits whatever is above:
 *
 *   - on `/audit-trail` and `/notifications`, #bbbbbb. Both screens carry
 *     `.filter .v-input { color: #BBBBBB }` — in *both* builds. On the Vue 2
 *     build the rule is inert here, because an inherited colour never reaches
 *     an element that declares its own.
 *   - everywhere else, #36405a: `app.scss`'s `[class*='text-']` rule, which
 *     matches every field because the root carries `v-text-field`. The same
 *     navy leak the veil cycle fixed one rule above; the selection was left
 *     behind because that fix names `.v-field__input` as an *input element*,
 *     and in a select that class is on a `div`.
 *
 * `.v-field__input` is the Vue 3 name for `.v-select__selections`: the box
 * holding the selection spans and the inner input. This migration already made
 * that mapping by hand once — `settings/dialog/DocumentNumberFormatDialog.vue`
 * says `.theme--light.v-select .v-select__selections` on the Vue 2 build and
 * `.v-theme--light.v-select .v-field__input` here.
 *
 * (0,3,0) is deliberate: it is Vuetify 2's own weight. Matching it makes every
 * tie resolve the way it resolved on the Vue 2 build — a page rule at (0,3,0)
 * still wins, because component styles are injected after `app.css`, and one at
 * (0,2,0) still loses. A weaker or stronger rule would quietly re-order screens
 * that match today.
 *
 * Three roots rather than one. Vuetify 2 put `v-select` on autocompletes and
 * comboboxes as well; Vuetify 3 does not — a `VAutocomplete` root carries
 * `v-autocomplete` alone. Probed with each root in turn: with only the
 * `.v-select` selector an autocomplete stays navy.
 *
 * The colour is stated on the box and not restated on `.v-select__selection`,
 * which is what lets three rules keep winning, each verified by probe at
 * `app.css`'s real position in the cascade:
 *
 *   - `.v-field--disabled .v-select__selection` further down this file, and
 *     `.general-container .v-input .v-select__selection` on the profile screen.
 *     Both sit on the span, and a direct declaration beats an inherited one
 *     whatever the weights.
 *   - the placeholder, which has its own rule higher up and stays at
 *     rgba(0,0,0,.38) on both builds.
 *
 * Vuetify 2's *other* missing declaration, `.theme--light.v-input
 * { color: rgba(0,0,0,.87) }`, would fix this too and is deliberately not
 * restored: prefix and suffix text measure navy on **both** builds, so a
 * baseline rule would push this build away from the Vue 2 one.
 */
.v-theme--light.v-select .v-field__input,
.v-theme--light.v-autocomplete .v-field__input,
.v-theme--light.v-combobox .v-field__input {
  color: rgba(0, 0, 0, 0.87);
}

/*
 * A field spaces its letters the way the browser spaces them.
 *
 * The second half of the same report: with the veil lifted, a *long* line in
 * the signature textarea still read grey while a short line under it read
 * black. Both lines, same element, same colour — so nothing was covering one
 * of them. Setting both lines to identical glyphs and screenshotting showed the
 * difference survives the glyph shapes, which rules out an artefact of thin
 * letters at 12px.
 *
 * Measured on both builds:
 *
 *   letter-spacing    | Vue 2 build | this build
 *   textarea (12px)   | normal      | 0.1125px
 *   input (14px)      | normal      | 0.13125px
 *   select selection  | normal      | 0.15px
 *
 * Vuetify 3 declares `letter-spacing: 0.009375em` on `.v-field` and restates it
 * on `.v-field__input`. Vuetify 2 declared neither. A fractional per-character
 * offset accumulates along a line, so each successive glyph lands further from
 * the pixel grid and is antialiased across two pixels instead of one: a
 * thirty-seven character line greys out while a six character line beside it
 * stays crisp. That is the "white shadow", and it is why it looked like it fell
 * on one line and not the other.
 *
 * Vuetify 2 needed no rule for the controls because the browser already has
 * one. Chrome's UA stylesheet resets `letter-spacing: normal` on `input` and
 * `textarea` — verified here rather than assumed, by putting both inside a div
 * at `letter-spacing: 5px`: the div's own text took 5px, a span took 5px, and
 * the two controls came out `normal`. Vuetify 3's declarations are author-level
 * and override that reset; Vuetify 2 never wrote one to override it with.
 *
 * So the two shapes are restored differently, and both values are deliberate:
 *
 *   - `inherit` on the field and on the box. `letter-spacing` is an inherited
 *     property, so `inherit` is exactly "undeclare this" — it puts the box back
 *     to whatever the surrounding text is, which is what Vuetify 2's equivalent
 *     div did. A select's selection is that div, and inside a card it should
 *     pick up the card's own spacing, which `normal` would have flattened.
 *   - `normal` on the controls, restoring the UA reset the author-level
 *     declaration took away. `inherit` alone would leave them overriding it.
 *
 * `.v-field.v-field` rather than `.v-field`: Vuetify declares the spacing at
 * one class and its stylesheet lands after this file.
 *
 * The select's own inner input is untouched — it carries no `.v-field__input`
 * class and sits at `opacity: 0` behind the selection either way.
 */
.v-field.v-field,
.v-field .v-field__input {
  letter-spacing: inherit;
}

.v-field input.v-field__input,
.v-field textarea.v-field__input {
  letter-spacing: normal;
}

/*
 * A field's border is a colour it declares, not one it inherits.
 *
 * Reported twice: the signature dialog's textarea border when active, and the
 * department dialog's input border when active. One defect with two faces.
 *
 * Vuetify 2 gave the border an explicit colour for every state. Vuetify 3 gives
 * it none and lets it inherit — every rule it ships for the outline sets
 * *opacity*, and `error` is the one and only place it names a colour:
 *
 *   .v-field__outline                       { --v-field-border-opacity: .38 }
 *   .v-field:hover .v-field__outline        { --v-field-border-opacity: .87 }
 *   .v-field.v-field--focused .v-field__outline { --v-field-border-opacity: 1 }
 *   .v-field--error:not(.v-field--disabled) .v-field__outline { color: rgb(var(--v-theme-error)) }
 *
 * So the border is `currentColor` at a varying opacity, and `currentColor` is
 * `#36405a` — the navy from `app.scss`'s `[class*='text-']` rule, which matches
 * every field because `.v-text-field` contains the substring. This is the same
 * leak the veil cycle fixed for field *text*; the border was left behind
 * because that fix named `.v-field__input` and this lives on
 * `.v-field__outline`.
 *
 * The Vue 2 values below are read from that build's own compiled stylesheet
 * rather than guessed, and confirmed against the live page:
 *
 *   outlined    resting rgba(0,0,0,.38) 1px   hover .86   focused primary 2px   disabled rgba(0,0,0,.26)
 *   underlined  resting rgba(0,0,0,.42)       hover .87   focused primary (::after)
 *   filled      as underlined
 *   solo        no border in either build — nothing to state
 *
 * Stating the colour as black and leaving Vuetify's opacity ramp to do the rest
 * is deliberate: `color: rgba(0,0,0,.38)` would multiply with that ramp and
 * land at .144. Only the two opacities Vuetify 3 picks differently are pinned.
 *
 * ── One deliberate deviation from the Vue 2 build ──────────────────────────
 *
 * Where the Vue 2 build paints a focused border it uses **#1976D2**. That is
 * Vuetify 2's *default* primary: that build's `plugins/vuetify.js` configures
 * no theme at all, so the framework default is showing through and nobody ever
 * chose it. Vuetify 2 painted the border through `primary--text`, i.e. the
 * theme's primary — and this build's theme names the design system's blue,
 * #2153CD (`resources/plugins/theme.js`), so the rule reads the token rather
 * than restating the value. The same token is what every `color="primary"`
 * paints, which is exactly Vuetify 2's mechanism.
 *
 * This is a departure from parity, made deliberately. A later parity sweep that
 * measures #1976D2 on the Vue 2 build and "corrects" this back would be
 * undoing a decision, not fixing a defect.
 *
 * ── Order and specificity ─────────────────────────────────────────────────
 *
 * `error` has to survive both rules, including on a field that is focused *and*
 * invalid. Vuetify's error rule is (0,3,0) against the focus rule's (0,2,0), so
 * it wins on weight — verified by building the two classes together rather than
 * reasoned about: an errored focused field stays #CF2E18 and does not turn
 * blue. The opacity pins are (0,2,0) and tie with Vuetify's own hover and focus
 * rules, which land after this file, so hover and focus keep Vuetify's ramp —
 * which is what the Vue 2 numbers want.
 *
 * A measurement note, because it cost a wrong first answer: reading a field's
 * computed border colour immediately after its focus class appears returns the
 * *previous* state. The first pass here recorded the Vue 2 focused border as
 * `rgba(0,0,0,.38)` — the resting value — and only a second reading, taken
 * after the class had settled, showed #1976D2. Vuetify 2's own stylesheet says
 * plainly that focus inherits the primary; when a measurement and the shipped
 * rule disagree, re-measure before believing the measurement.
 */
.v-field__outline {
  color: black;
}

.v-field--focused .v-field__outline {
  color: rgb(var(--v-theme-primary));
}

.v-field--variant-underlined .v-field__outline,
.v-field--variant-filled .v-field__outline {
  --v-field-border-opacity: 0.42;
}

.v-field--disabled .v-field__outline {
  --v-field-border-opacity: 0.26;
}

/*
 * A switch that names no colour still turns the app's blue.
 *
 * Vuetify 2 gave an uncoloured switch its primary when on. Vuetify 3 gives it
 * nothing, so a switch with no `color` prop paints the same grey on as off —
 * on `/settings/company-information?tab=backup-data` the automatic-backup
 * toggle read as off while it was on. Measured there:
 *
 *   on, track  | Vue 2 #1976D2 @ .32 | this build #424242 @ .6
 *   on, thumb  | Vue 2 #1976D2       | this build white
 *
 * The theme's primary rather than the Vue 2 build's #1976D2, for the reason
 * recorded on the field-border rule above: #1976D2 is Vuetify 2's *default*
 * primary, showing through a build that configures no theme. Vuetify 2's
 * `computedColor` fell back to `'primary'` for an uncoloured switch, and the
 * token is that fallback. The two rules must move together.
 *
 * A call site that names its own colour still wins the track: Vuetify writes
 * the prop as an inline `background-color` there, and this declaration is not
 * important. The thumb has no such inline, so this paints it unconditionally —
 * checked before relying on it, all six switches that pass a colour pass this
 * exact blue, and `NotificationSettings.vue` overrides the thumb `!important`
 * for its own.
 */
.v-switch .v-selection-control--dirty .v-switch__track {
  background-color: rgb(var(--v-theme-primary));
  opacity: 0.32;
}

.v-switch .v-selection-control--dirty .v-switch__thumb {
  background-color: rgb(var(--v-theme-primary));
}

/*
 * A label is the colour Vuetify 2 painted it, so an app rule cannot take it.
 *
 * `CompanyInformation.vue` carries `.company-info-container label { color:
 * #2153CD }`, which is (0,1,1). Vuetify 2's `.theme--light.v-label { color:
 * rgba(0,0,0,.6) }` is (0,2,0) and outranked it, so that rule never reached a
 * radio, a checkbox or a field label. Vuetify 3 ships `.v-label { color:
 * inherit }` at (0,1,0), which does not outrank it — and every label on the
 * backup screen came out blue.
 *
 * That is the fourth time this migration has hit the same shape: removing a
 * vendor declaration promotes an app rule that was never written to apply. The
 * others were the field's text, its letter-spacing and its border.
 *
 * Stated at (0,2,0), Vuetify 2's own weight, and deliberately not higher: every
 * app rule that outranked Vuetify 2 here still outranks this, so nothing the
 * app colours on purpose moves. Vuetify 3 leaves `.v-label` at opacity 1 inside
 * a selection control, so the colour carries the emphasis exactly as Vuetify 2's
 * did — measured `rgba(0,0,0,.6)` at opacity 1 on both builds.
 */
.v-theme--light .v-label {
  color: rgba(0, 0, 0, 0.6);
}

/*
 * A radio group's control fills its row, so a column in it is a column.
 *
 * Vuetify 2 gave `.v-input--radio-group__input` `width: 100%`. Vuetify 3's
 * equivalent, `.v-selection-control-group`, sits inside `.v-input__control`,
 * which is `flex: 0 1 auto` in a wrapping flex `.v-input` — so it sizes to its
 * content instead of the row. On the backup screen that left the control 1085px
 * wide inside an 1810px input, and the three `v-col-4` document-type cards took
 * a third of the wrong number: 362px against the Vue 2 build's 595.
 *
 * `width: 100%` on the group itself is not the fix and was tried first: the
 * group is already as wide as the control, so it changed nothing. The control
 * is what has to grow.
 *
 * Scoped to `.v-radio-group` because that is the scope Vuetify 2's rule had.
 * Checkbox and switch groups were measured in their own cycles and are right as
 * they stand.
 */
.v-radio-group .v-input__control {
  flex: 1 1 auto;
}

/*
 * A checked tick is the colour the control names, or the app's blue.
 *
 * Reported as checkbox and radio colours still differing. Measured on the
 * backup screen: the Vue 2 build paints a checked control `#1976D2` and an
 * unchecked one `rgba(0, 0, 0, .54)`; here both came out `rgba(0, 0, 0, .54)`,
 * so a selected radio was indistinguishable from an unselected one.
 *
 * The cause is this stylesheet's own `.v-icon { color: rgba(0, 0, 0, .54) }`
 * from the icon cycle. Vuetify 3 does not colour the tick directly at all — it
 * writes the colour on `.v-selection-control__wrapper` and lets the icon
 * inherit, so a blanket colour on `.v-icon` swallows it. Verified by neutralising
 * that one rule against a mounted control: the tick came back `#3F51B5` for
 * `color="indigo"` and `#2153CD` for `color="#2153CD"`, both of which the app
 * passes at real call sites and neither of which was reaching the screen.
 *
 * So this is two defects in one. **44 call sites pass `color="indigo"` and ten
 * more pass a hex**, and every one of them has been painting 54% black. The
 * remaining 115 pass nothing and want the Vue 2 default, which Vuetify 3 does
 * not have — `error` is the only state it names a colour for.
 *
 * The shape follows from where Vuetify writes the prop, which was measured
 * rather than assumed:
 *
 *   color="indigo"    -> class `text-indigo` on `.v-selection-control__wrapper`
 *   color="#2153CD"   -> inline `color` on the same element
 *   no prop           -> nothing at all
 *
 * The default is set on the *control*, one level above the wrapper, as the
 * conservative placement — it leaves the element Vuetify writes to untouched.
 *
 * It is worth being exact about what protects those 54 call sites, because a
 * first draft of this comment got it wrong and claimed the placement did.
 * It does not: Vuetify's colour utilities are `!important`
 * (`.text-indigo { color: … !important }`) and a hex prop lands inline, so a
 * named or hex colour outranks anything written here wherever it is written.
 * Moving this default onto the wrapper was tried and changed none of them.
 * What the placement buys is only that nothing here competes with the element
 * Vuetify owns.
 *
 * The theme's primary rather than the Vue 2 build's `#1976D2` is the deviation
 * settled in the field-border cycle and repeated for the switch: `#1976D2` is
 * Vuetify 2's *default* primary, showing through a build that configures no
 * theme, and Vuetify 2's `computedColor` fell back to `'primary'` for exactly
 * this case. Two components had already hand-patched this same defect locally
 * with `#2153CD`, the value the token now carries.
 *
 * The label is not affected: `.v-theme--light .v-label` is a declaration on the
 * label itself and outranks inheritance, so a checked control's text stays
 * `rgba(0, 0, 0, .6)`. The switch is not affected either — its track and thumb
 * carry explicit backgrounds from the cycle above.
 */
.v-selection-control .v-icon {
  color: inherit;
}

.v-selection-control:not(.v-selection-control--dirty) {
  color: rgba(0, 0, 0, 0.54);
}

.v-selection-control--dirty {
  color: rgb(var(--v-theme-primary));
}

/*
 * A dense list titles its rows the way Vuetify 2 did.
 *
 * Vuetify 2: `.v-list--dense .v-list-item__title { font-size: 0.8125rem;
 * font-weight: 500; line-height: 1rem }`. Vuetify 3 changes nothing about a
 * title by density, so every compact list drew Vuetify's 1rem/400/1.2 instead —
 * on a select's option card that is 16px/400/19.2px against the Vue 2 build's
 * 13px/500/16px.
 *
 * `--parity-list-floor` above already restores the *row* height for the same
 * lists; this is the type that went with it.
 *
 * Both selectors because Vuetify 2 named both: the density can sit on the list
 * or on a single item.
 */
.v-list--density-compact .v-list-item-title,
.v-list-item--density-compact .v-list-item-title {
  font-size: 0.8125rem;
  font-weight: 500;
  line-height: 1rem;
}

/*
 * A treeview label is not a dense list title, whatever Vuetify 3 builds it out of.
 *
 * Vuetify 2 drew a node label as `.v-treeview-node__label`, which declared no
 * type at all: the label took the font of whatever the call site put on the
 * treeview. Every one of them puts something there — `MassAcknowledge.vue`
 * writes `class="… text-caption treeview-to-select"`, so its labels were the
 * caption 12px/400 on the Vue 2 build.
 *
 * Vuetify 3 builds the treeview out of `VList`, and its list is
 * `density="compact"`, so the rule above — written for a select's option card —
 * reached every treeview label in the app and made it 13px/500/16px. Measured
 * on `/workflow?tab=mass-acknowledge`, ~250 file and folder rows:
 *
 *   Vue 2  `.v-treeview-node__label`  12px / 400 / 20px
 *   before `.v-list-item-title`       13px / 500 / 16px
 *   after                             12px / 400 / 18px
 *
 * 18px rather than the Vue 2 build's 20px is the brand's caption leading
 * (`_typography.scss`, `--type-caption-line: 1.5`), which is what every other
 * 12px line on the screen already reads.
 *
 * Three classes: the rule above is two and lands earlier in this file, and
 * Vuetify's own `.v-list-item-title` is injected after `app.css` altogether.
 * `letter-spacing` is deliberately left alone — the list-title block near the
 * top of this file takes it off every list title on purpose.
 */
.v-treeview .v-list-item-title.v-list-item-title {
  font-size: inherit;
  font-weight: inherit;
  line-height: inherit;
}

/*
 * An empty table says so in Vuetify 2's grey.
 *
 * Vuetify 2 painted the placeholder row itself:
 * `.theme--light.v-data-table .v-data-table__empty-wrapper { color:
 * rgba(0, 0, 0, 0.38) }`. Vuetify 3 renames the row `.v-data-table-rows-no-data`
 * and gives it no colour, so "No data available" inherits whatever the screen
 * puts on the table — measured on `/workflow?tab=mass-route`, where it is the
 * app's own `rgba(0, 0, 0, 0.87)`: the same weight as real data, in a row that
 * is telling you there is none.
 *
 * One class is enough because nothing declares a colour on the cell itself;
 * the 0.87 above it is inherited, and any declared value beats inheritance.
 */
.v-data-table-rows-no-data {
  color: rgba(0, 0, 0, 0.38);
}

/*
 * The chosen option is the colour Vuetify 2 painted it.
 *
 * Vuetify 2 gave `.v-list-item--active` its primary, so the option matching the
 * select's value stood out in the card. Vuetify 3 gives it a background overlay
 * and no colour at all, so it read as `rgba(0, 0, 0, .87)` — identical to every
 * other option. The sixth Vuetify 2 default with no Vuetify 3 equivalent this
 * migration has had to put back, after the switch and the selection-control
 * tick among others; the theme's primary for the reason recorded on the
 * field-border rule.
 *
 * Scoped to `.v-select__content`, which is the class Vuetify puts on a select's
 * overlay: a select's option card is what was measured. The sidebar and the
 * account menu style their own active rows and have not been compared.
 */
.v-select__content .v-list-item--active {
  color: rgb(var(--v-theme-primary));
}

/*
 * A disabled tick is grey, not a faded version of its colour.
 *
 * Reported on `/settings/permission-control`, where every checkbox is disabled.
 * Measured on both builds, 32 boxes each:
 *
 *   tick colour     | Vue 2 rgba(0, 0, 0, .38) | this build #2153CD
 *   control opacity | Vue 2 1                  | this build 0.38
 *
 * so the Vue 2 build draws flat grey and this one drew a washed-out blue.
 *
 * The cycle above gave a checked control the app's blue and let the tick
 * inherit it. That rule does not exclude disabled controls, and Vuetify 3 then
 * dims the whole control with
 * `.v-selection-control--disabled { opacity: var(--v-disabled-opacity) }`.
 * Vuetify 2 expressed disabled as a *colour at full opacity* instead — the
 * same colour-versus-opacity split this migration has already met in the
 * field's text, its border and its placeholder.
 *
 * The Vue 2 value is state-independent: checked and unchecked disabled boxes
 * are both `rgba(0, 0, 0, .38)`, checked by viewing two roles, and its
 * stylesheet says so directly — `.theme--light.v-input--is-disabled` sets that
 * colour and `.v-input--selection-controls.v-input--is-disabled .v-icon`
 * inherits it.
 *
 * Order matters as much as weight here, and both were checked against the
 * rules above: `.v-selection-control--dirty` is (0,1,0) and loses to this at
 * (0,2,0), but `.v-selection-control:not(.v-selection-control--dirty)` is also
 * (0,2,0) — so this has to sit *after* it in the file, which is why it is at
 * the end rather than beside its siblings. Vuetify's own disabled rule is
 * (0,1,0), so `opacity: 1` clears it.
 *
 * Scoped to `.v-checkbox-btn` and `.v-radio` deliberately. The same
 * `--disabled` class lands on a disabled *switch*, which Vuetify 2 styled with
 * explicit track and thumb colours rather than a dimmed tick; those numbers
 * were measured and closed in their own cycle, and `opacity: 1` reaching them
 * would stop a disabled switch dimming at all.
 */
.v-checkbox-btn.v-selection-control--disabled,
.v-radio.v-selection-control--disabled {
  color: rgba(0, 0, 0, 0.38);
  opacity: 1;
}

/*
 * A tab's label is styled by the tab, not by the page's button rules.
 *
 * Vuetify 2 put a tab's text straight in the tab, so it took the tab's own
 * typography. Vuetify 3 renders a tab as a `<button>` whose label is
 * `<span class="v-btn__content">` — a separate element that any rule a page
 * wrote for its buttons now reaches.
 *
 * On `/settings/version` that is `settings/Settings.vue`'s
 * `.doc-setting-container button span { font-size: 14px; font-weight: 700 }`,
 * written for the page's buttons and inert under Vuetify 2. Measured there: the
 * tab element is 12px/500 on both builds and correct, while its label came out
 * 14px/700 — which also widened the strip, 145.9 and 211.5 against the Vue 2
 * build's 123.9 and 180.
 *
 * **Eleven components render tabs and carry such a rule** — three in `academy`
 * and `library`, eight in `settings` — so this is the pattern rather than the
 * page. An earlier cycle fixed one of them, `PlatformPermission`, with a
 * page-scoped copy; that copy is deleted with this, because two rules doing one
 * job is how this file became hard to read.
 *
 * (0,2,0) beats a page's `button span` at (0,1,2). A rule a page writes *for
 * tabs* — `.container .v-tab span`, or anything `!important` — still wins,
 * which is what keeps a deliberately bolded selected tab bolded.
 *
 * `inherit` rather than restating 12px/500: the tab element already carries the
 * right values on every page checked, so it stays the one place that decides
 * them. Only these two properties are stated because only these two were
 * measured as differing; `letter-spacing` is the same shape, and two components
 * do set it in a `button span` rule, but neither renders tabs.
 */
.v-tab .v-btn__content {
  font-size: inherit;
  font-weight: inherit;
}

/*
 * A disabled field's text is dimmed by colour, as Vuetify 2 dimmed it.
 *
 * Measured on `/settings/version`, where the form is read-only:
 *
 *   disabled input / textarea | Vue 2 rgba(0,0,0,.38) | this build rgba(0,0,0,.87)
 *   disabled select selection | Vue 2 rgba(0,0,0,.38) | this build rgb(0,0,0)
 *
 * The same colour-versus-opacity split as the disabled tick a cycle ago:
 * Vuetify 2 said `.theme--light.v-input--is-disabled { color: rgba(0,0,0,.38) }`
 * and let the text inherit it, while this build keeps the enabled 87% the veil
 * cycle set — because an earlier cycle deliberately pinned
 * `.v-field--disabled { opacity: 1 }` so a read-only value stays legible.
 *
 * The weights are the design, because **this app uses `disabled` as a
 * read-only display mode** and one screen must not follow this rule: the
 * profile shows your name and timezone in black until you press Edit, and
 * `GeneralSetting.vue` says so in its own stylesheet. Both rules are written to
 * lose there:
 *
 *   - the input rule is (0,2,1). It ties with the veil cycle's
 *     `.v-field input.v-field__input` and therefore has to sit *after* it,
 *     which is why it is here at the end; and it ties with
 *     `.general-container .v-input input`, where the component's style is
 *     injected after `app.css` and so still wins.
 *   - the selection rule is (0,2,0), below that screen's
 *     `.general-container .v-input .v-select__selection` at (0,3,0).
 *
 * The autocomplete and combobox spellings were added with the select-value rule
 * further up this file. Vuetify 2 dimmed all three through the one class they
 * shared, `.v-select__selection`; Vuetify 3 renames the span per component, so
 * naming only the select would have left a disabled autocomplete's value at the
 * enabled 87% that rule gives it.
 */
.v-field--disabled input.v-field__input,
.v-field--disabled textarea.v-field__input {
  color: rgba(0, 0, 0, 0.38);
}

.v-field--disabled .v-select__selection,
.v-field--disabled .v-autocomplete__selection,
.v-field--disabled .v-combobox__selection {
  color: rgba(0, 0, 0, 0.38);
}

/*
 * A button carries Vuetify 2's text colour, so the app's text utilities cannot
 * paint it.
 *
 * Measured on `/settings/permission-control`, "Add New Role":
 *
 *   Vue 2 build | .theme--light.v-btn          (0,2,0) | rgba(0,0,0,.87)
 *   this build  | .v-application [class*='text-'] (0,2,0) | #36405a
 *
 * The recorded cause for this was wrong and is corrected here: the app rule
 * does **not** match `.v-btn--variant-text` — that class ends in `text`, with
 * no trailing hyphen, so `[class*='text-']` cannot see it. What it matches is
 * the app's own class on the button, `.text-btn` (70 uses), `.text-none` (57),
 * and a dozen more; it always matched them, on both builds.
 *
 * What changed is the other side of the tie. Vuetify 2 pinned a button's colour
 * at (0,2,0) and won on source order, because its stylesheet is injected after
 * `app.css`. Vuetify 3 states only
 * `.v-btn--variant-{plain,outlined,text,tonal} { color: inherit }` at (0,1,0),
 * so the app's rule now outranks it outright and the button takes #36405a.
 *
 * This restores the declaration Vuetify 3 dropped, at the weight Vuetify 2 had
 * it, which is the whole design:
 *
 *   - (0,2,0) beats the text-utility rule only because this file is imported at
 *     `app.scss:145` and that rule sits at :78 — the same source-order win
 *     Vuetify 2 had, not a specificity escalation. Written any heavier it would
 *     start beating component rules that legitimately colour a button, which on
 *     the Vue 2 build won this tie and must keep winning: `.toggle-button` in
 *     the header is `[data-v-…]`-scoped at (0,2,0) and draws it white.
 *   - it loses to Vuetify's own (0,2,0)
 *     `.v-btn--disabled.v-btn--variant-elevated`, whose sheet is injected after
 *     `app.css`, so a disabled button still dims to .26.
 *   - `.v-btn.v-theme--light` rather than `.v-application .v-btn`: the theme
 *     class is on the button, so it also reaches buttons Vuetify 3 teleports
 *     into `.v-overlay-container`, which is a sibling of the app root. That is
 *     where this is actually visible — see below.
 *
 * **Visible impact is narrow, and worth stating.** Across eleven pages measured
 * on both builds — including `/workflow` at 453 buttons and
 * `/settings/subscription` at 277 — not one button's *label* differed: the
 * label is a separate span and every component that ships such a button colours
 * it, so #36405a sat on the button element under paint. It becomes visible on a
 * button whose label inherits, which a teleported dialog button with
 * `text-none` and no component colour does: probed in the real
 * `.v-overlay-container`, label #36405a, and .87 with this rule.
 */
.v-btn.v-theme--light {
  color: rgba(0, 0, 0, 0.87);
}

/*
 * A pagination number takes the colour its screen sets on the item.
 *
 * The rule above restores the colour Vuetify 3 dropped from `.v-btn`, and in
 * Vuetify 3 a pagination number **is** a `v-btn` — so it started painting one,
 * and the number stopped inheriting. Measured on `/audit-trail`, the active page
 * went from the Vue 2 build's `#fff` to `rgba(0,0,0,.87)`: dark text on a
 * saturated blue pill. `/notifications` the same, `/settings/version` lost its
 * `#2153CD`, and every inactive number went `#4A4A4A` -> `rgba(0,0,0,.87)`.
 *
 * The scoping is what Vuetify 2 did, not a patch on top of it. Vuetify 2's
 * `.theme--light.v-btn` **never reached a pagination item**: the item was a
 * `<button class="v-pagination__item">` and took its colour from
 * `.theme--light.v-pagination .v-pagination__item` instead. Restoring that rule
 * must not extend its reach, so pagination is handed back its own inheritance.
 *
 * Every screen colours the `<li>` and lets the number inherit — `AuditTrail`,
 * `NotificationPage`, `Search`, `TrainingCode`, `Settings`,
 * `_workflow-container.scss`, and `App.vue:1200`'s active `!important` white.
 * Measured on the branch, **every one of those `<li>` colours was already
 * right**; only the button had stopped listening. So one line fixes all of them,
 * and `inherit` does not compete with a screen's intent — it implements it.
 *
 * (0,2,0), the same weight as `.v-btn.v-theme--light`, so it has to sit *after*
 * it — the placement is the design, as it was for the disabled field text.
 *
 * The pagination block earlier in this file already forwards `font-size`,
 * `font-weight` and `line-height` this way, and says of `color` that it "still
 * reaches the button on its own" — true when written, false once the button
 * carried a colour. This adds `color` to a list that already existed, which is
 * why the numbers' size and weight needed no fix: measured 10px/800 on
 * `/audit-trail` and `/notifications` and 12px/600 on `/settings/version`, on
 * both builds.
 */
.v-pagination__item .v-btn {
  color: inherit;
}

/*
 * …and the number's label takes the number's type.
 *
 * The forwarding above hands the item's `font-size`, `font-weight`,
 * `line-height` and `color` to the button. Vuetify 3 then puts the text one
 * level deeper again, in `<span class="v-btn__content">`, where a rule a screen
 * wrote for its own buttons can intercept it — so the chain has to continue.
 *
 * Measured on `/settings/version`, where `settings/Settings.vue`'s
 * `.doc-setting-container button span { font-weight: 700; font-size: 14px }`
 * (0,1,2) landed on it: the button was a correct 12px/600 and the digit inside
 * it drew 14px/700. On the Vue 2 build that rule matched nothing here — the
 * pagination item was a `<button class="v-pagination__item">` whose `innerHTML`
 * is literally `"1"`, with no span to hit.
 *
 * The same rule and the same mechanism as `.v-tab .v-btn__content` further down:
 * pagination is the second control Vuetify 3 wraps in a span where Vuetify 2 had
 * bare text. Ordinary buttons are not in that set — Vuetify 2 gave *them* a
 * `.v-btn__content` too, so a page's `button span` rule was always meant to
 * reach those and still does.
 *
 * Checked rather than assumed: 47 components carry a `button span` typography
 * rule, nine render `<v-pagination>`, and only `Settings.vue` is in both.
 * `/audit-trail` and `/notifications` measured 10px/800 on button and label
 * alike before this rule and are unmoved by it.
 *
 * (0,2,0) clears a page's (0,1,2). A rule a screen writes *for pagination text*
 * — `.container .v-pagination span` at (0,2,1) — still wins. Only the two
 * properties that were measured as differing: `line-height` already reaches the
 * label by inheritance, and `letter-spacing` is pinned to 0 app-wide by
 * `App.vue:1150`.
 */
.v-pagination .v-btn__content {
  font-size: inherit;
  font-weight: inherit;
}

/*
 * …and Vuetify 2's default for a pagination that sets no colour of its own:
 * `.theme--light.v-pagination .v-pagination__item { color: rgba(0,0,0,.87) }`.
 *
 * (0,1,0) on purpose. Every screen rule above is (0,2,0) or heavier and still
 * decides, and `App.vue`'s `!important` white for the active page still decides;
 * this only catches a pagination that would otherwise inherit whatever colour
 * its container happens to carry.
 */
.v-pagination__item {
  color: rgba(0, 0, 0, 0.87);
}

/*
 * A snackbar is the white card the Vue 2 build drew.
 *
 * Vuetify 2's snackbar wrapper was a `.v-sheet.theme--light`: white, with
 * `rgba(0,0,0,.87)` text. Vuetify 3 states
 * `.v-snackbar--variant-elevated { background: rgb(var(--v-theme-surface-variant));
 * color: rgb(var(--v-theme-on-surface-variant)) }` — `#424242` on `#EEEEEE`.
 *
 * Measured on the profile screen's "User profile updated" toast, triggered
 * through the component's own state rather than by saving anything:
 *
 *                 | Vue 2 build     | before
 *   background    | rgb(255,255,255)| rgb(66,66,66)
 *   text          | rgba(0,0,0,.87) | rgb(238,238,238)
 *
 * A rule dump on the live toast confirmed that Vuetify declaration is the only
 * one painting it. The radius, the blue-tinted shadow and the 344px width all
 * already matched — `ModalSuccess.vue` sets the first two itself.
 *
 * (0,2,0) clears Vuetify's (0,1,0). A snackbar given a `color` prop still wins:
 * Vuetify 3 renders those as `.bg-*` utilities carrying `!important`.
 */
.v-snackbar .v-snackbar__wrapper {
  background: #ffffff;
  color: rgba(0, 0, 0, 0.87);
}

/*
 * …and it sits where Vuetify 2 put it, not where the layout would.
 *
 * Vuetify 3 pads the snackbar overlay by the app's layout —
 * `.v-snackbar { padding: var(--v-layout-top) … var(--v-layout-left) }`, which
 * measured `56px 0 0 80px` here — so the toast clears the app bar and the nav
 * rail. Vuetify 2 had `.v-snack { padding: 0; inset: 0 }` with an 8px margin on
 * the wrapper, centring it in the viewport and overlapping the app bar:
 *
 *                 | Vue 2 build | before
 *   x / y         | 788 / 8     | 828 / 64
 *
 * (0,2,0) against Vuetify's (0,1,0). Nothing else is needed: Vuetify already
 * carries `margin: 8px` on `.v-snackbar` itself, which is the 8px the Vue 2
 * build got from its wrapper. Restating it on the wrapper as well — the first
 * attempt here — double-counted and put the card at y=16.
 */
.v-snackbar.v-snackbar {
  padding: 0;
}

/*
 * A card's actions are spaced the way Vuetify 2 spaced them.
 *
 * Vuetify 2 put no gap on `.v-card__actions` at all. It spaced *adjacent direct
 * children* instead — `.v-card__actions > .v-btn.v-btn + .v-btn { margin-left: 8px }`
 * — so two buttons sitting side by side got 8px, and anything wrapped in
 * something else got nothing. Vuetify 3 replaced that with a blanket
 * `.v-card-actions { gap: 0.5rem }`, which every child receives whether or not
 * the Vue 2 build would have spaced it.
 *
 * Measured on `/settings/version?tab=visibility`, where the Delete and Edit
 * setting buttons each sit inside a tooltip activator `<div>` and the first also
 * carries `mr-2`:
 *
 *                       | Vue 2 build | before
 *   Delete right edge   | 1753        | 1745
 *   Edit setting left   | 1761        | 1761
 *   gap                 | 8px (mr-2)  | 16px (mr-2 + the new gap)
 *
 * Both rules are needed, and the second is the half that is easy to forget: with
 * only `gap: 0` a dialog's Cancel / Create pair — which *are* direct children —
 * would have collapsed to 0 where the Vue 2 build gives them 8. Verified on that
 * screen's own dialog: Cancel ends at 1089 and Create New starts at 1097 on both
 * builds.
 *
 * `.v-card-actions.v-card-actions` is (0,2,0) because Vuetify states the gap at
 * (0,1,0) and its stylesheet is injected after this file. The sibling rule is
 * copied at Vuetify 2's own weight, doubled class and all.
 */
.v-card-actions.v-card-actions {
  gap: 0;
}

.v-card-actions > .v-btn.v-btn + .v-btn {
  margin-left: 8px;
}

/*
 * A select stands as tall as the field beside it, not three pixels taller.
 *
 * Vuetify 3 pads `.v-field__append-inner` — the box holding the dropdown arrow —
 * with 8px above and 4px below a 24px icon. That makes it 36px, and being the
 * field's tallest child it sets the field's height; a text field has no append
 * and stays at the 32px the block above gives it. So every default-density
 * select in the app stood 3px taller than the text fields on its row, and the
 * underlines did not line up.
 *
 * Measured on `/settings/general`, where three text fields and the timezone
 * select share a form:
 *
 *                  | Vue 2 build | before
 *   text field     | 32          | 32
 *   select         | 33          | 36
 *   select's line  | 357         | 360
 *
 * 5 + 24 + 4 = 33 is the Vue 2 build's own number, measured on its slot rather
 * than chosen to match the text field — the Vue 2 build is itself a pixel out
 * between the two, and that pixel is kept.
 *
 * (0,4,0) is required rather than tidy: Vuetify states this on
 * `.v-field.v-field--variant-underlined .v-field__append-inner` at (0,3,0) and
 * its stylesheet is injected after this file, so the same rule at three classes
 * ties and loses — measured, not assumed.
 *
 * The underlined variant is named because Vuetify names it: an outlined or
 * filled field keeps its own padding, and `density-compact` is left alone —
 * `/audit-trail` and `/notifications` depend on its 26px control.
 */
.v-input--density-default .v-field.v-field--variant-underlined .v-field__append-inner {
  padding-top: 5px;
  padding-bottom: 4px;
}

/*
 * Vuetify 2's two colour classes the templates still wear.
 *
 * The migration renamed the text utilities — `grey--text text--darken-2` became
 * `text-grey-darken-2` — and left the background classes as they were. Vuetify
 * 3 has no `.grey.lighten-3`; its spelling is `bg-grey-lighten-3`, which also
 * sets the text colour, where Vuetify 2's set the background and border only.
 * So the class is restored rather than renamed, declaration for declaration.
 *
 * Measured on the library's Visibility Setting dialog: its card is
 * `grey lighten-5` and its "Add user by type" card and table heading
 * `grey lighten-3` — `#fafafa` and `#eeeeee` on the Vue 2 build, white and
 * transparent here. Sixteen templates carry one of the two; no other colour
 * class of this shape survives (audited: `grep '\blighten-[0-9]'` over
 * `resources/js`), so these are the only two written.
 *
 * `.v-overlay-container` alongside `.v-application` because that is where a
 * dialog's card lives now.
 */
.v-application .grey.lighten-3,
.v-overlay-container .grey.lighten-3 {
  background-color: #eeeeee !important;
  border-color: #eeeeee !important;
}

.v-application .grey.lighten-5,
.v-overlay-container .grey.lighten-5 {
  background-color: #fafafa !important;
  border-color: #fafafa !important;
}

/*
 * `.layout` is a flex row, as Vuetify 2's legacy grid class was.
 *
 * Twenty-three templates write `<div class="layout justify-center">` — the
 * markup `<v-layout>` rendered — around a checkbox in a table cell. Vuetify 2
 * still shipped the class (`.layout { display: flex; flex: 1 1 auto;
 * flex-wrap: nowrap; min-width: 0 }`); Vuetify 3 does not, so `justify-center`
 * had no flex row to act on and the "Check or uncheck all" checkboxes in the
 * visibility dialog sat at the cell's left edge, x 678 against the Vue 2
 * build's 758, above a column of centred ones.
 */
.layout {
  display: flex;
  flex: 1 1 auto;
  flex-wrap: nowrap;
  min-width: 0;
}

/*
 * A dialog's card is padded as Vuetify 2 padded it.
 *
 * Vuetify 2 had four rules for a card directly inside a dialog
 * (VDialog.sass): `> .v-card__title { padding: 16px 24px 10px }`,
 * `> .v-card__text { padding: 0 24px 20px }`, `> .v-card__subtitle` the same,
 * `> .v-card__actions { padding: 8px 16px }`. Vuetify 3 pads a dialog's
 * `.v-card-item` and its `.v-card-text` (`16px 24px 24px`) and nothing else,
 * so a bare `<v-card-title>` — which is what 97 dialogs in this app write —
 * gets the component's own `.5rem 1rem`.
 *
 * Its dialog text rule also says `font-size: inherit; line-height: inherit`,
 * at (0,4,0). Vuetify 2's card text was `.875rem` at `1.375rem` in a dialog as
 * anywhere else, and the parity sheet restores those two hundred lines up at
 * (0,3,0) — which a dialog's text never saw. Measured on the visibility
 * dialog: 14.4px on 23.04 against the Vue 2 build's 14 on 22, and every
 * `<p>` in the dialog a pixel taller.
 *
 * Five classes on the text rule because Vuetify's is four and lands after
 * this file; the others compete with (0,1,0) and (0,4,0) rules of their own
 * and are written at the same weight for the same reason. `> form > .v-card`
 * is named because Vuetify names it.
 */
.v-dialog > .v-overlay__content > .v-card > .v-card-title,
.v-dialog > .v-overlay__content > form > .v-card > .v-card-title {
  padding: 16px 24px 10px;
}

.v-dialog > .v-overlay__content > .v-card > .v-card-text.v-card-text,
.v-dialog > .v-overlay__content > form > .v-card > .v-card-text.v-card-text {
  padding: 0 24px 20px;
  font-size: 0.875rem;
  line-height: 1.375rem;
  letter-spacing: 0.0071428571em;
}

.v-dialog > .v-overlay__content > .v-card > .v-card-subtitle,
.v-dialog > .v-overlay__content > form > .v-card > .v-card-subtitle {
  padding: 0 24px 20px;
}

.v-dialog > .v-overlay__content > .v-card > .v-card-actions.v-card-actions,
.v-dialog > .v-overlay__content > form > .v-card > .v-card-actions.v-card-actions {
  padding: 8px 16px;
  justify-content: normal;
}

/*
 * A card does not clip, and its first and last child take its corners.
 *
 * Vuetify 3's `.v-card` is `overflow: hidden` (VCard.css:1-12); Vuetify 2's
 * was not. That is not only about clipping: `overflow: hidden` makes the card
 * a block formatting context, so a child's margin no longer collapses through
 * it. The visibility dialog's "Add user by type" card holds a `v-row` — margin
 * −12px all round — as its only child. On the Vue 2 build the row's margins
 * collapsed through the card, whose painted box is the row's own 84px at the
 * row's own top; here the card is 63px tall and starts 12px lower, the same
 * flow height with 21px less grey. Every `<v-card><v-row>` in the app is the
 * same shape.
 *
 * What Vuetify 2 did instead of clipping was hand the corners down:
 * `.v-card > *:first-child:not(.v-btn):not(.v-chip):not(.v-avatar)` inherited
 * the top radii and `:last-child` the bottom ones — which is what rounded an
 * image at the top of a card. Both come with the overflow, or the academy's
 * course images would square off.
 *
 * A dialog's card is `overflow-y: auto` from Vuetify's own (0,3,0) dialog rule
 * and stays so.
 */
.v-card.v-card {
  overflow: visible;
}

.v-card > *:first-child:not(.v-btn):not(.v-chip):not(.v-avatar) {
  border-top-left-radius: inherit;
  border-top-right-radius: inherit;
}

.v-card > *:last-child:not(.v-btn):not(.v-chip):not(.v-avatar) {
  border-bottom-left-radius: inherit;
  border-bottom-right-radius: inherit;
}

/*
 * A button directly inside card actions is `0 8px`; one nested deeper is not.
 *
 * Vuetify 2: `.v-card__actions > .v-btn.v-btn { padding: 0 8px }` — direct
 * children only. Vuetify 3's VCardActions provides `VBtn: { slim: true }`
 * through its defaults provider, which reaches every button in the subtree.
 * The visibility dialog wraps its Cancel and Done in a `div.d-flex` and its
 * Copy Link two divs deep: 64 and 86 wide here against the Vue 2 build's 72
 * and 102. `AppBtn` now passes `slim: false` — an explicit prop beats a
 * default — and this rule gives the direct children Vuetify 2's `0 8px` back.
 */
.v-card-actions > .v-btn.v-btn {
  padding: 0 8px;
}

/*
 * An inset switch keeps Vuetify 2's box, and its off colour.
 *
 * The switch block above recorded "the control comes out 44px wide against
 * the Vue 2 build's 56" and left it, because the switches it measured sat at
 * the end of a `justify-space-between` row. The visibility dialog puts a label
 * *after* the switch, and the 12 missing pixels are the gap between them:
 * "Anyone with link can" started at 272 on the Vue 2 build and at 260 here,
 * touching the track. Vuetify 2's numbers: `.v-input--switch--inset
 * .v-input--selection-controls__input { width: 48px }` plus the ltr
 * `margin-right: 8px` every selection control carried.
 *
 * The off state was never restored either — the dirty rule below paints the
 * track blue when on. Vuetify 2's inset track when off was `currentColor` at
 * `rgba(0, 0, 0, .38)` with `opacity: .32`; Vuetify 3 paints
 * `--v-theme-surface-variant` (#424242) at `.6`, a noticeably darker grey:
 * measured on both builds' "Anyone with link can" toggle.
 *
 * Vuetify 2 also absolutely positioned the track 3px left of its box, which
 * Vuetify 3's inline-flex track has no equivalent of; the 44px track now sits
 * centred in the 48px wrapper, 5px right of the Vue 2 build's. Recorded, not
 * chased: the label lands on the Vue 2 x.
 *
 * Four classes on the wrapper's width because Vuetify's own is
 * `.v-switch.v-switch--inset .v-selection-control__wrapper { width: auto }` at
 * three, after this file — measured: at three the width stayed 44. The margin
 * is stated separately at two, Vuetify 2's own weight for it: Vuetify 3 sets
 * none, and a screen that sizes the gap itself — the pricing page's
 * `margin-right: 20px` under `.pricing-container` — outranked Vuetify 2's 8px
 * and must still outrank this. At four it did not, and "Yearly" moved 12px.
 */
.v-switch.v-switch--inset.v-switch--inset .v-selection-control__wrapper {
  width: 48px;
}

.v-switch .v-selection-control__wrapper {
  margin-inline-end: 8px;
}

.v-switch .v-selection-control:not(.v-selection-control--dirty) .v-switch__track {
  background-color: rgba(0, 0, 0, 0.38);
  opacity: 0.32;
}

/*
 * A multiple select's option row is the height Vuetify 2 drew it.
 *
 * Vuetify 2 rendered the checkbox of a multiple select in a
 * `.v-list-item__action` with `margin: 12px 12px 12px 0`, so a dense row was
 * 12 + 24 + 12 = 48 (49 with the `.v-simple-checkbox`'s inline-block leading)
 * and its title began 12px after the tick. Vuetify 3 puts the checkbox in the
 * item's prepend with no margin, and the parity list rule leaves a compact row
 * at its 40px floor with the title against the tick. Measured on the
 * visibility dialog's Visibility select: rows 40 against 49, menu 216 against
 * 261, title x 256 against 268.
 */
.v-select__content .v-list-item__prepend > .v-checkbox-btn {
  margin: 12px 12px 12px 0;
}

/*
 * A select is as wide as its own input made it on the Vue 2 build.
 *
 * Vuetify 2's select kept a real `<input>` in the flow after the selection
 * text — `flex: 1 1 0%; min-width: 0`, no horizontal padding — and the browser
 * sizes an input with no `size` at 20 characters. So in a container that takes
 * its width from its content, a select was `text + 20ch + icon`: the
 * visibility dialog's "View Document" select measures 293px on the Vue 2 build,
 * in a `d-flex` row beside its Copy Link button.
 *
 * Vuetify 3 takes that input out of the flow — `position: absolute; flex: 0 0;
 * width: 100%; padding-inline: inherit` (VSelect.css) — and the field shrinks to
 * the selection text alone: 140px here, with Copy Link 153px nearer the label.
 * The wrapper kit already hands every input Vuetify 2's `size="20"`
 * (propCompat.withVue2InputSize); this puts the select's input back where that
 * size can count. `flex-basis: 0%` rather than `auto` is Vuetify 2's own value
 * and the half that matters: an item's hypothetical size is 0, so it never
 * wraps below a row of chips, while its max-content contribution — which is
 * what a content-sized container sums — is still the 20 characters.
 * `padding-inline: 0` because Vuetify 2's input had none; Vuetify's `inherit`
 * exists to line up an absolute input's placeholder, which a static one no
 * longer needs. Measured with both: 293px, Copy Link at the Vue 2 x.
 *
 * Only where a container sizes to content does anything move; a select in a
 * grid column takes the column's width on both builds, as before.
 *
 * `.v-select.v-select` because Vuetify's rule is the same four parts, after
 * this file.
 */
.v-select.v-select .v-field .v-field__input > input {
  position: static;
  flex: 1 1 0%;
  min-width: 0;
  width: auto;
  padding-inline: 0;
}

/*
 * A card subtitle is a paragraph, as Vuetify 2's was.
 *
 * Vuetify 3's `.v-card-subtitle` is `white-space: nowrap; overflow: hidden;
 * text-overflow: ellipsis` — a one-line label, like its title. Vuetify 2's
 * `.v-card__subtitle` wrapped, at `line-height: 1.375rem` and
 * `letter-spacing: .0071428571em`, and its light theme coloured it
 * `rgba(0, 0, 0, .6)` at full opacity where Vuetify 3 keeps the text's own
 * colour and fades it to `.6`. The cookies consent dialog on an incognito
 * login is where it showed: a three-line paragraph (51px) cut to one line
 * with an ellipsis, and the Manage Cookies dialog's two paragraphs (153px)
 * to two lines; its `#4A4A4A` under `.6` came out `rgb(146,146,146)` where
 * the Vue 2 build draws `rgb(102,102,102)`.
 *
 * Three classes because Vuetify states the leading at `.v-card
 * .v-card-subtitle` (0,2,0) after this file; the colour at Vuetify 2's own
 * weight and shape (`.theme--light.v-card > .v-card__subtitle`, 0,3,0), so an
 * app rule that lost to it there still loses here — the cookies dialog's
 * `.cookies-consent__subtitle { color: #4A4A4A }` did, and the measurement
 * is Vuetify's grey.
 */
.v-card .v-card-subtitle.v-card-subtitle {
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
  line-height: 1.375rem;
  letter-spacing: 0.0071428571em;
  opacity: 1;
}

.v-theme--light.v-card > .v-card-subtitle {
  color: rgba(0, 0, 0, 0.6);
}

/*
 * Card actions are as tall as their content.
 *
 * Vuetify 3 gives `.v-card-actions` a `min-height: 52px`; Vuetify 2 gave it
 * none. The two agree wherever 36px buttons sit in Vuetify's 8px padding —
 * which is why it went unmeasured — and disagree the moment a site sets the
 * padding itself: the cookies consent's `pa-0` actions measured 52px against
 * the Vue 2 build's 36, with the buttons 8px lower and the dialog 5px taller.
 */
.v-card-actions.v-card-actions {
  min-height: 0;
}

/*
 * A dialog is at most 90% of the viewport tall, as it was.
 *
 * Vuetify 2: `.v-dialog:not(.v-dialog--fullscreen) { max-height: 90% }`.
 * Vuetify 3 sizes the overlay's content at `calc(100% - 48px)`, so a dialog
 * taller than the screen — Manage Cookies with its cookie tables open — was
 * 803px on an 851px viewport against the Vue 2 build's 766, and scrolled
 * less. `:not()` carries the class it names, so this is (0,3,0) against
 * Vuetify's (0,2,0).
 */
.v-dialog:not(.v-dialog--fullscreen) > .v-overlay__content {
  max-height: 90%;
}

/*
 * A button that names no colour is Vuetify 2's grey, not the surface.
 *
 * Vuetify 2: `.theme--light.v-btn.v-btn--has-bg { background-color: #f5f5f5 }`
 * — every elevated or depressed button without a `color`. Vuetify 3 paints
 * `rgb(var(--v-theme-surface))`, white. Measured on `/workflow`: Today, Show
 * More Task and Clear are white here against the Vue 2 build's `#f5f5f5`,
 * and the task cards' Preview button is `#EEEEEE` — its own
 * `.task .btn-detail { background: #EEEEEE }` at (0,2,0) *lost* to Vuetify 2's
 * (0,3,0) there and measured `#f5f5f5`, so it is written here at Vuetify 2's
 * weight and loses again. 182 `app-btn`s in the app pass no colour.
 *
 * Still winning over this: a `color` prop (an inline style, or a `bg-*`
 * utility with `!important`) and the disabled rule above (`!important`).
 * Icon buttons are `variant-text` through AppBtn and were never `--has-bg`.
 */
.v-theme--light.v-btn.v-btn--variant-elevated,
.v-theme--light.v-btn.v-btn--variant-flat {
  background-color: #f5f5f5;
}

/*
 * The month calendar is drawn in Vuetify 2's greys.
 *
 * Vuetify 3's VCalendar paints its lines `--v-border-color` at `.12`, its
 * outside-month cells `surface-light` (#eeeeee) and every weekday head
 * `on-surface` (black). Vuetify 2's light theme drew `#e0e0e0` lines,
 * `#f7f7f7` outside cells, and dimmed a *past* weekday head to
 * `rgba(0, 0, 0, .38)`. Measured on `/workflow`'s My Task calendar. Each
 * selector is Vuetify 2's own with the theme class renamed, which is one
 * class heavier than Vuetify 3's and so wins although its sheet comes later;
 * `MyTask.vue`'s own `.v-present` and `.v-past { color: unset }` rules are
 * heavier still and keep winning as they did.
 */
.v-theme--light.v-calendar-weekly {
  border-top: #e0e0e0 1px solid;
  border-left: #e0e0e0 1px solid;
}

.v-theme--light.v-calendar-weekly .v-calendar-weekly__head-weekday {
  border-right: #e0e0e0 1px solid;
}

.v-theme--light.v-calendar-weekly .v-calendar-weekly__head-weekday.v-past {
  color: rgba(0, 0, 0, 0.38);
}

.v-theme--light.v-calendar-weekly .v-calendar-weekly__head-weekday.v-outside,
.v-theme--light.v-calendar-weekly .v-calendar-weekly__day.v-outside {
  background-color: #f7f7f7;
}

.v-theme--light.v-calendar-weekly .v-calendar-weekly__day {
  border-right: #e0e0e0 1px solid;
  border-bottom: #e0e0e0 1px solid;
}

/*
 * A card title is padded 16px, as Vuetify 2's was.
 *
 * Vuetify 2: `.v-card__title { padding: 16px }`. Vuetify 3's `.v-card-title`
 * is `.5rem 1rem`, and pads a title 16px only inside a `.v-card-item`, which
 * this app never writes. The All Task stat cards measured 70px against the
 * Vue 2 build's 78: their title 8px from the top instead of 16. Three classes
 * because the dialog rule earlier in this file is five and must keep its
 * `16px 24px 10px`; Vuetify's own `.v-card-title` is one.
 */
.v-card .v-card-title.v-card-title {
  padding: 16px;
}

/*
 * A dense table is 32px a row, header included.
 *
 * Vuetify 2's `--dense` set both `th` and `td` to 32px. `dense` maps to
 * `density-compact`, whose tokens are 40 for the header and 36 for a row —
 * measured on Mass Route's file table, 40/36 against the Vue 2 build's 32/32.
 * Same shape as the default-density rule at the top of this file.
 */
.v-table.v-table--density-compact {
  --v-table-header-height: 32px;
  --v-table-row-height: 32px;
}

/*
 * A textarea's lines are 1.75rem apart, as Vuetify 2's were.
 *
 * Vuetify 2: `.v-textarea textarea { line-height: 1.75rem }`. Vuetify 3 sets
 * none, so the textarea inherits its field's — and sizes itself from it:
 * `rows × line-height + padding`. Mass Acknowledge's two-row description
 * measured 32px of text against the Vue 2 build's 56. Vuetify 3's 8px field
 * padding above and below, against Vuetify 2's 6 in all, is recorded and not
 * chased.
 */
.v-textarea textarea.v-field__input {
  line-height: 1.75rem;
}

/*
 * The expand and select columns are padded like any other cell, as Vuetify 2
 * padded them.
 *
 * Vuetify 2 drew an ordinary `<td>` for both, so they took the table's own
 * `0 16px`. Vuetify 3 marks them `v-data-table-column--no-padding` and gives
 * them `0 8px` (VDataTable.css:42-47) — measured on the document generator's
 * list, the expand chevron 8px left of where the Vue 2 build drew it. Four
 * expandable tables carry one of these columns and no table in the app uses
 * `show-select`, so this reaches exactly those four.
 *
 * The class is doubled because Vuetify's own rule is four selectors deep.
 */
.v-data-table .v-table__wrapper td.v-data-table-column--no-padding.v-data-table-column--no-padding,
.v-data-table .v-table__wrapper th.v-data-table-column--no-padding.v-data-table-column--no-padding {
  padding: 0 16px;
}

:root {
  --font-brand: "Manrope", sans-serif;
  --font-weight-light: 300;
  --font-weight-regular: 400;
  --font-weight-semibold: 600;
  --font-weight-bold: 700;
  --font-weight-extrabold: 800;
  --type-h1-size: 28px;
  --type-h1-line: 1.2142857;
  --type-h2-size: 22px;
  --type-h2-line: 1.3181818;
  --type-h3-size: 18px;
  --type-h3-line: 1.3333333;
  --type-body-size: 14px;
  --type-body-line: 1.4285714;
  --type-caption-size: 12px;
  --type-caption-line: 1.5;
  --type-small-size: 10px;
  --type-small-line: 1.51;
  --type-tracking: 0.2px;
}

.v-application.v-application,
.v-overlay-container.v-overlay-container {
  font-family: var(--font-brand);
  font-size: var(--type-body-size);
  line-height: var(--type-body-line);
  letter-spacing: var(--type-tracking);
  font-weight: var(--font-weight-regular);
  color: var(--color-gray-700);
}

.v-application h1,
.v-overlay-container h1 {
  font-family: var(--font-brand);
  font-size: var(--type-h1-size);
  line-height: var(--type-h1-line);
  letter-spacing: normal;
  font-weight: var(--font-weight-semibold);
}
.v-application h2,
.v-overlay-container h2 {
  font-family: var(--font-brand);
  font-size: var(--type-h2-size);
  line-height: var(--type-h2-line);
  letter-spacing: normal;
  font-weight: var(--font-weight-semibold);
}
.v-application h3,
.v-overlay-container h3 {
  font-family: var(--font-brand);
  font-size: var(--type-h3-size);
  line-height: var(--type-h3-line);
  letter-spacing: normal;
  font-weight: var(--font-weight-semibold);
}
.v-application h4, .v-application h5, .v-application h6,
.v-overlay-container h4,
.v-overlay-container h5,
.v-overlay-container h6 {
  font-family: var(--font-brand);
  font-size: var(--type-body-size);
  line-height: var(--type-body-line);
  letter-spacing: var(--type-tracking);
  font-weight: var(--font-weight-semibold);
}
.v-application small,
.v-overlay-container small {
  font-family: var(--font-brand);
  font-size: var(--type-small-size);
  line-height: var(--type-small-line);
  letter-spacing: var(--type-tracking);
}

.text-h1.text-h1, .text-h2.text-h2, .text-h3.text-h3, .text-h4.text-h4 {
  font-family: var(--font-brand) !important;
  font-size: var(--type-h1-size) !important;
  line-height: var(--type-h1-line) !important;
  letter-spacing: normal !important;
}

.text-h5.text-h5 {
  font-family: var(--font-brand) !important;
  font-size: var(--type-h2-size) !important;
  line-height: var(--type-h2-line) !important;
  letter-spacing: normal !important;
}

.text-h6.text-h6 {
  font-family: var(--font-brand) !important;
  font-size: var(--type-h3-size) !important;
  line-height: var(--type-h3-line) !important;
  letter-spacing: normal !important;
  font-weight: var(--font-weight-semibold);
}

.text-subtitle-1.text-subtitle-1, .text-body-1.text-body-1, .text-body-2.text-body-2 {
  font-family: var(--font-brand) !important;
  font-size: var(--type-body-size) !important;
  line-height: var(--type-body-line) !important;
  letter-spacing: var(--type-tracking) !important;
}

.text-subtitle-2.text-subtitle-2, .text-button.text-button {
  font-family: var(--font-brand) !important;
  font-size: var(--type-body-size) !important;
  line-height: var(--type-body-line) !important;
  letter-spacing: var(--type-tracking) !important;
  font-weight: var(--font-weight-semibold);
}

.text-caption.text-caption {
  font-family: var(--font-brand) !important;
  font-size: var(--type-caption-size) !important;
  line-height: var(--type-caption-line) !important;
  letter-spacing: var(--type-tracking) !important;
}

.text-overline.text-overline {
  font-family: var(--font-brand) !important;
  font-size: var(--type-small-size) !important;
  line-height: var(--type-small-line) !important;
  letter-spacing: var(--type-tracking) !important;
  font-weight: var(--font-weight-semibold);
}

.type-h1 {
  font-family: var(--font-brand) !important;
  font-size: var(--type-h1-size) !important;
  line-height: var(--type-h1-line) !important;
  letter-spacing: normal !important;
}

.type-h2 {
  font-family: var(--font-brand) !important;
  font-size: var(--type-h2-size) !important;
  line-height: var(--type-h2-line) !important;
  letter-spacing: normal !important;
}

.type-h3 {
  font-family: var(--font-brand) !important;
  font-size: var(--type-h3-size) !important;
  line-height: var(--type-h3-line) !important;
  letter-spacing: normal !important;
}

.type-body {
  font-family: var(--font-brand) !important;
  font-size: var(--type-body-size) !important;
  line-height: var(--type-body-line) !important;
  letter-spacing: var(--type-tracking) !important;
}

.type-caption {
  font-family: var(--font-brand) !important;
  font-size: var(--type-caption-size) !important;
  line-height: var(--type-caption-line) !important;
  letter-spacing: var(--type-tracking) !important;
}

.type-small {
  font-family: var(--font-brand) !important;
  font-size: var(--type-small-size) !important;
  line-height: var(--type-small-line) !important;
  letter-spacing: var(--type-tracking) !important;
}

.font-weight-semibold {
  font-weight: var(--font-weight-semibold) !important;
}

.font-weight-extrabold {
  font-weight: var(--font-weight-extrabold) !important;
}

.v-btn.brand-btn {
  font-family: var(--font-brand);
  font-weight: var(--font-weight-extrabold);
  letter-spacing: normal;
  text-transform: none;
  border-radius: 8px;
  box-shadow: none;
}
.v-btn.brand-btn > .v-btn__overlay {
  opacity: 0;
}
.v-btn.brand-btn.v-btn--size-default {
  --v-btn-height: 36px;
  font-size: 13px;
  padding: 0 14px;
}
.v-btn.brand-btn.v-btn--size-default:has(> .v-btn__content > .v-icon:first-child) {
  padding: 0 12px;
}
.v-btn.brand-btn.v-btn--size-default .v-icon {
  font-size: 14px;
}
.v-btn.brand-btn.v-btn--size-small {
  --v-btn-height: 30px;
  font-size: 12px;
  padding: 0 10px;
}
.v-btn.brand-btn.v-btn--size-small .v-icon {
  font-size: 13px;
}
.v-btn.brand-btn.v-btn--size-x-small {
  --v-btn-height: 22px;
  border-radius: 6px;
  font-size: 11px;
  padding: 0 8px;
}
.v-btn.brand-btn.v-btn--size-x-small:has(> .v-btn__content > .v-icon:first-child) {
  padding: 0 6px;
}
.v-btn.brand-btn.v-btn--size-x-small .v-icon {
  font-size: 10px;
}
.v-btn.brand-btn .v-btn__content > .v-icon--start {
  margin-inline: 0 4px;
}

.v-btn.brand-btn.brand-btn--gray {
  background-color: var(--text-white-3);
  color: var(--color-gray-700);
}
.v-btn.brand-btn.brand-btn--gray:hover {
  background-color: var(--text-white-4);
}
.v-btn.brand-btn.brand-btn--gray.v-btn--disabled {
  background-color: var(--text-white-3) !important;
  color: var(--text-dark-4) !important;
}

.v-btn.brand-btn.brand-btn--secondary {
  background-color: var(--color-primary-50);
  color: var(--color-primary-500);
}
.v-btn.brand-btn.brand-btn--secondary:hover {
  background-color: var(--color-primary-100);
}
.v-btn.brand-btn.brand-btn--secondary.v-btn--disabled {
  background-color: var(--color-primary-100) !important;
  color: var(--color-primary-300) !important;
}

.v-btn.brand-btn.brand-btn--main {
  background-color: var(--color-primary-500);
  color: var(--text-white-1);
}
.v-btn.brand-btn.brand-btn--main:hover {
  background-color: var(--color-primary-800);
}
.v-btn.brand-btn.brand-btn--main.v-btn--disabled {
  background-color: var(--color-primary-200) !important;
  color: var(--text-white-1) !important;
}

.v-btn.brand-btn.brand-btn--danger {
  background-color: var(--color-error-500);
  color: var(--text-white-1);
}
.v-btn.brand-btn.brand-btn--danger:hover {
  background-color: var(--color-error-700);
}
.v-btn.brand-btn.brand-btn--danger.v-btn--disabled {
  background-color: var(--color-error-200) !important;
  color: var(--text-white-1) !important;
}

.v-btn.brand-btn.brand-btn--danger-border {
  background-color: var(--color-error-50);
  color: var(--color-error-500);
  border: 1px solid var(--color-error-500);
}
.v-btn.brand-btn.brand-btn--danger-border:hover {
  background-color: var(--color-error-200);
}
.v-btn.brand-btn.brand-btn--danger-border.v-btn--disabled {
  background-color: var(--color-error-100) !important;
  color: var(--color-error-200) !important;
  border-color: var(--color-error-200) !important;
}

.v-btn.brand-btn.brand-btn--success {
  background-color: var(--color-success-500);
  color: var(--text-white-1);
}
.v-btn.brand-btn.brand-btn--success:hover {
  background-color: var(--color-success-900);
}
.v-btn.brand-btn.brand-btn--success.v-btn--disabled {
  background-color: var(--color-success-300) !important;
  color: var(--text-white-1) !important;
}

.v-btn.brand-btn.brand-btn--white {
  background-color: var(--text-white-1);
  color: var(--color-gray-700);
}
.v-btn.brand-btn.brand-btn--white:hover {
  background-color: var(--text-white-3);
}
.v-btn.brand-btn.brand-btn--white.v-btn--disabled {
  background-color: var(--text-white-1) !important;
  color: var(--text-dark-4) !important;
}
.v-btn.brand-btn.brand-btn--white:has(> .v-btn__content > .v-icon:first-child):hover {
  background-color: var(--color-primary-500);
  color: var(--text-white-1);
}

/*
 * `.workflow-container`, global because three components in two chunks use it.
 *
 * These rules were an unscoped block inside `Workflow.vue`. That worked on the
 * Vue 2 build for a reason that has nothing to do with styling: its `app.js`
 * has every `webpackChunkName` import commented out and imports the components
 * statically, so the whole app ships as one bundle and every component's CSS is
 * present on every page. This branch restored code splitting, and the moment it
 * did, `FormBuilder.vue` — which uses the class but lives in the `form-builder`
 * chunk — stopped receiving any of it.
 *
 * `/form-builder/list` was drawing its heading at 25.2px against the Vue 2
 * build's 16, and with none of the button, tab, table or text-colour rules
 * below. The 18px the route sweep saw was the measurable part of it.
 *
 * Moved verbatim: every selector is unchanged, so what these rules target is
 * exactly what they targeted before. Only *when* they load changes — from
 * injected at runtime with the workflow chunk to compiled into `app.css` — and
 * that is what the sweep after this change is checking.
 *
 * Users: `workflow/Workflow.vue`, `workflow/components/WorkflowDetail.vue`,
 * `form-builder/FormBuilder.vue`.
 */
.workflow-container .v-btn {
  letter-spacing: normal !important;
  text-transform: none !important;
}
.workflow-container .v-table.mobile-table > .v-table__wrapper > table {
  width: 860px !important;
  border-spacing: 0;
}
.workflow-container .empty-state {
  width: 100%;
  margin-top: 13%;
}
.workflow-container h3 {
  font-size: 16px;
  line-height: 22px;
}
.workflow-container h4 {
  font-size: 14px;
  line-height: 19px;
}
.workflow-container .normal-text, .workflow-container .v-field {
  font-size: 12px;
  line-height: 16px;
}
.workflow-container .fw-600 {
  font-weight: 600;
}
.workflow-container .mr-2px {
  margin-right: 2px;
}
.workflow-container .ml-2px {
  margin-left: 2px;
}
.workflow-container .gray-text {
  color: var(--color-gray-700);
}
.workflow-container .black-text {
  color: #000;
}
.workflow-container .light-gray-text {
  color: var(--color-gray-500);
}
.workflow-container .white-text {
  color: #fff;
}
.workflow-container .primary-text {
  color: var(--color-primary-500);
}
.workflow-container .red-text {
  color: var(--color-error-500) !important;
}
.workflow-container button {
  width: -moz-fit-content;
  width: fit-content;
  text-transform: none;
}
.workflow-container button:not(:where(.v-btn--icon)) i {
  font-size: 14px;
}
.workflow-container button:not(:where(.v-btn--icon)) span {
  font-weight: 700;
  font-size: 12px;
}
.workflow-container .primary-btn {
  padding: 10px 20px !important;
}
.workflow-container .primary-btn span {
  color: white;
}
.workflow-container .primary-btn i {
  font-size: 16px !important;
}
.workflow-container .secondary-btn {
  padding: 10px 20px !important;
}
.workflow-container .secondary-btn span {
  color: var(--color-gray-700);
}
.workflow-container .primary-small-btn {
  padding: 8px 16px !important;
}
.workflow-container .primary-small-btn span {
  color: white;
}
.workflow-container .primary-small-btn i {
  font-size: 16px !important;
}
.workflow-container .secondary-small-btn {
  padding: 8px 16px !important;
}
.workflow-container .secondary-small-btn span {
  color: var(--color-gray-700);
}
.workflow-container .link-btn {
  height: auto !important;
  min-width: auto !important;
}
.workflow-container .link-btn span {
  font-weight: 600;
  font-size: 12px;
  line-height: 16px;
  color: var(--color-primary-500);
  text-decoration: underline;
}
.workflow-container .link-btn i {
  font-size: 14px !important;
}
.workflow-container .link-btn::before {
  background-color: transparent !important;
}
.workflow-container .close {
  position: absolute;
  top: 16px;
  right: 16px;
  min-width: 12px !important;
  height: 12px !important;
}
.workflow-container .close i {
  font-size: 20px !important;
}
.workflow-container .close::before {
  background: transparent !important;
}
.workflow-container .frequency-type .v-field__input {
  text-transform: none;
}
.workflow-container .document-number-info .v-icon {
  color: var(--color-warning-500);
}
.workflow-container .no-data-container {
  height: 300px;
  text-align: center;
}
.workflow-container .v-tab.v-tab--selected {
  background-color: var(--color-primary-50);
  color: var(--color-primary-500) !important;
  font-weight: 700;
}
.workflow-container .v-tab {
  background-color: var(--text-white-2);
  color: var(--color-gray-500) !important;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: normal;
}
.workflow-container .v-tab:before {
  border-top-left-radius: 8px !important;
  border-top-right-radius: 8px !important;
}
.workflow-container .v-tabs {
  --v-tabs-height: 38px;
}
.workflow-container .text-transform-none {
  text-transform: unset !important;
}
.workflow-container .document-number-info {
  background-color: var(--color-warning-50);
  color: var(--color-gray-700);
  font-size: 12px;
  letter-spacing: normal;
}
.workflow-container .list-data-table thead th:last-of-type {
  text-align: right !important;
}
.workflow-container .v-btn--disabled span {
  color: rgba(0, 0, 0, 0.26) !important;
}
.workflow-container .edit-btn {
  text-transform: none;
  height: auto !important;
}
.workflow-container .edit-btn span, .workflow-container .edit-btn i {
  font-size: 12px !important;
  color: var(--color-primary-500);
}
.workflow-container .edit-btn::before {
  background-color: transparent !important;
}
.workflow-container .delete-btn {
  text-transform: none;
  height: auto !important;
}
.workflow-container .delete-btn span, .workflow-container .delete-btn i {
  font-size: 12px !important;
  color: var(--color-error-500);
}
.workflow-container .delete-btn::before {
  background-color: transparent !important;
}
.workflow-container .data-table-container .text-btn {
  text-transform: none;
  height: auto;
}
.workflow-container .data-table-container .text-btn span {
  font-size: 12px !important;
  font-weight: 600 !important;
  color: var(--color-primary-500);
}
.workflow-container .data-table-container .text-btn i {
  font-size: 14px !important;
}
.workflow-container .data-table-container .text-btn::before {
  background-color: transparent !important;
}
.workflow-container .data-table-container .selected {
  background: var(--text-white-2) !important;
}
.workflow-container .data-table-container .selected td {
  color: var(--color-gray-700);
  font-weight: 600;
}
.workflow-container .empty-state {
  width: 100%;
  margin-top: 13%;
}
.workflow-container .data-row:hover {
  cursor: pointer;
}
.workflow-container .visibility-input input, .workflow-container .visibility-input .v-select__selection {
  line-height: 16px !important;
  font-weight: 600 !important;
  padding: 0 !important;
}
.workflow-container .visibility-input .v-field__append-inner, .workflow-container .visibility-input .v-select__selection {
  margin-top: 0 !important;
  margin-bottom: 0 !important;
}
.workflow-container .visibility-input .v-field__append-inner {
  height: 20px !important;
}
.workflow-container .v-pagination .v-pagination__item {
  color: var(--color-gray-700);
  font-weight: 800;
  font-size: 10px;
  line-height: 14px;
}
.workflow-container .hint-container {
  background: #717171 !important;
  margin-bottom: 10px;
  position: relative;
  border-radius: 4px !important;
  opacity: 0.8;
}
.workflow-container .hint-btn {
  margin-top: 2px;
  box-shadow: none !important;
  min-width: auto !important;
}
.workflow-container .hint-btn i {
  font-size: 14px !important;
}
.workflow-container .hint-btn::before {
  background-color: transparent !important;
}
.workflow-container .hint-container:after {
  content: "";
  width: 0px;
  height: 0px;
  position: absolute;
  border-left: 5px solid #717171;
  border-right: 5px solid transparent;
  border-top: 5px solid #717171;
  border-bottom: 5px solid transparent;
  left: 5px;
  bottom: -10px;
}
.workflow-container .custom-position:after {
  left: 50px !important;
}
.workflow-container .v-alert {
  border-radius: 8px !important;
  box-shadow: 0px 0px 4px rgba(33, 83, 205, 0.04), 0px 2px 4px rgba(33, 83, 205, 0.04) !important;
}
.workflow-container .v-application .text-caption {
  letter-spacing: normal !important;
}
.workflow-container .mass-route-container .mdi-file-pdf {
  color: var(--color-error-500) !important;
}
.workflow-container .mass-route-container .mdi-file-word, .workflow-container .mass-route-container .mdi-file-image, .workflow-container .mass-route-container .mdi-image {
  color: var(--color-primary-500) !important;
}
.workflow-container .mass-route-container .mdi-file-excel {
  color: var(--color-success-700) !important;
}
.workflow-container .mass-route-container .mdi-folder, .workflow-container .mass-route-container .mdi-file-powerpoint {
  color: var(--color-warning-500) !important;
}
.workflow-container .mass-route-container .mdi-powerpoint {
  color: var(--color-warning-500) !important;
}
.workflow-container .mass-route-container .mdi-file {
  color: var(--text-white-3) !important;
}
.workflow-container .mass-route-container .chip-elevation {
  box-shadow: 0px 0px 4px rgba(33, 83, 205, 0.05), 0px 2px 4px rgba(33, 83, 205, 0.06);
}
.workflow-container .mass-route-container .search-library-input__selected-file .v-chip__close.v-icon {
  color: var(--color-gray-700) !important;
  font-size: 14px !important;
}
.workflow-container .mass-route-container .search-library-input__primary-btn {
  border-radius: 8px;
}
.workflow-container .mass-route-container .search-library-input__primary-btn span {
  color: var(--color-primary-500);
}
.workflow-container .mass-route-container .search-library-input__secondary-btn {
  border-radius: 8px;
}
.workflow-container .mass-route-container .search-library-input__secondary-btn span {
  color: var(--color-gray-700);
}
.workflow-container .v-breadcrumbs {
  color: var(--color-primary-500);
}
.workflow-container .v-breadcrumbs .v-breadcrumbs-item:hover {
  cursor: pointer;
}
.workflow-container .deselect-text {
  color: var(--color-error-500);
}
.workflow-container .all-task-container.empty-state {
  width: 100%;
  margin-top: 13%;
}
.workflow-container .all-task-container .v-table .v-table__wrapper {
  border: 1px solid var(--text-white-3);
  border-radius: 8px;
}
.workflow-container .all-task-container .v-table td {
  border-bottom: none !important;
}
.workflow-container .all-task-container .v-table tbody tr:hover {
  background: var(--color-primary-50) !important;
}
.workflow-container .all-task-container .v-table .v-data-table__th:last-of-type .v-data-table-header__sort-icon {
  display: none;
  pointer-events: none;
  cursor: default;
}
.workflow-container .all-task-container .v-table tbody tr {
  color: var(--color-gray-500);
}
.workflow-container .all-task-container .v-table tbody tr:hover {
  background: var(--text-white-2) !important;
}
.workflow-container .all-task-container .v-table thead {
  border-bottom: 0.5px solid var(--text-white-3);
  background-color: var(--color-primary-50);
}
.workflow-container .all-task-container .v-table thead th {
  color: var(--color-primary-500);
  font-weight: 800;
  font-size: 14px;
  height: 48px;
  padding: 0 16px;
}
.workflow-container .business-process-container .v-table thead th, .workflow-container .archived-container .v-table thead th {
  color: var(--color-gray-700);
  font-weight: 600;
  font-size: 12px;
}
.workflow-container .business-process-container .v-table .v-table__wrapper, .workflow-container .archived-container .v-table .v-table__wrapper {
  border: 1px solid var(--text-white-3);
  border-radius: 8px;
}
.workflow-container .business-process-container .v-table td, .workflow-container .archived-container .v-table td {
  border-bottom: none !important;
}
.workflow-container .business-process-container .v-table th:first-of-type, .workflow-container .archived-container .v-table th:first-of-type {
  padding: 16px 0 16px 16px !important;
}
.workflow-container .business-process-container .v-table tbody tr:hover, .workflow-container .archived-container .v-table tbody tr:hover {
  background: var(--color-primary-50) !important;
}
.workflow-container .business-process-container .v-table .v-data-table__th:last-of-type .v-data-table-header__sort-icon, .workflow-container .archived-container .v-table .v-data-table__th:last-of-type .v-data-table-header__sort-icon {
  display: none;
  pointer-events: none;
  cursor: default;
}
.workflow-container .business-process-container .v-table tbody tr, .workflow-container .archived-container .v-table tbody tr {
  color: var(--color-gray-500);
}
.workflow-container .business-process-container .v-table tbody tr:hover, .workflow-container .archived-container .v-table tbody tr:hover {
  background: var(--text-white-2) !important;
}
.workflow-container .business-process-container .v-table thead, .workflow-container .archived-container .v-table thead {
  border-bottom: 0.5px solid var(--text-white-3);
}
.workflow-container .business-process-container .v-table thead th, .workflow-container .archived-container .v-table thead th {
  color: var(--color-gray-700);
  font-weight: 600;
  font-size: 12px;
}
.workflow-container .file-preview-mass-signing {
  min-height: 20vh;
}
.workflow-container .file-preview-mass-signing .task-list-col {
  max-height: 80vh;
  overflow-y: scroll;
}
.workflow-container .file-preview-mass-signing .task-list-col.fixed-right {
  position: fixed;
  padding-left: 40px;
  right: 0;
}
.workflow-container .file-preview-mass-signing .affected-attachment-panel .v-expansion-panel-title {
  background: var(--text-white-3);
  width: 100%;
  padding: 12px 8px;
  min-height: auto !important;
  font-size: 12px;
  font-weight: 800;
}
.workflow-container .file-preview-mass-signing .affected-attachment-panel .v-expansion-panel-title i {
  font-size: 18px;
}
.workflow-container .file-preview-mass-signing .affected-attachment-panel .v-expansion-panel-text__wrapper {
  padding: 12px;
}
.workflow-container .affected-attachment-dialog .v-card-title {
  font-size: 16px;
  background: var(--text-white-3);
}
.workflow-container .affected-attachment-dialog .affected-attachment-filename {
  white-space: nowrap;
  text-overflow: ellipsis;
  overflow: hidden;
}
.workflow-container .affected-attachment-dialog .close {
  position: absolute;
  top: 16px;
  right: 16px;
  min-width: 12px !important;
  height: 12px !important;
}
.workflow-container .affected-attachment-dialog .close i {
  font-size: 20px !important;
}
.workflow-container .affected-attachment-dialog .close::before {
  background: transparent !important;
}

/*
 * Menu content, global because every menu in the app depends on it.
 *
 * These rules were an unscoped block at the end of
 * `workflow/components/AvatarGroup.vue` — a component with nothing to do with
 * the header — and they style the account menu, the notification menu and every
 * other overlay. On the Vue 2 build that worked because it ships as one bundle;
 * here the account menu was 651px tall on `/library` and 603px on `/workflow`,
 * the same menu styled differently depending on which chunk happened to be
 * loaded.
 *
 * Moved verbatim apart from one correction. `height: fit-content` on the
 * subheader had never worked on this build: Vuetify 3 sizes
 * `.v-list-subheader` with `min-height: 40px`, which a `height` cannot beat, so
 * the subheader stayed 40px even on the route where this block was loaded.
 * `min-height: 0` is what lets the height take effect, and takes it to the Vue 2
 * build's 21px.
 *
 * `ToggleCompany.vue` and `NotificationBar.vue` keep their own
 * `.v-overlay__content` blocks: those position their own menus and live in the
 * header's chunk already.
 */
.v-overlay__content .v-divider {
  margin-top: 8px !important;
  margin-bottom: 8px !important;
}
.v-overlay__content .v-list-subheader {
  min-height: 0 !important;
  height: -moz-fit-content !important;
  height: fit-content !important;
}
.v-overlay__content .v-list-item-action {
  margin-right: 12px !important;
}

/*
 * A menu's own card: the Vue 2 build's corner and its shadow.
 *
 * Both were app rules on the Vue 2 build, and both stopped reaching the screen
 * for different reasons.
 *
 * The corner was `library/Header.vue`'s `.v-menu__content { border-radius:
 * 10px }`. The migration renamed the class to `.v-overlay__content`, which is
 * still (0,1,0) — and Vuetify 3 ships
 * `.v-menu > .v-overlay__content.v-select__content { border-radius: 4px }` at
 * (0,3,0), so it never applied again. Nothing competed with it under Vuetify 2,
 * where every menu in the app was 10px. That rule is deleted from `Header.vue`
 * in the same change: two rules disagreeing about one element is how this
 * stylesheet became hard to read in the first place. It is also the over-reach
 * an earlier cycle recorded — `.v-overlay__content` catches dialogs too, and
 * `.v-menu >` is what keeps this off them.
 *
 * The shadow has a less dignified history. On the Vue 2 build it comes from an
 * unscoped block at the end of `academy/Cart.vue` — a component with nothing to
 * do with menus — which leaked to every menu in the app. This build scoped that
 * component's rule to `.cart-dialog`, which was right, and took the shadow the
 * screen has always had with it. It is reproduced here rather than in a
 * component so that it does not depend on which chunk happened to load, and
 * recorded as an accident so nobody goes looking for the decision behind it.
 *
 * Three repeats of `.v-overlay__content`, and the count is measured rather than
 * guessed. Vuetify's radius rule is
 * `.v-menu > .v-overlay__content.v-select__content` — also (0,3,0) — and its
 * stylesheet is injected after `app.css`, so it wins the tie. Two repeats left
 * the card at Vuetify's 4px; the fourth class is what carries it.
 */
.v-menu > .v-overlay__content.v-overlay__content.v-overlay__content {
  border-radius: 10px;
  box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0), 0 8px 10px 1px rgba(0, 0, 0, 0), 0 3px 14px 2px rgba(0, 0, 0, 0.08);
  /*
   * Vuetify 2's `.v-menu__content { overflow: hidden auto; contain: content }`:
   * the content box clips its children at its own corner, which is what made
   * the corner above visible on a white list in the first place.
   */
  overflow: hidden auto;
}

/*
 * A list inside a menu is flat, as Vuetify 2's was.
 *
 * Vuetify 2: `.v-sheet.v-list { border-radius: 0 }` and a `box-shadow` of
 * `0 0 0 0` — the menu's corner and shadow sat on the content box above.
 * Vuetify 3 moves both onto each *direct child* of the content
 * (`.v-menu > .v-overlay__content > .v-list { border-radius: inherit;
 * box-shadow: <elevation 8> }`, VMenu.css) — one list, one card, which is why
 * nothing showed until a menu held several. The workflow canvas's plus menu
 * renders thirteen lists through a `v-for`, and drew thirteen white cards with
 * 10px corners and a shadow between every pair of rows. The same rule also
 * paints Vuetify's 0.2/0.14/0.12 shadow under the app's 8% one on every
 * single-list menu; with the content clipping again, that goes too.
 *
 * Two repeats because Vuetify's rule is (0,3,0) and lands after this file.
 * `> .v-card` is left alone: a Vuetify 2 card kept an elevation of its own.
 */
.v-menu > .v-overlay__content.v-overlay__content > .v-list,
.v-menu > .v-overlay__content.v-overlay__content > .v-sheet {
  border-radius: 0;
  box-shadow: none;
}

/*
 * The two boxes `<v-app>` used to emit, put back.
 *
 * Thirteen components were a nested `<v-app>` on the Vue 2 build and are now
 * `<div class="app-root-content">` — Vuetify 3 allows only one application root,
 * so the class marks where the second one was. It carried no rules, and that
 * cost the pages a definite height.
 *
 * `<v-app>` rendered two elements, not one:
 *
 *     .v-application        display: flex                    <- a row container
 *       .v-application--wrap  display: flex; flex-direction: column
 *
 * Being a flex item stretched on the *cross* axis of a row container is what
 * made `.v-application--wrap` — and everything under it — a definite height. On
 * /login that is what let `Login.vue:14`'s `height: 100%` resolve: it measures
 * 851 on the Vue 2 build at a viewport of 851, and 647.88 here, which is the
 * content height. Deleting that inline height on the Vue 2 build gives 647.875,
 * so the content is identical and only the resolution differs.
 *
 * Reproducing the inner box alone does not work — `display: flex;
 * flex-direction: column` on `.app-root-content`, with or without a
 * `min-height`, still measures 647.88. The outer box has to be the flex
 * container, which is why `.v-main` is named here.
 *
 * `min-height` is deliberately not carried over. Vuetify 2's wrap had one (93vh
 * once the app's override is counted), but /login does not need it —
 * `.login-container` declares its own `min-height: 100vh` — and the other twelve
 * pages sit below an app bar, where a viewport floor buys a scrollbar.
 */
.v-main:has(> .app-root-content) {
  display: flex;
}

.app-root-content {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  max-width: 100%;
}

/*
 * The dialog's own shape, which is not shared with the export.
 *
 * These two came from the same seven unscoped dialog components as the button
 * rule now in the parity file, and they repeat their class for the same reason
 * — Vuetify's stylesheet is injected after this one, so at equal specificity
 * Vuetify wins. They stay here because a dialog is never part of an exported
 * document: the export lifts `#body-asdf`, and an overlay is teleported out of
 * that subtree entirely.
 *
 * The `form` arm is not decoration. A dialog that wraps its card in
 * `<v-form>` — the signature-limit dialog on the profile page is one — puts a
 * `<form class="v-form">` between the two, and a direct-child selector walks
 * straight past it. That dialog's corner measured 4px, Vuetify's own card
 * radius, against the Vue 2 build's 16. `_vuetify-parity.scss` already carries
 * the same pair of arms for the same reason a few hundred lines along; this is
 * the second rule to need them and it will not be the last.
 *
 * The Vue 2 build reaches 16px by a different route, which is worth writing
 * down because it decides *which* element to name here. There, `.v-dialog` is
 * itself the white box — background, `border-radius: 16px`, `overflow: auto` —
 * and it clips the 4px card inside it, so the corner you see is the dialog's.
 * Under Vuetify 3 the box that paints white is the card: `.v-overlay__content`
 * is transparent and does not clip. So the radius has to land on the card, and
 * Vuetify's `overflow: hidden auto` on `.v-card` clips its contents to it.
 */
.v-dialog > .v-overlay__content > .v-card.v-card,
.v-dialog > .v-overlay__content > form > .v-card.v-card {
  border-radius: 16px;
}

/*
 * The app's own button classes.
 *
 * These lived at the top level of `Library.vue`'s unscoped style block, so they
 * styled every route — but only once the library chunk had loaded. Loading
 * /form-builder/list directly drew its twenty `primary-button` buttons white on
 * white; arriving there from /library drew them correctly. Chunk order is not a
 * design decision.
 *
 * They used to carry the colours and a `10px 22px 12px 16px` padding of their
 * own. The colours are the brand's Main / Secondary / Gray cases now, which
 * `AppBtn` recognises by these class names and `_buttons.scss` paints — rest,
 * hover, disabled and the Medium box (36px, `0 14px`, `0 12px` beside an
 * icon). Only the mobile shape, which the sheet does not describe, stays here.
 */
@media (max-width: 1024px) {
  .primary-button,
.secondary-button,
.gray-button {
    min-width: 32px !important;
    padding: 16px !important;
  }
  .primary-button i,
.secondary-button i,
.gray-button i {
    margin-right: 0 !important;
  }
}

/*
 * `font-weight-600` is the app's own, and has to be here to be everywhere.
 *
 * 81 templates use it; 15 components define it, six of them in an unscoped
 * block that leaks globally *once that component's chunk has loaded*. On the
 * Vue 2 build one of those six always arrived early, so the class was live from
 * the first paint. Vue 3 splits the chunks differently, and on a direct load of
 * a route that pulls none of the six — `/training-code`, among others — the
 * class matched nothing and 81 files' worth of markup rendered at 400.
 *
 * Measured, direct-loading /training-code on each build: 600 there, 400 here.
 *
 * A class used across the app cannot live in a component. The existing
 * definitions are left alone: a scoped copy outranks this one inside its own
 * component, and an unscoped copy is identical.
 *
 * `!important`, because that is what the Vue 2 build's rendering rests on.
 * Four of the fifteen component copies spell it that way, and they are the ones
 * that were winning: `.text-caption` sets `font-weight: 400` and Academy's
 * `.v-tab` sets 500, both of which outrank a plain utility class. A repeated
 * class gets past the first and not the second — measured, the academy's
 * "Company Course" tab stayed at 500 where the Vue 2 build draws 600.
 *
 * It is the right idiom here rather than a shortcut: this is a single
 * declaration whose whole purpose is to override, the app already writes it
 * this way in four places, and a component that means to win still can — the
 * selected tab's own `font-weight: 700 !important` is untouched by this.
 *
 * The neighbouring utilities — `gray-text`, `small-text`, `light-gray-text`,
 * `primary-text`, `white-text`, `normal-text` — look like the same problem and
 * are not: their copies are scoped, so they resolve to nothing on a bare
 * element on *both* builds. Making them global here would change the app rather
 * than restore it.
 */
.font-weight-600 {
  font-weight: 600 !important;
}

/*
 * Vuetify 2's `grey--text`, for the places where the app's own `.text-grey`
 * now owns Vuetify 3's spelling of it.
 *
 * Vuetify 3 renamed the colour utilities `x--text` → `text-x`, and the
 * migration renamed the templates with it. Inside `.detail-container`
 * (WorkflowDetail, PresetSetting, the task action bar) the app had long had a
 * `.text-grey` of its own — `#4A4A4A`, 58 sites — which Vuetify 3's
 * `.text-grey { color: #9e9e9e !important }` then overrode, and which now says
 * `!important` back to win as it did. That leaves nine "(+N others)" hints in
 * those files that were Vuetify's `grey--text` on the Vue 2 build and would
 * otherwise turn `#4A4A4A`. They wear this instead: Material grey 500, the
 * value Vuetify 2's utility was.
 */
.text-grey-500 {
  color: #9e9e9e !important;
}

/*
 * Captions used to have no letter-spacing, anywhere in the app: the Vue 2
 * build undid Vuetify's `0.0333em` with an unscoped rule in the main bundle,
 * and a parity cycle carried that here. The brand identity tracks captions
 * +0.2px, so the rule now lives in `_typography.scss` with the rest of the
 * scale — the brand supersedes the Vue 2 number.
 */
.v-dialog > .v-overlay__content .close {
  position: absolute;
  top: 12px;
  right: 12px;
  min-width: 20px !important;
  height: 20px !important;
}
