/*
  CSD theme.

  The token structure is borrowed from the Receivx project so the two systems feel
  related; the values are not. Receivx is green-on-warm-paper because it is a receiving
  console. This is an aviation compliance system whose output is a regulatory document,
  so the palette is cool, neutral and deliberately unexciting, anchored on the navy CSD
  already used.

  One palette. Receivx offers light/midnight/slate and a switcher; a compliance record
  does not need three appearances, and a theme nobody chose is one more thing that can
  render a printed form differently from the screen it was checked on.

  Everything below is a token or an override of a Bootstrap class. No new markup is
  required, which is deliberate - the class names the views already use keep working.
*/

:root {
    /* Surfaces, coolest to warmest. */
    --bg: #f4f6f8;
    --surface: #ffffff;
    --surface-2: #f8fafb;
    --surface-3: #eef1f4;

    --border: #dfe4e9;
    --border-bright: #c7ced6;

    --text: #16202a;
    --text-dim: #4d5a67;
    --text-muted: #7b8794;

    /* The accent is CSD's existing navy, so this reads as a refinement and not a
       rebrand. --csd-navy is kept as an alias because site.css and the views use it. */
    --accent: #16324f;
    --accent-bg: #e8edf3;
    --accent-hover: #102639;
    --accent-fg: #ffffff;

    --csd-navy: var(--accent);
    --csd-navy-dark: var(--accent-hover);

    /* Status colours carry regulatory meaning, so they are muted for legibility
       rather than tuned for contrast on a dashboard. */
    --draft: #5a6570;   --draft-bg: #eef1f4;   --draft-border: #d4dae0;
    --issued: #1f5c3d;  --issued-bg: #e9f2ec;  --issued-border: #bcd7c6;
    --error: #a3302a;   --error-bg: #fbeceb;   --error-border: #eec9c6;
    --warn: #8a5a00;    --warn-bg: #fdf3e3;    --warn-border: #eeddb8;
    --info: #1a4f7a;    --info-bg: #e8f0f7;    --info-border: #c2d7e8;

    /* Flatter than Receivx: this is a document system, not a dashboard. */
    --shadow-sm: 0 1px 2px rgba(16, 32, 48, 0.05);
    --shadow-md: 0 2px 8px rgba(16, 32, 48, 0.07);
    --shadow-lg: 0 12px 32px rgba(16, 32, 48, 0.12);
    /* The same weight thrown upward, for a bar that sits at the foot of the viewport. */
    --shadow-lg-up: 0 -12px 32px rgba(16, 32, 48, 0.12);

    --radius: 6px;
    --radius-sm: 4px;

    /* ---- page width ------------------------------------------------------
       ONE width and ONE gutter, shared by the navbar, the content and the
       footer. Nothing else may set a page width: a card with its own
       max-width is how the filter bar ended up stopping short of the table
       it sits above.

       Form measures are a different thing and stay - see --form-max.

       1760px, not 1400: on a 1920 monitor 1400 left ~260px of dead margin each
       side while the list was squeezed hard enough to wrap the Issued date onto
       two lines. 1760 keeps a real gutter at 1920, uses a 2560 screen, and is a
       no-op below ~1800, where the viewport was already the narrower of the two.
       --------------------------------------------------------------------- */
    --page-max: 1760px;
    --page-gutter: clamp(0.9rem, 2vw, 2rem);

    /* Short forms get a reading measure rather than the page width: a text
       input 1400px wide is not a better text input. This replaces the
       col-lg-5 / col-lg-6 the admin forms used, which - being columns with
       no .row parent - also sat 0.75rem right of their own heading. */
    --form-max: 42rem;
}

/* ---- typography ---------------------------------------------------------
   Latin resolves from the system face; Thai is not in Segoe UI, so it falls
   through per glyph to Leelawadee UI, which ships with Windows and renders Thai
   noticeably better than the Tahoma the plain system stack lands on - lighter,
   more open, and with room for the tone marks on labels like
   "สถานภาพการรักษาความปลอดภัย". Nothing is downloaded for this.

   Sarabun is deliberately absent here. It is vendored for the PRINTED form only,
   where matching the official document matters; on screen the system face reads
   better at UI sizes.
   ------------------------------------------------------------------------- */
/* Sticky footer.

   The footer used to flow straight after the content, so on a short page - the login
   card above all - it sat mid-screen with empty background beneath it.

   body is the flex column; <main> absorbs the free space, so the footer lands on the
   viewport floor when content is short and after the content when it is long. NOT
   position: fixed - the footer has to scroll away on the list screen and the editor
   rather than sit over them.

   html gets height: 100% but body deliberately does not. A definite height on body
   would cap the flex container at one viewport, and a long page's content would spill
   out of it instead of growing it. min-height is what makes it stretch-but-not-cap.

   The three <script> tags are also children of body; they are display:none from the
   UA stylesheet, so they never become flex items. */
