/* ==========================================================================
   Fluid — an Apple-inspired design layer for the whole system.

   WHY THIS IS ONE FILE AND NOT 171 TEMPLATE REWRITES
   Every screen in this codebase already reaches for the same handful of
   idioms: `.card` / `.bg-white.shadow-sm.rounded` for panels, `.btn-*` for
   actions, `.table` for data, `.modal` for dialogs. Restyling those idioms
   restyles every page at once — the same trick the inherited mobile-app
   theme uses, and the reason a design change here does not need a diff in
   127 staff templates.

   Loaded LAST so it wins on specificity ties without !important shouting.

   THE PRINCIPLES IT ENCODES (WWDC "Designing Fluid Interfaces" et al.)
   - Response: feedback on pointer-DOWN, never on release. §1
   - Materials: translucent chrome with content scrolling under it, weight
     encoding hierarchy, never light-glass stacked on light-glass. §12
   - Depth: layered shadows (ambient + direct), not one soft blur. Real
     objects cast two shadows.
   - Typography: tracking is size-specific — tighten as text grows, never
     one letter-spacing for everything. §15
   - Motion: springs, interruptible, from the current on-screen value. The
     CSS here covers non-gesture cases; fluid.js covers the rest. §3/§4
   - Accessibility: reduced motion, reduced transparency and increased
     contrast are three INDEPENDENT signals, each handled. §14
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens
   Spacing and radii are in rem so a larger browser text size scales the
   layout with the text instead of breaking it (Dynamic Type, §15).
   -------------------------------------------------------------------------- */
:root {
  /* Elevation. Two shadows per level: a tight direct shadow for the contact
     edge, a wide ambient one for the room. One blurry shadow reads as fog;
     two read as an object. Larger surfaces get deeper ambient spread. */
  --fl-e1: 0 1px 2px rgba(16, 24, 40, .06), 0 1px 3px rgba(16, 24, 40, .04);
  --fl-e2: 0 1px 3px rgba(16, 24, 40, .07), 0 4px 12px rgba(16, 24, 40, .05);
  --fl-e3: 0 2px 6px rgba(16, 24, 40, .08), 0 12px 28px rgba(16, 24, 40, .08);
  --fl-e4: 0 4px 10px rgba(16, 24, 40, .10), 0 24px 56px rgba(16, 24, 40, .12);

  /* Materials. Bigger surfaces read as thicker: more blur, more saturation. */
  --fl-mat-thin: rgba(255, 255, 255, .62);
  --fl-mat-regular: rgba(255, 255, 255, .74);
  --fl-mat-thick: rgba(255, 255, 255, .86);
  --fl-blur-thin: blur(12px) saturate(170%);
  --fl-blur-regular: blur(20px) saturate(180%);
  --fl-blur-thick: blur(30px) saturate(190%);
  /* The bright top edge is light catching the lip of a real material. */
  --fl-mat-edge: rgba(255, 255, 255, .55);

  --fl-radius-sm: .5rem;
  --fl-radius: .75rem;
  --fl-radius-lg: 1rem;
  --fl-radius-xl: 1.25rem;

  --fl-hairline: rgba(16, 24, 40, .09);
  --fl-ink: #101828;
  --fl-ink-muted: #5a6b7d;

  /* Springs expressed as CSS curves. These are for state changes the user
     cannot grab mid-flight (hover, focus, press). Anything gesture-driven
     must NOT use a CSS transition — it cannot be interrupted and reversed —
     and is handled by fluid.js instead. §3
     --fl-spring overshoots slightly; --fl-settle does not. */
  --fl-spring: linear(
    0, .0064 1.3%, .0248 2.7%, .0932 5.5%, .3363 12.2%, .4409 15.3%, .5323,
    .6151 21.6%, .6889, .7542 27.5%, .8113, .8615 33.6%, .9048, .9418 39.9%,
    .9679 43.4%, .9887 47.3%, 1.0033 51.6%, 1.0126 56.4%, 1.0168 62%,
    1.0146 71.3%, 1.0007 89.2%, 1);
  --fl-settle: cubic-bezier(.32, .72, 0, 1);
  --fl-press: cubic-bezier(.4, 0, .2, 1);

  --fl-t-fast: 110ms;    /* press feedback — must feel instantaneous */
  --fl-t-base: 260ms;
  --fl-t-slow: 400ms;    /* Apple's "response 0.4" for repositioning */
}

