/* ============================================================================================
   shell.css — the ONE stylesheet the shared application shell is drawn from (feature-018, slice 0).
   Served at /_content/ModelingEvolution.AppShell/shell.css.

   NFR-4 / T-3: there is not one raw colour literal in this file. Every colour is a var(--…) that resolves
   to a member of ShellPalette, emitted once by ShellPalette.ToCssVariables() into a single style attribute
   on .shell-root. That is what makes OQ-3's accent a one-VALUE change instead of a sweep.

   Provenance: everything down to the "new in slice 0" divider was LIFTED from Erp.Web/wwwroot/app.css
   (the ~90 shell rules interleaved with that file's table/KPI/chart/form styles) and from
   Erp.Web/Components/Layout/MainLayout.razor.css. Both are deleted in the same commit — no slice ends with
   two shells alive. Two host-named classes were renamed on the way out, because a class called `erp-` in a
   shared shell is host knowledge (AC-4): .erp-appbar → .shell-appbar, .erp-drawer → .shell-drawer.
   .drawer-top → .shell-drawer-top and .drawer-note → .shell-drawer-footnote for the same reason: the shell
   owns the region, the host owns what goes in it.
   ============================================================================================ */

/* The root contributes the palette and NO BOX. MudAppBar/MudDrawer are position:fixed, so a wrapper that
   established a containing block would silently reposition the whole chrome. Custom properties inherit
   through display:contents, which is the only reason this element exists. */
.shell-root { display: contents; }

/* F-M7's minimum touch target USED TO BE DECLARED HERE as `:root { --shell-target-min: 44px }`. It is
   ShellMetrics.TargetMinPx now, emitted with every other token, because a distance declared in the
   stylesheet is a distance a host cannot configure — and O-1's whole ruling is that layout is
   configuration. Nothing else changes: the same custom property, the same 44 px on both hosts.
   ⚠ The portal's shipped booking chips are ~32 px (portal.css:103,106). Those are BODY content, not shell;
   they must not be lifted into this file in slice 2 just because they are small. */

/* ── App bar ──────────────────────────────────────────────────────────────────────────────── */
.mud-appbar.shell-appbar { background: var(--bg); border-bottom: 1px solid var(--line-2); box-shadow: none; }

.search {
  flex: 1; max-width: 360px; display: flex; gap: 8px; align-items: center;
  background: var(--surface); border: 1px solid var(--line-2);
  /* 9px, NOT var(--shell-radius): the token is the MudTheme's DefaultBorderRadius (10 px, every Mud
     surface), and this is the search box's own shipped corner. Slice 4's no-literal sweep owns it. */
  border-radius: 9px;
  padding: 8px 12px; color: var(--faint); font-size: 12.5px;
}
.who { margin-left: auto; display: flex; gap: 14px; align-items: center; color: var(--faint); font-size: 12px; }
.who .okled { color: var(--good); }
/* The initials chip. The shipped rule (app.css:90) was a #2b6ea3→accent gradient with #05222a letters, and
   NEITHER of those two colours is in the 17-token palette, so neither can survive NFR-4. The staff mockup
   (§Design Annotations, Identity cluster) draws it as "a 28 px initials chip in AccentDim with Text
   letters" — a drawn answer in palette terms, so K-11's tie-break takes it. */
.ava {
  width: 27px; height: 27px; border-radius: 50%;
  background: var(--accent-dim);
  display: grid; place-items: center; font-size: 11px; font-weight: 700; color: var(--text);
}

/* C-17. A real form post, never an @onclick: signing out clears a cookie and a circuit has no HTTP response
   to clear it on. The inline style this replaces was on the <form> at MainLayout.razor:73. */
.shell-signout-form { display: inline; margin-left: 10px; }
.signout { background: none; border: 0; padding: 2px 4px; color: var(--muted); cursor: pointer; line-height: 0; }
.signout:hover { color: var(--text); }

/* ── Brand — C-01..C-04 ───────────────────────────────────────────────────────────────────── */
.brand { display: flex; align-items: center; gap: 10px; padding: 6px 8px 10px; }
/* C-03's default mark. The shipped conic-gradient's mid-stop was #2b6ea3 — again a colour outside the
   palette; var(--info) is the palette's blue and is what the sweep resolves it to. */
.brand .mk { width: 24px; height: 24px; border-radius: 7px;
             background: conic-gradient(from 210deg, var(--accent), var(--info) 55%, var(--accent)); }