html {
    height: 100%;
}

body {
    font-family: system-ui, -apple-system, "Segoe UI", "Leelawadee UI",
                 "Noto Sans Thai", Tahoma, Arial, sans-serif;
    font-size: 0.925rem;
    background: var(--bg);
    color: var(--text);

    min-height: 100vh;
    display: flex;
    flex-direction: column;
}

/* 1 0 auto rather than a bare 1: grow into the free space, never shrink below the
   content's own height on a long page. */
main {
    flex: 1 0 auto;
}

.text-monospace-awb,
code, kbd, samp {
    font-family: ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", monospace;
    letter-spacing: 0.02em;
}

/* ---- chrome ------------------------------------------------------------- */

.bg-dark-blue {
    background-color: var(--accent);
}

.navbar.bg-dark-blue {
    box-shadow: var(--shadow-sm);
}

.navbar.bg-dark-blue .navbar-brand {
    letter-spacing: 0.06em;
}

.navbar.bg-dark-blue .nav-link {
    color: rgba(255, 255, 255, 0.78);
    border-radius: var(--radius-sm);
    padding-inline: 0.6rem;
}

.navbar.bg-dark-blue .nav-link:hover,
.navbar.bg-dark-blue .nav-link:focus {
    color: #fff;
    background: rgba(255, 255, 255, 0.09);
}

/* The agent badge is the one thing on the page that says whose data this is, so it
   is given the most contrast of anything in the bar rather than the least. */
.agent-badge {
    font-weight: 700;
    letter-spacing: 0.04em;
}

footer {
    flex-shrink: 0;
    background: var(--surface);
    border-top: 1px solid var(--border) !important;
    color: var(--text-muted) !important;
}

/* ---- controls ----------------------------------------------------------- */

.btn {
    border-radius: var(--radius-sm);
    font-weight: 500;
}

.btn-primary {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--accent-fg);
}

.btn-primary:hover,
.btn-primary:focus,
.btn-primary:active {
    background: var(--accent-hover);
    border-color: var(--accent-hover);
    color: var(--accent-fg);
}

.btn-outline-secondary {
    color: var(--text-dim);
    border-color: var(--border-bright);
}

.btn-outline-secondary:hover,
.btn-outline-secondary:focus {
    background: var(--surface-3);
    border-color: var(--border-bright);
    color: var(--text);
}

/* Destructive actions are outlined rather than filled: a red block invites the click
   it is warning about. */
.btn-danger,
.btn-outline-danger {
    color: var(--error);
    background: transparent;
    border-color: var(--error-border);
}

.btn-danger:hover, .btn-danger:focus,
.btn-outline-danger:hover, .btn-outline-danger:focus {
    background: var(--error-bg);
    border-color: var(--error);
    color: var(--error);
}

.form-control,
.form-select {
    background: var(--surface);
    border-color: var(--border-bright);
    color: var(--text);
    border-radius: var(--radius-sm);
}

.form-control:focus,
.form-select:focus {
    border-color: var(--accent);
    box-shadow: 0 0 0 0.18rem var(--accent-bg);
}

.form-control::placeholder {
    color: var(--text-muted);
}

.form-control:disabled,
.form-control[readonly] {
    background: var(--surface-3);
    color: var(--text-dim);
}

.form-label,
.form-check-label {
    color: var(--text-dim);
}

.form-check-input:checked {
    background-color: var(--accent);
    border-color: var(--accent);
}

.form-check-input:focus {
    border-color: var(--accent);
    box-shadow: 0 0 0 0.18rem var(--accent-bg);
}

.form-text {
    color: var(--text-muted);
}

/* ---- validation ---------------------------------------------------------

   Written rather than copied: Receivx posts its forms with JavaScript and has no
   server-rendered validation at all, so there was no pattern to bring across.

   These two selectors are load-bearing. The messages they carry are the only thing
   that tells a user why a form came back, and they have been lost twice already -
   once when the spans were missing from four boxes, once when an error had nowhere
   to render on Form A. They are styled to be conspicuous on purpose.
   ------------------------------------------------------------------------- */

.validation-summary-errors {
    background: var(--error-bg);
    border: 1px solid var(--error-border);
    border-left: 3px solid var(--error);
    border-radius: var(--radius-sm);
    color: var(--error);
    padding: 0.7rem 0.9rem;
    margin-bottom: 1rem;
}

.validation-summary-errors ul {
    margin: 0;
    padding-left: 1.1rem;
}

.validation-summary-errors li {
    line-height: 1.55;
}

.validation-summary-errors li:only-child {
    list-style: none;
    margin-left: -1.1rem;
}

/* An empty summary must not paint an empty red box. */
.validation-summary-valid {
    display: none;
}

