﻿/*
    The only stylesheet this application has, and it names NO COLOUR.

    Every value below is a MudBlazor palette variable, which MudThemeProvider emits from
    Iris.Web/IrisTheme.cs. That is what keeps "correcting the palette is one edit" true — and it
    matters here more than usual, because the palette was eyeballed from screenshots of another
    application and is expected to be replaced wholesale the day a real stylesheet turns up. A hex
    literal in this file would be invisible from the theme and would be the first thing to go stale.

    Iris.Repo.Tests/ThemeIsOnePlaceTests enforces it.
*/

/* ── The shell ─────────────────────────────────────────────────────────── */

.iris-appname {
    font-weight: 600;
}

/* The wordmark is the route to Home when signed in. It inherits the bar's colour rather than taking
   a link colour — it should read as the application's name that happens to be clickable, not as a
   link that happens to be the name. The underline on hover is the whole affordance. */
.iris-appname-link {
    color: inherit;
}

.iris-appname-link:hover .iris-appname {
    text-decoration: underline;
}

/* The thin vertical divider after the application name. */
.iris-bar-divider {
    display: inline-block;
    width: 1px;
    height: 24px;
    margin: 0 16px;
    background-color: var(--mud-palette-appbar-text);
    opacity: .3;
}

/* `Logged in`, in green, and the only green in the chrome. */
.iris-logged-in {
    color: var(--mud-palette-success);
    font-size: .8125rem;
    font-weight: 500;
}

/* The user chip sits on a slightly lighter panel within the bar. */
.iris-user-chip-inner {
    display: flex;
    align-items: center;
    padding: 4px 12px;
    border-radius: 2px;
    background-color: var(--mud-palette-dark);
    color: var(--mud-palette-appbar-text);
    font-size: .8125rem;
    cursor: pointer;
}

/* `Logged out`, in red. The counterpart to `.iris-logged-in`: two states, always one of them
   showing, so "am I signed in?" is answered by reading rather than by noticing an absence. */
.iris-logged-out {
    color: var(--mud-palette-error);
    font-size: .8125rem;
    font-weight: 500;
}

/* THE FOOTER IS BOTTOM-ALIGNED, and this is the second attempt.

   The first made `body` the full-height flex column and gave `.iris-shell` `flex: 1 0 auto`. Every
   rule in it was right and it did nothing, because Blazor mounts the application inside
   `<div id="app">`: body's only flex child is that div, and the layout and the footer are its
   GRANDCHILDREN. `flex-grow` distributes space among a flex container's own children and stops
   there. So the shell grew nothing, the footer followed the content, and on the sign-in screen it
   floated mid-window — exactly the symptom the rules were added to fix, which is why the fix read as
   applied.
 
   `#app` is the flex container now. Grow the shell, hold the footer at its natural height, and it
   sits at the bottom of the viewport on a short page and below the content on a tall one. */
html, body {
    min-height: 100vh;
}

body {
    margin: 0;
}

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

.iris-shell {
    flex: 1 0 auto;
}

.iris-footer {
    flex-shrink: 0;
}

/* The other end of the frame the app bar starts, so it takes the same two variables and cannot
   drift away from it. */
.iris-footer {
    background-color: var(--mud-palette-appbar-background);
    color: var(--mud-palette-text-disabled);
}

.iris-footer a {
    color: var(--mud-palette-text-disabled);
}

/* The sign-in and reset cards: centred, narrow, and matching the grid's chrome — a 1 px border,
   no shadow, and a light grey header strip carrying the title. */
/* LESS TOP PADDING WHILE THE ENROLMENT PANEL IS OPEN, because the panel is taller than the form the
   card was sized for. Measured rather than guessed: at 800 x 600 — the stated floor — the typed key
   landed at y=681 with 3rem of padding and a stacked layout, which is below the fold on the one screen
   where somebody most needs to read it. The panel is wider too (see `.iris-card.iris-wide`), which lets
   the QR and the key sit side by side instead of one under the other. */
.iris-login {
    display: flex;
    justify-content: center;
    padding-top: 3rem;
}

.iris-login:has(.iris-wide) {
    padding-top: 1rem;
}

.iris-card {
    width: 100%;
    max-width: 26rem;
    border: 1px solid var(--mud-palette-table-lines);
    border-radius: 2px;
}