/* epic-081 ⑤ — the REAL mark (ShellLogoMark). It overrides the placeholder above rather than replacing
   it: the gradient chip is still C-03's answer for a host that sets no BrandMark, and deleting it would
   take that answer away from a consumer this repo does not contain.

   ⚠ height, NOT width, is the fixed dimension. logo.svg's viewBox is 287.33×193 — a ~1.49:1 LOCKUP, not
   a square glyph — so pinning it to the chip's 24×24 would squash it by a third. The three white sweeps
   run to x=0 and would be the visible casualty. `width:auto` against `height:24px` keeps the drawn
   proportion and lands it at 35.72px wide — paintedRatio 1.488 against the file's natural 1.487 — which
   the 236px drawer takes with room to spare. MEASURED on both palettes, with the placeholder chip as the
   control: docs/evidence/epic-081-portal-hub/brand.mjs.

   background:none and border-radius:0 are not tidying: without them the chip's gradient paints BEHIND a
   logo whose own background is transparent, and its 7px radius clips the sweeps' left end. */
.brand .mk-logo { width: auto; height: 24px; border-radius: 0; background: none;
                  display: block; flex: 0 0 auto; }
.brand b { font-weight: var(--shell-fw-strong); }
/* --shell-mono is a ShellTypography token now, so it is ALWAYS emitted and the inline fallback that used to
   stand here is deleted rather than kept. A fallback beside a token that cannot be missing is a branch no
   input reaches — it would go on looking like the safety net for the case O-1 removed. */
.brand span { display: block; color: var(--faint); font-size: 10px;
              font-family: var(--shell-mono); letter-spacing: .12em; }

/* ── Drawer ───────────────────────────────────────────────────────────────────────────────── */
.mud-drawer.shell-drawer { background: var(--surface); border-right: 1px solid var(--line-2); }
/* a flex column, so the footnote can pin to the bottom */
.shell-drawer .mud-drawer-content { display: flex; flex-direction: column; height: 100%; }

/* C-20 — the region; the host fills it. */
.shell-drawer-top { border-bottom: 1px solid var(--line-2); padding-bottom: 10px; margin-bottom: 6px; }
/* C-21 — pinned to the bottom, lightened. */
.shell-drawer-footnote { margin-top: auto; font-size: 10.5px; color: var(--faint); opacity: .8; padding: 12px 14px; }

/* ── Nav fold — C-11/C-12/C-13 ────────────────────────────────────────────────────────────── */
/* ONE row-height rule — tight ~30 px rows, so an 11-section / 39-item drawer never overflows. */
.mud-nav-menu .mud-nav-link,
.mud-nav-menu .mud-nav-link .mud-nav-link-text { font-size: 13px; }
.shell-drawer .mud-nav-menu .mud-nav-link { min-height: 30px; padding-top: 3px; padding-bottom: 3px; margin: 1px 0; }
.shell-drawer .mud-nav-link { min-height: 32px; }
.shell-drawer .mud-navmenu { padding-top: 2px; }

.grp { font-family: var(--shell-mono); font-size: 10px;
       letter-spacing: .15em; text-transform: uppercase; color: var(--faint); padding: 11px 18px 4px; }

.navlink { display: flex; align-items: center; gap: 9px; padding: 6px 12px 6px 26px; margin: 0 8px;
           border-radius: 7px; color: var(--muted); font-size: 13px; cursor: pointer;
           border: 1px solid transparent; text-decoration: none; }
.navlink:hover { background: var(--surface2); color: var(--text); }
.navlink.active { background: var(--accent-soft); color: var(--text); border-color: var(--accent-dim); }

/* MudNavLink wraps its children in .mud-nav-link-text, so re-establish the flex row and the right-aligned
   badge and neutralise Mud's own padding/typography. */
.mud-nav-link.navlink { padding: 4px 12px 4px 20px; font-size: 13px; font-weight: var(--shell-fw); }
.mud-nav-link.navlink .mud-nav-link-text { display: flex; align-items: center; gap: 9px; flex: 1; min-width: 0; }

/* leading icons fill the left gutter and tint with state */
.navlink .mud-nav-link-icon { min-width: 0; margin-right: 10px; color: var(--faint); }
.navlink .mud-nav-link-icon .mud-icon-root { font-size: 18px; }
.navlink:hover .mud-nav-link-icon { color: var(--muted); }
.navlink.active .mud-nav-link-icon { color: var(--accent); }
/* the active item is anchored with a left accent bar */
.mud-nav-link.navlink.active { box-shadow: inset 2px 0 0 var(--accent); color: var(--text); font-weight: 500; }