.field-validation-error {
    display: block;
    color: var(--error);
    font-size: 0.82rem;
    line-height: 1.45;
    margin-top: 0.2rem;
}

.field-validation-valid {
    display: none;
}

/* The field itself, when jQuery unobtrusive validation marks it. */
.input-validation-error,
.form-control.is-invalid,
.form-select.is-invalid {
    border-color: var(--error);
    background: var(--error-bg);
}

.input-validation-error:focus {
    border-color: var(--error);
    box-shadow: 0 0 0 0.18rem var(--error-bg);
}

/* ---- surfaces ----------------------------------------------------------- */

.card {
    background: var(--surface);
    border-color: var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
}

.card-header,
.card-header-tight {
    background: var(--surface-2);
    border-bottom-color: var(--border);
    color: var(--text-dim);
}

.card-header-tight {
    padding: 0.45rem 0.85rem;
    font-weight: 600;
}

/* The box frame the editor uses, one per numbered box on the official form. */
.form-section {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    margin-bottom: 0.9rem;
    box-shadow: var(--shadow-sm);
}

.form-section > .form-section-head {
    background: var(--surface-2);
    border-bottom: 1px solid var(--border);
    border-radius: var(--radius) var(--radius) 0 0;
    padding: 0.45rem 0.8rem;
    color: var(--text-dim);
}

.form-section > .form-section-body {
    padding: 0.8rem;
}

/* The box number carried through from the official layout, so the editor and the
   printed form can be read side by side. */
.box-no {
    display: inline-block;
    min-width: 1.6rem;
    font-weight: 700;
    color: var(--accent);
}

.box-en {
    font-style: italic;
    font-weight: 400;
    color: var(--text-muted);
}

/* ---- tables -------------------------------------------------------------
   Mapped onto CSD's existing .table-csd rather than importing Receivx's
   .data-table, so no view markup changes and no test selector moves.
   ------------------------------------------------------------------------- */

.table-csd {
    --bs-table-bg: var(--surface);
    border-color: var(--border);
}

.table-csd thead th {
    background: var(--surface-3);
    border-bottom: 1px solid var(--border-bright);
    color: var(--text-dim);
    font-weight: 600;
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    white-space: nowrap;
    /* A little more than the data rows, so the header band reads as a band. */
    padding: 0.9rem 0.6rem;
}

.table-csd td,
.table-csd th {
    vertical-align: middle;
    border-color: var(--border);
}

/* Row height comes from cell padding, never a fixed height - a row whose
   Contents cell wraps has to grow, not clip. These are two-class selectors so
   they beat .table-sm's 0.25rem on specificity, which lets the markup keep
   .table-sm (and its borders and font size) instead of dropping the class to
   win a padding fight. The type size is deliberately untouched: the list was
   cramped for space, not small. */
.table-csd tbody td {
    padding: 0.75rem 0.6rem;
}

/* The gap that makes the header a header rather than the first row. Collapsed
   borders leave nowhere for a margin to go, so the separation is the first
   row's own top padding, under the header's bottom rule. */
.table-csd tbody tr:first-child > td {
    padding-top: 1.3rem;
}

/* Row actions. The wrapper is what carries the gap: as inline-blocks the
   buttons were spaced by whatever whitespace the Razor loop happened to emit,
   and the delete button - alone in a <form> - sat to a different rhythm from
   the rest. One flex row gives every button the same gap and the same
   baseline, and the extra right padding lifts the last one off the row edge. */
.row-actions {
    display: flex;
    flex-wrap: nowrap;
    justify-content: flex-end;
    align-items: center;
    gap: 0.35rem;
}

.table-csd tbody td:has(> .row-actions) {
    padding-right: 0.9rem;
}

.table-csd tbody tr:hover > * {
    background: var(--surface-2);
}

/* ---- messages ----------------------------------------------------------- */

.alert {
    border-radius: var(--radius-sm);
}

.alert-success {
    background: var(--issued-bg);
    border-color: var(--issued-border);
    color: var(--issued);
}

.alert-danger {
    background: var(--error-bg);
    border-color: var(--error-border);
    color: var(--error);
}

.alert-warning {
    background: var(--warn-bg);
    border-color: var(--warn-border);
    color: var(--warn);
}

.alert-info {
    background: var(--info-bg);
    border-color: var(--info-border);
    color: var(--info);
}

.badge.text-bg-light {
    background: var(--surface) !important;
    color: var(--accent) !important;
}

/* ---- sign in ------------------------------------------------------------
   Two panels, identity beside the form - the structure borrowed from Receivx.
   It collapses to one column below md, where a brand panel above a form is just
   something to scroll past on a phone, so it is hidden there rather than stacked.
   ------------------------------------------------------------------------- */