/* The sign-in card holds two fields and a button; the enrolment panel holds a QR, a typed key, an
   instruction and a six-digit field, which is more than that. 26rem forces the QR and the key to
   stack; 34rem lets them sit side by side and still fits inside the 800px floor with margin. */
.iris-card.iris-wide {
    max-width: 34rem;
}

.iris-card-head {
    background-color: var(--mud-palette-background-gray);
    border-bottom: 1px solid var(--mud-palette-table-lines);
    padding: .75rem 1.5rem;
}

/* Authenticator enrolment: the QR beside the typed key, wrapping on a narrow screen.

   THE TYPED KEY IS BESIDE THE CODE AND NOT BEHIND A LINK, which is not a layout preference: Google
   rejected a hand-typed key outright on 2026-08-12, the encoded slash in the issuer is still
   [unverified] against a real app, and this is the only path for anyone whose phone or camera policy
   will not scan a code. `flex-wrap` is what makes it survive the sign-in card, which is ~420px wide —
   inside it the two always stack, and a QR above its own key reads as one instruction rather than as
   two columns crammed into a narrow box. 800 x 600 is the stated floor. */
.iris-enrol {
    display: flex;
    gap: 1.25rem;
    flex-wrap: wrap;
    align-items: flex-start;
    margin: .5rem 0 1rem;
}

.iris-qr {
    border: 1px solid var(--mud-palette-table-lines);
    background-color: var(--mud-palette-surface);
    max-width: 100%;
    height: auto;
}

/* `min-width: 0` so the key column may shrink below its content: without it a 32-character
   monospace string sets a flex item's minimum and the CARD scrolls horizontally, which is the failure
   the 800 x 600 floor exists to catch. `word-break` below then has something to break inside. */
.iris-enrol-key {
    flex: 1 1 14rem;
    min-width: 0;
}

/* THE KEY BREAKS BETWEEN GROUPS, NEVER INSIDE ONE. *Corrected 2026-08-14, from the walkthrough.*

   It was `word-break: break-all`, which is right for an unbroken 32-character string and wrong for
   this one: the key is rendered in four-character groups separated by spaces, and `break-all` ignores
   the spaces and breaks wherever the line ends. The owner read `QLZ / A PADP ZM4P` — a group split
   across two lines with three characters on one and one on the next. This string exists to be
   transcribed by hand into a phone, so a break in the middle of a group is a mistranscription waiting
   to happen, and it is the one string in the application where that costs somebody their account.

   `normal` breaks at the spaces, which are exactly the safe places. The groups are four characters
   long, so nothing can overflow the 14rem column that `break-all` was protecting. */
.iris-key {
    display: block;
    margin: .25rem 0 .5rem;
    font-size: 1rem;
    letter-spacing: .05em;
    word-break: normal;
    overflow-wrap: normal;
}

/* ── The header menu, and when it becomes the burger ────────────────────── */