/* group headers read as real, collapsible controls; one padding; banded separators */
.navgrp { margin: 0; }
.navgrp .mud-nav-item { margin: 0; }
.navgrp .grp { padding: 8px 12px 3px; font-size: 10.5px; color: var(--muted); font-weight: 600; cursor: pointer; }
.navgrp .grp:hover { color: var(--text); }
.navgrp + .navgrp .grp { border-top: 1px solid var(--hair); margin-top: 3px; }
.navgrp .mud-nav-group-collapse, .navgrp > .mud-collapse-container { padding: 0; }
.navgrp .mud-nav-link-icon-right { color: var(--faint); transition: transform .15s; }
.navgrp .mud-expanded .mud-nav-link-icon-right { color: var(--accent); }

/* The badge pill. ⚠ CHANGED FROM THE SHIPPED RULE, deliberately and once: app.css:154 was
   `background:var(--accent-soft); color:#8fe0eb` — and #8fe0eb is a colour the 17-token palette does not
   have, so it cannot survive NFR-4. The staff mockup (§Design Annotations, Badges) draws the pill as
   "AccentDim with Text digits", and K-11's tie-break gives the mockup the visual answer. Recorded as the
   one intentional pixel delta in slice 1. */
.navlink .badge { margin-left: auto; font-size: 10px; background: var(--accent-dim); color: var(--text);
                  border: 1px solid var(--accent-dim); border-radius: 20px; padding: 0 6px; }

/* ── C-19 — the deployment banner's SHIFT ─────────────────────────────────────────────────── */
/* The host supplies the banner's content and its look; the shell owns the consequence — everything below it
   moves down, in one rule, with no host arithmetic. The 26 px is this file's number and the host's
   .open-mode-banner is sized to match it; the two are coupled and both say so.

   ⚠⚠ THE COUPLING IS DECLARED AND NOT ENFORCED, AND IT IS BROKEN TODAY AT NARROW WIDTHS (epic-080).
   The host's banner has NO height — it is padding plus wrapped text — so "sized to match" holds only while
   that text is ONE LINE. MEASURED on rc140 (/crm/deals): 26.9 px at 1000 px wide, 43.8 px at 768, and
   60.6 px at 390, against a shift that is 26 px at every width. The banner is z-index 1301 over an app bar
   at 1300, so the overhang lands ON the drawer toggle: at 390 px elementFromPoint at the toggle's centre
   returned the banner and page.tap() on the hamburger TIMED OUT. That is the owner's "app breadcrumb — top
   left doesn't work (tested on mobile)". The same overhang covers the top of the drawer when it opens.

   THE TAP is fixed in app.css (pointer-events:none on a role="alert" with no controls in it), so the control
   RESPONDS at every width. THE VISUAL OVERLAP IS NOT FIXED and this comment is not a substitute for fixing
   it — at 390 px the hamburger is still drawn underneath the banner.

   ⚠ AND THE OBVIOUS FIX IS THE WRONG ONE. Turning 26 into a ShellMetrics token would give the number one
   name instead of two, which sounds right and changes nothing: the banner's height comes from its CONTENT,
   so a token declaring 26 px is FALSE at 390 px whatever it is called. ShellMetrics' own doc-comment already
   rules that shape out, for AppBarHeightPx, in exactly these words — "a number that can be WRONG without
   anything failing — worse than absent". A second wrong number with a better name is still a wrong number.

   WHAT ACTUALLY REMOVES IT is to stop hard-coding the offset and let the banner occupy real layout space
   (banner in flow, .mud-appbar position:sticky top:0), so the shift IS the banner's height at every width
   and this block's three numbers disappear. MEASURED as working at 390 px — the toggle moved to y=60.6,
   clear of the banner, and the bar still pins to the top on scroll — but it also moves the drawer, whose
   fixed box would then start at the viewport top on desktop. That is a bigger change than the reported bug
   and it is owner-ruled work, not a side effect to smuggle in under a tap fix (decision-log §6.183). */
.has-open-mode-banner { padding-top: 26px; }
.has-open-mode-banner .mud-appbar { top: 26px; }
.has-open-mode-banner .mud-drawer { top: 26px; height: calc(100vh - 26px); }