.auth-card {
    display: grid;
    grid-template-columns: 1fr 1fr;
    max-width: 52rem;
    margin: 6vh auto;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-lg);
    overflow: hidden;
}

.auth-brand {
    background: var(--accent);
    color: #fff;
    padding: 2.2rem 2rem;
    display: flex;
    flex-direction: column;
    gap: 0.55rem;
}

.auth-wordmark {
    font-size: 1.9rem;
    font-weight: 800;
    letter-spacing: 0.12em;
    line-height: 1;
    margin-bottom: 0.6rem;
}

.auth-title-th {
    font-size: 0.98rem;
    font-weight: 600;
    line-height: 1.65;
}

.auth-title-en {
    font-size: 0.82rem;
    color: rgba(255, 255, 255, 0.72);
    letter-spacing: 0.02em;
}

.auth-brand-foot {
    margin-top: auto;
    padding-top: 1.6rem;
    font-size: 0.78rem;
    line-height: 1.7;
    color: rgba(255, 255, 255, 0.66);
}

.auth-brand-foot span {
    color: rgba(255, 255, 255, 0.5);
}

.auth-form {
    padding: 2.2rem 2rem;
    display: flex;
    flex-direction: column;
    justify-content: center;
}

/* A field with a glyph inside it. The icons are inline SVG in the view - two of
   them did not justify vendoring an icon font for three glyphs. */
.input-with-icon {
    position: relative;
}

.input-with-icon svg {
    position: absolute;
    left: 0.7rem;
    top: 50%;
    transform: translateY(-50%);
    width: 1rem;
    height: 1rem;
    fill: var(--text-muted);
    pointer-events: none;
}

.input-with-icon .form-control {
    padding-left: 2.1rem;
}

@media (max-width: 767.98px) {
    .auth-card {
        grid-template-columns: 1fr;
        margin: 3vh auto;
        max-width: 26rem;
    }

    .auth-brand {
        display: none;
    }
}


/* ---- Bootstrap modals ---------------------------------------------------
   Nothing in CSD uses a Bootstrap modal today. These are here so that the
   first one added inherits the theme rather than arriving in Bootstrap's
   defaults next to everything else that does not.
   ------------------------------------------------------------------------- */

.modal-content {
    background: var(--surface);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}

.modal-header {
    border-bottom-color: var(--border);
    background: var(--surface-2);
}

.modal-footer {
    border-top-color: var(--border);
    background: var(--surface-2);
}

.modal-title {
    font-size: 1.02rem;
    font-weight: 600;
    color: var(--text);
}

/* ---- confirmAction ------------------------------------------------------
   The dialog that stands between a click and something that cannot be undone.
   Behaviour is in js/confirm-action.js; this is only how it looks.
   ------------------------------------------------------------------------- */

.confirm-backdrop {
    position: fixed;
    inset: 0;
    z-index: 1080;
    background: rgba(16, 32, 48, 0.45);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1rem;
}

.confirm-card {
    width: 100%;
    max-width: 26rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-lg);
    color: var(--text);
    padding: 1.4rem;
}

.confirm-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.2rem;
    height: 2.2rem;
    border-radius: 50%;
}

.confirm-icon svg {
    width: 1.15rem;
    height: 1.15rem;
}

.confirm-icon.danger  { background: var(--error-bg); }
.confirm-icon.danger svg  { fill: var(--error); }
.confirm-icon.warning { background: var(--warn-bg); }
.confirm-icon.warning svg { fill: var(--warn); }
.confirm-icon.info    { background: var(--info-bg); }
.confirm-icon.info svg    { fill: var(--info); }

.confirm-title {
    font-size: 1.02rem;
    font-weight: 600;
    line-height: 1.45;
    margin: 0.85rem 0 0.35rem;
}

.confirm-message {
    font-size: 0.86rem;
    line-height: 1.6;
    color: var(--text-dim);
    margin: 0;
}

.confirm-details {
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: 0.6rem 0.75rem;
    margin-top: 0.85rem;
    font-size: 0.84rem;
}

.confirm-row {
    display: flex;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.1rem 0;
}

.confirm-row-label {
    color: var(--text-muted);
}

.confirm-buttons {
    display: flex;
    justify-content: flex-end;
    gap: 0.6rem;
    margin-top: 1.1rem;
}

.confirm-btn {
    padding: 0.42rem 1rem;
    border-radius: var(--radius-sm);
    border: 1px solid;
    font-family: inherit;
    font-size: 0.875rem;
    font-weight: 500;
    line-height: 1.4;
    cursor: pointer;
}

.confirm-btn:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.confirm-btn-cancel {
    background: transparent;
    border-color: var(--border-bright);
    color: var(--text-dim);
}

.confirm-btn-cancel:hover {
    background: var(--surface-3);
    color: var(--text);
}

.confirm-btn-commit {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--accent-fg);
}