/* Dark and colored system themes. Materials invert: glass over a dark room
   is a dark glass, and the catch-light on its edge gets fainter. */
[data-system-theme="dark"],
[data-system-theme="colored"] {
  --fl-mat-thin: rgba(24, 28, 36, .62);
  --fl-mat-regular: rgba(24, 28, 36, .76);
  --fl-mat-thick: rgba(20, 24, 31, .88);
  --fl-mat-edge: rgba(255, 255, 255, .10);
  --fl-hairline: rgba(255, 255, 255, .10);
  --fl-ink: #e9edf3;
  --fl-ink-muted: #9aa8b8;
  /* Shadows do less work in the dark; depth comes from the edge highlight. */
  --fl-e1: 0 1px 2px rgba(0, 0, 0, .34);
  --fl-e2: 0 2px 6px rgba(0, 0, 0, .38);
  --fl-e3: 0 6px 18px rgba(0, 0, 0, .44);
  --fl-e4: 0 16px 44px rgba(0, 0, 0, .54);
}

/* --------------------------------------------------------------------------
   2. Typography — tracking is size-specific, leading is inverse to size
   A single letter-spacing value is wrong somewhere: large text reads too
   loose without negative tracking, small text too tight without positive. §15
   -------------------------------------------------------------------------- */
body {
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  font-optical-sizing: auto;
}

h1, .h1, .display-1, .display-2, .display-3 {
  letter-spacing: -.022em;
  line-height: 1.08;
  font-weight: 700;
}
h2, .h2 { letter-spacing: -.018em; line-height: 1.15; font-weight: 700; }
h3, .h3 { letter-spacing: -.014em; line-height: 1.22; font-weight: 650; }
h4, .h4, h5, .h5 { letter-spacing: -.009em; line-height: 1.3; font-weight: 600; }
h6, .h6 { letter-spacing: -.004em; line-height: 1.35; font-weight: 600; }

/* Small text needs the opposite treatment — a touch of positive tracking so
   the letters do not collide at size. */
small, .small, .form-text, .badge, .text-muted.small {
  letter-spacing: .006em;
}
.table > :not(caption) > * > * { letter-spacing: .002em; }

/* Tabular figures everywhere money or counts appear. Without this, a column
   of rupee amounts jitters as digit widths change — the single most common
   typographic tell of an amateur finance screen. */
