/* ============================================================================
   Monetary — mesh surface
   ============================================================================
   The animated dark-blue surface used by the title band, applied to smaller
   elements: the four summary cards on Portfolio, Ledger and Rentals, the dark
   toolbars, and the Settings section banners.

   It replaced a flat two-stop gradient (--brand-deep -> --brand-mid). This
   file is loaded AFTER each page's own <style> block by app.php, so the plain
   `.card` / `.toolbar` rules in those pages are overridden without needing
   !important anywhere.

   HOW TO USE
     Add `mesh-surface` plus one modifier to the element:

       <div class="card mesh-surface mesh-surface--card">
       <div class="toolbar no-print mesh-surface mesh-surface--bar">

     js/mesh-surface.js finds every .mesh-surface, inserts a <canvas> as its
     first child and animates the point-mesh. If that script never runs the
     surface still looks correct — it simply sits still.

   WHY THE SCRIM
     ::after is a flat wash over the mesh and the aurora, under the content.
     Without it the 11px sub-line on a card drops to about 3.7:1 wherever the
     aurora blob drifts behind it. With it, every text role on this surface
     clears WCAG AA even where all three gradient layers stack on one pixel.

   TO REVERT
     Remove the two classes from the markup. This file and the script can stay
     — they do nothing on their own.
   ========================================================================= */

.mesh-surface {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  /* Floor moved 4 rungs up the slate ramp — slate-900/1000 -> slate-600/800 —
     so the toolbars ("Manage properties" and the rest) and the four summary
     cards read noticeably lighter. Both modifiers share this one rule; the
     title band (#mBand in app.php, .band on the home page) is intentionally
     NOT part of this and stays at slate-900/1000. Re-checked against WCAG AA
     at the worst case (every gradient layer stacked on one pixel): the
     tightest role, value.neg, sits at 4.62:1 — a thin margin above the 4.5:1
     floor, so this is close to the practical limit for that colour without
     brightening it separately. */
  background:
    radial-gradient(130% 150% at 10% -30%, rgba(var(--c-accent-rgb), .45), transparent 62%),
    radial-gradient(100% 130% at 94% 0%,   rgba(var(--c-cyan-rgb),   .20), transparent 66%),
    linear-gradient(160deg, var(--c-slate-600), var(--c-slate-800));
  color: var(--c-white);
}

/* ---- the drifting blob ---------------------------------------------------
   Sized and placed from custom properties so a wide toolbar and a small card
   can share one rule. Modifiers below set them. */
.mesh-surface::before {
  content: "";
  position: absolute;
  z-index: 0;
  pointer-events: none;
  width:  var(--ms-blob, 220px);
  height: var(--ms-blob, 220px);
  top:   var(--ms-blob-top, -120px);
  right: var(--ms-blob-right, -70px);
  border-radius: var(--radius-circle);
  filter: blur(var(--ms-blur, 46px));
  opacity: .82;
  background: radial-gradient(circle, var(--aurora-1), transparent 70%);
  animation: meshSurfaceDrift 26s ease-in-out infinite;
}

/* ---- the legibility scrim ------------------------------------------------ */
.mesh-surface::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: linear-gradient(150deg,
    rgba(var(--c-deep-rgb), .34), rgba(var(--c-deep-rgb), .26));
}

/* DO NOT REMOVE THE scale() HERE. It was taken out in the Aug 2026 animation
   pass -- a blurred layer that only translates can be composited, one that
   scales has to have its blur rebuilt every frame -- and it was put back the
   same day. On the BAR modifier the blob is 320px across at top:-190px in a
   strip about 50px tall under a 62px blur, so nearly all of it sits above the
   element and only its bottom fringe shows. Growing to 1.22 is what pushes
   enough of the blob's body into view to read as a coloured glow; translating
   alone slides a thin low-contrast edge across and reads as a grey wash. Most
   obvious on Settings, which is nothing but bar surfaces.
   If the blur cost has to come down, change --ms-blur or the blob geometry and
   re-check how the bars read. */
@keyframes meshSurfaceDrift {
  0%, 100% { transform: translate(0, 0) scale(1); }
  50%      { transform: translate(-38px, 26px) scale(1.22); }
}