/* Filled, unlike the outlined .btn-danger elsewhere. The outlined treatment is for
   the button that OPENS this dialog, sitting among other row actions where a red
   block would invite the click it is warning about. By this point the user has
   asked for the action and is being asked to mean it, so the commit button is the
   plainest thing on the card. */
.confirm-btn-commit.danger {
    background: var(--error);
    border-color: var(--error);
    color: #fff;
}

.confirm-btn-commit.danger:hover {
    background: #8c2823;
    border-color: #8c2823;
}

/* ---- list screen --------------------------------------------------------
   All of this is CSS over the markup that was already there. The results grid
   is a real <table> with a <tbody>, and it stays one: HtmlErrors.ResultGrid
   reads that tbody for the tenant-isolation assertions, so restructuring it
   into anything else would quietly change what four isolation tests mean.
   ------------------------------------------------------------------------- */

/* The filter bar. It is a card already; this stops it competing with the
   results, which are what the operator came for. The child selector went with the
   <details> wrapper - the body is now inside it. */
form.card .card-body {
    padding: 0.9rem 1rem;
}

form.card .form-label {
    font-size: 0.78rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-muted);
}

/* Sortable column headings. The active one is the only bold thing in the row,
   so the sort is readable at a glance rather than only from the arrow. */
.table-csd thead a {
    color: var(--text-dim);
    text-decoration: none;
}

.table-csd thead a:hover {
    color: var(--accent);
}

.table-csd thead a.fw-bold {
    color: var(--accent);
}

/* Status and form-type pills.
   Bootstrap's own .text-bg-* colours are overridden rather than the markup
   being changed - success green next to this palette reads as a different
   application. Tinted rather than filled: on a compliance record the state is
   information, not a call to action. */
.badge.text-bg-success {
    background: var(--issued-bg) !important;
    color: var(--issued) !important;
    border: 1px solid var(--issued-border);
}

.badge.text-bg-warning {
    background: var(--warn-bg) !important;
    color: var(--warn) !important;
    border: 1px solid var(--warn-border);
}

.badge.text-bg-danger {
    background: var(--error-bg) !important;
    color: var(--error) !important;
    border: 1px solid var(--error-border);
}

.badge.text-bg-secondary {
    background: var(--surface-3) !important;
    color: var(--text-dim) !important;
    border: 1px solid var(--border-bright);
}

.badge {
    font-weight: 600;
    letter-spacing: 0.03em;
}

/* Row actions sit quietly until the row is hovered or focused within, so a
   table of declarations reads as data rather than as a wall of buttons. */
.table-csd tbody .btn-sm {
    --bs-btn-padding-y: 0.15rem;
    --bs-btn-padding-x: 0.5rem;
    --bs-btn-font-size: 0.78rem;
}

/* The empty state is a sentence, not a gap. */
.table-csd tbody td[colspan] {
    color: var(--text-muted);
}

/* ---- editor -------------------------------------------------------------
   The box frame is styled in the surfaces section. This is the rhythm inside
   it: the official form is dense, and an editor that is not reads as a
   different document from the one being filled in.
   ------------------------------------------------------------------------- */

.form-section .form-check {
    margin-bottom: 0.1rem;
}

.form-section .form-label {
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--text-muted);
}

/* Box 2 and the waybill columns read as identifiers, not prose. */
.form-section input.text-monospace-awb,
.form-section .form-control[name^="MasterAwbs"],
.form-section .form-control[name^="HouseAwbs"] {
    font-family: ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", monospace;
    letter-spacing: 0.02em;
}

/* ---- the page container -------------------------------------------------
   Applied to the navbar's inner container, to <main> and to the footer's
   inner wrapper, so all three share an edge on any screen. container-fluid's
   own 0.75rem is a phone gutter; on a desktop it puts the first label hard
   against the glass.

   theme.css loads after bootstrap.min.css, so this wins the padding on equal
   specificity - which is why .container-fluid can stay on the navbar and keep
   Bootstrap's own .navbar > .container-fluid flex rules working.
   ------------------------------------------------------------------------- */

.page-width {
    width: 100%;
    max-width: var(--page-max);
    margin-inline: auto;
    padding-inline: var(--page-gutter);
}

/* The footer's rule reaches the glass; only its text lines up with the page. */
footer.page-edge {
    padding-inline: 0;
}

/* A short form, at a width somebody can actually read. */
.page-form {
    max-width: var(--form-max);
}

/* ---- filter bar ---------------------------------------------------------
   Ten fields were laid out as equal columns, so Origin - three characters -
   was as wide as House AWB. They are on a 12-column grid now, sized to what
   each one holds, and the whole bar is capped so nothing stretches to the
   edge of a 4K screen.
   ------------------------------------------------------------------------- */