/* A BREAKPOINT, AND IT REPLACED A MEASUREMENT THAT TOOK THE PAGE DOWN.
   ==================================================================

   Until 2026-08-13 this was decided by `wwwroot/js/shell.js`, which measured the header and set
   `data-nav="wide"` or `"collapsed"` on the shell. The argument for measuring was sound and is
   still sound: the header's natural width depends on how many destinations the caller's permissions
   offer and on how long their tenant's canonical name is, so the width at which four buttons stop
   fitting is genuinely different for a SuperAdmin at a short name and a TenantAdmin at a fifty-
   character one. Any single number is wrong for somebody.

   It was reversed anyway, because THE COSTS ARE NOT SYMMETRIC. A breakpoint that collapses slightly
   early costs one user a burger menu they did not quite need — cosmetic, and reversible by dragging.
   The measurement, in practice, made the client page unresponsive after a few window resizes, and a
   refresh then landed on sign-in. Wrong-for-somebody beats down-for-anybody.

   WHAT ACTUALLY WENT WRONG, since "flapping" was anticipated and guarded and the guards cancelled
   each other out. `MainLayout.OnAfterRenderAsync` re-registered the observer after EVERY render, so
   that signing in — which changes the header's width — would be re-measured. Re-registering called
   `stopWatching()`, which reset the remembered answer to null so the next measurement could not be
   compared against a threshold chosen for a previous header. Both decisions were individually
   right. Together: report a change -> Blazor re-renders -> re-register -> forget the previous answer
   -> the hysteresis that existed to stop oscillation is gone -> the geometry now differs because the
   menu moved in or out of flow -> report the opposite -> re-render. Every render fed the next one.
   The two mechanisms designed to prevent flapping were the mechanism that caused it.

   HOW THE NUMBER WAS CHOSEN — the widest realistic header, added up rather than guessed:

       app name "Servicer3D/IRIS" ~155   divider 17   Home+Clients+Reports+Settings ~330
       "Logged in" + gutter ~90          tenant name (capped at 14rem below) 240
       user chip (icon + address at 14rem + caret) ~290      app-bar gutters ~24
       ------------------------------------------------------------------------------
       ~1146px, for the widest case: four destinations, and both ellipsis fields at their cap.

   1280px is the threshold, giving ~130px of headroom and coinciding with MudBlazor's `lg`. A
   1366-wide laptop keeps its menu; a 1280x800 one collapses very slightly early, which is the
   compromise named above and accepted deliberately.

   THE TWO CAPS BELOW ARE PART OF THIS ARITHMETIC. Raising `max-width` on `.iris-tenant` or
   `.iris-chip-email` raises the widest realistic header and makes 1280px too small. Change them and
   change this together.

   ── AND THE OTHER TWO NUMBERS: THE MINIMUM IS 800 × 600 ─────────────────────────────────────────

   All three numbers are stated here together because they are facts about one layout, and reading any
   one alone invites changing it against the wrong constraint. 1280px is where the header stops
   fitting. 800px is the narrowest the page lays out for. 600px is the shortest.

   THIS REVERSES A 320px FLOOR SET ONE PASS EARLIER, AND THE REVERSAL IS RECORDED RATHER THAN
   OVERWRITTEN. That number was chosen carefully and sourced: phone-simulator.com's "Most Popular
   Mobile Screen Resolutions in 2026" calls 320px the "minimum-width layout stress test", still
   notable on older and low-end devices, and lists 320/360/375/390–430 as the widths to test. Nothing
   about that reading was wrong.

   IT WAS OVERRULED BY A PRODUCT FACT THE REFERENCE COULD NOT KNOW: **this is not a mobile-first
   application.** It is staff and back-office loan servicing — sixty clients' worth of administrators
   and LSS operators, at desks. A device-population survey answers "what widths do phones have", and
   the question that actually decides this is "who uses this software, on what". The owner's answer is
   800 × 600, and it is his to give.

   THE 320px CITATION IS KEPT ABOVE ON PURPOSE. Deleting it would leave a later reader unable to tell
   an informed decision that was overruled from an uninformed one that was corrected. It was the
   former.

   WHAT 800px COSTS, AND IT IS NOT NOTHING:

     * BETWEEN 800px AND 1280px THE DRAWER IS THE ONLY NAVIGATION. That band is now a supported range
       rather than a curiosity, which promotes the latent defect found while consolidating
       `NavSections` into the one that matters most: the bar and the drawer used to hold separate
       hand-written copies of the destination list, and a destination missing from the drawer copy
       would be missing ENTIRELY for every user in this band. Both now read `NavSections.For`.

     * 600px OF HEIGHT IS A CONSTRAINT TOO, and it interacts with the bottom-aligned footer. A footer
       held at the bottom of the viewport plus a tall form is exactly how content becomes unreachable:
       if the shell were `height: 100vh` with internal scrolling, a form taller than the remaining
       space would be cut off with no way to scroll to it. It is `min-height` and the footer is
       `flex-shrink: 0`, so the document grows past the viewport and the window scrolls normally. The
       footer is bottom-of-viewport on a short page and below the content on a tall one — which is
       what "bottom-aligned" was always supposed to mean, and is why nothing traps vertically.

   BELOW THE MINIMUM THE PAGE SCROLLS RATHER THAN REFLOWING, deliberately, and that is unchanged from
   the 320px version: a scrollbar keeps controls at their real size and labels on one line, where a
   reflow produces something technically visible and practically unusable that looks like a design.

   Still not a `<meta viewport>` change. `index.html` declares `width=device-width`, so a phone gets
   its own width and its own scale; this only stops the LAYOUT being computed narrower than 800. */

