﻿/*  ============================================================================================
    responsive.css

    **EVERY MEDIA QUERY IN THIS PROJECT IS IN THIS FILE, and this is the only file that owns a
    breakpoint.** See docs/reference/app/stylesheets.md and docs/decisions/layout-ownership.md.

    THE TIERS

        Phone     360-575px    <- the floor. Every screen in the product must work here; there is
                                  no "desktop by agreement" list.
        Tablet    576-1279px   portrait and landscape
        Desktop   >= 1280px

    The boundaries are Bootstrap 5's own sm and xl edges, deliberately: two sources of truth for a
    breakpoint is how a layout ends up correct at 575px and broken at 576px, and the grid classes
    this markup is full of are already keyed to those numbers.

    TWO TRAPS, both silent

    1.  **`var()` DOES NOT WORK IN A MEDIA QUERY** - it never matches. The custom properties below
        are for ordinary declarations only and the queries carry their numbers literally, which is
        why every rule that tests a breakpoint has to live together in this one file.

    2.  **A SCOPED RULE BEATS A GLOBAL ONE.** CSS isolation appends an attribute to every selector
        in a `.razor.css`, and a media query adds no specificity - so a rule here cannot override a
        scoped one, and a declaration that has to change at a breakpoint must MOVE out of the
        scoped file. Each scoped file that lost rules says where they went.

    DIRECTION: mobile-first for anything newly written, `max-width` overrides where an existing
    desktop layout is being patched. Each section says which it is.
    ============================================================================================ */

:root {
    /*  For ordinary declarations only. See trap 1: these cannot be used in a media query. */
    --bp-phone-max: 575.98px;
    --bp-tablet-min: 576px;
    --bp-desktop-min: 1280px;

    /*  A touch target, applied on purpose to the wizard's controls and the picker's buttons -
        not swept across every button in the product. */
    --touch-target: 2.75rem;
}

/* =============================================================================================
   The one global guard. Mobile-first.

   `overflow-x: hidden` on the body is not here and should not be added: it hides the symptom of a
   layout that overflows and takes scroll-into-view with it. What is here stops the two things
   that actually overflow a 360px screen - an unbroken email address and an unconstrained image.
   ============================================================================================= */

img {
    max-width: 100%;
    height: auto;
}

/*  The assignment form's worker list is the one table in this app that is not a list page - no
    sorting, no paging, no expander - so it keeps its own markup and gets here the scrolling box
    the others get from ListTable. */
.table-scroll {
    overflow-x: auto;
}

/* =============================================================================================
   The list tables.

   At the tablet tier and up a table scrolls inside its own box; below the phone tier each row
   becomes a card and each cell a labelled line, from the cell's own `data-label`. The expander
   opens under the card exactly as it does under the row.
   ============================================================================================= */

.list-table {
    /*  Tablet and up: the table scrolls, not the page. */
    overflow-x: auto;
}

.list-table .sort-header {
    padding: 0;
}

/*  The sort control is a real button filling the header cell. A clickable `<th>` takes no keyboard
    focus, which makes sorting - the only way to reorder any list here - mouse-only. */
.list-table .sort-button {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    width: 100%;
    min-height: var(--touch-target);
    padding: 0.5rem;
    background: none;
    border: 0;
    font: inherit;
    font-weight: inherit;
    color: inherit;
    text-align: left;
    cursor: pointer;
}

.list-table .sort-indicator {
    color: #adb5bd;
    font-size: 0.8em;
}

.list-table .sort-header[aria-sort="ascending"] .sort-indicator,
.list-table .sort-header[aria-sort="descending"] .sort-indicator {
    color: #212529;
}

.list-table .list-row {
    cursor: pointer;
}

.list-table .list-row:hover {
    background-color: aliceblue;
}

/*  A focusable row needs a visible focus ring, or making it focusable moves the problem rather
    than fixing it. */
.list-table .list-row:focus-visible {
    outline: 2px solid #1e70b2;
    outline-offset: -2px;
}

