/* ══════════════════════════════════════════════════════════════════════════
   canon-books.css — the books half of the canon v5 vocabulary.

   A PORT, NOT AN INTERPRETATION: the component extensions of
   public/_canon-books-app-mockup.html, verbatim. That mockup is the spec;
   scripts/canon-diff.js renders it against this file and fails on any pixel
   that moves.

   The one departure is spelling, not design: ten colour literals the mockups
   write inline are declared as --c-* tokens in canon.src.css and referenced
   here, because .stylelintrc.json exempts these files from the spacing rule but
   deliberately not from color-no-hex. Every value is unchanged, which is why
   the differ still reads zero.

   Vocabulary a ledger needs and a day does not: the proportion meter, the
   reconcile match row, the form field, the tinted advice band and the page index.

   Loads AFTER css/canon.css, which declares the layer order. Declaring
   @layer components here slots these rules into the layer canon.css already
   opened, so the utilities layer still outranks them — which is what lets a
   utility in the markup override a component, exactly as it does in the mockup.

   WHY THIS IS NOT PART OF canon.css. The apps reuse short component names for
   different things: .fld is a text input in Books and a tool row in the job
   view, .meta is a flex strip in work and a definition grid in field, and .trk
   and .chk disagree between dispatch and work. Five selectors collide across
   the four extension sets. Merged into one file the last one loaded wins and
   the other app breaks silently — no error, no failing selector, just a
   component quietly wearing someone else's shape. So each app gets its own
   file and a page loads exactly one of them.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── Appended for the money app. Six rules, nothing above is retuned. ───── */

@layer components {
  /* A proportion meter. A progress bar, not a chart: pill radius, sunken ground. */
  .track { display:flex; height:12px; border-radius:999px; overflow:hidden;
           background:var(--c-sunken); border:1px solid var(--c-border); gap:1px; }
  .track > span { display:block; }
  .track.sm { height:7px; }

  /* The reconcile match row: the bank line, then the thing it is probably the
     same as. It is an ACTION, not a record, so it lifts under the pointer. */
  .mrow {
    display:flex; flex-wrap:wrap; align-items:center;
    padding:12px 20px; border-top:1px solid var(--c-border);
    transition:background var(--dur) var(--ease);
  }
  .mrow:first-child { border-top:0; }
  .mrow:hover { background:var(--c-card-2); }
  /* Our guess is drawn quieter than the bank's own words. */
  .ghost-row { color:var(--c-text-2); }

  /* A document field that looks editable without being a form: invisible at
     rest, sunken on hover, accent-edged on focus. Negative margins keep the
     text on the same baseline grid it would sit on as plain copy. */
  .fld {
    display:block; width:100%; min-width:0; box-sizing:border-box;
    background:transparent; border:1px solid transparent; border-radius:4px;
    padding:4px 7px; margin:-4px -7px; font:inherit; color:inherit;
    letter-spacing:inherit; transition:background var(--dur) var(--ease), border-color var(--dur) var(--ease);
  }
  .fld:hover { background:var(--c-sunken); border-color:var(--c-border); }
  .fld:focus { background:var(--c-card); border-color:var(--c-accent-edge);
               box-shadow:0 0 0 3px var(--c-accent-soft); outline:none; }
  .fld.r { text-align:right; }
  .fld::placeholder { color:var(--c-cap); }

  /* One fact plus one action on a tinted wash. Ink is ALWAYS --c-on-wash-*. */
  .band {
    display:flex; align-items:center; gap:10px; flex-wrap:wrap;
    padding:10px 14px; border:1px solid var(--c-border); border-radius:6px;
    background:var(--c-sunken); color:var(--c-text-2); font-size:13px;
  }
  .band > i { font-size:17px; flex:none; }
  .band.accent { color:var(--c-on-wash-accent); border-color:var(--c-accent-edge); background:var(--c-accent-soft); }
  .band.warn   { color:var(--c-on-wash-warn);   border-color:color-mix(in srgb, var(--c-warn) 34%, transparent);   background:color-mix(in srgb, var(--c-warn) 10%, transparent); }
  .band.danger { color:var(--c-on-wash-danger); border-color:color-mix(in srgb, var(--c-danger) 36%, transparent); background:var(--c-danger-soft); }
  .band.ok     { color:var(--c-on-wash-ok);     border-color:color-mix(in srgb, var(--c-ok) 32%, transparent);     background:color-mix(in srgb, var(--c-ok) 10%, transparent); }

  /* A quote option column. The recommended one takes the accent edge. */
  .opt {
    border:1px solid var(--c-border); border-radius:10px; padding:16px;
    background:var(--c-card); display:flex; flex-direction:column; min-width:0;
    transition:border-color var(--dur) var(--ease), background var(--dur) var(--ease);
  }
  .opt:hover { border-color:var(--c-border-2); }
  .opt[data-pick="true"] {
    border-color:var(--c-accent-edge); background:var(--c-accent-soft);
    box-shadow:inset 0 0 0 1px var(--c-accent-edge);
  }

  /* CONTRAST, same fix as the copied block's --c-on-wash-* pair, extended to the
     places the showcase never put small ink on a wash. Measured in light:
     --c-muted as 11px ink on --c-sunken is 3.92:1 (.seg's off state, .pill.draft),
     --c-accent-h is 4.0-4.47:1 and --c-danger 4.27:1 as ink at 10-17px. These six
     declarations swap the INK only; every ground, border and token is untouched.
     Figures at 24px+ keep the raw state colour, which passes at the 3:1 floor. */
  .seg button { color:var(--c-cap); }
  .pill.draft { color:var(--c-cap); }
  .ink-danger { color:var(--c-on-wash-danger); }
  .ink-warn   { color:var(--c-on-wash-warn); }
  .ink-ok     { color:var(--c-on-wash-ok); }
  .ink-accent { color:var(--c-on-wash-accent); }

  /* MOCKUP SCAFFOLDING — the page index in the bottom strip. Not the product. */
  .idx { display:flex; flex-wrap:nowrap; gap:3px; overflow-x:auto; padding-bottom:2px;
         /* overflow-x:auto computes overflow-y to auto as well, so this strip is a
            scrollport. Nothing sticky lives inside it, so that is harmless here. */ }
  .idx button {
    height:22px; padding:0 8px; border-radius:4px; border:1px solid var(--c-border);
    background:var(--c-card); color:var(--c-text-2); font:inherit; font-size:11px;
    font-weight:500; cursor:pointer; white-space:nowrap;
    transition:background var(--dur) var(--ease), color var(--dur) var(--ease);
  }
  .idx button:hover { background:var(--c-card-2); color:var(--c-text); }
  .idx button[aria-pressed="true"] { background:var(--c-cta); border-color:var(--c-cta); color:var(--c-accent-fg); }
}

