/* ============================================================================
   screens/cases.css - the ticket board

   Owns three things and nothing else:
     1. the screen's flex skeleton (toolbar + stage),
     2. the kanban (.kb / .kcol / lane dots); the list view is dataTable(),
     3. the small screen-local atoms: the filter chip that wraps a <select>,
        the toolbar summary line, the mobile group wrapper.

   Everything else - cards, badges, tags, pills, buttons, the empty state - is
   the shared vocabulary from components.css. This file adds no colour of its
   own: every value is a token from tokens.css.

   Class names are cases-only (.cases*, .kb, .kcol*, .ct-*, .cfilter*), so
   nothing here can leak onto another screen even though the file is linked
   globally from index.html. The one exception is deliberate and scoped:
   `.cases-msearch`. Two former exceptions moved to components.css, where they
   belonged: the disabled .icon-btn look and the .fchip-n count.

   Motion. cases.js rebuilds the whole stage on every render - a filter
   keystroke, a view switch, a claim - so every entrance here is an animation
   with `backwards` fill (held through its delay, released at its end, as
   components.css explains) that starts the moment the node exists; nothing
   waits for a class JS might not add. The keyframes are the shared rise-in /
   fade-in from components.css. Order comes from the `--i` property cases.js writes
   on every card and row (its index, capped at 12), turned into a delay with
   the --stagger token; the card entrance itself is components.css's .kcard
   rule, this file only replaces its positional guess with the real index.
   Every duration is a --t-* token, so base.css's reduced-motion block can
   collapse all of it by overriding the tokens.
   ============================================================================ */


/* ---------------------------------------------------------------------------
   1. Screen skeleton

   FIX: the prototype hung the board inside an unclassed
   <div style="height:calc(100% - 56px)">. 56px was a guess at the toolbar's
   height - which the toolbar never declared, and which grew the moment its
   left-hand group wrapped, so the board either overflowed or left a gap.
   The height is now nobody's business: the toolbar is flex:none, the stage is
   flex:1 with min-height:0, and the arithmetic disappears.
   --------------------------------------------------------------------------- */

.cases{
  display: flex;
  flex-direction: column;
}

.cases > .tbar{
  flex: none;
}

.cases-stage{
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  min-width: 0;
  /* A floor rather than 0: on a very short window the lanes stop shrinking and
     .content - the screen's real scroller - takes over. Lanes still scroll
     internally, so nothing is ever unreachable. */
  min-height: 260px;
}


/* ---------------------------------------------------------------------------
   2. Toolbar - filter chips and the derived summary line
   --------------------------------------------------------------------------- */

.cases-filters{
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
  min-width: 0;
}

/* A <label> wearing .chip, wrapping a real <select>. Implicit labelling means
   no id is needed, so the desktop toolbar and the mobile header can render the
   same control twice without colliding. */
.cfilter{
  gap: var(--s-2);
  padding-block: var(--s-2);
  transition: background var(--t-fast), border-color var(--t-fast), box-shadow var(--t-fast);
}

/* The same quiet hover the shared button.chip gets, so the two chip kinds on
   this toolbar answer the pointer alike. */
.cfilter:hover{
  background: var(--gray-50);
}

/* Small-caps label: 600 at .08em tracking is the app's one label voice. */
.cfilter-l{
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--muted);
}

.cfilter-s{
  max-width: 168px;
  min-width: 0;
  padding: 0;
  border: 0;
  background: transparent;
  font-family: var(--f-ui);
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1.3;
  color: var(--navy);
  text-overflow: ellipsis;
  appearance: none;
  cursor: pointer;
}

/* The ring moves to the chip so the whole control reads as focused. That
   replacement is what licenses dropping the select's own ring below. The
   transition above is what lets the ring fade in rather than snap. */
.cfilter:focus-within{
  border-color: var(--gold-600);
  box-shadow: var(--focus-ring);
}

.cfilter-s:focus-visible{
  outline: none;
  box-shadow: none;
}


.cases-summary{
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--s-3);
  min-width: 0;
}

.cases-count{
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
  font-size: 12.5px;
  color: var(--muted);
}

.cases-sep{
  color: var(--gray-400);
}