@media (max-width: 575.98px) {
    /*  Phone: the table stops being a table. Patching an existing layout, so max-width. */
    .list-table {
        overflow-x: visible;
    }

    .list-table table,
    .list-table tbody,
    .list-table tr,
    .list-table td {
        display: block;
        width: auto;
    }

    /*  The header row survives as a row of sort chips. Hiding it would take sorting - the only way
        to reorder any list in this product - away from every phone user. */
    .list-table thead,
    .list-table thead tr {
        display: block;
    }

    .list-table thead tr {
        display: flex;
        flex-wrap: wrap;
        gap: 0.25rem;
        border: 0;
        margin-bottom: 0.75rem;
    }

    .list-table thead th {
        display: block;
        border: 0;
        background: none;
    }

    .list-table .sort-button {
        width: auto;
        min-height: 2.25rem;
        padding: 0.25rem 0.625rem;
        border: 1px solid #dee2e6;
        border-radius: 1rem;
        font-size: 0.8125rem;
        white-space: nowrap;
    }

    /*  `table-striped` paints alternating rows, which reads as noise once a row is a card.

        It has to be turned off through Bootstrap's own custom property rather than with a
        `background-color`: since 5.3 the striping is painted onto the *cells*
        (`.table-striped > tbody > tr:nth-of-type(odd) > *` sets `--bs-table-bg-type`), so a
        background on the row loses to it. */
    .list-table .table {
        --bs-table-striped-bg: transparent;
    }

    .list-table .list-row {
        border: 1px solid #dee2e6;
        border-radius: 0.5rem;
        margin-bottom: 0.75rem;
        padding: 0.25rem 0.75rem;
        background-color: #fff;
    }

    .list-table .list-row.selected {
        border-color: #1e70b2;
        margin-bottom: 0;
        border-bottom-left-radius: 0;
        border-bottom-right-radius: 0;
    }

    .list-table .list-row td {
        display: flex;
        flex-wrap: wrap;
        gap: 0.25rem 0.75rem;
        justify-content: space-between;
        align-items: baseline;
        border: 0;
        padding: 0.3rem 0;
        text-align: right;
    }

    .list-table .list-row td + td {
        border-top: 1px solid #f1f3f5;
    }

    .list-table .list-row td::before {
        content: attr(data-label);
        font-weight: 600;
        text-align: left;
        color: #495057;
    }

    /*  A cell with no label - the report template list's download column - would otherwise render
        an empty label box and a gap where one is not wanted. */
    .list-table .list-row td:not([data-label])::before,
    .list-table .list-row td[data-label=""]::before {
        content: none;
    }

    .list-table .list-row td:not([data-label]) {
        justify-content: flex-end;
    }

    .list-table .details-row {
        display: block;
    }

    .list-table .details-row > td {
        display: block;
        text-align: left;
        border: 1px solid #1e70b2;
        border-top: 0;
        border-bottom-left-radius: 0.5rem;
        border-bottom-right-radius: 0.5rem;
        margin-bottom: 0.75rem;
        padding: 0.75rem;
    }

    /*  A row of buttons in an expander is the delete flow's entry point, so it wraps rather than
        overflowing. `.buttons a/button { margin-left: .5rem }` in site.css is a left margin on a
        wrapped row, which stacks the indents; a gap does not. */
    .list-table .details-row .buttons {
        display: flex;
        flex-wrap: wrap;
        gap: 0.5rem;
        margin-right: 0;
    }

    .list-table .details-row .buttons a,
    .list-table .details-row .buttons button {
        margin-left: 0;
    }
}

/* =============================================================================================
   The pagination bar. Mobile-first: it is a wrapping flex row at every width, and the
   phone tier only drops the numeric pages.
   ============================================================================================= */

.pagination-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem 1rem;
    margin: 1rem 0;
}

.pagination-bar .pager {
    /*  Takes the slack so the count and the page size sit at the end of the row. */
    flex: 1 1 auto;
}

.pagination-bar .pagination {
    margin-bottom: 0;
    flex-wrap: wrap;
}

.pagination-bar .page-size {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 0;
    white-space: nowrap;
}

.pagination-bar .page-size .form-select {
    width: auto;
}

/*  The current page reads "3" in a pager that shows its neighbours, and "3 of 7" when it is on its
    own. Same element either way, so the phone tier only has to reveal the suffix. */
.pagination-bar .of-total {
    display: none;
}

