﻿body {
    display: flex;
    min-height: 100vh;
}

.page {
    flex: 1;
    width: 100%;
}

main {
    display: flex;
    flex-direction: column;
}

.page-container {
    flex: 1;
    align-self: auto !important;
}

:root {
    --label-color: rgba(0,0,0,.54);
    --label-border-color: #e0e0e0;
    /* The site's one primary blue is #0288ff. --bs-primary itself comes from bootstrap.min.css;
       the recolor lives in the theme files (bootstrap.min/style.min/style.css swapped from the
       template's #2f55d4, fabric-fem.css generated from Syncfusion's fabric by
       scratch/RecolorFabric.ps1 - rerun it after a Syncfusion.Blazor.Themes upgrade). This
       hover shade is ours: bootstrap computes its own inline and never publishes one. */
    --bs-primary-hover: #0274d9;
}

html {
    scroll-behavior: smooth;
}

fieldset.no-header-group legend.e-group-title::after {
    border-bottom: none !important;
}

fieldset.no-header-group {
    margin-top: 1px !important;
}

.display-label {
    color: var(--label-color);
    font-size: 12px;
    font-weight: 400;
}

.display-section-header {
    color: var(--label-color, rgba(0, 0, 0, 0.87));
    font-size: 16px;
    font-weight: 400;
    margin-top: 10px;
    margin-bottom: 10px;
    border-bottom: 1px solid var(--label-border-color);
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

.display-field {
    padding-bottom: 15px;
}

.display-section-content {
    padding-left: 10px;
    padding-bottom: 15px;
}

.reg-section-button {
    margin-top: 12px;
    width: 100%
}

.e-data-form {
    padding-left: 4px;
    padding-right: 4px;
    padding-bottom: 4px;
}

.accordion-content {
    padding: 4px;
}

.page-container {
    padding: 130px 0 100px;
    background-size: cover !important;
    align-self: center;
    position: relative !important;
    background-position: center center;
}

.mobile-toolbar-item .e-tbar-btn-text {
    display: none;
}

.data-container {
    border-top: 1px solid #dee2e6;
    padding: 24px 0px 24px 0px;
    align-items: center;
}

.data-header {
    font-weight: bold;
    color: #6c757d;
}

.data-value {
    text-align: right;
    font-size: 1.2em;
}

/* ---------------------------------------------------------------------------
   The stacking order of everything that floats over the page. Written down in
   one place because the numbers are not arbitrary and cannot be chosen locally.

   Left to itself, Syncfusion does not give a dialog a fixed z-index. It works
   one out when the dialog opens - the highest z-index it can find, plus one -
   so the dialog is above everything by construction. Raising an overlay to get
   over it only means the next open raises the dialog past it again. No number
   can win that argument, which is why EditDialog pins ZIndex="10500" instead:
   ej2 skips its calculation once the value is no longer the 1000 default.

   Everything below therefore sits above a known ceiling rather than a moving
   one. Keep it that way - an overlay that needs to clear the dialog belongs in
   this list, not on a number picked to beat whatever it lost to today.

     10500  edit dialog       - pinned, see EditDialog.razor (its modal backdrop
                                sits one below, at 10499)
     11000  media viewer      - a receipt has to be readable over the form it
                                belongs to, which is the whole point of opening it
     11100  close confirmation - asks a question that has to be answered before
                                the form underneath can go anywhere
     11200  assistant panel   - always on top, and reachable while a modal dialog
     11300  assistant button    is open, which is the point of a global assistant
     11400  consent gate      - see FirstLoginGate.razor: nothing on the site, the
                                assistant included, is usable before the account's
                                missing consents are answered
   --------------------------------------------------------------------------- */
.image-viewer-overlay {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    z-index: 11000;
    background-color: rgba(0,0,0,0.8);
    display: flex;
    align-items: center;
    justify-content: center;
}

    .image-viewer-overlay img {
        max-width: 90%;
        max-height: 90%;
        object-fit: contain;
    }

/* Single-image case: a spinner until the image's onload - on a slow cellular link a receipt
   photo takes seconds, and a black overlay with nothing in it reads as broken. The spinner is
   the container's ::after, so the arriving image simply paints over the question. */
.image-viewer-overlay .media-single {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    max-width: 90%;
    max-height: 90%;
    min-width: 120px;
    min-height: 120px;
}

    .image-viewer-overlay .media-single img {
        max-width: 100%;
        max-height: 90vh;
        object-fit: contain;
    }

    .image-viewer-overlay .media-single:not(.loaded)::after {
        content: "";
        position: absolute;
        width: 42px;
        height: 42px;
        border: 4px solid rgba(255, 255, 255, 0.25);
        border-top-color: #fff;
        border-radius: 50%;
        animation: fem-spin 0.9s linear infinite;
    }

@keyframes fem-spin {
    to { transform: rotate(360deg); }
}

/* Paged PDF view (touch devices - iOS renders only page 1 of a PDF in an iframe): a scrollable
   column of pdf.js-rendered canvases with a load-more bar after them. */
.image-viewer-overlay .pdf-pager {
    width: min(92%, 900px);
    max-height: 92%;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 12px;
    -webkit-overflow-scrolling: touch;
}

.pdf-pager-page {
    position: relative;
    background: #fff;
    /* flex-shrink 0 is load-bearing: the pager is a height-capped flex column, and shrinkable
       items get compressed to fit while their content overflows - which stacked every page 50%
       over the one before it on the first phone test. */
    flex-shrink: 0;
    /* A placeholder tall enough to scroll into before its page is painted; the painted canvas
       replaces the guess with its real height. */
    min-height: 200px;
}

    /* Shimmer until pdf.js paints the page and femPdfPager adds .loaded - pdf.js leaves the
       page background transparent, so the shimmer must be switched off, not covered. */
    .pdf-pager-page::before {
        content: "";
        position: absolute;
        inset: 0;
        background: linear-gradient(100deg, #f0f0f0 40%, #fafafa 50%, #f0f0f0 60%);
        background-size: 200% 100%;
        animation: fem-shimmer 1.2s linear infinite;
    }

    .pdf-pager-page.loaded::before {
        display: none;
    }

    .image-viewer-overlay .pdf-pager-page canvas {
        position: relative;
        display: block;
        width: 100%;
        height: auto;
    }

@keyframes fem-shimmer {
    from { background-position: 200% 0; }
    to   { background-position: 0 0; }
}

.pdf-pager-bar {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 12px;
    color: #fff;
    padding: 4px 0 20px;
    flex-shrink: 0;
}

.pdf-pager-error {
    background: #fff;
    border-radius: 6px;
    padding: 1rem;
}

/* Close on top, download beneath it, both in the top-left corner. Stacked by a flex column rather
   than by giving each button its own offset: the second offset would have to encode the first
   button's height, and would quietly stop lining up the day either button changes size. */
.image-viewer-actions {
    position: absolute;
    top: 1rem;
    left: 1rem;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.image-viewer-close-button,
.image-viewer-download-button {
    font-weight: bold !important;
    color: white !important;
    opacity: 0.6;
    border-width: 3px !important;
}

    .image-viewer-close-button:hover, .image-viewer-download-button:hover {
        color: black !important;
    }

.changed-value {
    font-weight: bold !important;
}

.tight-button {
    padding-left: 0 !important;
    padding-right: 0 !important;
}

.avatar-menu-li {
    margin-top: -6px !important;
}

.mobile-avatar-link {
    display: none;
}

.avatar-badge {
    background: linear-gradient(135deg, #bbdfff, var(--bs-primary, #0288ff)) !important;
    border-width: 2px !important;
    border-color: var(--bs-primary, #0288ff) !important;
    color: #f6f3f3 !important;
}

    .avatar-badge:hover {
        background: linear-gradient(135deg, #f6f3f3, #bcbcbc) !important;
        color: rgba(0,0,0,.54) !important;
    }

/* The expense grid's column set outgrew Bootstrap's 1140px container when the Cycle column
   joined, leaving a horizontal scrollbar (Evgueni ok'd a wider default). Expenses pages only
   and full-desktop widths only - every other page keeps the site's standard container, and
   the expand toggle's container-fluid still overrides when it is on. */
@media (min-width: 1200px) {
    .expenses-container.container {
        max-width: 1320px;
    }
}

/* The expense-cycle chip: rides among the tag badges (grid Tags column, phone card, the
   expense form's read-only view) but in dark yellow, so a cycle reads as a cycle at a
   glance and never as one more tag. */
.e-cycle-badge {
    background-color: #b8860b;
    color: #fff;
    /* A long cycle name inside a narrow cell (the grid's Cycle column) trims to an ellipsis
       instead of clipping mid-letter; anywhere roomy, max-width 100% never engages. */
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    vertical-align: bottom;
}

.guardian-item {
    display: flex;
    flex-direction: column;
}

.guardian-header {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    column-gap: .25rem;
    row-gap: .25rem;
}

    .guardian-header .remove-btn {
        margin-left: .25rem;
    }

.tight-link-btn {
    padding-left: 0px !important;
    padding-right: 0px !important;
}

.resend-btn {
    flex: 0 0 auto;
}

.spinner-container {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    width: 80vw;
    height: 50vh;
}

.inline-small-warning {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 4px;
    background-color: #fff4ce;
    font-size: smaller;
    margin-top: 4px;
}

.receipt-sidebar-target {
    height: 100vh;
}

.expense-tag-editor {
    border: 1px solid #ced4da;
    border-radius: 4px;
    padding: 8px 8px 8px 12px;
}

.expense-tag-chip-list {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-bottom: 8px;
}

.expense-tag-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background-color: #f3f3f3;
    border: 1px solid #d0d0d0;
    border-radius: 16px;
    padding: 4px 10px;
}

.expense-tag-chip-remove {
    border: none;
    background: transparent;
    padding: 0;
    line-height: 1;
    font-size: 16px;
    color: #6c757d;
}

.expense-tag-input-row {
    display: inline-flex;
    gap: 8px;
    align-items: center;
    flex-wrap: nowrap;
}

.expense-tag-input {
    width: 110px !important;
    min-width: 110px;
    max-width: 110px;
    flex: 0 0 110px;
    border: none !important;
    outline: none !important;
    box-shadow: none !important;
    padding: 4px 0;
    background-color: transparent;
}

    .expense-tag-input:focus,
    .expense-tag-input:focus-visible {
        border: none !important;
        outline: none !important;
        box-shadow: none !important;
    }

/* Gap between the left header rail and the content box. Scoped to the vertical (wide-screen)
   orientation: in the narrow band the headers sit on top, where a right margin would only pull
   the header row out of line with the content border below it. */
.account-tabs .e-tab-header.e-vertical {
    margin-right: 5px;
}

.account-tabs .e-content {
    border: 1px solid #ced4da;
    border-radius: 4px;
    min-height: 300px;
}

#sidebar-target {
    position: fixed !important;
    top: 0;
    left: 0;
    width: 100%;
    z-index: 1050;
    background-color: rgba(255, 255, 255, 0.5);
}

#topnav {
    background-color: white;
}

@media (min-width: 992px) {
    #topnav .navigation-menu {
        -webkit-box-pack: right;
        -ms-flex-pack: right;
        justify-content: right;
        /* The menu is the one thing in the header that must not wrap: a second row makes the
           fixed header taller than the page's top padding allows for, and it lands on the
           content. Whatever shares the row (the global search) gives way instead - see below. */
        flex-wrap: nowrap;
    }

    /* The header row as a flex line rather than the theme's floats, for a desktop. The floats
       cannot say who yields when the row is short - they just drop the menu under the logo -
       and the row IS short once the global search sits in it: the container caps at 1140px
       however wide the screen is, and the signed-in menu needs ~520px of that.
       Order on the line is DOM order: logo, search (GlobalSearch.razor.css sizes it as the
       flexible one), the mobile toggle's empty wrapper, then the menu, which the auto margin
       keeps at the right edge. Below 992px nothing here applies and the floats carry on. */
    #topnav .container {
        display: flex;
        align-items: center;
    }

    #topnav #navigation {
        flex: 0 0 auto;
        margin-left: auto;
    }
}