.table td, .table th,
.fl-num, [class*="text-end"] {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* --------------------------------------------------------------------------
   3. Surfaces — the codebase's panel idiom, upgraded in place
   -------------------------------------------------------------------------- */
.card,
.bg-white.shadow-sm.rounded,
.bg-white.shadow-sm {
  border-radius: var(--fl-radius-lg);
  border: 1px solid var(--fl-hairline);
  box-shadow: var(--fl-e2);
  background-clip: padding-box;
  transition:
    box-shadow var(--fl-t-base) var(--fl-settle),
    transform var(--fl-t-base) var(--fl-settle),
    border-color var(--fl-t-base) var(--fl-settle);
}

/* Lift on hover only where the surface is actually a target. A static panel
   that rises when the cursor drifts over it is noise, not feedback — so the
   lift is opt-in via .fl-lift (or an anchor/button wrapping a card). */
.fl-lift,
a > .card,
a.card {
  cursor: pointer;
}
.fl-lift:hover,
a > .card:hover,
a.card:hover {
  box-shadow: var(--fl-e3);
  transform: translateY(-2px);
}
/* Press beats hover: the surface goes DOWN and slightly in, like it has been
   pushed into the page. Fires on :active, i.e. pointer-down. §1 */
.fl-lift:active,
a > .card:active,
a.card:active {
  transform: translateY(0) scale(.994);
  box-shadow: var(--fl-e1);
  transition-duration: var(--fl-t-fast);
}

.card-header {
  background: transparent;
  border-bottom: 1px solid var(--fl-hairline);
  font-weight: 600;
  letter-spacing: -.006em;
}

/* --------------------------------------------------------------------------
   4. Buttons — response is the whole game
   Feedback appears on pointer-down and is fast enough to feel like contact,
   not like an animation playing. §1
   -------------------------------------------------------------------------- */
.btn {
  border-radius: var(--fl-radius-sm);
  font-weight: 550;
  letter-spacing: -.005em;
  transition:
    transform var(--fl-t-fast) var(--fl-press),
    box-shadow var(--fl-t-fast) var(--fl-press),
    filter var(--fl-t-fast) var(--fl-press);
}
.btn:hover {
  filter: saturate(1.06) brightness(1.03);
  will-change: transform;   /* hinted only while a press is plausible — see note below */
}
.btn:active {
  transform: scale(.965);
  filter: saturate(1.1) brightness(.97);
}
.btn-lg { border-radius: var(--fl-radius); }
.btn-lg:active { transform: scale(.978); }  /* larger targets move less */
.btn-sm:active { transform: scale(.95); }   /* smaller targets move more */

/* Filled buttons get a contact shadow so they read as sitting ON the page. */
.btn-primary, .btn-success, .btn-danger, .btn-warning, .btn-dark, .btn-info {
  box-shadow: var(--fl-e1);
}
.btn-primary:hover, .btn-success:hover, .btn-danger:hover,
.btn-warning:hover, .btn-dark:hover, .btn-info:hover {
  box-shadow: var(--fl-e2);
}
.btn-primary:active, .btn-success:active, .btn-danger:active,
.btn-warning:active, .btn-dark:active, .btn-info:active {
  box-shadow: none;   /* pressed flush — no gap left to cast a shadow into */
}

/* A focus ring that is obviously a focus ring, and never clipped. */
.btn:focus-visible,
.form-control:focus, .form-select:focus,
.nav-link:focus-visible, a:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3.5px color-mix(in srgb, var(--bs-primary, #0070c0) 28%, transparent);
}

/* --------------------------------------------------------------------------
   5. Chrome as material — content scrolls UNDER, not beside
   §12: nav and toolbars are a floating translucent layer, not an opaque strip
   that eats a fixed band of the viewport.
   -------------------------------------------------------------------------- */
.navbar,
.fl-chrome {
  background-color: var(--fl-mat-regular);
  -webkit-backdrop-filter: var(--fl-blur-regular);
  backdrop-filter: var(--fl-blur-regular);
  border-bottom: 1px solid var(--fl-hairline);
}
/* Bigger surface, thicker material. */
.fl-chrome-thick {
  background-color: var(--fl-mat-thick);
  -webkit-backdrop-filter: var(--fl-blur-thick);
  backdrop-filter: var(--fl-blur-thick);
}
/* The catch-light along the top lip. Only on surfaces that float ABOVE
   content — a bottom bar gets it on its top edge instead. */
.fl-chrome::before,
.fl-sheet::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 1px;
  background: var(--fl-mat-edge);
  pointer-events: none;
}

/* Scroll edge effect: a soft fade where content passes under floating chrome,
   rather than a 1px rule pretending to be a boundary. §12
   Applied by fluid.js only once content actually scrolls under. */
.fl-scrolled {
  border-bottom-color: transparent;
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 10px), transparent);
  mask-image: linear-gradient(to bottom, #000 calc(100% - 10px), transparent);
}

/* Navigation outranks page content, always. Bootstrap gives .dropdown-menu z-index 1000,
   but a sibling that creates its own stacking context can still paint above it. Lifting the
   chrome itself puts the question beyond doubt rather than relying on nothing downstream ever
   creating a stacking context. */
.navbar { position: relative; z-index: 1035; }
.navbar .dropdown-menu { z-index: 1040; }
.fl-3d, .fl-3d-card { z-index: 0; }

/* --------------------------------------------------------------------------
   6. Modals & sheets — materialize, don't fade
   §12: animate blur and scale together so the surface reads as a real
   material arriving, not a rectangle whose opacity changed.
   -------------------------------------------------------------------------- */
.modal-content {
  border: 1px solid var(--fl-hairline);
  border-radius: var(--fl-radius-xl);
  box-shadow: var(--fl-e4);
  background-color: var(--fl-mat-thick);
  -webkit-backdrop-filter: var(--fl-blur-thick);
  backdrop-filter: var(--fl-blur-thick);
  overflow: hidden;   /* keeps the header/footer inside the rounded corner */
}
.modal-header, .modal-footer { border-color: var(--fl-hairline); }

