/* ============================================================================
   MinMax Pro - CLIENT PORTAL · "Daylight" theme
   ----------------------------------------------------------------------------
   A polished, professional, friendly portal for the general public - the
   customers/clients of the businesses that use MinMax Pro. Light and inviting, but
   never flat or stark: a soft neutral canvas, crisp elevated white cards, gentle
   shadows, and a rich per-mode accent gradient hero as the focal point.

   Scoped to <html data-portal="on"> so it NEVER touches the staff console. The
   mode's accent arrives via <html data-scheme="…"> and is the brand accent
   throughout.
   ========================================================================== */

/* ── 1 · Token re-point (light, refined) ───────────────────────────────────
   Compound :root[data-portal="on"] = (0,2,0), loaded after theme.css, so it
   reliably beats any per-scheme surface override in theme.css.
   We deliberately do NOT override --teal/--accent-2/--ink or the status tokens -
   the scheme owns the accent and the light status tints already read well.

   WHITE-LABEL NOTE - these are the STANDARD-tier canvas, i.e. the product's own
   room, worn by every tenant who hasn't bought custom branding. A custom-tier
   brand pack overrides each of them with an INLINE custom property on :root
   (brand.js → deriveSurfaceVars), and an inline declaration outranks any
   selector, so the tenant's canvas simply wins here without !important and
   without this file needing to know the pack exists. Keep every value below a
   plain literal for that reason: a var() here would be re-resolved against the
   tenant's own token and the fallback would stop being a fallback. */
:root[data-portal="on"] {
  --paper:      #e8ebf1;   /* substantial soft cool-neutral canvas (not white) */
  --card:       #ffffff;
  --card-2:     #f3f6fb;
  --line:       #e2e7ef;
  --line-soft:  #eef1f7;

  /* Text, moved down one rung with the console's (theme.css, 2026-08-11). This
     is the surface a CLIENT reads - an invoice total, an appointment time, a
     "waiting on you" label - and --tx-mute at #8a93a3 measured 3.1:1 on the card
     and 2.6:1 on the canvas, i.e. below AA everywhere it was used. Each rung
     keeps its place in the hierarchy; --tx-mute now clears 4.6:1 on the card and
     4.1:1 on --paper, which is the floor for the 11px labels that carry it. */
  --tx:         #262d39;
  --tx-soft:    #4b5462;
  --tx-mute:    #626b7b;
  --head:       #161d29;   /* strong heading ink (not pure black) */

  /* ── The accent, re-pointed for THIS canvas ────────────────────────────────
     The header above says this file deliberately does not override --teal,
     because "the scheme owns the accent". That held while every scheme accent
     was a mid-dark color that happened to read on white. It stopped holding when
     the product moved to a charcoal canvas: the accents were re-cut to sit on
     near-black, and #00E5FF on this file's white card is 1.54:1 - the balance
     figure, the count on a tile, the step dots and the active tab all became
     decorations you cannot read.

     So the portal now states its own accent ramp. Same hue, stepped down until
     it passes on paper, mirroring exactly what the console does in the other
     direction. Contrast vs --card (#fff): 4.75:1 / 5.59:1 - AA for text at any
     size. A custom-tier tenant still overrides all of these inline and wins.

     --on-teal has to come with them: it is the ink ON the accent, and the
     console's near-ink (#06181B) is correct on #00E5FF and unreadable on the
     deeper step below. */
  --teal:        #0B7E93;
  --teal-deep:   #0A7285;
  --teal-bright: #12A6C0;
  --on-teal:     #ffffff;

  /* Aliases the bespoke portal widgets reference. */
  --surface:      #ffffff;
  --text:         var(--tx);
  --text-soft:    var(--tx-soft);
  --font-display: var(--f-head);
  --font-mono:    var(--f-mono);
  --amber:        var(--gold);

  --accent:        var(--teal);
  --accent-bright: var(--teal-bright);
  --accent-deep:   var(--teal-deep);
  --hairline:      var(--line);

  /* Soft, diffuse slate shadows - the "premium SaaS" float. */
  --sh-1: 0 1px 2px rgba(22,29,41,.06);
  --sh-2: 0 12px 30px -16px rgba(22,29,41,.22);
  --sh-3: 0 36px 70px -30px rgba(22,29,41,.32);
}

/* ── 2 · Canvas - soft neutral with a gentle per-mode accent wash ──────────── */
html[data-portal="on"] { background: var(--paper); }
html[data-portal="on"] body { color: var(--tx); min-height: 100vh; background: var(--paper); }
/* A single fixed wash anchors a soft accent glow at the top + a cool counter-tone
   at the bottom - atmosphere without heaviness, no blur, no jank. */
html[data-portal="on"] body::before {
  content: ''; position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background:
    radial-gradient(110% 70% at 100% -8%, color-mix(in srgb, var(--accent) 16%, transparent), transparent 50%),
    radial-gradient(95% 60% at -8% 4%, color-mix(in srgb, var(--accent-2) 10%, transparent), transparent 55%);
}
html[data-portal="on"] #app, html[data-portal="on"] .portal { position: relative; z-index: 1; }

html[data-portal="on"] :is(h1, h2, h3, h4) { color: var(--head); }
html[data-portal="on"] .muted { color: var(--tx-mute); }
html[data-portal="on"] a { color: var(--accent-deep); }

/* ── 3 · Shared components, refined for light ───────────────────────────────
   WRITE `var(--card, #fff)`, NOT `#fff`, for any fill that means "the raised
   surface". Every one of them resolves to #ffffff for a standard-tier org and
   for every light brand pack - `--card` IS #ffffff two blocks up - so this is
   the same paint it has always been. It stops being the same paint for a pack
   whose canvas is dark (see darkCanvas() in src/core/brand.js): there a literal
   white renders a white card on a near-black page, and the tenant's own token
   renders the card they asked for. The genuinely-white values below are the ones
   sitting ON the accent gradient (.ov-hero, .brand-mark, the inset highlights) -
   those stay literals, because the gradient is bright at every tier. */