@media (max-width: 1200px) {
    .external-provider-name {
        display: block;
    }
}

@media (max-width: 991px) {
    .mobile-avatar-link {
        display: inline-block;
        margin-top: 18px;
        float: right;
        margin-right: 10px;
    }

    .avatar-menu-li {
        display: none !important;
    }
}

/* From the desktop width up the menu drops Sign Out - the More submenu replaced the
   Home/Sign Out pair there, and signing out lives on the Account page behind the avatar.
   The hamburger keeps the link: on a phone the Account page is a tap further away.
   !important for the same reason .avatar-menu-li needs it: the template's
   "#topnav .navigation-menu > li { display: block }" outguns a bare class. */
@media (min-width: 992px) {
    .signout-menu-li {
        display: none !important;
    }
}

@media (max-width: 768px) {
    /* The top padding only has to clear the fixed header, a single ~70px row on a phone -
       the 160px it inherited from the template assumed a taller stacked header and left a
       band of dead space above every page. */
    .page-container {
        padding: 90px 0 60px;
    }

    .data-value {
        text-align: left;
    }

    .phone-img {
        margin-top: 3em;
    }
}

/* ---------------------------------------------------------------------------
   CopyToClipboard's button (Components/Shared/CopyToClipboard.razor). Here rather than in a
   scoped .razor.css because the scoped bundle reaches the page through an unfingerprinted
   Fem.Web.styles.css whose cached copy points at the previous fingerprinted bundle - which is
   exactly how this button shipped unstyled once already. app-custom.css is linked directly and
   always current.

   Styled after the Azure portal's copy affordance: a bare muted glyph riding beside the value,
   no box, no border, no background - it reads as part of the text until hovered.
   appearance:none matters as much as border:0: the browser's default button chrome (the beveled
   square) survives border/background overrides unless appearance is reset too.
   --------------------------------------------------------------------------- */