html {
    min-width: 800px;
}

body {
    min-width: 800px;
    overflow-x: auto;
}

/* The menu itself. `min-width: 0` is what lets it be squeezed rather than overflowing the bar — a
   flex item's default minimum is its content width. */
.iris-nav {
    display: flex;
    flex: 0 1 auto;
    align-items: center;
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
}

/* The flexible gap between the menu and the right-hand group. It was also what the measurement read
   as free space; that job is gone and the layout one remains, which is why it is still here rather
   than back to a MudSpacer. */
.iris-bar-slack {
    flex: 1 1 auto;
    min-width: 0;
}

/* WIDE — the default, so a stylesheet that fails to load leaves the menu visible rather than
   leaving no navigation at all. The burger is in the DOM at every width and hidden here. */
.iris-burger {
    display: none;
}

/* NARROW. `display: none` on the menu is safe now in a way it was not before: nothing measures it,
   so it no longer has to keep reporting a width while hidden. That whole position/visibility
   contortion existed only to serve the measurement. */
@media (max-width: 1279.98px) {
    .iris-nav {
        display: none;
    }

    .iris-burger {
        display: inline-flex;
    }
}

/* ── The role checkboxes ────────────────────────────────────────────────── */

/* THE ROLE CHECKBOXES ARE FIVE CONTROLS IN A CELL, and at the default weight they read as the most
   important thing on the row. They are not — the address and the standing are. `Dense` and
   `Size.Small` shrink the box; this shrinks the label to match, because a small box beside a
   full-weight 1rem label looks like a mistake rather than a lighter control.

   The ripple is dropped for the same reason: five expanding circles is a lot of motion for setting a
   role, and the checkbox's own state change is the feedback that matters. */
.iris-role-check .mud-checkbox-label,
.iris-role-check .mud-typography {
    font-size: .8125rem;
    line-height: 1.4;
}

.iris-role-check .mud-icon-button {
    padding: 4px;
}

.iris-role-check .mud-ripple-element {
    display: none;
}

/* ITEMS THAT MUST SHORTEN TRUNCATE RATHER THAN WRAP. A bar that grows a second line pushes the
   whole page down and moves every control on it, which is worse than an ellipsis on a long tenant
   name — and the name is also in the page title on every screen that acts on a client. */
.iris-tenant,
.iris-chip-email {
    display: inline-block;
    max-width: 14rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    vertical-align: middle;
}

/* ── The way back off a page ────────────────────────────────────────────── */

/* Above the title, and pulled up into the container's top margin so it reads as chrome attached to
   the page rather than as the page's first line of content. */
.iris-back {
    margin: -0.5rem 0 0.25rem -0.5rem;
}

/* ── The grid ──────────────────────────────────────────────────────────── */

/* Bold dark header labels. MudDataGrid's default header weight is the body weight, which leaves a
   dense grid with no visible boundary between the header row and the first row of data. */
.iris-grid .mud-table-head th {
    font-weight: 700;
    color: var(--mud-palette-text-primary);
    background-color: var(--mud-palette-surface);
}

/* Grid DATA text is a dark brown/maroon rather than black. THE LEAST CERTAIN RULE IN THIS FILE —
   the reference marks the colour "verify — may be a link colour rather than body text", and a
   screenshot cannot tell the two apart. Header cells above are deliberately excluded. */
.iris-grid .mud-table-body td {
    color: var(--mud-palette-tertiary);
}

/* A WIDE TABLE SCROLLS INSIDE THE GRID, NOT BY WIDENING THE PAGE.
   *Added 2026-08-14, and it makes true a claim two harnesses had already been asserting.*

   The People grid has nine columns. At the 800px floor their natural widths total about 1338px, and
   without this rule the table pushed the whole document wider: measured at **67px of body overflow**
   against the deployed build, with the grid's own scroller reporting **0** — so `Deactivate` in the
   page title was clipped off the right edge, on a page whose body was scrolling horizontally.

   Two things about how that was found are worth keeping. It is **pre-existing**, not new: the same
   clipping is visible in the screenshot taken of the build *before* this pass, so the nine columns
   never fitted. And it survived because a browser check asserted `bodyOverflow <= 0` at 1400 wide and
   only screenshotted at 800 — the assertion and the artefact were at different widths, so neither
   covered the case. Adding the `Rename` label made the Name column about 86px wider and turned a
   latent defect into a visible one, which is the useful kind of change.

   `min-width: 0` on the flex/grid item is what actually permits the shrink; `overflow-x: auto` alone
   does nothing if the container is sized by its content. */