@media (max-width: 575.98px) {
    .pagination-bar .page-item.number:not(.current) {
        display: none;
    }

    .pagination-bar .of-total {
        display: inline;
    }

    .pagination-bar .pager {
        flex: 1 0 100%;
    }
}

/* =============================================================================================
   FormSection, the form card every form page is built from.

   The heading column was `col-md-4 d-none d-md-block`, so below 768px the title and description
   were `display: none` - content loss on every form in the product, and on the answer wizard the
   title is "Question 3 of 11", the only progress indication there is. It stacks above the content
   now. These rules were FormSection.razor.css, which is deleted: the border side and the padding
   both change at a breakpoint, and per trap 2 a scoped rule cannot be overridden from here.
   ============================================================================================= */

.form-section {
    border: solid 1px rgb(201, 199, 199);
    margin-bottom: 1rem;
}

.form-section > .section-heading {
    background-color: #2f495f;
    border-right: solid 4px rgb(185, 183, 183);
    padding: 1rem;
    color: #fff;
}

.form-section > .section-content {
    padding: 2rem;
    min-height: 15rem;
}

.form-section .section-description {
    margin-bottom: 0;
}

/*  site.js's updateSectionValidations adds `invalid` to `.section` and scrolls to the first one. */
.form-section.invalid > .section-heading {
    background-color: #fff6db;
    color: #f00;
}

@media (max-width: 767.98px) {
    /*  The md edge rather than the phone floor, because that is where the grid stacks the two
        columns - the heading is above the content from here down, so the divider has to turn with
        it or it draws a line down the middle of nothing. */
    .form-section > .section-heading {
        border-right: 0;
        border-bottom: solid 4px rgb(185, 183, 183);
    }

    .form-section > .section-heading h4 {
        font-size: 1.15rem;
        margin-bottom: 0.25rem;
    }
}

@media (max-width: 575.98px) {
    /*  2rem of padding either side of a 360px screen is 20% of it. */
    .form-section > .section-content {
        padding: 1rem;
        min-height: 0;
    }
}

/* =============================================================================================
   The answer wizard.

   The one screen in this product that has to work on a phone: a Worker's only way in is a link in
   an assignment email, there is no registration, and no App Link association exists - so the link
   opens in the phone's browser and the phone's browser is this app.
   ============================================================================================= */

.wizard-nav {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 0.5rem;
    margin: 1rem 0 2rem;
}

.wizard-nav .btn {
    min-height: var(--touch-target);
}

/*  An inline calendar with a time panel is a fixed-size widget and its internals are Radzen's, not
    this project's CSS - so do not assume a width here contains it. The wrapper scrolls rather than
    the page, which is what guarantees the document never widens; whether the widget is *usable* at
    360px has not been measured on a device. If it is not, the fallback is to drop `Inline` below
    the phone tier so it opens as an overlay, or to split date and time into two fields. */
.wizard-picker {
    overflow-x: auto;
}

@media (max-width: 575.98px) {
    .wizard-nav {
        justify-content: space-between;
    }

    /*  Next/Finish is the action; it gets the full width once the row has wrapped, and it is last
        in the DOM so it is last under a thumb. */
    .wizard-nav .wizard-primary {
        flex: 1 1 100%;
        order: 3;
    }
}

/* =============================================================================================
   The picker that replaced drag-to-reorder, on the questionnaire and plan forms.
   Mobile-first: a stacked list of rows with their controls, at every width.
   ============================================================================================= */

.pick-list h5 {
    text-align: center;
    color: #ededed;
    padding: 1rem;
    border-radius: 5px;
    font-weight: bolder;
    background: #2f495f;
}

.pick-list .pick-list-items {
    list-style: none;
    margin: 0;
    padding: 0.5rem;
    background-color: #ededed;
    border-radius: 5px;
    min-height: 6rem;
}

.pick-list .pick-list-items > li {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.35rem 0.5rem;
    margin-bottom: 0.5rem;
    border: solid 1px #ccc;
    border-radius: 5px;
    background-color: #fff;
}

.pick-list .pick-list-empty {
    justify-content: center;
    color: #6c757d;
    background: none;
    border-style: dashed;
}

.pick-list .pick-list-position {
    flex: 0 0 1.5rem;
    text-align: right;
    color: #6c757d;
    font-variant-numeric: tabular-nums;
}