/* ---------------------------------------------------------------------------
   3. Kanban

   FIX: .kcol declared no overflow and no min-height, so a lane holding more
   cards than fit spilled them straight through its own rounded background with
   no scrollbar in sight. Each lane is now its own scroller, and because a grid
   item defaults to min-height:auto it needs the explicit 0 to be allowed to be
   shorter than its contents.
   --------------------------------------------------------------------------- */

/* The board is a fresh node on every render, so this one fade is both the
   Board -> List cross-fade and the settle after a filter change. */
.kb{
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s-4);
  flex: 1 1 auto;
  min-height: 0;
  animation: fade-in var(--t-base) backwards;
}

/* A lane is a soft well: the canvas tone, a hairline, the panel radius. The
   white cards inside carry the contrast; the lane itself stays quiet. */
.kcol{
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  min-height: 0;                    /* FIX - allow the lane to be shorter than its cards */
  overflow: auto;                   /* FIX - and to scroll them instead of spilling */
  overscroll-behavior: contain;     /* a lane at its end does not scroll the page behind it */
  padding: var(--s-3);
  border: 1px solid var(--gray-200);
  border-radius: var(--r-lg);
  background: var(--gray-50);
}

/* The lane header rides along at the top of its own scroller, so you can still
   see which lane you are in with twenty cards below you. Its padding-bottom
   plus a matching negative margin cancel the flex gap, so the background covers
   the strip a card scrolls through on its way underneath. */
.kcol-h{
  position: sticky;
  top: 0;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: var(--s-2);
  padding: 2px var(--s-1) var(--s-3);
  margin-bottom: calc(var(--s-3) * -1);
  background: var(--gray-50);
}

.kcol-t{
  display: flex;
  align-items: center;
  gap: var(--s-2);
  min-width: 0;
  font-family: var(--f-ui);
  font-size: 13px;
  font-weight: 600;
  color: var(--navy);
}

/* Lane dots. .dot paints itself with currentColor, so the tint is a colour
   declaration and never a second background rule. */
.kdot--new  { color: var(--gray-500); }
.kdot--prog { color: var(--gold); }
.kdot--done { color: var(--green); }

/* Cards cascade into their lane. components.css gives every .kcard its
   rise-in and a positional nth-child guess; here the lane header is the first
   child, so the guess runs one step late. cases.js writes the card's real
   index as `--i`, and this 0,2,0 rule outranks the :where() guess, so a card
   re-rendered in the next lane after a claim arrives at the delay of its new
   position - and never snaps. */
.kcol > .kcard{
  animation-delay: calc(var(--i, 0) * var(--stagger));
}


/* ---------------------------------------------------------------------------
   4. Resolved cards

   FIX: `.kcard.is-done{opacity:.72}` dimmed the entire subtree, including the
   11px --gray-500 meta line, which lands near 2.6:1 against white - below the
   3:1 floor for any text, let alone small text. "Resolved" is now carried by
   three channels that cost no contrast: a green edge, a badge that says the
   word, and the check glyph inside it. Every character on the card stays at
   full strength.
   --------------------------------------------------------------------------- */

.kcard--resolved{
  box-shadow: inset 3px 0 0 var(--green), var(--sh-sm);
}

/* components.css restores the ring on .kcard:focus-visible (0,2,0); this keeps
   the green edge as well when a resolved card takes focus after a claim. */
.kcard--resolved:focus-visible{
  box-shadow: var(--focus-ring), inset 3px 0 0 var(--green), var(--sh-sm);
}

/* A resolved card that lifts keeps its edge under the hover shadow. */
.kcard--resolved:hover{
  box-shadow: inset 3px 0 0 var(--green), var(--sh-md);
}


/* ---------------------------------------------------------------------------
   5. List view

   The prototype's `Board | List` segment had no List markup anywhere in the
   file, so the control could not do anything at all. The list is one table
   rendered from the same array the board uses.
   --------------------------------------------------------------------------- */

/* The other half of the Board / List cross-fade: whichever container is
   rendered fades in, so the switch never flashes. */
.cases-list{
  flex: 1 1 auto;
  min-height: 0;
  animation: fade-in var(--t-base) backwards;
}