/* ── C-23 / ADR-9 / ADR-11 — the ONE published strip frame ────────────────────────────────── */
/* `shell-strip` is on the notice in BOTH placements, and the host's own #circuit-readonly notice reuses it
   with its own content — no second slot, because a second slot invites a third and each one is a new way for
   two notices about the same fault to drift apart. T-14 scans for this CLASS rather than naming particular
   notices, so a future notice can neither quietly adopt the shape nor quietly miss it.
   ADR-11: the shape means "chrome — the page is not live". A BODY explanation never wears it.
   Lifted from MainLayout.razor.css:79-98, where it was SCOPED to a component that no longer exists. */
/* `display:none` is load-bearing and so is `block`: blazor.web.js reveals this element by writing
   `style.display = 'block'` inline. A flex or grid rule here would be silently overwritten the moment the
   strip actually mattered.
   The shipped rule was `background: lightyellow; color-scheme: light only` — a CSS named colour, which NFR-4
   does not allow and which is also the Blazor template's default rather than anything this product chose.
   The staff mockup (§Reconnect) rules it: "the same frame with Bad intent and a reload link". */
/* THE FRAME — look only, never `display`. blazor.web.js reveals this element by writing
   `style.display = 'block'` inline, so a display declared here is either overridden or fighting it. */
.shell-strip {
  box-sizing: border-box;
  padding: 0.6rem 1.25rem 0.7rem 1.25rem;
  background: var(--bad); color: var(--bg);
  border-top: 1px solid var(--line-2);
  font-size: 12.5px;
  min-height: var(--shell-target-min);
}
.shell-strip .reload { color: var(--bg); text-decoration: underline; margin-left: 10px; }
.shell-strip .dismiss { cursor: pointer; position: absolute; right: 0.75rem; top: 0.5rem; }

/* PLACEMENT — the only thing C-23 decides. Overlay is fixed and MAY cover (staff: a click that will not land
   on a stale VAT figure is worth blocking for); Inline sits in the flow and NEVER covers (a buyer's
   quotation must not be obscured by chrome). */
.shell-strip.is-overlay { position: fixed; bottom: 0; left: 0; width: 100%; z-index: 1000; }
.shell-strip.is-inline  { position: relative; width: 100%; }

/* Two strips may legitimately be on screen at once — max one of each, 8 px apart, never stacked into a wall. */
.shell-strip + .shell-strip { margin-top: 8px; }

/* The framework's own contract, and the only thing left keyed to the id: hidden until blazor.web.js shows it. */
#blazor-error-ui { display: none; }

/* ============================================================================================
   New in slice 0 — regions the hand-written chrome never had.
   ============================================================================================ */

/* C-07 — the readable measure. It NEVER yields to a region (ADR-7); it narrows only when the VIEWPORT
   narrows, because a viewport is not a region. */
/* C-29 -- padding-inline is the measure's own gutter and is a METRIC, because the two hosts genuinely
   disagree about it (24 staff / 20 buyer, design.md B-5) and neither number belongs in this file.
   box-sizing:border-box is the hosts' (`* { box-sizing: border-box }`), so the 736 px max-width is the
   OUTER width and the rendered column is 736-2*gutter -- 696 on the portal, which is what ships today. */
.shell-measure { width: 100%; max-width: var(--shell-measure); margin-inline: auto;
                 padding-inline: var(--shell-measure-gutter); }

/* ── Master-detail split — ONE ratio, both hosts (epic-081 ②, decision-log §6.194) ─────────────
   OWNER RULING 2026-08-23, taken on measured evidence (docs/evidence/epic-081-portal-hub/split-*.png):
   the portal's chat/booking panel uses the ERP's master-detail clamp AS-IS.

   ⚠ THIS SUPERSEDES §6.146's LITERAL "occupy 50% of the width". The owner was shown both candidates
   rendered — 50/50, and this clamp reading 44.7% at 1400px and 34% at 1920px — and chose this one.
   Anyone reading §6.146 alone will think 34% is a defect and "restore" the 50%. It is not; the paint pin
   carries 50% as its KNOWN-BAD reference for exactly that reason.

   Lifted here from Erp.Web/wwwroot/app.css so the two hosts read ONE rule rather than one each — the
   "one height, two files" shape already filed in docs/backlog/portal-css-reads-no-shell-radius-and-
   restates-one-height.md, refused before it could happen a second time.

   ⚠ WHAT DELIBERATELY DID NOT MOVE: `.md-view` and `.md-detail .view`. Both target `.view`, which is the
   ERP's own page wrapper and does not exist on the portal — they are HOST GLUE, not the split. And
   `.md-view{max-width:none}` ties with app.css's `@media (max-width:1280px){ .view{max-width:100%} }` on
   specificity, so it is decided by ORDER: shell.css loads BEFORE app.css, so moving it would have handed
   that tie to the media query. Measured across the move to prove the ERP's paint did not shift —
   docs/evidence/epic-081-portal-hub/split-cascade.mjs.

   Selection is server state (a route param), so the split is pure CSS — no viewport JS to be flaky.
   No selection → full-width list; wide+selection → grid left / panel right; narrow+selection → the panel
   REPLACES the list, which is what makes it full-screen on a phone. */