/* The disclosure header. It is the whole hit area, so the bar opens from
   anywhere along the top rather than from a caret the size of a full stop. */
.filter-summary {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    padding: 0.7rem 1rem;
    cursor: pointer;
    user-select: none;
    list-style: none;
    font-size: 0.78rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-dim);
}

.filter-summary::-webkit-details-marker {
    display: none;
}

.filter-summary:hover {
    color: var(--text);
}

.filter-summary:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
    border-radius: var(--radius);
}

/* The "Filtered" tag is a value, not a heading. */
.filter-summary .badge {
    text-transform: none;
    letter-spacing: 0.01em;
}

.filter-caret {
    flex: none;
    width: 0.42rem;
    height: 0.42rem;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    transform: rotate(-45deg);
    transition: transform 0.15s ease;
}

.filter-panel[open] .filter-caret {
    transform: rotate(45deg);
}

.filter-panel > .card-body {
    padding-top: 0;
}

/* Actions on the same left edge as the fields above them. Export is a
   different kind of act from Search, so it is pushed to the far end rather
   than queued behind it. */
.filter-actions {
    margin-top: 1.1rem;
    padding-top: 0.9rem;
    border-top: 1px solid var(--border);
}

/* ---- document detail ----------------------------------------------------
   Was one flat definition list of fifteen rows, full-bleed across the
   screen, with the labels at the far left and the values somewhere near the
   middle. Now: a capped column, five cards that group the boxes the way the
   form does, and a fixed label column so the pair reads as a pair.
   ------------------------------------------------------------------------- */

.detail-page {
    display: flex;
    flex-direction: column;
    gap: 0.9rem;
}

.detail-head {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
}

.detail-title {
    margin: 0 0 0.45rem;
    font-size: 1.45rem;
    font-weight: 700;
    line-height: 1.2;
    color: var(--text);
}

.detail-pills {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}

.detail-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
}

/* Delete used to sit against Back - the one control a user reaches for
   without reading it. A rule and a gap now separate the two. */
.detail-delete {
    margin-left: 0.5rem;
    padding-left: 1rem;
    border-left: 1px solid var(--border);
}

.detail-card > .card-body {
    padding: 0.35rem 1.1rem 0.55rem;
}

/* The value column is capped at a reading measure rather than the row width.
   At 1760px a three-word value sat alone in ~1500px of empty row, and the
   hairline under it ran the whole way to prove it. The label column is
   unchanged; the cap is on the value, so the rules stop where the text does. */
.detail-list {
    display: grid;
    grid-template-columns: 220px minmax(0, 70ch);
    column-gap: 1.25rem;
    margin: 0;
}

.detail-list dt {
    grid-column: 1;
    margin: 0;
    padding: 0.42rem 0;
    font-size: 0.8rem;
    font-weight: 500;
    line-height: 1.7;
    color: var(--text-dim);
}

.detail-list dd {
    grid-column: 2;
    margin: 0;
    padding: 0.42rem 0;
    line-height: 1.7;
    color: var(--text);
}

/* A hairline per row. Enough to track a long value back to its label, not
   enough to turn the card into a table. */
.detail-list dt:not(:first-of-type),
.detail-list dd:not(:first-of-type) {
    border-top: 1px solid var(--surface-3);
}

/* An empty box is a fact, and reads as one: the dash is muted so a filled
   row and an empty row are distinguishable without reading either. */
.detail-empty {
    color: var(--text-muted);
}

.detail-pre {
    white-space: pre-wrap;
}

/* Provenance, inside the last card instead of floating under a rule. */
.detail-foot {
    padding: 0.5rem 1.1rem;
    border-top: 1px solid var(--border);
    border-radius: 0 0 var(--radius) var(--radius);
    background: var(--surface-2);
    font-size: 0.8rem;
    color: var(--text-muted);
}

@media (max-width: 767.98px) {
    /* The label stacks above its value rather than squeezing a 220px column
       into a phone. */
    .detail-list {
        grid-template-columns: minmax(0, 1fr);
    }

    .detail-list dt,
    .detail-list dd {
        grid-column: 1;
    }

    .detail-list dt {
        padding-bottom: 0;
    }

    .detail-list dd {
        padding-top: 0.1rem;
        border-top: 0;
    }

    .detail-actions {
        width: 100%;
    }
}

/* ---- editor action bar --------------------------------------------------
   The 16-box editor is longer than a viewport, so Create sat below the fold
   and saving meant scrolling to the end of the form to find it.

   position: sticky, not fixed. Sticky pins the bar to the foot of the
   viewport for the whole scroll and then lets it settle into its own slot at
   the end of the form - which means the last field is never hidden behind it
   and the page footer is never trapped under it. A fixed bar would need
   compensating padding on both, and would still cover the footer at the
   bottom of the scroll.
   ------------------------------------------------------------------------- */