/* ---- the canvas, and lifting the real content above it ------------------- */
.mesh-surface > canvas.mesh-layer {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  display: block;
  pointer-events: none;
}
.mesh-surface > *:not(canvas.mesh-layer) {
  position: relative;
  z-index: 1;
}

/* ============================================================================
   Modifier: card
   ========================================================================= */
.mesh-surface--card {
  border-color: rgba(var(--c-white-rgb), .14);
  box-shadow: var(--shadow-md);
  --ms-blob: 220px;
  --ms-blob-top: -120px;
  --ms-blob-right: -70px;
  --ms-blur: 46px;
}

/* Offset the four cards in a row so they never pulse in unison. */
.mesh-surface--card:nth-child(2)::before { animation-delay: -6s;  background: radial-gradient(circle, var(--aurora-2), transparent 70%); }
.mesh-surface--card:nth-child(3)::before { animation-delay: -13s; }
.mesh-surface--card:nth-child(4)::before { animation-delay: -19s; background: radial-gradient(circle, var(--aurora-2), transparent 70%); }

/* ============================================================================
   Modifier: bar  (toolbars, Settings section banners — the surface behind
   "Manage properties" and the other section labels)
   ----------------------------------------------------------------------------
   Deliberately does NOT set border-color: .toolbar and .settings-intro carry a
   5px orange left border — the brand mark — and a border-color shorthand here
   would silently repaint it.

   .toolbar-hint (defined per page, not here — PropertyPortfolioApp.php,
   ExpensesApp.php, RentalApp.php) was bumped from 55% to 70% white alongside
   this change: at 55% it measured 4.33:1 against this lighter surface's
   worst case, under the 4.5:1 AA floor.
   ========================================================================= */
.mesh-surface--bar {
  --ms-blob: 320px;
  --ms-blob-top: -190px;
  --ms-blob-right: -110px;
  --ms-blur: 62px;
  box-shadow: 0 6px 18px -10px rgba(var(--c-deep-rgb), .6);
}

/* ============================================================================
   Text on the surface
   ----------------------------------------------------------------------------
   Measured against the worst case — the point where the base gradient, the
   cyan gradient and the aurora blob all peak on the same pixel.
   ========================================================================= */
.mesh-surface .label { color: rgba(var(--c-white-rgb), .74); }
.mesh-surface .value { color: var(--c-white); }
.mesh-surface .sub   { color: rgba(var(--c-white-rgb), .66); }

/* Gain and loss still have to read as gain and loss. The dark green and red
   used on white fall to roughly 2:1 here, so they take the on-dark pair. */
.mesh-surface .value.pos { color: var(--c-green-on-dark); }
.mesh-surface .value.neg { color: var(--c-red-on-dark); }

/* A deeper shadow so the coloured icon tiles still lift off the darker box.
   Their own fills and CSS animations are untouched. */
.mesh-surface .icon { box-shadow: 0 6px 16px -6px rgba(var(--c-deep-rgb), .75); }

@media (prefers-reduced-motion: reduce) {
  .mesh-surface::before { animation: none; }
}

/* Settings page "Site animations" toggle (Display group) — driven by a
   manual preference instead of the OS/browser setting. html.no-animations is
   added server-side (see system/app.php) only on signed-in app pages where
   the preference is off; it never appears on pages app.php doesn't route
   through, so these rules simply never match there.

   TWO DIFFERENT TREATMENTS, deliberately. ::before is the blurred colour
   wash behind the surface — freezing it (like prefers-reduced-motion does
   above) still leaves a normal-looking coloured card, just without the drift,
   so animation:none is enough. canvas.mesh-layer is the moving dot-and-line
   network drawn on top of it; a single static frame of that reads as a
   tangle of stray lines rather than as a calm background, so it is hidden
   outright instead. Both take effect the instant the class lands — no
   change needed in js/mesh-surface.js. */
html.no-animations .mesh-surface::before { animation: none; }
html.no-animations .mesh-surface > canvas.mesh-layer { display: none !important; }

@media print {
  .mesh-surface { background: none !important; color: var(--ink) !important; }
  .mesh-surface::before,
  .mesh-surface::after,
  .mesh-surface > canvas.mesh-layer { display: none !important; }
  .mesh-surface .label,
  .mesh-surface .value,
  .mesh-surface .sub { color: var(--ink) !important; }
}