.modal.fade .modal-dialog {
  transform: scale(.94) translateY(12px);
  filter: blur(6px);
  opacity: 0;
  transition:
    transform var(--fl-t-slow) var(--fl-settle),
    filter var(--fl-t-base) var(--fl-settle),
    opacity var(--fl-t-base) var(--fl-settle);
}
.modal.show .modal-dialog {
  transform: none;
  filter: blur(0);
  opacity: 1;
}
/* Dim to focus: a modal blocks, so the room behind it goes back and down. §12 */
.modal-backdrop.show { opacity: .38; }
body.modal-open .fl-dimmable {
  filter: brightness(.96);
  transform: scale(.994);
  transition: filter var(--fl-t-slow) var(--fl-settle),
              transform var(--fl-t-slow) var(--fl-settle);
}

/* --------------------------------------------------------------------------
   7. Data — tables that read as data, not as a spreadsheet grid
   -------------------------------------------------------------------------- */
.table { border-color: var(--fl-hairline); }
.table > thead {
  /* Uppercase micro-label for column heads: it stops the header competing
     with the data for attention, which a bold sentence-case row does. */
  font-size: .72rem;
  text-transform: uppercase;
  letter-spacing: .06em;
  font-weight: 650;
}
.table > thead.table-dark { --bs-table-bg: transparent; }
.table > thead.table-dark th {
  background: color-mix(in srgb, var(--fl-ink) 92%, transparent);
  color: #fff;
  border-bottom: none;
}
.table > thead.table-dark th:first-child { border-top-left-radius: var(--fl-radius-sm); }
.table > thead.table-dark th:last-child { border-top-right-radius: var(--fl-radius-sm); }
.table > tbody > tr { transition: background-color var(--fl-t-fast) var(--fl-press); }
.table-hover > tbody > tr:hover > * {
  background-color: color-mix(in srgb, var(--bs-primary, #0070c0) 5%, transparent);
}
.table > :not(caption) > * > * { padding: .7rem .75rem; }

.badge {
  border-radius: 999px;
  font-weight: 600;
  letter-spacing: .01em;
  padding: .34em .7em;
}

.progress {
  border-radius: 999px;
  background-color: color-mix(in srgb, var(--fl-ink) 8%, transparent);
}
.progress-bar { transition: width var(--fl-t-slow) var(--fl-settle); }

/* --------------------------------------------------------------------------
   8. Forms
   -------------------------------------------------------------------------- */
.form-control, .form-select {
  border-radius: var(--fl-radius-sm);
  border-color: var(--fl-hairline);
  transition: border-color var(--fl-t-fast) var(--fl-press),
              box-shadow var(--fl-t-fast) var(--fl-press);
}
.form-label { font-weight: 600; letter-spacing: -.004em; }

/* Press state for containers that :active cannot serve — table rows, list
   items. Driven by fluid.js, which also cancels it if the pointer drags
   away, the way a real button does. §10 */
.fl-pressed {
  background-color: color-mix(in srgb, var(--bs-primary, #0070c0) 8%, transparent) !important;
  transition: background-color var(--fl-t-fast) var(--fl-press);
}

/* --------------------------------------------------------------------------
   9. Depth — the 2.5D layer
   Cards can tilt toward the pointer. Kept deliberately shallow: the goal is
   for a surface to feel like it has thickness, not to perform a trick. The
   rotation is driven by fluid.js from real pointer position; this is only
   the stage it happens on.
   -------------------------------------------------------------------------- */
.fl-3d { perspective: 900px; }
.fl-3d-card {
  transform-style: preserve-3d;
  transition: transform var(--fl-t-slow) var(--fl-settle),
              box-shadow var(--fl-t-slow) var(--fl-settle);
}
/* `will-change` is a HINT, not a decoration, and it is applied only while the pointer is
   actually over the card.
   Leaving it on permanently did two bad things: it gave every card its own compositor layer
   (memory, for nothing, on a page with forty of them), and — the visible bug — it made each
   card a STACKING CONTEXT. A stacking context paints in document order against its siblings,
   so the dashboard's stat cards rendered on top of the navbar's open dropdown, cutting the
   menu in half. */
.fl-3d-card:hover { will-change: transform; }
/* Lifting a child in Z is what makes the tilt read as depth rather than as a
   flat rectangle being skewed. */
.fl-3d-card > * { transform: translateZ(0.01px); }
.fl-3d-card .fl-3d-raise { transform: translateZ(26px); }
.fl-3d-card:hover { box-shadow: var(--fl-e4); }

/* A specular sheen that tracks the pointer, so the tilt has a light source.
   Positioned by fluid.js via --fl-mx / --fl-my. */
.fl-3d-card::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--fl-t-base) var(--fl-settle);
  background: radial-gradient(
    22rem circle at var(--fl-mx, 50%) var(--fl-my, 50%),
    rgba(255, 255, 255, .28), transparent 42%);
}
.fl-3d-card:hover::after { opacity: 1; }

/* --------------------------------------------------------------------------
   10. Entrance — content arrives, it does not blink into place
   Staggered by fluid.js. Deliberately small: an 8px rise reads as arrival,
   a 40px one reads as a slideshow.

   CRITICAL: the hidden state is gated behind `.fl-js`, which a tiny inline
   script in <head> sets. Without that gate, `opacity: 0` here would make the
   content INVISIBLE for anyone whose JavaScript did not run — a failed load,
   a crawler, a reader mode, a print. Content must never depend on script to
   become visible; the animation is the enhancement, the content is not.
   The class is set inline (not by fluid.js, which is deferred) so there is no
   flash of visible content being hidden after paint.
   -------------------------------------------------------------------------- */
.fl-js .fl-enter {
  opacity: 0;
  transform: translateY(8px);
}
.fl-js .fl-enter-in {
  opacity: 1;
  transform: none;
  transition: opacity var(--fl-t-base) var(--fl-settle),
              transform var(--fl-t-slow) var(--fl-settle);
}

/* Printing a statement or a quotation must never lose a section that simply
   had not been scrolled into view. */
@media print {
  .fl-js .fl-enter { opacity: 1 !important; transform: none !important; }
  .fl-3d-card { transform: none !important; box-shadow: none !important; }
  .fl-3d-card::after { display: none; }
}

/* --------------------------------------------------------------------------
   11. Accessibility — three independent signals, three independent answers
   Reduced motion is NOT reduced feedback: opacity and colour changes that aid
   comprehension stay; it is the vestibular movement that goes. §14
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
  /* Keep the affordance, drop the travel. */
  .btn:active { transform: none; filter: brightness(.94); }
  .fl-lift:hover, a > .card:hover, a.card:hover { transform: none; }
  .fl-3d-card { transform: none !important; }
  .fl-3d-card::after { display: none; }
  .fl-enter { opacity: 1; transform: none; }
  .modal.fade .modal-dialog { transform: none; filter: none; }
}

@media (prefers-reduced-transparency: reduce) {
  :root {
    --fl-mat-thin: #ffffff;
    --fl-mat-regular: #ffffff;
    --fl-mat-thick: #ffffff;
  }
  [data-system-theme="dark"], [data-system-theme="colored"] {
    --fl-mat-thin: #171b22;
    --fl-mat-regular: #171b22;
    --fl-mat-thick: #12161c;
  }
  .navbar, .fl-chrome, .fl-chrome-thick, .modal-content, .fl-sheet {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}

@media (prefers-contrast: more) {
  :root { --fl-hairline: rgba(16, 24, 40, .45); }
  [data-system-theme="dark"], [data-system-theme="colored"] {
    --fl-hairline: rgba(255, 255, 255, .45);
  }
  .card, .bg-white.shadow-sm.rounded, .modal-content, .form-control, .form-select {
    border-width: 1.5px;
  }
  .btn { border-width: 1.5px; }
  .text-muted { color: var(--fl-ink-muted) !important; }
}

/* Coarse pointers have no hover state to speak of, and a tilt driven by a
   finger that is also scrolling fights the scroll. */
@media (hover: none), (pointer: coarse) {
  .fl-3d-card { transform: none !important; }
  .fl-3d-card::after { display: none; }
  .fl-lift:hover, a > .card:hover, a.card:hover { transform: none; }
}