.editor-actionbar {
    position: sticky;
    bottom: 0;
    z-index: 3;
    margin-top: 1rem;
    background: var(--surface);
    border-top: 1px solid var(--border);
    box-shadow: var(--shadow-lg-up);
}

.editor-actionbar-inner {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 0.55rem;
    padding: 0.65rem 1rem;
}

/* Document state travels with the button that commits it. */
.editor-state-label {
    margin: 0;
    font-size: 0.78rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-muted);
}

.editor-state {
    width: auto;
    min-width: 8rem;
    margin-right: 0.75rem;
}

/* ---- icons --------------------------------------------------------------
   One inline SVG per glyph, emitted by the csd-icon tag helper. Sized in em
   and stroked in currentColor, so a single definition works on a filled
   primary button, an outlined secondary one and the danger variant without
   any of them knowing about it.
   ------------------------------------------------------------------------- */

.csd-icon {
    flex: none;
    vertical-align: -0.125em;
    margin-right: 0.4em;
}

/* On a button the glyph is part of the label, not a bullet before it. */
.btn .csd-icon {
    margin-right: 0.45em;
}

/* Row actions are tight; the icon earns its place only if it does not push the
   label out of the column. */
.table-csd tbody .btn-sm .csd-icon {
    margin-right: 0.3em;
}

/* ---- editor field measures ----------------------------------------------
   The box grid is proportional, so widening the page widened every input with
   it: box 3's textarea reached 1669px and box 1's identifier 1098px for a
   ten-character registration number. A box is capped at what its value is,
   not at what its column happens to be.
   ------------------------------------------------------------------------- */

/* Free text gets the same measure as a Details value. */
.form-section-body .form-control,
.form-section-body .form-select {
    max-width: 70ch;
}

/* Short, fixed-shape values are sized to what they hold. These come after the
   rule above and match it on specificity, so they win by order. */
.form-section-body .input-code {
    max-width: 7rem;
}

.form-section-body .input-ident {
    max-width: 22rem;
}

/* ---- slide-over panel ---------------------------------------------------
   A view of ?open=<id> on the list, not a mode. The element is always in the
   page and only its contents are swapped, which is what makes opening a
   second document a swap rather than a stack.
   ------------------------------------------------------------------------- */

/* Held rather than snapped: overflow:hidden freezes the list where it is,
   where position:fixed would jump it to the top and back. The stable gutter
   stops the page shifting sideways as the scrollbar goes. */
html {
    scrollbar-gutter: stable;
}

html.panel-open {
    overflow: hidden;
}

.panel-backdrop {
    position: fixed;
    inset: 0;
    z-index: 1060;
    background: rgba(16, 32, 48, 0.45);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.22s ease;
}

.panel-backdrop.is-open {
    opacity: 1;
    pointer-events: auto;
}

.detail-panel {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    z-index: 1070;
    display: flex;
    flex-direction: column;
    width: min(720px, 100vw);
    background: var(--bg);
    border-left: 1px solid var(--border);
    box-shadow: -12px 0 32px rgba(16, 32, 48, 0.16);

    /* visibility, not display: it has to be untabbable when closed, and
       display:none cannot be transitioned. */
    visibility: hidden;
    transform: translateX(100%);
    transition: transform 0.22s ease, width 0.22s ease, visibility 0s linear 0.22s;
}

.detail-panel.is-open {
    visibility: visible;
    transform: none;
    transition: transform 0.22s ease, width 0.22s ease;
}

/* Maximized leaves a gutter on purpose: the list stays visibly behind the
   panel, so it still reads as a layer over the list and not as a new page. */
.detail-panel.is-maximized {
    width: calc(100vw - 4rem);
}

.detail-panel:focus {
    outline: none;
}

.detail-panel:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}

.panel-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.9rem 1.1rem;
    background: var(--surface);
    border-bottom: 1px solid var(--border);
}

.panel-title {
    margin: 0 0 0.4rem;
    font-size: 1.15rem;
    font-weight: 700;
    line-height: 1.2;
    color: var(--text);
}

.panel-head-controls {
    display: flex;
    gap: 0.25rem;
}

.panel-icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    padding: 0;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-dim);
    cursor: pointer;
}

.panel-icon-btn .csd-icon {
    margin: 0;
    font-size: 1rem;
}

.panel-icon-btn:hover {
    background: var(--surface-3);
    color: var(--text);
}

.panel-icon-btn:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}

/* One button, two glyphs, no markup rewriting on toggle. */
.detail-panel:not(.is-maximized) [data-panel-maximize] .icon-contract,
.detail-panel.is-maximized [data-panel-maximize] .icon-expand {
    display: none;
}

/* The fetched fragment is one element deep, so IT has to be the column - the
   panel's own flex children are just this wrapper, and head/body/foot are its
   grandchildren. Without this the footer falls off the bottom of the panel. */