/* ── Page crossfade (feature_books_crossfade) ──────────────────────────────
   BEHAVIOUR, NOT MOCKUP VOCABULARY — nothing here matches without html.nd-xfade,
   which only the flag sets (scripts/books-xfade-preflight.src.js from a cached
   value before first frame; js/books/books-router.js once the flag is read), so
   the differ never sees it. It lives in this file because this is the one
   stylesheet every Books page loads and no other app does.

   Moving between Books pages is a View Transition — document.startViewTransition
   around the router's DOM swap, or a cross-document one for an ordinary page
   load. By default that crossfades a snapshot of the WHOLE page, rail included,
   which is exactly what the flag exists to stop: the rail is meant to hold
   still while only the content below it changes.

   So the rail is given its own name, which lifts it out of the page snapshot
   into a group of its own, and that group is told to do nothing:

     · both ends have a rail (an SPA hop — it is the same node): the OLD image is
       a frozen screenshot and the NEW one is live. Crossfading them would fade
       out a copy of the underline parked on the tab you left while the real one
       travels, a double marker for the length of the fade. Drop the old image,
       show the live one at full strength, no animation — the rail on screen is
       the real rail throughout, and its own .34s underline transition keeps
       travelling, live, while the content fades beneath it.
     · only the old end has one (a document load: the incoming page builds its
       rail at runtime, after first render): keep the old image, opaque and
       unanimated, over the space the preflight reserves for the new one. The
       preflight holds it until the real rail mounts.

   The page itself crossfades in 160ms, well inside the rail's own transition,
   because a running transition swallows the pointer for as long as it lasts. */
html.nd-xfade #ndBooksNav { view-transition-name: nd-books-rail; }

/* A FADE-THROUGH, not a crossfade. Two Books pages are dense and unrelated
   (tables, KPI bands, charts), and the default simultaneous crossfade puts both
   on screen at half strength — a double exposure, legible as neither. So the
   old page is gone by 70ms and the new one only starts at 50ms: the eye reads
   "out, in" through the page ground, still inside the same 160ms. */
html.nd-xfade::view-transition-group(root) { animation-duration: 160ms; }
html.nd-xfade::view-transition-old(root) { animation: nd-xfade-out 70ms ease-in both; }
html.nd-xfade::view-transition-new(root) { animation: nd-xfade-in 110ms ease-out 50ms both; }
@keyframes nd-xfade-out { to { opacity: 0; } }
@keyframes nd-xfade-in { from { opacity: 0; } }

html.nd-xfade::view-transition-group(nd-books-rail),
html.nd-xfade::view-transition-new(nd-books-rail) { animation: none; mix-blend-mode: normal; }
html.nd-xfade::view-transition-old(nd-books-rail) { display: none; }
html.nd-xfade::view-transition-old(nd-books-rail):only-child { display: block; animation: none; mix-blend-mode: normal; }

/* The incoming page's rail-to-be, held open while books-bar.js builds the real
   one, so the content is revealed where it will stay instead of 54px too high.
   The height is the OUTGOING rail's, measured at pageswap — on a phone the rail
   wraps its section selector beneath it and is taller than --nr-h, which is not
   even defined until rail-canon.css loads. Scoped to nd-nav-pending, the state
   in which the static bar is hidden and nothing else is holding that space. */
html.nd-xfade-hold.nd-nav-pending body::before { content: ""; display: block; height: var(--nd-xfade-rail); }