.md-layout { width: 100%; }
.md-layout .md-detail { display: none; }
.md-layout.has-sel .md-master { display: none; }   /* narrow + selection: the panel replaces the list */
.md-layout.has-sel .md-detail { display: block; }

@media (min-width: 1250px) {
  /* The list fills the remaining width; the panel is a comfortable reading band — it scales at 34% but is
     floored at 520px and capped at 900px, so a document preview never gets too wide while the grid soaks
     up the 4K space to its left. The floor is why this reads ~44% at 1400px and ~34% only past ~1530px. */
  .md-layout.has-sel { display: grid; grid-template-columns: minmax(0, 1fr) clamp(520px, 34%, 900px);
                       gap: 32px; align-items: start; }
  .md-layout.has-sel .md-master { display: block; }   /* wide: both columns */
  .md-layout.has-sel .md-detail { display: block; position: sticky; top: 16px;
                                  max-height: calc(100vh - 32px); overflow: auto;
                                  border-left: 1px solid var(--line-2); padding-left: 24px; }
}

/* C-26 / ADR-8 — below the content and INSIDE the measure, so it prints with the document and cannot be
   mistaken for a second app bar. Carries no off-origin link: the page is reached by a capability secret and
   a footer is where that leak is traditionally introduced. */
.shell-footer { margin-top: 32px; padding-top: 16px; border-top: 1px solid var(--hair);
                color: var(--faint); font-size: 12px; }

/* C-22 / ADR-7 — the yield order, stated ONCE so neither host can disagree with it.
   Yield order: the side pane FIRST, the drawer SECOND, the readable measure NEVER.

     [drawer] │G│ [measure] │G│ [pane] │G│      G = --shell-gutter = 24px
     docked + persistent drawer:  236 + 24 + 736 + 24 + 400 + 24 = 1444
     docked + temporary drawer:         24 + 736 + 24 + 400 + 24 = 1208
   THREE gutters in both cases, never four: the drawer's left edge is the viewport's, so it contributes its
   WIDTH only and the drawer↔content boundary is already the first gutter. (F-M12: the first draft of
   SidePaneDockPx counted four in one branch and returned 1468. T-12 recomputes rather than comparing to a
   literal, so no hard-coded 1444 can agree with a wrong implementation.)

   The ladder is INTENTIONALLY non-monotonic and is not a bug to fix: narrowing past 1443 un-docks the pane,
   and 1279 RE-DOCKS it, because the drawer going temporary genuinely hands back 236 px. The re-dock is not
   animated — an un-prompted 400 px slide during a resize reads as a glitch.

   ⚠ WHAT IS AND IS NOT BUILT HERE. Slice 0 ships the OVERLAY presentation only. The DOCKED (displacing)
   half of the ladder is deliberately NOT written yet, and this is a decision, not an oversight:
     · SidePane = None on both hosts at slice 1, so no consumer exercises it (staff mockup, §Finding);
     · T-12 — the pin that asserts the measure is exactly ReadableWidthPx at EVERY row of the table — is a
       slice-4 pin, so a cascade written now would ship untested;
     · a drawn-but-unbuilt rule is exactly the trap design.md §6 flags about the print face: the next reader
       assumes a rule that exists means a behaviour that works.
   The ARITHMETIC lives here now (that is ADR-7's "state the rule once"), and SidePaneDockPx computes it in
   AppShellOptions, so the first consumer inherits the answer instead of authoring one. */
.shell-side-pane {
  position: fixed; top: 0; right: 0; bottom: 0; z-index: 1200;
  width: min(var(--shell-pane), 100vw);
  background: var(--surface); border-left: 1px solid var(--line-2);
  overflow: auto; transition: none;
}

/* ── Responsive ───────────────────────────────────────────────────────────────────────────── */
@media (max-width: 600px) {
  .search { display: none; }
  .who { gap: 8px; }
}