.pick-list .pick-list-label {
    flex: 1 1 auto;
    min-width: 0;
    overflow-wrap: break-word;
}

.pick-list .pick-list-actions {
    display: flex;
    flex: 0 0 auto;
    gap: 0.25rem;
}

/*  These are the whole control now, so they are the one other place the touch target is honoured
    on purpose. */
.pick-list .pick-list-actions .btn {
    min-width: var(--touch-target);
    min-height: var(--touch-target);
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

/* =============================================================================================
   The completed-questionnaire review card.

   These three rules were in CompletedQuestionnaires/DetailsPage.razor.css and had to move here
   whole rather than be overridden: per trap 2, a scoped selector outranks anything in this file,
   so an `@media` block here could not have reached them.

   **The `h3`'s negative margin is coupled to the card's padding.** It is a header bleed - the
   heading is pulled out to the card's edges by exactly the padding that is inset from them - so
   any change to one that is not made to the other breaks the header, silently and at one width
   only. That coupling is the reason these are adjacent and commented rather than left where a
   later responsive change would have found one of them.
   ============================================================================================= */

.question-number {
    font-size: 3rem;
    background: #ccc;
    border-radius: 50%;
    width: 6rem;
    height: 6rem;
    text-align: center;
    padding-top: 0.7rem;
}

.question-card .col {
    min-height: 20rem;
    border: solid 1px #ccc;
    border-radius: 20px;
    padding: 1rem 2rem;
    margin-bottom: 3rem;
}

.question-card .col h3 {
    background-color: #2f495f;
    margin: -2rem;
    border-top-right-radius: 20px;
    border-top-left-radius: 20px;
    margin-bottom: 2rem;
    padding: 1rem;
    color: white;
}

/* =============================================================================================
   Radzen widgets inside a Bootstrap layout.

   **Bootstrap owns page layout and spacing; Radzen owns the widget inside a cell; neither one's
   utility classes appear in the other's territory.** What is left here is the seam - a Radzen popup
   is appended to the document body, outside every container the page has, so nothing Bootstrap does
   can constrain it and no scoped stylesheet can reach it.
   ============================================================================================= */

/*  The user-lookup control is a data grid inside a dropdown, at five call sites - assignment
    create, assignment details, subscription details and report details. Three columns in a popup
    on a 360px screen cannot fit, so the popup is capped to the viewport and the grid inside it
    scrolls. This is the control most likely to need replacing rather than restyling on a real
    device; the fallback is an autocomplete or a plain filtered list. */
.rz-dropdown-panel,
.rz-popup,
.rz-datepicker-panel {
    max-width: calc(100vw - 1rem) !important;
}

.rz-dropdown-panel .rz-datatable,
.rz-popup .rz-datatable {
    overflow-x: auto;
}

/*  A Radzen dialog is positioned and sized by Radzen from the DialogOptions the caller passed, so
    a width in pixels wins until the viewport is narrower than it. Both delete dialogs set
    `CloseDialogOnOverlayClick = false`, which makes this load-bearing rather than cosmetic: if the
    dialog exceeds the viewport, the only way out of a destructive flow is a button that has to be
    on screen. */
.rz-dialog {
    max-width: calc(100vw - 1rem);
}

@media (max-width: 575.98px) {
    .rz-dialog {
        max-width: 100vw;
        max-height: 100vh;
        overflow-y: auto;
    }
}

/* =============================================================================================
   The rest of the fixed geometry, where it needs a breakpoint.
   ============================================================================================= */

@media (max-width: 575.98px) {
    /*  Toasts, on a screen where the on-screen keyboard takes the bottom half. Tighter to the
        edges than the desktop inset, and full width so a two-line message does not wrap to four. */
    #toast-container {
        right: 0.5rem;
        left: 0.5rem;
        bottom: 0.75rem;
        max-width: none;
    }

    .question-card .col {
        padding: 1rem;
        min-height: 0;
    }

    /*  The bleed again, at the new padding. */
    .question-card .col h3 {
        margin: -1rem -1rem 1rem;
    }

    .question-number {
        width: 4rem;
        height: 4rem;
        font-size: 2rem;
        padding-top: 0.5rem;
    }

}