:root[data-portal="on"] .card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-1), 0 18px 40px -28px rgba(22,29,41,.28);
}

/* Buttons - THE SAME OBJECT AS THE CONSOLE'S, IN THE TENANT'S ACCENT.
   These used to re-declare the whole button: a flat bright→base fill under a
   22px (then 30px) cast shadow mixed from the accent, and a .btn-ghost pushed
   back to the --card rectangle the console had already left behind. So the
   same class was a different button depending on which page you were on, and
   the portal kept exactly the glow Jeremy asked to cut (2026-09-20).

   Now they only RE-POINT THE HUE. theme.css owns the shape - the lit top edge,
   the tight shadow, the press, the glass weights - and the portal says which
   color it is drawn in, which is the only thing that was ever portal-specific.
   --btn-hue is the same handle the console family is written against. */
:root[data-portal="on"] .btn-primary {
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--accent) 88%, #ffffff 12%),
    var(--accent) 58%,
    color-mix(in srgb, var(--accent) 86%, #0b1015 14%));
  color: var(--on-teal);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.45),
              inset 0 -1px 0 rgba(0,0,0,.12),
              0 2px 6px -1px color-mix(in srgb, var(--accent) 45%, transparent),
              0 1px 2px rgba(0,0,0,.30);
}
:root[data-portal="on"] .btn-primary:hover {
  /* The same lit hover as theme.css, in the tenant's accent - the note over
     .btn-primary:hover there is why the white ramp went. */
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--accent) 80%, #ffffff 20%),
    color-mix(in srgb, var(--accent) 93%, #ffffff 7%) 34%,
    var(--accent) 62%,
    color-mix(in srgb, var(--accent) 91%, #0b1015 9%));
  background: linear-gradient(180deg,
    oklch(from var(--accent) calc(l + (1 - l) * .30) c h),
    oklch(from var(--accent) calc(l + (1 - l) * .10) c h) 34%,
    var(--accent) 62%,
    oklch(from var(--accent) calc(l * .94) c h));
  box-shadow: inset 0 1px 0 rgba(255,255,255,.62),
              inset 0 10px 16px -12px rgba(255,255,255,.70),
              inset 0 -1px 0 rgba(0,0,0,.16),
              0 5px 14px -6px color-mix(in srgb, var(--accent) 58%, transparent),
              0 2px 4px rgba(0,0,0,.28);
}
:root[data-portal="on"] :is(.btn-ghost, .btn-quiet, .btn-danger) { --btn-hue: var(--accent); }
:root[data-portal="on"] .btn-ghost:hover { color: var(--accent-deep); }

/* Forms */
:root[data-portal="on"] .input,
:root[data-portal="on"] .select,
:root[data-portal="on"] .textarea {
  background: var(--card, #fff); border: 1px solid var(--line); color: var(--tx);
}
:root[data-portal="on"] .input:focus,
:root[data-portal="on"] .select:focus,
:root[data-portal="on"] .textarea:focus {
  border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);
}
:root[data-portal="on"] .field label { color: var(--tx-soft); }
:root[data-portal="on"] ::placeholder { color: var(--tx-mute); }

/* Avatar */
:root[data-portal="on"] .avatar {
  background: color-mix(in srgb, var(--accent) 14%, var(--card, #fff));
  color: var(--accent-deep);
  border: 1px solid color-mix(in srgb, var(--accent) 22%, var(--line));
}

/* Modal + scrim */
:root[data-portal="on"] .scrim { background: rgba(22,29,41,.4); -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px); }
:root[data-portal="on"] .modal .box { background: var(--card, #fff); border: 1px solid var(--line); box-shadow: var(--sh-3); }
:root[data-portal="on"] .modal .box-head { border-bottom-color: var(--line-soft); background: linear-gradient(180deg, color-mix(in srgb, var(--accent) 6%, var(--card, #fff)), var(--card, #fff)); }
:root[data-portal="on"] .modal .box-foot { border-top-color: var(--line-soft); }

:root[data-portal="on"] .sub { font-size: 12.5px; color: var(--tx-soft); }

/* ============================================================================
   4 · PORTAL CHROME  (the shell defined by portal.html)
   ========================================================================== */
html[data-portal="on"] .portal { max-width: 760px; margin: 0 auto; padding: 0 18px 80px; min-height: 100vh; }

.portal-top {
  display: flex; align-items: center; gap: 13px;
  padding: 22px 6px 18px; margin-bottom: 8px;
  position: sticky; top: 0; z-index: 20;
}
/* a clean frosted bar that fades in as you scroll under it */
.portal-top::before {
  content: ''; position: absolute; inset: 0 -18px; z-index: -1;
  background: linear-gradient(180deg, color-mix(in srgb, var(--paper) 92%, var(--card, #fff)), color-mix(in srgb, var(--paper) 70%, transparent) 70%, transparent);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  border-bottom: 1px solid color-mix(in srgb, var(--tx) 6%, transparent);
  -webkit-mask-image: linear-gradient(180deg, #000 62%, transparent); mask-image: linear-gradient(180deg, #000 62%, transparent);
}
/* ── Brand lockup ───────────────────────────────────────────────────────────
   One slot, two renderings. A tenant WITH a logo gets the logo alone - a logo
   already contains the company name, and setting a wordmark beside it is the
   classic tell of a white-labeled template. A tenant WITHOUT one gets the
   monogram tile + wordmark, which is the look the portal ships with. The engine
   (brand.js → paintLockup) swaps the contents and toggles .has-logo. */
.brand-lockup { display: flex; align-items: center; gap: 13px; min-width: 0; }
.brand-lockup.has-logo { gap: 10px; }
.brand-lockup.has-logo .brand-mark,
.brand-lockup.has-logo .brand-wrap { display: none; }
.brand-lockup-img { flex-shrink: 0; }
/* When a logo is present the portal label rides beneath it as a quiet caption. */
.brand-lockup-sub {
  font-size: 10.5px; letter-spacing: .16em; text-transform: uppercase;
  color: var(--accent-deep); font-weight: 700; font-family: var(--f-mono);
  align-self: flex-end; padding-bottom: 3px; white-space: nowrap;
}
@media (max-width: 480px) { .brand-lockup-sub { display: none; } }

.signin-top { justify-content: center; padding-bottom: 8px; }
.signin-top::before { display: none; }   /* no frosted bar behind the sign-in lockup */

.brand-mark {
  width: 42px; height: 42px; border-radius: 13px; flex-shrink: 0;
  display: grid; place-items: center;
  font-family: var(--f-head); font-weight: 800; font-size: 20px; color: var(--on-teal);
  background: linear-gradient(150deg, var(--accent-bright), var(--accent-deep));
  box-shadow: 0 8px 20px -8px color-mix(in srgb, var(--accent) 65%, transparent), inset 0 1px 0 rgba(255,255,255,.4);
  position: relative; overflow: hidden;
}
.brand-mark::after { content: ''; position: absolute; inset: 0; background: radial-gradient(120% 90% at 30% 0%, rgba(255,255,255,.45), transparent 60%); opacity: .8; }
.brand-wrap { display: flex; flex-direction: column; line-height: 1.15; }
.brand-wrap b { font-family: var(--f-head); font-weight: 800; font-size: 17px; color: var(--head); letter-spacing: -.01em; }
.brand-wrap small { font-size: 10.5px; letter-spacing: .16em; text-transform: uppercase; color: var(--accent-deep); font-weight: 700; font-family: var(--f-mono); margin-top: 1px; }
.portal-top .who { margin-left: auto; display: flex; align-items: center; gap: 12px; }
.portal-top .who .nm { font-weight: 600; font-size: 13.5px; color: var(--tx); }

/* Tab bar - clean white pill rail with a soft accent-filled active chip */
.ptabs {
  display: flex; gap: 3px; overflow-x: auto; padding: 5px; margin-bottom: 22px;
  background: var(--card, #fff); border: 1px solid var(--line);
  border-radius: 15px; -webkit-overflow-scrolling: touch;
  box-shadow: var(--sh-1), 0 14px 30px -24px rgba(22,29,41,.3);
}
.ptabs::-webkit-scrollbar { display: none; }
.ptabs button {
  font-family: var(--f-body); font-size: 13px; font-weight: 600; white-space: nowrap;
  color: var(--tx-soft); background: transparent; border: 0; padding: 9px 15px;
  border-radius: 10px; cursor: pointer; transition: color .16s, background .16s; flex: 0 0 auto;
  display: inline-flex; align-items: center;
}
.ptabs button:hover { color: var(--tx); background: var(--card-2); }
.ptabs button.on {
  color: var(--accent-deep);
  background: color-mix(in srgb, var(--accent) 13%, var(--card, #fff));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 26%, transparent);
}

/* ── Sign-in screen ──────────────────────────────────────────────────────────
   The portal's one PRE-LOGIN surface, and the only place in this file where the
   chrome is dark. It matches assets/preauth.css - the shared system behind
   /login, /reset-password, /booking and /review - so a customer who books a
   visit, then gets a review link, then signs in here is looking at one product
   rather than three. Everything past this screen keeps the portal's own
   frosted-light chrome; "Daylight" is the SIGNED-IN experience and this does
   not touch it.

   .portal-signin is set on the container by renderSignIn() and cleared by
   renderShell(). It drops the 760px column so the bar can reach both edges -
   full-bleed by removing the constraint rather than by a 100vw + negative
   margin trick, which overflows by the width of the scrollbar. */
html[data-portal="on"] .portal.portal-signin {
  max-width: none;
  padding: 0 0 40px;
  /* The pre-auth canvas, not the portal's own. The signed-in portal sits on a
     calm neutral field on purpose (it is a working surface someone reads for
     several minutes); the DOOR is lit from the top right like every other
     pre-auth page in the package, so the five standalone pages and this one
     read as one product. Painted on the container rather than on body::before,
     which portal-skin already owns for every screen after this one. */
  background:
    radial-gradient(84% 62% at 100% -6%, color-mix(in srgb, var(--accent-bright, var(--accent)) 16%, transparent), transparent 66%),
    radial-gradient(76% 58% at -8% 104%, color-mix(in srgb, var(--accent-2, var(--accent)) 8%, transparent), transparent 68%);
}

.signin-bar {
  position: relative;
  background: linear-gradient(180deg, color-mix(in srgb, var(--ink, #20242c) 88%, #fff), var(--ink, #20242c));
  border-bottom: 1px solid rgba(255, 255, 255, .10);
  /* The inset catch-light and the drop shadow are what make this read as a
     panel sitting in front of the page rather than a stripe painted on it. */
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .06), 0 10px 30px -18px rgba(12, 16, 22, .55);
}
/* The accent hairline that makes the bar read as lit rather than as a black
   strip laid on the page - the same one preauth.css draws. */
.signin-bar::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: -1px; height: 1px; pointer-events: none;
  background: linear-gradient(90deg, transparent,
    color-mix(in srgb, var(--accent-bright, var(--accent)) 55%, transparent) 22%,
    color-mix(in srgb, var(--accent-bright, var(--accent)) 20%, transparent) 62%, transparent);
}
/* FLUSH LEFT, with the same 24px inset the other five pre-auth bars use. The
   portal's own 760px column is wrong here for two reasons: the bar spans the
   viewport, so centring its contents on a narrow track left the business's
   lockup floating in the middle of a wide monitor with nothing either side of
   it; and the identity belongs at the leading edge, which is where a visitor
   arriving from any other page in the package has just seen it. */
.signin-bar-in {
  min-height: 62px;
  display: flex; align-items: center; gap: 14px; padding: 0 24px;
}
.signin-bar-word {
  font-family: var(--f-mono); font-size: 11px; font-weight: 600;
  letter-spacing: .16em; text-transform: uppercase; color: rgba(238, 240, 245, .68);
  padding-left: 16px; border-left: 1px solid rgba(255, 255, 255, .12); white-space: nowrap;
}
@media (max-width: 520px) { .signin-bar-word { display: none; } }
/* The lockup sits on dark here, so its typed half has to flip. The monogram
   tile already carries the accent gradient and needs nothing. */
.signin-bar .brand-wrap b { color: #eef0f5; }
.signin-bar .brand-wrap small,
.signin-bar .brand-lockup-sub { color: rgba(238, 240, 245, .68); }

.signin-wrap { max-width: 460px; margin: 9vh auto 0; padding: 0 18px; }
.signin-wrap .lede { color: var(--tx-soft); margin: 7px 0 22px; }
/* The accent strap along the card's top edge - the mark that tells a pale card
   on pale paper where the page actually starts. */
.signin-card { position: relative; overflow: hidden; padding: 30px 28px; }
.signin-card::before {
  content: ''; position: absolute; left: 0; right: 0; top: 0; height: 3px;
  background: linear-gradient(90deg, var(--accent-bright, var(--accent)), var(--accent), transparent 78%);
}

/* Section labels + list/job cards */
.section-label { font-family: var(--f-mono); font-size: 11px; letter-spacing: .13em; text-transform: uppercase; color: var(--tx-mute); font-weight: 600; margin: 4px 2px 4px; }
.jcard { display: flex; flex-direction: column; gap: 6px; }
.jcard .when { font-family: var(--f-mono); font-size: 12px; color: var(--accent-deep); font-weight: 600; letter-spacing: .02em; }
.jcard .ttl { font-family: var(--f-head); font-weight: 700; font-size: 16.5px; color: var(--head); }
.jcard .meta { font-size: 13px; color: var(--tx-soft); }
.listrow { display: flex; align-items: center; gap: 12px; }
.listrow .grow { flex: 1; min-width: 0; }
.listrow .num { font-family: var(--f-mono); font-weight: 700; font-size: 14px; color: var(--head); }
.listrow .sub { font-size: 12.5px; color: var(--tx-soft); margin-top: 2px; }
.amt-lg { font-family: var(--f-num); font-weight: 800; font-size: 17px; color: var(--head); white-space: nowrap; }

.sigpad { border: 1.5px dashed var(--line); border-radius: var(--r-sm); background: var(--card-2); touch-action: none; width: 100%; display: block; }
.lines-tbl { width: 100%; border-collapse: collapse; margin-top: 4px; }
.lines-tbl td { padding: 8px 4px; border-bottom: 1px solid var(--line-soft); font-size: 13.5px; color: var(--tx); }
.lines-tbl td.num { text-align: right; font-family: var(--f-mono); white-space: nowrap; }
.lines-tbl tr:last-child td { border-bottom: 0; }

/* ============================================================================
   5 · PORTAL WIDGETS
   ========================================================================== */

/* ── Overview hero - the rich accent gradient focal point ─────────────────── */
.ov-hero {
  position: relative; overflow: hidden; border-radius: 22px; padding: 32px 30px 30px;
  color: var(--on-teal);
  background:
    radial-gradient(120% 130% at 100% 0%, color-mix(in srgb, var(--accent-bright) 60%, var(--accent)), transparent 60%),
    linear-gradient(135deg, var(--accent), var(--accent-deep));
  box-shadow: 0 1px 0 rgba(255,255,255,.25) inset, 0 22px 46px -22px color-mix(in srgb, var(--accent) 60%, transparent);
}
.ov-hero::after {
  content: ''; position: absolute; right: -50px; top: -70px; width: 260px; height: 260px; border-radius: 50%;
  background: radial-gradient(circle, rgba(255,255,255,.4), transparent 70%);
  opacity: .55; animation: ovGlow 10s ease-in-out infinite;
}
@keyframes ovGlow { 0%,100% { transform: translate(0,0) scale(1); opacity: .5; } 50% { transform: translate(-12px,10px) scale(1.07); opacity: .62; } }
.ov-hero-in { position: relative; z-index: 1; }
.ov-eyebrow { font-family: var(--f-mono); font-size: 11px; letter-spacing: .18em; text-transform: uppercase; color: rgba(255,255,255,.85); margin-bottom: 10px; }
.ov-hero h2 { font-family: var(--f-head); font-weight: 800; font-size: clamp(25px, 5vw, 31px); margin-bottom: 9px; color: #fff; letter-spacing: -.02em; }
.ov-hero p { font-size: 14.5px; line-height: 1.6; color: rgba(255,255,255,.88); max-width: 54ch; }
.ov-hero p b { font-weight: 700; color: #fff; }
/* Keep the hero title white (beats the global heading-ink rule). */
html[data-portal="on"] .ov-hero h2 { color: #fff; }

/* ── Stat tiles ──────────────────────────────────────────────────────────── */
.ov-tiles { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
@media (max-width: 620px) { .ov-tiles { grid-template-columns: 1fr 1fr; } }
.ov-tile {
  position: relative; text-align: left; display: flex; flex-direction: column; gap: 4px;
  padding: 17px 17px 16px; border-radius: 16px; overflow: hidden;
  border: 1px solid var(--line); background: var(--card, #fff);
  box-shadow: var(--sh-1);
  cursor: pointer; font-family: inherit;
  transition: border-color .16s, transform .12s, box-shadow .16s;
}
.ov-tile::before {
  content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: linear-gradient(var(--accent-bright), var(--accent)); opacity: 0; transition: opacity .16s;
}
.ov-tile[data-tab=""] { cursor: default; }
.ov-tile:hover { transform: translateY(-3px); border-color: color-mix(in srgb, var(--accent) 38%, var(--line)); box-shadow: 0 18px 34px -22px color-mix(in srgb, var(--accent) 40%, rgba(22,29,41,.5)); }
.ov-tile:hover::before { opacity: 1; }
.ov-tile-l { font-size: 11.5px; color: var(--tx-soft); font-weight: 600; }
.ov-tile-v { font-family: var(--f-num); font-size: 21px; color: var(--head); font-weight: 700; line-height: 1.1; }
.ov-tile-v b { font-weight: 800; }
.ov-tile-s { font-size: 11.5px; color: var(--tx-mute); }
.ov-tile.warn .ov-tile-v { color:var(--warn-tx, var(--warn)); }
.ov-tile.info .ov-tile-v { color: var(--accent-deep); }

/* ── "What needs you" rows ───────────────────────────────────────────────── */
.ov-needs { display: flex; flex-direction: column; gap: 9px; margin-top: 10px; }
.ov-need {
  display: flex; align-items: center; gap: 12px; padding: 13px 15px; border-radius: 13px;
  border: 1px solid var(--line); background: var(--card-2);
  cursor: pointer; font-family: inherit; font-size: 14px; color: var(--tx); text-align: left;
  transition: border-color .16s, background .16s, transform .12s, box-shadow .16s;
}
.ov-need:hover { border-color: color-mix(in srgb, var(--accent) 40%, var(--line)); background: var(--card, #fff); transform: translateX(2px); box-shadow: var(--sh-1); }
.ov-need .grow { flex: 1; }
.ov-need-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--accent); flex-shrink: 0; box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 16%, transparent); }
.ov-need.warn .ov-need-dot { background: var(--warn); box-shadow: 0 0 0 4px color-mix(in srgb, var(--warn) 16%, transparent); }
.ov-need-arrow { color: var(--tx-mute); transition: transform .16s, color .16s; }
.ov-need:hover .ov-need-arrow { transform: translateX(3px); color: var(--accent-deep); }

/* ── Tab count badge ─────────────────────────────────────────────────────── */
.ptab-badge {
  display: inline-grid; place-items: center; min-width: 18px; height: 18px; padding: 0 5px; margin-left: 7px;
  border-radius: 9px; font-size: 11px; font-weight: 800; vertical-align: middle;
  background: color-mix(in srgb, var(--accent) 18%, var(--card, #fff)); color: var(--accent-deep);
}
.ptabs button.on .ptab-badge { background: color-mix(in srgb, var(--accent) 28%, var(--card, #fff)); color: var(--accent-deep); }

/* ── Secure message thread ───────────────────────────────────────────────── */
.p3-thread { display: flex; flex-direction: column; gap: 11px; max-height: 58vh; overflow-y: auto; padding: 4px 2px 10px; }
.p3-row { display: flex; }
.p3-row.me { justify-content: flex-end; }
.p3-row.them { justify-content: flex-start; }
.p3-bubble { max-width: 82%; border-radius: 16px; padding: 10px 14px; background: var(--card-2); border: 1px solid var(--line); font-size: 13.5px; line-height: 1.5; color: var(--tx); }
.p3-row.them .p3-bubble { border-bottom-left-radius: 5px; }
.p3-row.me .p3-bubble { border-bottom-right-radius: 5px; background: linear-gradient(180deg, var(--accent-bright), var(--accent)); border-color: transparent; color: var(--on-teal); }
.p3-meta { font-size: 11px; color: var(--tx-mute); margin-bottom: 4px; }
.p3-row.me .p3-meta { color: rgba(255,255,255,.85); }
.p3-reply { display: flex; gap: 8px; align-items: flex-end; margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--line-soft); }
.p3-reply .textarea { flex: 1; resize: vertical; min-height: 46px; }

/* ── Consulting: engagements + deliverables ──────────────────────────────── */
.cs-proj { border: 1px solid var(--line); border-radius: 16px; padding: 17px; background: var(--card, #fff); box-shadow: var(--sh-1); }
.cs-proj-h { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
.cs-proj-h b { font-size: 15.5px; color: var(--head); }
.cs-bar { height: 8px; border-radius: 5px; background: var(--line-soft); overflow: hidden; margin-top: 12px; }
.cs-bar > span { display: block; height: 100%; border-radius: 5px; background: linear-gradient(90deg, var(--accent), var(--accent-bright)); animation: csBar 1s cubic-bezier(.22,1,.36,1) both; }
@keyframes csBar { from { width: 0 !important; } }
.cs-ms { display: flex; flex-direction: column; gap: 8px; margin-top: 4px; }
.cs-ms-row { display: flex; align-items: center; gap: 10px; font-size: 13.5px; flex-wrap: wrap; }
/* min-width:0 so the title yields to the pill and the date instead of forcing
   the row wider than the card - the session name is the one part of this row
   that can afford to be cut, and without it the row's min-content width was the
   longest word in the title plus every fixed element beside it. */
.cs-ms-row .grow { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The date column is right-aligned against a fixed 150px inline width on a
   surface whose content column can be under 275px. Below that it wraps to its
   own line and takes the width it needs. */
@media (max-width: 480px) {
  .cs-ms-row > .sub { min-width: 0 !important; text-align: left !important; flex-basis: 100%; }
}
.cs-ms-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.cs-ms-dot.ok { background: var(--ok); } .cs-ms-dot.info { background: var(--accent); } .cs-ms-dot.neutral { background: var(--tx-mute); } .cs-ms-dot.bad { background: var(--bad); }
.pill.sm { font-size: 11px; padding: 2px 8px; }
.cs-deliv { display: flex; align-items: center; gap: 13px; border: 1px solid var(--line); border-radius: 14px; padding: 13px 14px; background: var(--card, #fff); box-shadow: var(--sh-1); transition: border-color .16s, transform .1s, box-shadow .16s; }
.cs-deliv:hover { border-color: color-mix(in srgb, var(--accent) 32%, var(--line)); transform: translateY(-1px); box-shadow: var(--sh-2); }
.cs-deliv .grow { flex: 1; min-width: 0; }
.cs-deliv b { font-size: 13.5px; display: block; color: var(--head); }
.cs-deliv-ic { width: 38px; height: 38px; border-radius: 11px; background: color-mix(in srgb, var(--accent) 12%, var(--card, #fff)); color: var(--accent-deep); display: grid; place-items: center; flex-shrink: 0; border: 1px solid color-mix(in srgb, var(--accent) 18%, var(--line)); }
.cs-deliv-ic svg { width: 18px; height: 18px; }
.cs-deliv-act { display: flex; flex-direction: column; align-items: flex-end; gap: 8px; flex-shrink: 0; }

/* ── Motion niceties ─────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  .ov-hero::after, .cs-bar > span { animation: none; }
}

/* ============================================================================
   6 · WHITE-LABEL LAYER
   ----------------------------------------------------------------------------
   Everything below responds to a custom-tier brand pack. The pack sets inline
   custom properties on :root (which outrank this file) plus the attributes
   data-brand-tier / data-density / data-shadow, which is what these rules key
   off. A standard-tier org matches none of them and renders exactly as before.
   ========================================================================== */

/* ── Type scale ───────────────────────────────────────────────────────────
   A brand with a small x-height needs a touch more size to read the way it does
   on the tenant's own site. Applied at the root so every rem/em-based size and
   the whole rhythm scale together; --f-scale is clamped to 0.9–1.15 in the
   engine so this can never make the portal unusable. */
html[data-portal="on"][data-brand-tier="custom"] {
  font-size: calc(100% * var(--f-scale, 1));
}

/* ── Density ──────────────────────────────────────────────────────────────
   Some brands are airy, some are dense and businesslike. This scales the padding
   of the repeating containers only - never the tap targets, which stay at their
   accessible size regardless. */
html[data-portal="on"][data-density="compact"] .card-pad { padding: 14px 15px; }
html[data-portal="on"][data-density="compact"] .portal { padding-bottom: 56px; }
html[data-portal="on"][data-density="compact"] .ov-hero { padding: 24px 22px 22px; }
html[data-portal="on"][data-density="compact"] .ov-tile { padding: 13px 13px 12px; }
html[data-portal="on"][data-density="compact"] .stack { gap: 10px; }

html[data-portal="on"][data-density="spacious"] .card-pad { padding: 24px 26px; }
html[data-portal="on"][data-density="spacious"] .ov-hero { padding: 40px 36px 36px; }
html[data-portal="on"][data-density="spacious"] .ov-tile { padding: 21px 20px 20px; }
html[data-portal="on"][data-density="spacious"] .stack { gap: 18px; }

/* ── Elevation personality ────────────────────────────────────────────────
   'flat' is a real choice, not a degraded one: plenty of brands read as wrong
   the instant you float their cards. The engine already zeroes --sh-*; these
   rules also remove the bespoke shadows the widgets set inline in this file,
   and restore a crisper border so the layout still has structure without depth. */
html[data-portal="on"][data-shadow="flat"] :is(.card, .ov-tile, .ptabs, .cs-proj, .cs-deliv) {
  box-shadow: none;
}
html[data-portal="on"][data-shadow="flat"] .ov-hero {
  box-shadow: 0 1px 0 rgba(255, 255, 255, .18) inset;
}
html[data-portal="on"][data-shadow="flat"] :is(.card, .ov-tile, .ptabs) {
  border-color: color-mix(in srgb, var(--tx) 14%, var(--line));
}
html[data-portal="on"][data-shadow="flat"] :is(.ov-tile:hover, .cs-deliv:hover) {
  transform: none;
  box-shadow: none;
  border-color: var(--accent);
}

html[data-portal="on"][data-shadow="crisp"] .card {
  box-shadow: var(--sh-1), 0 8px 18px -12px color-mix(in srgb, var(--tx) 40%, transparent);
}

/* ── Footer ───────────────────────────────────────────────────────────────
   Who to call, without leaving the portal. Present on every tier; the contact
   row simply stays empty for a tenant that hasn't supplied one. */
.portal-foot {
  margin-top: 40px; padding: 20px 4px 8px;
  border-top: 1px solid var(--line-soft);
  display: flex; flex-direction: column; align-items: center; gap: 7px;
  text-align: center;
}
.portal-foot-contact {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap; justify-content: center;
  font-size: 13px; color: var(--tx-soft);
}
.portal-foot-contact a { color: var(--accent-deep); font-weight: 600; text-decoration: none; }
.portal-foot-contact a:hover { text-decoration: underline; }
.portal-foot-contact .sep { color: var(--tx-mute); }
.portal-foot-legal { font-size: 11.5px; color: var(--tx-mute); max-width: 62ch; line-height: 1.5; }
.portal-foot-credit { font-size: 11px; color: var(--tx-mute); letter-spacing: .02em; }

/* The platform credit is a quiet mark of provenance, not an advertisement -
   it must never compete with the tenant's own identity. */
.portal-foot-credit { opacity: .72; }

/* ============================================================================
   9 - TEXTURE AND DEPTH
   ----------------------------------------------------------------------------
   Everything above this line draws the portal correctly and draws it flat. A
   white card on a pale page, a white tile on a pale page and a white tab rail
   on a pale page are the same object at three sizes, and a screen of them reads
   as a list rather than as a product - which is the note this section answers.

   The console solved it and the portal never picked the recipe up. It has three
   parts and they only work together:

     1 - THE CANVAS IS LIT, NOT PAINTED. Two flat gradients meeting on a
         near-white page is what makes a light UI read as a picture of a page
         rather than as a lit surface. A 160px fractal-noise tile (--grain, see
         theme.css for the recipe) plus a 64px grid give the eye something to
         land on between the cards, at a few percent of coverage - no color
         moves, and nothing that reads as a pattern.

     2 - A CARD IS AN OBJECT, NOT A RECTANGLE. Three shadows, not one: a lit 1px
         top edge (the card catching the light), a tight contact shadow (the
         card touching the page) and a wide ambient one (the card above it).
         Drop any of the three and the other two stop reading as depth.

     3 - A SURFACE THAT CAN BE PRESSED LOOKS PRESSABLE. The tab rail is a
         channel cut into the page with the active tab raised out of it, rather
         than a tinted chip inside a white box. Same for the tiles: each one
         carries its accent rail at rest instead of revealing it on hover, so a
         row of them reads as a set of measures.

   TOKENS, NEVER LITERALS, for anything that means "the raised surface" - a
   custom-tier pack re-points --card, --line and the accent inline on :root and
   has to keep winning here. The literals below are white highlights and neutral
   shadow ink, which are correct at every tier.
   ========================================================================== */

:root[data-portal="on"] {
  /* The grid line. Keyed off the text ink rather than a literal so it inverts
     with a dark pack's canvas instead of disappearing into it. */
  --px-grid: color-mix(in srgb, var(--tx) 4.5%, transparent);
  /* The WHITE speckle, for the dark surfaces this portal puts on a light page -
     the side rail and the live tracker. theme.css's --grain is a BLACK speckle
     (and a dark tenant pack swaps it for a white one), so neither of those can
     read it: on this surface the page is light and the object is dark, which is
     the one combination the polarity swap does not cover. Same recipe as
     theme.css otherwise - see the note there for what each filter step does. */
  --grain-lit: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 .21 .72 .07 0 0'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='gamma' exponent='3'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23g)' opacity='.16'/%3E%3C/svg%3E");
  /* The three shadow layers, named so every surface below composes the same
     depth rather than inventing its own. --lift-e is the lit top edge. */
  --lift-e: inset 0 1px 0 rgba(255, 255, 255, .9);
  --lift-1: 0 1px 2px rgba(22, 29, 41, .06);
  --lift-2: 0 2px 6px -1px rgba(22, 29, 41, .07);
  --lift-3: 0 20px 40px -26px rgba(22, 29, 41, .38);
}

/* -- 9.1 - The canvas ------------------------------------------------------
   Grain goes on TOP of the wash, not under it: a noise layer beneath a gradient
   is a gradient. The two accent radials are the ones that were already here. */
html[data-portal="on"] body::before {
  background:
    var(--grain) 0 0 / 160px 160px,
    linear-gradient(var(--px-grid) 1px, transparent 1px) 0 0 / 64px 64px,
    linear-gradient(90deg, var(--px-grid) 1px, transparent 1px) 0 0 / 64px 64px,
    radial-gradient(110% 70% at 100% -8%, color-mix(in srgb, var(--accent) 16%, transparent), transparent 50%),
    radial-gradient(95% 60% at -8% 4%, color-mix(in srgb, var(--accent-2) 10%, transparent), transparent 55%);
}

/* -- 9.2 - Cards ---------------------------------------------------------- */
:root[data-portal="on"] .card {
  border-color: color-mix(in srgb, var(--tx) 11%, var(--line));
  box-shadow: var(--lift-e), var(--lift-1), var(--lift-2), var(--lift-3);
}
/* A card head is where the accent enters the page: a wash of it under the
   title and a hairline of it along the bottom edge. */
:root[data-portal="on"] .card-head {
  position: relative;
  background: linear-gradient(180deg, color-mix(in srgb, var(--accent) 6%, var(--card, #fff)), var(--card, #fff));
  border-bottom-color: color-mix(in srgb, var(--accent) 15%, var(--line-soft));
}
:root[data-portal="on"] .card-head h3 { color: var(--head); letter-spacing: -.01em; }

/* The mono section label carries an accent tick, so a stack of sections reads
   as a spine of headings rather than as gray type floating above gray type. */
:root[data-portal="on"] .section-label {
  display: flex; align-items: center; gap: 9px;
  color: var(--tx-soft);
}
:root[data-portal="on"] .section-label::before {
  content: ''; flex: none; width: 3px; height: 12px; border-radius: 2px;
  background: linear-gradient(var(--accent-bright), var(--accent));
}

/* -- 9.3 - The tab rail ----------------------------------------------------
   A channel with a raised chip in it. The inset pair is the channel; the chip
   gets the lit edge and the contact shadow that every other raised thing here
   carries, plus a 2px accent foot so the active section is legible at a glance
   and not by tint alone. */
.ptabs {
  background: linear-gradient(180deg, color-mix(in srgb, var(--tx) 6%, var(--card-2)), var(--card-2));
  border-color: color-mix(in srgb, var(--tx) 9%, var(--line));
  box-shadow: inset 0 1px 3px rgba(22, 29, 41, .09), 0 1px 0 rgba(255, 255, 255, .8);
}
.ptabs button { position: relative; transition: color .16s, background .16s, box-shadow .16s; }
.ptabs button:hover { background: color-mix(in srgb, var(--card, #fff) 70%, transparent); }
.ptabs button.on {
  color: var(--accent-deep);
  background: var(--card, #fff);
  box-shadow: var(--lift-e), 0 1px 2px rgba(22, 29, 41, .12), 0 8px 16px -10px rgba(22, 29, 41, .5);
}
.ptabs button.on::after {
  content: ''; position: absolute; left: 13px; right: 13px; bottom: 4px; height: 2px; border-radius: 2px;
  background: linear-gradient(90deg, var(--accent-bright), var(--accent));
}

/* -- 9.4 - Stat tiles ------------------------------------------------------
   The console's tile, brought over: a corner wash, a diagonal tint and a rail
   that is lit at rest. The tone classes tint the whole tile rather than only
   recolouring the number, so a balance that needs paying is visible from across
   a desk without reading it. */
.ov-tile {
  --tile-a: var(--accent);
  background:
    radial-gradient(120% 130% at 100% 0%, color-mix(in srgb, var(--tile-a) 11%, transparent), transparent 62%),
    linear-gradient(158deg, var(--card, #fff), color-mix(in srgb, var(--tile-a) 6%, var(--card, #fff)));
  border-color: color-mix(in srgb, var(--tile-a) 24%, var(--line));
  box-shadow: var(--lift-e), var(--lift-1), 0 14px 28px -20px rgba(22, 29, 41, .34);
}
.ov-tile::before {
  width: 4px; opacity: .6;
  background: linear-gradient(color-mix(in srgb, var(--tile-a) 55%, var(--card, #fff)), var(--tile-a));
}
.ov-tile:hover {
  border-color: color-mix(in srgb, var(--tile-a) 46%, var(--line));
  box-shadow: var(--lift-e), 0 18px 34px -20px color-mix(in srgb, var(--tile-a) 46%, rgba(22, 29, 41, .5));
}
.ov-tile.warn { --tile-a: var(--warn); }
.ov-tile.info { --tile-a: var(--accent); }
.ov-tile-l { color: var(--tx-soft); letter-spacing: .01em; }
.ov-tile-v { font-variant-numeric: tabular-nums; letter-spacing: -.02em; }

/* -- 9.5 - "Waiting on You" rows -------------------------------------------
   These are the whole reason a customer opened the portal, and they were the
   quietest thing on the page: gray boxes on a white card. Each one is now a
   raised row with its tone on the left edge. */
.ov-need {
  position: relative; overflow: hidden;
  background: linear-gradient(180deg, var(--card, #fff), var(--card-2));
  border-color: color-mix(in srgb, var(--tx) 10%, var(--line));
  box-shadow: var(--lift-e), var(--lift-1);
  padding-left: 17px;
}
.ov-need::before {
  content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: var(--accent); opacity: .85;
}
.ov-need.warn::before { background: var(--warn); }
.ov-need:hover {
  transform: translateX(2px);
  border-color: color-mix(in srgb, var(--accent) 42%, var(--line));
  box-shadow: var(--lift-e), var(--lift-1), 0 14px 26px -18px rgba(22, 29, 41, .42);
}
.ov-need .grow { font-weight: 500; }

/* -- 9.6 - Empty states ----------------------------------------------------
   An empty tab is a real answer, so it gets a real object to say it with
   instead of a floating glyph. */
:root[data-portal="on"] .empty .ic {
  display: grid; place-items: center; width: 54px; height: 54px; margin: 0 auto 14px;
  border-radius: 17px; font-size: 22px; line-height: 1;
  background: linear-gradient(158deg, var(--card, #fff), color-mix(in srgb, var(--accent) 10%, var(--card, #fff)));
  border: 1px solid color-mix(in srgb, var(--accent) 22%, var(--line));
  box-shadow: var(--lift-e), var(--lift-1), 0 12px 24px -18px rgba(22, 29, 41, .4);
}

/* -- 9.7 - The frosted top bar ---------------------------------------------
   It fades out down the page, so its bottom edge was a hairline that stopped
   mid-air. A matching accent thread under it gives the bar a finished edge. */
.portal-top::after {
  content: ''; position: absolute; left: -18px; right: -18px; bottom: 0; height: 1px;
  background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--accent) 34%, transparent), transparent);
  opacity: .8;
}

/* -- 9.8 - The flat-shadow tier --------------------------------------------
   A pack that asked for flat shadows gets structure from borders and the lit
   edge alone - it asked for no float, not for no depth. */
html[data-portal="on"][data-shadow="flat"] :is(.card, .ov-tile, .ov-need, .ptabs, .ptabs button.on, .empty .ic) {
  box-shadow: var(--lift-e);
}

/* -- 9.9 - The mark inside an icon slot ------------------------------------
   Three slots in this portal held a raw emoji (a pencil, a tick, a speech
   bubble) and now hold the product's own stroke glyphs. An <svg> with a viewBox
   and no size falls to the 16px floor in theme.css, which is a third of the
   tile it sits in - so each slot states the size of its own mark. Tinted to the
   slot's meaning rather than left at the inherited text ink: these marks are
   the only color some of these cards have. */
:root[data-portal="on"] .empty .ic svg,
:root[data-portal="on"] .pv-thread-empty .ic svg {
  width: 24px; height: 24px; color: var(--accent-deep);
}
:root[data-portal="on"] .pv-thread-empty .ic {
  background: linear-gradient(158deg, var(--card, #fff), color-mix(in srgb, var(--accent) 10%, var(--card, #fff)));
  border-color: color-mix(in srgb, var(--accent) 22%, var(--line));
  box-shadow: var(--lift-e), var(--lift-1), 0 12px 24px -18px rgba(22, 29, 41, .4);
}
/* The action cards are amber by default - an estimate waiting to be approved -
   so their mark takes the amber text step. The quote hero is the exception and
   states its own accent two files over, so it is excluded by name rather than
   overridden back. */
:root[data-portal="on"] .pv-action .ic svg { width: 20px; height: 20px; }
:root[data-portal="on"] .pv-action:not(.pv-quote-hero) .ic svg { color: var(--gold-tx, var(--gold)); }
:root[data-portal="on"] .pv-action .ic {
  box-shadow: var(--lift-e), inset 0 0 0 1px color-mix(in srgb, var(--gold) 28%, transparent);
}
