/* MapStaticAssets fingerprints this file, so its URL changes whenever the contents do. If a browser
   ever cached a bad response for one of those URLs (before Program.cs started returning 404 rather
   than redirecting missing assets to /login, a retired name answered with a 302 to an HTML page,
   which is cacheable), the only clean recovery is a fingerprint it has never seen. Editing this file
   at all produces one. */

html, body {
    font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

/* ---- App shell ---------------------------------------------------------- */

.page {
    display: flex;
    min-height: 100vh;
}

.sidebar {
    width: 15rem;
    flex-shrink: 0;
    background: #1f2937;
    color: #e5e7eb;
    /* Column flex so the Tech911 mark can be pushed to the bottom of the rail regardless of how
       many nav items the signed-in role happens to see. */
    display: flex;
    flex-direction: column;
}

.sidebar .nav-brand {
    border-bottom: 1px solid rgba(255, 255, 255, .1);
}

/* Owned here rather than via Bootstrap's .flex-column/.px-2, whose !important would block the
   mobile media query from turning this into a horizontal strip. */
.nav-main {
    flex-direction: column;
    flex-wrap: nowrap;
    padding: .5rem;
}

.sidebar .nav-link {
    color: #cbd5e1;
    border-radius: .375rem;
    /* Roomier than Bootstrap's default: this rail has at most nine items and is read at a glance,
       so vertical breathing room matters more than fitting more in. */
    padding: .6rem .75rem;
    margin-bottom: .15rem;
    display: flex;
    align-items: center;
    gap: .7rem;
}

.sidebar .nav-link:hover {
    background: rgba(255, 255, 255, .06);
    color: #fff;
}

.sidebar .nav-link.active {
    background: rgba(255, 255, 255, .12);
    color: #fff;
}

/* Zero-state count. Legible against the dark sidebar but deliberately quiet, so a non-zero
   warning badge is what draws the eye. */
.sidebar .nav-count-zero {
    background: rgba(255, 255, 255, .13);
    color: #cbd5e1;
    font-weight: 500;
}

main {
    flex: 1;
    min-width: 0;
}

.top-row {
    height: 3.5rem;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    border-bottom: 1px solid #e5e7eb;
    background: #fff;
}

.content {
    padding-top: 1.5rem;
    padding-bottom: 3rem;
}

/* ---- Chrome-free pages (sign-in, customer connect) ---------------------- */

.auth-shell {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    background: #f3f4f6;
    padding: 1.5rem;
}

.auth-card {
    background: #fff;
    border: 1px solid #e5e7eb;
    border-radius: .75rem;
    padding: 2rem;
    width: 100%;
    max-width: 24rem;
    box-shadow: 0 1px 3px rgba(0, 0, 0, .06);
}

/* Wider variant for the customer-facing connect page, which carries explanatory copy. */
.auth-card--wide {
    max-width: 34rem;
}

/* ---- Per-firm branding on the customer-facing connect page --------------- */
/* --brand / --brand-accent are set inline from the firm's record, after being validated as literal
   hex. The fallbacks keep an unbranded firm looking deliberate rather than broken. */

.brand-header {
    border-bottom: 3px solid var(--brand, #1f2937);
    padding-bottom: .75rem;
}


.brand-name {
    font-size: 1.2rem;
    font-weight: 700;
    color: var(--brand, #1f2937);
}

.brand-btn {
    background: var(--brand, #1f2937);
    border-color: var(--brand, #1f2937);
    color: #fff;
}

.brand-btn:hover:not(:disabled),
.brand-btn:focus {
    background: var(--brand-accent, var(--brand, #111827));
    border-color: var(--brand-accent, var(--brand, #111827));
    color: #fff;
}

.brand-btn:disabled {
    opacity: .55;
}

/* The reassurance block on the connect page. Visually distinct from body copy because it answers
   the questions a customer actually has before granting bank access. */
.reassure {
    background: #f9fafb;
    border: 1px solid #e5e7eb;
    border-radius: .5rem;
    padding: 1rem 1.15rem;
}

.reassure-item {
    font-size: .9rem;
    color: #374151;
}

.reassure-item + .reassure-item {
    margin-top: .7rem;
}

/* Brand color swatch on the admin firms list. */
.color-chip {
    display: inline-block;
    width: .75rem;
    height: .75rem;
    border-radius: 2px;
    border: 1px solid rgba(0, 0, 0, .15);
    vertical-align: -1px;
    margin: 0 .25rem 0 .5rem;
}

/* Authenticator enrollment QR. Sized in rem so it tracks the user's text size, and framed in white
   because a QR on a tinted background is the classic reason a camera refuses to lock on. */
.qr-frame {
    display: inline-block;
    padding: 0.75rem;
    background: #fff;
    border: 1px solid #dee2e6;
    border-radius: 0.5rem;
}

.qr {
    display: block;
    width: 11rem;
    height: 11rem;
}

/* ---- Sidebar icons, footer mark -------------------------------------------- */

.nav-icon {
    width: 1.15rem;
    height: 1.15rem;
    flex-shrink: 0;
    /* Slightly dimmer than the label so the wordmark stays what you read first. */
    opacity: .85;
}

.sidebar .nav-link:hover .nav-icon,
.sidebar .nav-link.active .nav-icon {
    opacity: 1;
}

/* margin-top:auto on the footer pins it to the bottom of the flex column. */
.nav-footer {
    margin-top: auto;
    padding: 1rem .9rem 1.15rem;
    border-top: 1px solid rgba(255, 255, 255, .08);
}

/* Belt and braces with the width/height attributes on the <img>. The source is 1024x1024, so a
   missing or stale stylesheet must not be able to render it at intrinsic size. */
.nav-logo {
    width: 1.6rem;
    height: 1.6rem;
    max-width: 100%;
    /* Deliberately NOT filter: invert(1). The mark is a black ring with a white face and a red
       "911"; inverting turns that red cyan, which is worse than the ring simply blending into the
       dark rail. The white face carries the mark on this background on its own. */
    opacity: .9;
}

/* ---- Account menu ---------------------------------------------------------- */

.account-menu {
    position: relative;
}

.account-trigger {
    list-style: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    border-radius: 50%;
    color: #4b5563;
    background: #f3f4f6;
}

/* Both are needed: WebKit uses the pseudo-element, everyone else honours list-style. */
.account-trigger::-webkit-details-marker { display: none; }
.account-trigger::marker { content: ""; }

.account-trigger:hover {
    background: #e5e7eb;
    color: #111827;
}

.account-menu[open] .account-trigger {
    background: #e5e7eb;
    color: #111827;
}

.account-icon {
    width: 1.3rem;
    height: 1.3rem;
}

.account-panel {
    position: absolute;
    right: 0;
    top: calc(100% + .5rem);
    z-index: 20;
    min-width: 14rem;
    padding: .35rem;
    background: #fff;
    border: 1px solid #e5e7eb;
    border-radius: .5rem;
    box-shadow: 0 8px 24px rgba(0, 0, 0, .12);
}

.account-email {
    padding: .5rem .65rem;
    margin-bottom: .25rem;
    border-bottom: 1px solid #f3f4f6;
    font-size: .82rem;
    color: #6b7280;
    /* Long addresses should wrap rather than widen the panel off the edge of the viewport. */
    overflow-wrap: anywhere;
}

.account-item {
    display: block;
    width: 100%;
    padding: .45rem .65rem;
    border: 0;
    border-radius: .375rem;
    background: none;
    text-align: left;
    font-size: .9rem;
    color: #374151;
    text-decoration: none;
}

.account-item:hover {
    background: #f3f4f6;
    color: #111827;
}

.account-signout {
    color: #b91c1c;
}

.account-signout:hover {
    background: #fef2f2;
    color: #991b1b;
}

/* ---- Banner dismiss -------------------------------------------------------- */

/* Replaces the old top-right Refresh link: navigates to the same page without msg/msgType, so a
   message that has been read does not come back on reload. */
.alert-dismiss {
    line-height: 1;
    font-size: 1.35rem;
    text-decoration: none;
    color: inherit;
    opacity: .45;
}

.alert-dismiss:hover {
    opacity: .9;
    color: inherit;
}

/* ---- Mobile shell ---------------------------------------------------------- */
/* The desktop layout is a fixed 15rem rail beside the content. On a 375px phone that leaves about
   135px to work in, which is unusable. Below 820px the rail becomes a header: brand on the left, nav
   as a horizontally scrolling strip beneath it.

   A scroll strip rather than a drawer because this app loads no JavaScript. The <details> trick for
   a CSS-only drawer depends on overriding the UA stylesheet's display rule for closed details, which
   is inconsistent across engines; a strip is plain flexbox and behaves identically everywhere. Every
   destination stays one tap away, which a drawer cannot claim. */
@media (max-width: 820px) {
    .page {
        flex-direction: column;
    }

    .sidebar {
        width: 100%;
        /* Height must come from content now that it is no longer a full-height column. */
        min-height: auto;
    }

    .sidebar .nav-brand {
        padding: .55rem .9rem;
    }

    .nav-main {
        flex-direction: row;
        flex-wrap: nowrap;
        overflow-x: auto;
        /* Without this a flex child refuses to shrink below its content width, and the strip pushes
           the page into horizontal scroll instead of scrolling itself. */
        min-width: 0;
        gap: .25rem;
        padding: .4rem .5rem;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: thin;
    }

    .sidebar .nav-link {
        white-space: nowrap;
        margin-bottom: 0;
        /* ~42px tall, near the 44px touch-target guideline. The labels keep their own width via
           nowrap, which is also what stops them being squeezed by the flex row. */
        padding: .55rem .7rem;
        flex-shrink: 0;
    }

    /* The PlatformAdmin separator is a horizontal rule in the column layout; in a row it has to
       become a vertical one. flex-shrink is the load-bearing part: it has no content, so as a flex
       item in an overflowing row it shrinks to zero width and disappears entirely. */
    .nav-main hr {
        width: 1px;
        height: 1.4rem;
        margin: 0 .3rem;
        align-self: center;
        opacity: .25;
        flex-shrink: 0;
    }

    /* ms-auto pushes the pending-requests badge to the far right in the column layout. In a row that
       would stretch the item across the remaining strip. */
    .nav-main .badge.ms-auto {
        margin-left: .4rem !important;
    }

    /* The Tech911 mark costs a whole row of vertical space on a phone for no navigational value. */
    .nav-footer {
        display: none;
    }

    .top-row {
        height: 3rem;
    }

    .content {
        padding-top: 1rem;
        padding-left: 1rem !important;
        padding-right: 1rem !important;
    }

    /* Keep the account dropdown inside the viewport on a narrow screen. */
    .account-panel {
        min-width: 12rem;
        max-width: calc(100vw - 1.5rem);
    }

    /* Long unbroken strings (emails, Plaid ids, original descriptions) are the usual cause of a
       phone scrolling sideways. */
    .content {
        overflow-wrap: break-word;
    }
}

/* Below this the QR and its frame start crowding the card's padding. */
@media (max-width: 400px) {
    .qr {
        width: 9.5rem;
        height: 9.5rem;
    }
}

/* ---- Touch targets and iOS zoom -------------------------------------------- */
@media (max-width: 820px) {
    /* iOS Safari zooms the whole page when a focused input has a font-size below 16px, and leaves
       the layout scrolled sideways afterwards. Bootstrap's .form-control-sm is 0.875rem, and the
       rules, report and staff screens use it throughout, so this would fire constantly. Setting the
       computed size rather than the class keeps the compact desktop look intact. */
    .form-control,
    .form-select,
    input[type="text"],
    input[type="email"],
    input[type="password"],
    input[type="number"],
    textarea,
    select {
        font-size: 16px;
    }

    /* Comfortable thumb targets. .btn-link is excluded deliberately: those are the inline word
       actions inside table rows ("archive", "disable", "delete"), and giving them a 44px box would
       break the sentence they sit in. */
    .form-control,
    .form-select,
    .btn:not(.btn-link) {
        min-height: 2.75rem;
    }
}

/* ---- Dashboard -------------------------------------------------------------- */

.dash-hero {
    background: linear-gradient(135deg, #1f2937 0%, #0f766e 100%);
    color: #f8fafc;
    border-radius: .75rem;
    padding: 1.75rem 1.9rem;
}

.dash-hero p {
    color: #cbd5e1;
    max-width: 46rem;
}

/* Whole tile is the link, not just the number, so the tap target on a phone is the full card. */
.dash-tile {
    display: flex;
    flex-direction: column;
    gap: .15rem;
    height: 100%;
    padding: 1rem 1.1rem;
    background: #fff;
    border: 1px solid #e5e7eb;
    border-radius: .6rem;
    text-decoration: none;
    color: inherit;
    transition: border-color .12s ease, box-shadow .12s ease;
}

.dash-tile:hover {
    border-color: #cbd5e1;
    box-shadow: 0 2px 10px rgba(0, 0, 0, .06);
    color: inherit;
}

.dash-tile-label {
    font-size: .78rem;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: #6b7280;
}

.dash-tile-value {
    font-size: 1.9rem;
    font-weight: 600;
    line-height: 1.1;
    color: #111827;
}

.dash-tile-note {
    font-size: .8rem;
    color: #6b7280;
}

/* Uncategorized and broken connections are the two numbers that cost the tenant something if
   ignored, so they are the only ones allowed to use colour. */
.dash-tile-warn {
    border-color: #fcd34d;
    background: #fffbeb;
}

.dash-tile-warn .dash-tile-value {
    color: #92400e;
}

.dash-tile-danger {
    border-color: #fca5a5;
    background: #fef2f2;
}

.dash-tile-danger .dash-tile-value {
    color: #991b1b;
}

.dash-step {
    display: flex;
    gap: .9rem;
    padding-bottom: 1.15rem;
    margin-bottom: 1.15rem;
    border-bottom: 1px solid #f1f5f9;
}

.dash-step-last {
    padding-bottom: 0;
    margin-bottom: 0;
    border-bottom: 0;
}

.dash-step-num {
    flex-shrink: 0;
    width: 1.75rem;
    height: 1.75rem;
    border-radius: 50%;
    background: #0f766e;
    color: #fff;
    font-size: .85rem;
    font-weight: 600;
    display: flex;
    align-items: center;
    justify-content: center;
}

.dash-facts {
    list-style: none;
    padding: 0;
    margin: 0;
}

.dash-facts li {
    position: relative;
    padding-left: 1.35rem;
    margin-bottom: .6rem;
}

.dash-facts li:last-child {
    margin-bottom: 0;
}

/* A CSS tick rather than an icon font or another inline SVG per bullet. */
.dash-facts li::before {
    content: "";
    position: absolute;
    left: .1rem;
    top: .34rem;
    width: .42rem;
    height: .72rem;
    border: solid #0f766e;
    border-width: 0 2px 2px 0;
    transform: rotate(45deg);
}

@media (max-width: 820px) {
    .dash-hero {
        padding: 1.25rem 1.25rem;
    }

    .dash-tile-value {
        font-size: 1.6rem;
    }
}

/* ---- Clients list ----------------------------------------------------------- */

.invite-summary {
    display: flex;
    align-items: center;
    gap: .5rem;
    padding: .9rem 1.1rem;
    cursor: pointer;
    /* Both forms needed: WebKit uses the pseudo-element, everyone else honours list-style. */
    list-style: none;
}

.invite-summary::-webkit-details-marker { display: none; }
.invite-summary::marker { content: ""; }

.invite-summary:hover {
    background: #f9fafb;
}

/* The disclosure arrow, pushed to the right edge by margin-left:auto. A CSS chevron rather than
   another inline SVG, since it has to animate between two states. */
.invite-summary::after {
    content: "";
    margin-left: auto;
    width: .5rem;
    height: .5rem;
    border-right: 2px solid #6b7280;
    border-bottom: 2px solid #6b7280;
    transform: rotate(-45deg);
    transition: transform .15s ease;
}

.invite-panel[open] > .invite-summary::after {
    transform: rotate(45deg);
}

/* Hint text is redundant once the panel is open and the real labels are visible. */
.invite-panel[open] > .invite-summary .invite-summary-hint {
    display: none;
}

/* Each data cell is its own link so the whole row navigates. Inheriting colour and dropping the
   underline keeps it reading as a table rather than a wall of links. */
.client-cell {
    display: block;
    color: inherit;
    text-decoration: none;
}

.client-cell:hover,
.client-table tbody tr:hover .client-cell {
    color: inherit;
    text-decoration: none;
}

.client-table tbody tr {
    cursor: pointer;
}

/* ---- Client detail, inline field editing ------------------------------------ */

.field-row {
    display: flex;
    align-items: baseline;
    gap: .75rem;
    padding: .6rem 0;
    border-bottom: 1px solid #f1f5f9;
}

.field-label {
    flex: 0 0 9rem;
    font-size: .82rem;
    color: #6b7280;
}

.field-value {
    flex: 1 1 auto;
    min-width: 0;
    overflow-wrap: anywhere;
}

.field-value-empty {
    color: #9ca3af;
    font-style: italic;
}

/* Kept visible rather than revealed on hover: hover does not exist on a phone, and an edit control
   nobody can find is the same as no edit control. */
.field-edit {
    flex: 0 0 auto;
    color: #9ca3af;
    text-decoration: none;
    line-height: 1;
}

.field-edit:hover {
    color: #0f766e;
}

@media (max-width: 820px) {
    .field-row {
        flex-wrap: wrap;
    }

    .field-label {
        flex: 0 0 100%;
    }
}

.field-edit-icon {
    width: .95rem;
    height: .95rem;
    display: block;
}

/* ---- Connect page footer ---------------------------------------------------- */

/* Operator attribution on the customer-facing connect page. */
.connect-footer {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: .5rem;
    margin-top: 2rem;
    padding-top: 1rem;
    border-top: 1px solid #f1f5f9;
    font-size: .78rem;
    color: #6b7280;
}

.connect-footer-logo {
    flex-shrink: 0;
    opacity: .8;
}

/* Shown when the firm has uploaded no logo of their own. Stacked and centred so the mark reads as
   the page's operator rather than as a stray icon in a sentence. */
.connect-footer--partner {
    flex-direction: column;
    gap: .6rem;
    text-align: center;
    font-size: .82rem;
}

.connect-footer-mark {
    flex-shrink: 0;
    opacity: .9;
}

/* ---- Row actions on the clients list ---------------------------------------- */

.row-actions {
    display: flex;
    justify-content: flex-end;
    gap: 1rem;
}

/* Icon above label. A word under an icon removes the guesswork about what the icon means, which
   matters most for the destructive one. */
.row-action {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: .2rem;
    padding: .35rem .5rem;
    border: 0;
    border-radius: .375rem;
    background: none;
    color: #6b7280;
    font-size: .72rem;
    line-height: 1.1;
    white-space: nowrap;
}

.row-action:hover {
    background: #f3f4f6;
    color: #111827;
}

.row-action-danger {
    color: #b91c1c;
}

.row-action-danger:hover {
    background: #fef2f2;
    color: #991b1b;
}

.row-action-icon {
    width: 1.25rem;
    height: 1.25rem;
}

/* The "you can close this now" callout on the connect confirmation. Tinted rather than plain text
   because it is the one instruction on the page, and a customer who has just granted bank access
   should not have to hunt for the part that says they are finished. */
.done-note {
    background: #f0fdf4;
    border: 1px solid #bbf7d0;
    border-radius: .5rem;
    padding: .9rem 1.1rem;
    font-size: .92rem;
    color: #14532d;
}

/* ---- Categories ------------------------------------------------------------- */

.cat-group {
    padding-bottom: .5rem;
    margin-bottom: .5rem;
    border-bottom: 1px solid #f1f5f9;
}

.cat-group:last-child {
    border-bottom: 0;
    margin-bottom: 0;
}

.cat-row {
    display: flex;
    align-items: center;
    padding: .3rem 0;
}

.cat-row-parent {
    padding-top: .1rem;
}

/* Indent and a rule, so the hierarchy is readable without an icon per row. */
.cat-row-child {
    padding-left: 1.1rem;
    border-left: 2px solid #e5e7eb;
    margin-left: .35rem;
    font-size: .93rem;
}

/* ---- One-time code entry ---------------------------------------------------- */
/* One input styled to look like six boxes, not six inputs. Six real fields would need JavaScript to
   move focus between them, would break paste and the browser/phone autofill that arrives via
   autocomplete="one-time-code" as a single 6-character string, and would leave anyone without JS
   unable to type past the first box. This is one field the whole way down: paste works, backspace
   works, autofill works, and the boxes are a background image.

   TWO THINGS HERE EXIST BECAUSE THEY WERE GOT WRONG FIRST.

   1. The field is wider than the boxes and must never be allowed to scroll. Six characters with
      letter-spacing occupy 6 x step -- a whole gap wider than six boxes and five gaps -- and centring
      the first digit costs another half box. Size the field to the visible track and the browser
      scrolls the text the moment the caret passes the sixth digit: every digit slides left and the
      first is clipped. Sizing it to exactly the text looked correct in one engine and still scrolled
      about 18px in another, because the caret allowance past the last glyph is not something the spec
      pins down. So the field now carries a whole extra --otp-step of trailing slack. The wrapper clips
      it, so it is free, and no engine has a reason to scroll.

      Note that the ALIGNMENT is font-independent even though 1ch is not: letter-spacing is
      step - 1ch, so every character advances exactly one step whatever the font resolves to, and the
      centring inset is half of whatever is left. Only the total width was ever font-sensitive.

   2. There is no focus decoration, by request. Three were tried and all looked wrong: an outline on
      the field traced its dead space and hung off the card, an outline on the wrapper hugged the
      track but read as a box drawn around six boxes, and bordering the boxes themselves was too
      loud. The boxes are identical at rest and focused, and the caret is the only indicator -- which
      is the native convention for a text field, but it IS the whole indicator now, so do not also
      remove caret-color or suppress it while the field is full. */
.otp-field {
    /* Whole pixels at a 16px root, and the SVG below is drawn at exactly these proportions, so the
       desktop case renders 1:1. */
    --otp-box: 2.5rem;
    --otp-gap: .5rem;
    --otp-step: calc(var(--otp-box) + var(--otp-gap));
    --otp-track: calc(6 * var(--otp-box) + 5 * var(--otp-gap));
    --otp-inset: calc((var(--otp-box) - 1ch) / 2);
    --otp-edge: calc(var(--otp-inset) + var(--otp-gap));

    box-sizing: border-box;
    display: block;
    width: var(--otp-track);
    height: var(--otp-box);
    margin: 0 auto;
    /* clip, NOT hidden -- and this is the whole bug. overflow:hidden still makes an element a scroll
       CONTAINER; it only hides the scrollbars. The input inside starts at -(--otp-edge) and the caret
       after the sixth digit lands past this element's right edge, so the browser scrolls the wrapper
       to reveal it and the entire row jumps left by --otp-edge. Chromium here declined to; the
       reporter's browser did it every time, which is exactly the kind of difference that makes "I
       could not reproduce it" worthless as evidence. overflow:clip is not a scroll container at all
       and cannot be scrolled by anything, so the question stops being which engine feels like it.
       hidden stays first as the fallback for Safari below 16. */
    overflow: hidden;
    overflow: clip;
    border-radius: .5rem;   /* matches the boxes' own radius, which sit flush with these edges */
}

.otp-input {
    box-sizing: border-box;   /* explicit: Bootstrap sets border-box globally and the maths assumes it */
    display: block;
    border: 0;
    background-color: transparent;

    /* All six boxes in one image. Filled rather than stroked at rest: a fill scales cleanly to any
       --otp-box, where a stroke would thicken on one axis at the mobile size. */
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 280 40' preserveAspectRatio='none'%3E%3Cg fill='%23eef1f6'%3E%3Crect x='0' width='40' height='40' rx='8'/%3E%3Crect x='48' width='40' height='40' rx='8'/%3E%3Crect x='96' width='40' height='40' rx='8'/%3E%3Crect x='144' width='40' height='40' rx='8'/%3E%3Crect x='192' width='40' height='40' rx='8'/%3E%3Crect x='240' width='40' height='40' rx='8'/%3E%3C/g%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-size: var(--otp-track) 100%;
    background-position: var(--otp-edge) center;

    /* Content box is the text plus a full step of slack -- see (1) above. The negative margin slides
       the leading dead space out under the wrapper's clip. */
    padding: 0 0 0 calc(var(--otp-inset) + var(--otp-edge));
    width: calc(var(--otp-inset) + var(--otp-edge) + 7 * var(--otp-step));
    height: var(--otp-box);
    margin-left: calc(-1 * var(--otp-edge));

    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    /* Stays above the 16px floor, below which iOS Safari zooms the page on focus and leaves it
       scrolled sideways. */
    font-size: 1.35rem;
    font-weight: 600;
    line-height: var(--otp-box);
    color: #111827;
    caret-color: #0f766e;

    letter-spacing: calc(var(--otp-step) - 1ch);
}

/* Suppresses the browser's own ring, which would trace the field's real width -- dead space, trailing
   slack and all -- rather than the six boxes. See (2): the caret is what remains. */
.otp-input:focus {
    outline: none;
    box-shadow: none;
}

@media (max-width: 420px) {
    /* Only the track has to fit: 6 x 36px + 5 x 6px = 246px, inside the 263px a 375px phone leaves
       once the shell's 1.5rem and the card's 2rem are taken off either side. Whole pixels again. */
    .otp-field {
        --otp-box: 2.25rem;
        --otp-gap: .375rem;
    }

    .otp-input {
        font-size: 1.15rem;
    }
}

/* ---- Reports ----------------------------------------------------------------- */
/* Heading rows carry the subtotal, so they need to read as a band rather than as another line
   item — an accountant scanning for "what did Operations cost" should land on it without counting
   rows. Tinted rather than bold-only: the child rows are already indented, and two weights of the
   same colour is a weaker signal than a change of background. */
.report-heading td {
    background: #f3f4f6;
    border-top: 1px solid #e5e7eb;
}

/* ---- Transaction ledger ------------------------------------------------------ */
/* One size for the whole table, set here rather than with .small on individual cells. Date and
   Account had it and Description and Amount did not, so the two columns a reader actually scans
   rendered larger than the two beside them. Per-cell sizing is what allowed that to drift, and it
   would drift again the next time a column is added. */
.txn-table {
    font-size: .875rem;
}

/* The category picker sits in its own column and would otherwise set the row height. */
.txn-table .form-select-sm {
    font-size: .875rem;
}