.copy-btn {
    appearance: none;
    -webkit-appearance: none;
    display: inline-flex;
    align-items: center;
    background: none;
    border: 0;
    box-shadow: none;
    cursor: pointer;
    padding: 0;
    margin-left: 6px;
    color: #8a959e;
    font-size: 12px;
    line-height: 1;
    vertical-align: baseline;
}

.copy-btn:hover {
    color: var(--bs-primary, #0288ff);
}

/* Keyboard users still get a focus ring; mouse clicks don't paint one. */
.copy-btn:focus {
    outline: none;
}

.copy-btn:focus-visible {
    outline: 1px solid #80c3ff;
    outline-offset: 2px;
    border-radius: 2px;
}

.copy-btn-done {
    color: #198754;
}

/* --- StagedFileUpload (ExpenseForm's receipt picker) -----------------------------------------
   In app-custom.css rather than a scoped .razor.css: new scoped styles hide behind the
   unfingerprinted Fem.Web.styles.css bundle and go stale in clients' caches.

   The root is display:contents so in a flex host the uploader, hint and notices participate in
   the host's row directly - the div exists purely to scope these rules. Host chrome stays in
   the host's own CSS. */

.staged-file-upload {
    display: contents;
}

/* Syncfusion's file-list wrapper, flattened: only the <ul> and <li> are its now - what goes
   inside each item is drawn by the component's pill template - so these rules exist purely to
   stop the wrapper imposing a row's worth of height and border on a pill.

   The class is doubled deliberately: the theme's own selectors (.e-upload .e-upload-files ...)
   tie a single-class version on specificity, and fabric.css loads after this file, so a tie
   loses - the li kept its 90px min-height and the pill ballooned. Doubling wins the cascade
   without leaning on !important. */
.staged-file-upload.staged-file-upload .e-upload-files {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    flex: 1 1 auto;
    min-width: 0;
    border: none;
    /* Breathing room between the browse box and the pills. The chat strip zeroes this back out
       in AiChat.razor.css - there the pills sit beside the paperclip in one row. */
    margin: 6px 8px 8px;
    padding: 0;
    list-style: none;
}

.staged-file-upload.staged-file-upload .e-upload-files .e-upload-file-list {
    display: inline-flex;
    width: auto;
    max-width: 100%;
    height: auto;
    min-height: 0;
    margin: 0;
    padding: 0;
    border: none;
    background: none;
}

/* Any button the uploader still renders alongside the template - the pill carries its own. */
.staged-file-upload.staged-file-upload .e-upload-files .e-file-delete-btn,
.staged-file-upload.staged-file-upload .e-upload-files .e-file-remove-btn,
.staged-file-upload.staged-file-upload .e-upload-files .e-file-abort-btn {
    display: none;
}

/* The pill itself. */
.staged-file-upload .sfu-pill {
    display: inline-flex;
    align-items: center;
    max-width: 100%;
    padding: 3px 4px 3px 10px;
    border: 1px solid rgba(0, 0, 0, 0.15);
    border-radius: 999px;
    background: rgba(0, 0, 0, 0.03);
    font-size: 12px;
    line-height: 1.5;
}

.staged-file-upload .sfu-pill.uploading {
    border-style: dashed;
}

.staged-file-upload .sfu-pill-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 180px;
}