/* The list view is dataTable() since September 2026; the hand-built .ctable
   rules that described a second table style were removed on 22 October 2026. */
.ct-id{
  font-family: var(--f-mono);
  font-size: 11.5px;
  font-weight: 500;
  color: var(--muted);
}

/* The one column allowed to wrap - a ticket title is the row's real content. */
.ct-title{
  min-width: 260px;
  white-space: normal;
  font-weight: 600;
  color: var(--navy);
}

.ct-age{
  font-family: var(--f-mono);
  font-size: 11.5px;
  color: var(--gray-600);
}

.ct-who{
  text-align: right;
}

.ct-person{
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  color: var(--navy);
}



/* ---------------------------------------------------------------------------
   6. Empty state

   The fade is the shared .empty rule's; only the sizing is local.
   --------------------------------------------------------------------------- */

.cases-empty{
  flex: 1 1 auto;
  min-height: 0;
  gap: var(--s-3);
}

.cases-empty .note{
  max-width: 44ch;
}


/* ---------------------------------------------------------------------------
   7. Mobile

   The same cases as compact cards, grouped by the same three statuses the
   board uses. .phead is a column with its own gap, so the search field only
   has to opt out of the desktop flex basis it inherits from .tb-search.
   --------------------------------------------------------------------------- */

.cases-msearch{
  flex: none;
}

.cases-group{
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  min-width: 0;
}

/* The compact cards cascade too. cases.js keeps `--i` running across the
   groups, so the first card of "In progress" follows the last card of "New"
   instead of racing it - something a per-group nth-child count cannot do. */
.cases-group > .kcard{
  animation-delay: calc(var(--i, 0) * var(--stagger));
}


/* ---------------------------------------------------------------------------
   8. Print

   print.css already flattens .kb and .kcol into stacked sections; these are the
   screen-local leftovers it cannot know about. Anything that scrolls or sticks
   on screen has to lie flat on paper or it prints as a single clipped frame,
   and anything that animates in has to already be there.
   --------------------------------------------------------------------------- */

@media print{

  .cases{
    display: block;
  }

  .cases-stage{
    display: block;
    min-height: 0;
  }

  .kcol-h{
    position: static;
    margin-bottom: 0;
  }

  /* An entrance caught at its first frame would print as a blank row. */
  .kb,
  .cases-list{
    animation: none;
  }

  /* Controls do not print, and the summary would read as a claim about a page
     the reader cannot filter. */
  .cases-filters,
  .cases-summary{
    display: none;
  }
}


/* ---------------------------------------------------------------------------
   Touch floor for the card title, phones only.

   .kcard-open carried no rule at all, so it inherited the browser default and
   came out 18px tall: a one line title on a 390px screen was a 292 by 18 pixel
   target. It is the primary action of the card, the thing a technician taps to
   open a case while standing in a corridor. Measured on 11 September 2026 at
   320, 390 and 430px.

   The title is not made taller, it is given room around itself, so the card
   rhythm on the board is unchanged above the breakpoint.
   --------------------------------------------------------------------------- */
@media (max-width: 860px){
  .kcard-open{
    display: flex;
    align-items: center;
    width: 100%;
    min-height: 44px;
    text-align: left;
  }
}


/* ---------------------------------------------------------------------------
   The mobile New case button.

   cases.js has been rendering <button class="cases-fab"> into the fab slot
   since the screen was written, and no stylesheet ever claimed that class. The
   result on a phone was a naked 18 by 20 pixel plus sign floating in the bottom
   corner: the primary action of the busiest screen in the app, rendered as
   browser default. Found on 11 September 2026 by measuring every control at
   320, 390 and 430px.

   Written to match .cl-fab on Clients and .iv-fab on Inventory exactly, so the
   three read as one component rather than three drifting copies. It clears the
   tab bar the same way they do, through the tokens rather than a literal.
   --------------------------------------------------------------------------- */
.cases-fab{
  position: fixed;
  right: calc(var(--s-4) + var(--safe-r));
  bottom: calc(var(--bottombar-h) + var(--safe-b) + var(--s-4));
  z-index: 44;
  display: none;
  align-items: center;
  justify-content: center;
  width: var(--fab-h);
  height: var(--fab-h);
  border: 0;
  border-radius: 999px;
  background: var(--gold);
  color: var(--navy-900);
  box-shadow: var(--sh-lg);
  cursor: pointer;
  touch-action: manipulation;
  transition: transform var(--t-base), background var(--t-fast);
}