.iris-grid .mud-table-container {
    overflow-x: auto;
    max-width: 100%;
    min-width: 0;
}

/* 1 px light borders throughout, and no drop shadow: elevation is expressed as a border. */
.iris-grid .mud-table-container table {
    border-collapse: collapse;
}

.iris-grid .mud-table-cell {
    border: 1px solid var(--mud-palette-table-lines);
}

.iris-grid {
    border: 1px solid var(--mud-palette-table-lines);
    border-radius: 2px;
}

/* The command toolbar sits on a light grey strip. */
.iris-grid .mud-table-toolbar {
    background-color: var(--mud-palette-background-gray);
    border-bottom: 1px solid var(--mud-palette-table-lines);
    min-height: 44px;
}

/* The light grey band on a selected row. */
.iris-grid .iris-row-selected > td {
    background-color: var(--mud-palette-table-hover);
}

/* A true boolean: a small blue dot. False renders nothing at all — see Components/Dot.razor. */
.iris-dot {
    display: inline-block;
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background-color: var(--mud-palette-info);
}

/* Numeric and short-code columns are centred; text columns are left-aligned, which is the default. */
.iris-grid .iris-centre,
.iris-grid th.iris-centre {
    text-align: center;
}

/* The one account standing that asks an administrator to act names its failure under the chip.
   Deliberately allowed to wrap: this sentence is the remedy, and truncating it with an ellipsis
   would hide the difference between an address that does not exist and one the allowlist refused —
   which is the difference that decides what to do next. */
.iris-attention {
    color: var(--mud-palette-error-text);
    max-width: 20rem;
    line-height: 1.3;
}