/* Percentage label while the file's transfer is in flight. */
.staged-file-upload .sfu-pill-progress {
    margin-left: 6px;
    font-variant-numeric: tabular-nums;
    font-size: 11px;
    color: var(--bs-primary, #0288ff);
    white-space: nowrap;
}

.staged-file-upload .sfu-pill-remove {
    display: inline-flex;
    align-items: center;
    margin-left: 4px;
    padding: 2px;
    border: none;
    border-radius: 50%;
    background: transparent;
    color: inherit;
    font-size: 10px;
    line-height: 1;
    cursor: pointer;
    opacity: 0.6;
}

.staged-file-upload .sfu-pill-remove:hover {
    background: rgba(0, 0, 0, 0.1);
    opacity: 1;
}

/* Watermark for an empty strip. pointer-events:none so it cannot swallow a drop aimed at the
   text; the flex properties only matter in a flex host, where it sits beside the browse button
   rather than under it. */
.staged-file-upload .sfu-hint {
    flex: 0 1 auto;
    color: #adb5bd;
    font-size: 12px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    user-select: none;
    pointer-events: none;
}

/* Why a file the user picked is not in the row. flex-basis:100% puts it on its own line under
   the pills in a flex host. */
.staged-file-upload .sfu-notice {
    flex: 0 0 100%;
    color: #b02a37;
    font-size: 11px;
    line-height: 1.4;
}

/* While the HEIC shim (sfuInstallHeicConversion in app-custom.js) is converting a picked photo
   to JPEG - a class toggled from JS, drawn as a pseudo-element so nothing is inserted into
   Blazor's DOM for it to fight over. The root is display:contents, but its pseudo-elements
   still generate boxes and join the host's flex row like the notices do. */
.staged-file-upload.sfu-converting::after {
    content: "Converting photo\2026";
    flex: 0 0 100%;
    color: #6c757d;
    font-size: 11px;
    line-height: 1.4;
}

/* Cancel next to AiEnabledAction's "Analyzing..." label - only rendered while a run is
   cancellable. Styled as a quiet link so the primary action keeps the visual weight. */
.ai-enabled-action .ai-action-cancel {
    background: none;
    border: 0;
    padding: 0;
    margin-left: 8px;
    color: #dc3545;
    font-size: 12px;
    cursor: pointer;
    text-decoration: underline;
}

.ai-enabled-action .ai-action-cancel:hover {
    color: #b02a37;
}

/* --- Penny's launcher, mid-drag (the launcher-drag section of app-custom.js) -----------------
   The closed fist has to hold for the whole drag, wherever the pointer happens to be: the badge
   is repositioned per pointermove, so a fast pull leaves the pointer over other content for a
   frame at a time, and a cursor set only on the button flickers back to whatever is underneath.
   Hence everything, with !important, for exactly as long as the html-level class is on - the
   script adds it at the moment a press becomes a drag and removes it when the pointer is
   released. Here rather than in AiChat.razor.css because a scoped file cannot address html or
   elements outside its own component. */
html.ai-chat-fab-dragging,
html.ai-chat-fab-dragging * {
    cursor: grabbing !important;
}

/* --- Account on a phone: full-bleed ----------------------------------------------------------
   Below the Small breakpoint (the same 991px line where the account tabs move their headers
   from the left rail to the top) the page runs edge to edge: the layout container's side
   gutters come off, and the accordions lose their side walls and corner rounding - vertical
   borders read as clutter against the screen edge. Each tab panel's own p-3 stays: that inner
   padding is the one side inset the content keeps. */
@media (max-width: 991px) {
    .account-container {
        padding-left: 0;
        padding-right: 0;
        /* Not just the gutters: between 576px and 991px bootstrap caps .container at
           540/720px and centers it, which kept the page a floating card with whitespace on
           both sides. Full width pulls the tab strip and panels all the way to the edges. */
        max-width: 100%;
    }

    /* The tab panel keeps only its top border at full bleed - side and bottom walls (and
       rounded corners) read as a box against the screen edge. */
    .account-tabs .e-content {
        border-width: 1px 0 0 0;
        border-radius: 0;
    }

    .account-tabs .e-accordion,
    .account-tabs .e-accordion .e-acrdn-item {
        border-left-width: 0 !important;
        border-right-width: 0 !important;
        border-radius: 0;
    }
}

/* --- The grids' Archived toggle (Expenses, Drafts, Cycles) --------------------------------
   One dress for all three: the eye badge over the trash icon, and the washed-out look while
   the toggle is off. Formerly in ExpenseGrid's inline style block, where the other grids'
   pages never received it. */
.e-archive-icon {
    position: relative;
}

.e-archive-icon::after {
    content: "\e345";
    font-family: "e-icons";
    /* px, not em: the badge must render identically on every page that hosts a grid -
       em let the hosting context scale it, which is how the Cycles toggle drifted. */
    font-size: 11px;
    line-height: 1;
    position: absolute;
    bottom: 2px;
    right: -4px;
    background-color: #ffffff;
    padding: 1px 2px;
    border-radius: 2px;
}

.e-archived-off .e-tbar-btn,
.e-archived-off .e-tbar-btn:hover {
    opacity: 0.25;
    color: rgb(33, 37, 41)
}