.cases-fab .ic{
  width: 22px;
  height: 22px;
}

.cases-fab:hover{
  background: var(--gold-600);
}

.cases-fab:active{
  transform: scale(.94);
}

@media (max-width: 860px){
  /* The floating button only exists where the tab bar it clears also exists. */
  .cases-fab{
    display: inline-flex;
  }
}

/* ------------------------------------------------------------------ *
 * Case detail drawer (.cd-*)
 *
 * The drawer the board and the list open for one case. Its markup is built
 * in cases.js detailBody(); until 28 September 2026 no rule styled it, so the
 * facts ran together as a plain stack and the client's area stuck to its
 * name. Same vocabulary as the Clients detail: small uppercase keys over
 * their values, hairlines between facts, one bordered row for the client.
 * ------------------------------------------------------------------ */
.cd{ display:flex; flex-direction:column; gap:var(--s-5); min-width:0; }

.cd-head{ display:flex; flex-direction:column; gap:var(--s-2); min-width:0; }
.cd-t{ margin:0; font-size:17px; line-height:1.35; font-weight:600; color:var(--navy); overflow-wrap:anywhere; }
.cd-badges{ display:flex; flex-wrap:wrap; align-items:center; gap:var(--s-2); }

.cd-actions{ display:flex; flex-wrap:wrap; gap:var(--s-2); }

.cd-client{
  display:flex;
  align-items:center;
  gap:var(--s-3);
  padding:var(--s-3) var(--s-4);
  border:1px solid var(--gray-200);
  border-radius:var(--r);
  background:var(--white);
  color:inherit;
  text-decoration:none;
  transition:border-color var(--t-fast);
}
.cd-client:hover{ border-color:var(--gray-300); }
.cd-client-b{ flex:1; min-width:0; display:flex; flex-direction:column; gap:2px; }
.cd-client-b b{ color:var(--navy-900); overflow-wrap:anywhere; }

.cd-facts{
  display:grid;
  grid-template-columns:repeat(auto-fit, minmax(220px, 1fr));
  gap:0 var(--s-5);
  margin:0;
}
.cd-fact{
  display:flex;
  flex-direction:column;
  gap:2px;
  min-width:0;
  padding:var(--s-2) 0;
  border-bottom:1px solid var(--gray-100);
}
.cd-fact dt{
  font-size:11px;
  font-weight:600;
  letter-spacing:.06em;
  text-transform:uppercase;
  color:var(--gray-600);
}
.cd-fact dd{ margin:0; color:var(--ink); overflow-wrap:anywhere; }

.cd-sec{ display:flex; flex-direction:column; gap:var(--s-2); min-width:0; }
.cd-body{ margin:0; line-height:1.55; white-space:pre-line; overflow-wrap:anywhere; }

.cd-panel{ padding-top:var(--s-3); }
.cd-comments,
.cd-activity{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:var(--s-4); }

.cd-comment{ display:flex; align-items:flex-start; gap:var(--s-3); }
.cd-comment-b{ flex:1; min-width:0; display:flex; flex-direction:column; gap:var(--s-1); }
.cd-comment-h{ margin:0; display:flex; flex-wrap:wrap; align-items:center; gap:var(--s-2); }
.cd-comment-t{ margin:0; line-height:1.55; white-space:pre-line; overflow-wrap:anywhere; }
.cd-comment--internal .cd-comment-b{
  padding:var(--s-2) var(--s-3);
  border-radius:var(--r-sm);
  background:var(--gold-100);
}
.cd-composer{ margin-top:var(--s-4); }

.cd-act{ display:flex; align-items:flex-start; gap:var(--s-3); }
.cd-act-dot{
  flex:none;
  width:8px;
  height:8px;
  margin-top:7px;
  border-radius:50%;
  background:var(--gray-300);
}
.cd-act-b{ flex:1; min-width:0; }
.cd-act-t{ margin:0; overflow-wrap:anywhere; }