/* Long values truncate with an ellipsis rather than wrapping and doubling the row height. */
.iris-grid .iris-truncate {
    max-width: 22rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The active pager page in a boxed highlight. */
.iris-grid .mud-table-pagination .mud-button-root.mud-button-filled {
    border: 1px solid var(--mud-palette-primary);
}

/* ── The enabled toggle, and its not-permitted form ─────────────────────── */

/* When ADM-05 is not offered there is NO switch — a disabled switch in the off position and a live
   switch in the off position look nearly identical, which would be worse than the icon ambiguity the
   switch replaced. The state becomes plain text, with the reason one hover away. */
.iris-enabled-fixed {
    display: flex;
    flex-direction: column;
    align-items: center;
    line-height: 1.2;
}

/* Deliberately quiet and deliberately present. It is not an action, so it must not look like one —
   but "why not?" is the question an administrator is holding, and a state with no explanation is the
   defect this whole pass keeps finding. */
.iris-enabled-why {
    color: var(--mud-palette-text-secondary);
    text-decoration: underline dotted;
    cursor: help;
}

/* Quiet, because "not enrolled" is the ordinary state under ACC-11 as amended — MFA is offered and
   required of nobody, so most accounts read this and it must not look like a warning. It is a word
   rather than an icon because an administrator deciding whether Reset MFA will do anything should not
   have to hover a picture to find out. */
.iris-not-enrolled {
    color: var(--mud-palette-text-secondary);
}

/* ACC-12a's recorded state, and quiet for the same reason: "Not offered" is the ordinary state of
   every account that predates the panel, and all three states are facts rather than problems. Styled
   at all only so the words sit at the same weight as the column beside them, which they must NOT be
   confused with — one says whether they hold a factor now, the other whether they were ever asked. */
.iris-offer-state {
    color: var(--mud-palette-text-secondary);
}



/* ── Rename ───────────────────────────────────────────────────────────────────────────────────────
   ONE TREATMENT, BOTH PLACES, PERMANENTLY VISIBLE. A `Rename` button sits beside the name in the
   grid and beside the name in the title, and the two are the same control rendered by the same
   component, so finding either one teaches the other.

   *Rewritten 2026-08-14, and the block it replaced is worth keeping in view.* It read: "The pencil is
   HIDDEN UNTIL HOVER OR FOCUS in the grid ... a column of five pencils beside five names is visual
   noise on a screen whose complaint was already too many undifferentiated icons". It then paired
   `opacity: 0` with `:focus-within` so the control stayed keyboard-reachable — careful work, in
   service of a conclusion that made the feature invisible. The owner reported twice that he could
   not see how to rename anything. A control you have to already know about in order to reveal is
   not an affordance, however correct its focus behaviour. See `decisions.md`, "The rename was built,
   deployed, verified — and could not be found".

   Nothing here hides anything now, so there is no `:focus-within` rule to get right. */
.iris-rename,
.iris-rename-editing {
    display: inline-flex;
    align-items: center;
    gap: .25rem;
    min-width: 0;
}

.iris-rename-value {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* THE EDITOR HAS TO FIT THE CELL IT OPENS IN, and it did not.
   *Added 2026-08-14, projectmem issue 0029, measured against the deployed build.* (The issue number is
   written without a leading hash deliberately: `ThemeIsOnePlaceTests` reads a hash followed by four
   hex digits as a colour literal, and a four-digit issue number is exactly that shape. The guard is
   right to be that strict — a colour outside the palette is what it exists to catch — so the prose
   gives way, not the guard.)

   The editing state is a text field, Save and Cancel on one line inside a `iris-truncate` cell with
   `overflow: hidden`. The two buttons have intrinsic widths and the field does not, so the field lost:
   **0px wide at the 800px floor** (in a 177px cell) and **53px at 1400**. The person rename could not
   be typed into at all at the width the owner works at.

   Pre-existing and NOT caused by the `Rename` label — the editing branch renders no Rename button, so
   this state is byte-identical before and after that change. It went unseen for the reason that makes
   it worth writing down: the trigger was a hover-only pencil, so nobody had ever opened the editor.
   Hiding a control hides its bugs too.

   Wrapping rather than widening, because the cell cannot get wider without pushing the table out
   again. `min-width` on the field is what makes it claim space instead of collapsing; the buttons drop
   to a second line and the row simply gets taller, which `overflow: hidden` permits vertically. */
.iris-rename-editing {
    flex-wrap: wrap;
    row-gap: .25rem;
}

.iris-rename-editing .mud-input-control {
    flex: 1 1 7rem;
    min-width: 7rem;
}

/* THE NAME CELL IS THE CONTROL NOW — right-click opens the menu, and `.iris-rename-button` is gone
   with the button it styled. Its comment argued that a label must never be truncated to "Rena…", which
   was true and is no longer the shape: there is no label in the row at all.

   `cursor: context-menu` is the only visible affordance besides the tooltip, so it is not cosmetic.
   The dotted underline is the second: it marks the text as interactive without adding a control, in the
   way an abbreviation is marked. Neither is sufficient on its own and both are cheap. */
.iris-name-cell {
    display: inline-block;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    cursor: context-menu;
    text-decoration: underline dotted var(--mud-palette-text-secondary);
    text-underline-offset: .2em;
}

/* Focusable for `Shift+F10` and the Menu key, so the focus ring has to be visible or the keyboard path
   is invisible as well as undiscoverable. */
.iris-name-cell:focus-visible {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: 2px;
}

/* `position: fixed`, because the cell sits inside `.iris-truncate` (`overflow: hidden`) AND inside the
   grid's horizontal scroller. An absolute menu is clipped by both; a fixed one is laid out against the
   viewport. The z-index is above MudBlazor's popovers (1200s) for the same reason a dialog is. */
.iris-cell-menu {
    position: fixed;
    z-index: 1400;
    min-width: 9rem;
    padding: .25rem 0;
}

.iris-cell-menu-item {
    justify-content: flex-start;
    padding-left: .75rem;
    padding-right: .75rem;
}

/* The click-away. Transparent and full-viewport, so the next click anywhere closes the menu instead of
   doing something else — which is the behaviour a context menu has everywhere else. */
.iris-menu-away {
    position: fixed;
    inset: 0;
    z-index: 1399;
    background: transparent;
}

/* In the h4 title, the field must not inherit the heading's 2.125rem or the row grows by 40px the
   moment the pencil is pressed and everything below it jumps. */
.iris-title-rename {
    display: inline-flex;
    align-items: center;
    font-size: 1rem;
    font-weight: 400;
}

.iris-title-rename .iris-rename-value {
    font-size: 2.125rem;
    font-weight: 500;
    max-width: 40ch;
}

.iris-title-rename .iris-rename-editing {
    min-width: 24rem;
}

/* THE `MFA` BAND above `Declined` and `Second factor`. A header row of its own, so the two columns
   under it keep their own titles — which is the whole reason `HeaderColSpan` is not the mechanism.
   Quieter than the column headers, because a band names a group and the headers name the data. */
.iris-band th {
    border-bottom: none;
    padding-bottom: 0;
}

.iris-band-label {
    text-align: center;
    font-size: .75rem;
    font-weight: 600;
    letter-spacing: .04em;
    color: var(--mud-palette-text-secondary);
    border-bottom: 1px solid var(--mud-palette-table-lines);
}

/* THE OPTIONAL-AND-NOT-ENROLLED SHIELD IS DARK GREY, and the reason is the enable/disable lesson.
   Light grey reads as *disabled* — a control that cannot be pressed — and this is a STATE. The primary
   text colour is the darkest thing the palette offers that is not a semantic colour, which is right: the
   glyph should read as present and unremarkable, not as faulty (red) and not as absent (faded). */
.iris-shield-optional {
    color: var(--mud-palette-text-primary);
}

/* THE `MFA` BAND MUST READ AS A GROUP HEADER, NOT A STRANDED LABEL.
   *Reworked 2026-08-14 from the walkthrough:* "'MFA' looks like a stranded label, not a group header
   grouping the MFA related columns."

   S8 centred the label over the group and measured that the centres agreed — correct, and insufficient in
   exactly the way an `aria-label` is insufficient when a visible label is what was needed. Geometry is not
   containment. What makes a band read as a band is a visible BOUNDARY: side rules that mark where the
   group starts and stops, and a tint that separates the group from the columns either side of it.

   The side rules are on the label cell, so they land on the group's own edges whatever the column widths
   underneath do. */
/* `.iris-grid thead tr.iris-band` rather than `.iris-band-label` alone, and that is not decoration: the
   bare class LOST a specificity contest with MudBlazor's own `th` rule and the band computed to plain white
   while the columns under it took the tint — measured in the browser, and it is the containment defect
   inverted. Matching the selector's shape fixes it without `!important`, which would have hidden the cause.

   (The measured values are quoted in `decisions.md` rather than here, because `ThemeIsOnePlaceTests` reads
   a CSS colour function as a colour literal — including one written inside a comment. It is right to be
   that strict: a colour outside the palette is exactly what it exists to catch. Fifth time a guard has
   tripped on the prose explaining it, and the prose gives way every time.) */
.iris-grid thead tr.iris-band th.iris-band-label {
    background-color: var(--mud-palette-background-gray);
    border-left: 2px solid var(--mud-palette-table-lines);
    border-right: 2px solid var(--mud-palette-table-lines);
    border-top: 2px solid var(--mud-palette-table-lines);
    padding-top: .25rem;
    padding-bottom: .25rem;
}

/* The columns under the band keep the tint, so the group reads as one region down the header rather than
   as a label floating above three unrelated columns. `:nth-child` is positional like the band itself, and
   the column order both depend on is asserted in WalkthroughExpectationTests. */
.iris-grid thead tr:not(.iris-band) th:nth-child(6),
.iris-grid thead tr:not(.iris-band) th:nth-child(7),
.iris-grid thead tr:not(.iris-band) th:nth-child(8) {
    background-color: var(--mud-palette-background-gray);
}

/* A REPORT READS AS SOMETHING YOU OPEN. The list was a bare `MudList` of grey text, and the owner never
   tried the reports because the list "looks like a placeholder" — so the fix is the card's chrome, not its
   contents. A border and the grid's own line colour, so it belongs to the same family as every other
   bounded thing on screen; the title in the link colour, so it reads as a destination. */
.iris-report-card {
    border: 1px solid var(--mud-palette-table-lines);
    border-radius: 2px;
}

.iris-report-title {
    color: var(--mud-palette-primary);
}

/* `min-width: 0` so a long title ellipsises inside the card instead of pushing the Open button off the
   right-hand edge at the 800px floor. */
.iris-report-text {
    min-width: 0;
}