.detail-panel > [data-panel-content] {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

.panel-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 0.9rem 1.1rem;
    display: flex;
    flex-direction: column;
    gap: 0.9rem;
}

.panel-foot {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.7rem 1.1rem;
    background: var(--surface);
    border-top: 1px solid var(--border);
    box-shadow: var(--shadow-lg-up);
}

.panel-hints {
    margin: 0;
    font-size: 0.78rem;
    color: var(--text-muted);
}

.panel-hints kbd {
    padding: 0.05rem 0.35rem;
    border: 1px solid var(--border-bright);
    border-bottom-width: 2px;
    border-radius: var(--radius-sm);
    background: var(--surface-2);
    color: var(--text-dim);
    font-size: 0.72rem;
}

.panel-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
}

/* Same rule as the Details page: Delete is not adjacent to anything reached
   for without looking. */
.panel-delete {
    margin-left: 0.35rem;
    padding-left: 0.85rem;
    border-left: 1px solid var(--border);
}

@media (max-width: 767.98px) {
    .detail-panel,
    .detail-panel.is-maximized {
        width: 100vw;
        border-left: 0;
    }

    /* Already full width here, so the control would toggle nothing. */
    .detail-panel [data-panel-maximize] {
        display: none;
    }
}

/* ---- attachments --------------------------------------------------------
   One file per document. The card reads as part of the document rather than
   as a widget bolted beside it, so it uses the same surface and header as
   the numbered-box cards.
   ------------------------------------------------------------------------- */

.attachment-current {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.75rem;
    padding: 0.6rem 0.75rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface-2);
}

.attachment-facts {
    flex: 1 1 14rem;
    min-width: 0;
}

.attachment-name {
    font-weight: 600;
    color: var(--text);
    overflow-wrap: anywhere;
}

.attachment-meta {
    font-size: 0.8rem;
    color: var(--text-muted);
}

.attachment-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}

.attachment-empty {
    color: var(--text-muted);
}

/* The paperclip on the list. Muted until the row is hovered, like the row
   actions - it is a hint, not a column. */
.attachment-clip {
    color: var(--text-muted);
}

.attachment-clip .csd-icon {
    margin-right: 0;
    margin-left: 0.3em;
}

.table-csd tbody tr:hover .attachment-clip {
    color: var(--accent);
}

/* One visible target, spanning the card, with its commit button inside it.
   "Choose a file" is a real <label for>, so click-to-browse works with no
   script; the box is the drop target and, with script, a click target too. */
.attachment-dropzone {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.35rem;
    width: 100%;
    margin-top: 0.75rem;
    padding: 1.25rem 1rem;
    border: 1px dashed var(--border-bright);
    border-radius: var(--radius);
    background: var(--surface-2);
    color: var(--text-dim);
    cursor: pointer;
    text-align: center;
}

.attachment-dropzone-text,
.attachment-dropzone-hint {
    margin: 0;
}

/* The label carries the affordance, so it looks like the thing you click. */
.attachment-browse {
    color: var(--accent);
    font-weight: 600;
    text-decoration: underline;
    text-underline-offset: 0.15em;
    cursor: pointer;
}

.attachment-dropzone:hover .attachment-browse {
    color: var(--accent-hover);
}

/* The button is a different act from picking - it sends what you picked - so it
   is separated by a rule rather than sitting flush against the text. */
.attachment-dropzone .btn {
    margin-top: 0.55rem;
}

.attachment-dropzone:hover,
.attachment-dropzone.is-dragging {
    border-color: var(--accent);
    background: var(--accent-bg);
    color: var(--text);
}

.attachment-dropzone:focus-within {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.attachment-dropzone .csd-icon {
    margin-right: 0;
    font-size: 1.35rem;
    color: var(--text-muted);
}

.attachment-dropzone-hint {
    font-size: 0.78rem;
    color: var(--text-muted);
}

/* Visually hidden, not display:none - the input has to stay focusable, or the
   zone would be unreachable by keyboard. */
.attachment-input {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.attachment-chosen {
    margin: 0;
    padding: 0.2rem 0.6rem;
    border-radius: var(--radius-sm);
    background: var(--surface-3);
    font-size: 0.82rem;
    color: var(--text-dim);
}

.attachment-chosen.is-too-big {
    color: var(--error);
    font-weight: 600;
}

/* On the editor the card is the last one in the form body, wearing .form-section
   like the numbered boxes above it. */
.form-section.attachment-card {
    margin-top: 0;
}

/* What stands in for that card on Create, where there is nothing to attach to yet.
   A quiet line, not a panel: it says one thing and gets out of the way. */
.editor-note {
    margin: 0.25rem 0 0;
    font-size: 0.85rem;
    color: var(--text-muted);
}
