/* ═══════════════════════════════════════════════════════════════
   Jarvis inbox card — the object Cole actually looks at
   Loaded after modern-theme.css. Scoped to .msg-jcard*; adds no tokens.
   ═══════════════════════════════════════════════════════════════

   DESIGN POSITION. Interior Moderna's own voice is architectural, quiet,
   gallery-driven — copy recedes, the object commands. The WOW here has to come
   from motion, depth and restraint, not decoration: a gallery wall label, not
   a SaaS notification. This is an internal tool so the storefront copy rules
   don't bind it, but it should read unmistakably as the same company.

   WHAT THE CARD SHOWS. The DECISION, never the raw email. Who, what they want
   in one line, why Jarvis raised it, and the two things Cole can do. The email
   itself lives one disclosure away.

   NO NEW TOKENS AND NO NEW EASINGS. Everything below uses modern-theme.css
   variables and the two curves already in the codebase (--ease-standard,
   --ease-spring). A new easing per component is how an interface starts
   feeling incoherent, and a fourth palette is how a design system dies.

   NEVER EDIT dashboard.css FOR THIS. It owns .icon-btn and .form-input
   globally; reaching into it from a feature is a documented past regression.
   ═══════════════════════════════════════════════════════════════ */

/* ── The card ─────────────────────────────────────────────────── */
.msg-jcard {
  position: relative;
  display: grid;
  gap: var(--space-2, 9px);
  padding: 14px 16px 12px;
  margin: 8px 0 4px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg, 14px);
  box-shadow: var(--shadow-sm);
  /* Only transform/opacity/shadow animate — all compositor properties, so the
     card stays at 60fps and never triggers layout. */
  transition:
    box-shadow 220ms var(--ease-standard),
    transform 220ms var(--ease-standard),
    border-color 220ms var(--ease-standard);
  /* Reserved so the card does not resize as content resolves. Zero layout
     shift is the non-negotiable half of "smooth". */
  min-height: 92px;
  overflow: hidden;
}

@media (hover: hover) {
  .msg-jcard:hover {
    border-color: var(--border-hover);
    box-shadow: var(--shadow-md);
    transform: translateY(-1px);
  }
}

/* The kind, carried by a hairline down the left edge rather than a badge.
   notify = FYI · question = missing information · review = approve something.
   Collapsing those three is what makes ambient systems feel naggy, so they are
   visually distinct — but quietly, at 2px. */
.msg-jcard::before {
  content: '';
  position: absolute;
  inset: 0 auto 0 0;
  width: 2px;
  background: var(--text-dim);
  opacity: 0.5;
}
.msg-jcard--question::before { background: var(--accent); opacity: 1; }
.msg-jcard--review::before   { background: var(--yellow); opacity: 1; }

/* ── Head: who, and when ──────────────────────────────────────── */
.msg-jcard__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
.msg-jcard__who {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-muted);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.msg-jcard__when {
  font-size: 11px;
  color: var(--text-dim);
  flex-shrink: 0;
  font-variant-numeric: tabular-nums;
}

/* ── The ask — the one line that earns the interruption ───────── */
.msg-jcard__ask {
  font-size: 16.5px;
  line-height: 1.35;
  color: var(--text);
  letter-spacing: -0.011em;
  /* Two lines, then ellipsis. A subject long enough to need three lines is a
     subject nobody reads — the full text is one disclosure away. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ── Why Jarvis raised it. Quiet, italic, never the hero. ──────── */
.msg-jcard__why {
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--text-muted);
  font-style: italic;
}

/* An override is the one thing that must never be quiet: it means a rule Cole
   set was deliberately ignored, and a rule that looks like it silently failed
   is worse than no rule. */
.msg-jcard__override {
  display: flex;
  align-items: flex-start;
  gap: 7px;
  font-size: 12.5px;
  line-height: 1.4;
  font-style: normal;
  color: var(--yellow-strong, #854d0e);
  background: var(--yellow-dim);
  border-radius: var(--radius-sm, 6px);
  padding: 7px 10px;
}

/* ── Actions ──────────────────────────────────────────────────── */
/* One row, always (Cole 2026-08-03: "lets make these more compact so look under
   the hood is next to the other buttons to save space"). `flex-wrap: wrap` put
   the disclose on a line of its own the moment the three items outgrew the
   card, which on a normal-width thread is always — costing a whole row of
   height per card on a surface whose entire promise is ≤5 cards you actually
   read. The disclose keeps `margin-left: auto`, so it still sits hard right;
   it just no longer drops. Wrapping returns below 480px, where one row genuinely
   cannot hold three touch targets. */
.msg-jcard__actions {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: nowrap;
  margin-top: 2px;
}
.msg-jcard__actions > * { white-space: nowrap; }
@media (max-width: 480px) {
  .msg-jcard__actions { flex-wrap: wrap; gap: 8px; }
}

.msg-jcard__btn {
  font: inherit;
  font-size: 12.5px;
  font-weight: 500;
  padding: 6px 12px;
  border-radius: var(--radius-sm, 6px);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  cursor: pointer;
  /* 44px on touch: thumb-sized targets are a house rule, and this surface is
     read on the phone PWA more than the desktop. */
  min-height: 32px;
  transition:
    background 160ms var(--ease-standard),
    border-color 160ms var(--ease-standard),
    transform 160ms var(--ease-spring);
}
.msg-jcard__btn:hover { border-color: var(--border-hover); background: var(--surface-2); }
.msg-jcard__btn:active { transform: scale(0.97); }
.msg-jcard__btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.msg-jcard__btn--primary {
  background: var(--text);
  border-color: var(--text);
  color: var(--surface);
}
.msg-jcard__btn--primary:hover { background: var(--accent-strong); border-color: var(--accent-strong); }
.msg-jcard__btn--quiet { border-color: transparent; color: var(--text-muted); }
.msg-jcard__btn--quiet:hover { color: var(--text); background: var(--surface-2); }

@media (pointer: coarse) {
  .msg-jcard__btn { min-height: 44px; padding: 11px 17px; }
}

/* ── Moment 1 · Arrival ───────────────────────────────────────
   Cards SETTLE rather than appear. Transform + opacity only, so the whole
   thing stays on the compositor. The stagger is set inline per card
   (--jcard-i) so several landing together arrive as a sequence, not a flash. */
@keyframes jcardSettle {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}
.msg-jcard--arriving {
  animation: jcardSettle 220ms var(--ease-spring) both;
  animation-delay: calc(var(--jcard-i, 0) * 40ms);
}

/* ── Moment 2 · The confirm ───────────────────────────────────
   The single most important micro-interaction here. In-flight state happens
   IN PLACE — no spinner overlay, no layout shift — and on success the card
   performs one calm collapse into a settled line. It should feel decisive,
   like a physical switch. This is where "smooth" is earned or lost. */
.msg-jcard__btn[aria-busy='true'] {
  color: transparent;
  position: relative;
  pointer-events: none;
}
.msg-jcard__btn[aria-busy='true']::after {
  content: '';
  position: absolute;
  inset: 0;
  margin: auto;
  width: 13px;
  height: 13px;
  border: 1.5px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  color: var(--surface);
  animation: jcardSpin 620ms linear infinite;
}
.msg-jcard__btn--quiet[aria-busy='true']::after,
.msg-jcard__btn:not(.msg-jcard__btn--primary)[aria-busy='true']::after { color: var(--text-muted); }
@keyframes jcardSpin { to { transform: rotate(360deg); } }

.msg-jcard--settled {
  /* grid-template-rows is animatable; height:auto is not. The card folds to a
     single line instead of vanishing, so the thread keeps its history. */
  min-height: 0;
  animation: jcardCollapse 320ms var(--ease-standard) both;
}
@keyframes jcardCollapse {
  from { opacity: 1; }
  to   { opacity: 1; }
}
.msg-jcard--settled .msg-jcard__ask,
.msg-jcard--settled .msg-jcard__why,
.msg-jcard--settled .msg-jcard__actions,
.msg-jcard--settled .msg-jcard__override {
  display: none;
}
.msg-jcard__done {
  display: none;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--green-strong, #166534);
}
.msg-jcard--settled .msg-jcard__done { display: flex; }

/* ── Moment 3 · Under the hood ────────────────────────────────
   grid-template-rows 0fr → 1fr, which IS animatable — unlike height:auto,
   which is the reason most disclosures in the wild just snap. */
.msg-jcard__more {
  display: grid;
  grid-template-rows: 0fr;
  /* A zero-height grid row still earns the card's 12px gap, which read as a
     band of dead space under the actions on every closed card — measured, not
     eyeballed. Cancelling it here is better than dropping the element to
     display:none, because display is not animatable and the disclosure would
     snap instead of opening. */
  margin-top: calc(var(--space-3, 12px) * -1);
  transition:
    grid-template-rows 260ms var(--ease-standard),
    margin-top 260ms var(--ease-standard);
}
.msg-jcard[data-open='1'] .msg-jcard__more { margin-top: 0; }
.msg-jcard__more > div { overflow: hidden; min-height: 0; }
.msg-jcard[data-open='1'] .msg-jcard__more { grid-template-rows: 1fr; }
.msg-jcard__more-body {
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--text-muted);
  white-space: pre-wrap;
  word-break: break-word;
  padding-top: 10px;
  margin-top: 2px;
  border-top: 1px solid var(--border);
  max-height: 340px;
  overflow-y: auto;
}
.msg-jcard__disclose {
  background: none;
  border: 0;
  padding: 4px 0;
  font: inherit;
  font-size: 12px;
  color: var(--text-dim);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-left: auto;
}
.msg-jcard__disclose:hover { color: var(--text-muted); }
/* Last to get space, first to give it up — the two real actions must never be
   the things that shrink. */
.msg-jcard__disclose { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.msg-jcard__disclose::after {
  content: '⌄';
  transition: transform 260ms var(--ease-standard);
  line-height: 1;
}
.msg-jcard[data-open='1'] .msg-jcard__disclose::after { transform: rotate(180deg); }

/* ── Moment 4 · Thinking ──────────────────────────────────────
   A restrained three-dot rhythm in the card's OWN typographic colour, not a
   generic spinner borrowed from somewhere else in the app. */
.msg-jcard__thinking {
  display: inline-flex;
  gap: 4px;
  align-items: center;
  padding: 2px 0;
}
.msg-jcard__thinking span {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--text-dim);
  animation: jcardPulse 1250ms var(--ease-standard) infinite;
}
.msg-jcard__thinking span:nth-child(2) { animation-delay: 160ms; }
.msg-jcard__thinking span:nth-child(3) { animation-delay: 320ms; }
@keyframes jcardPulse {
  0%, 60%, 100% { opacity: 0.28; transform: translateY(0); }
  30%           { opacity: 1;    transform: translateY(-2px); }
}

/* ── Moment 5 · Swipe, on mobile ──────────────────────────────
   Swipe-left to suppress, matching the existing swipe-to-reply vocabulary in
   messages.js so the gesture language stays consistent. */
@media (pointer: coarse) {
  .msg-jcard { touch-action: pan-y; }
  .msg-jcard__swipe {
    position: absolute;
    inset: 0 0 0 auto;
    width: 96px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--surface-3);
    color: var(--text-muted);
    font-size: 12px;
    opacity: 0;
    pointer-events: none;
    transition: opacity 140ms var(--ease-standard);
  }
  .msg-jcard[data-swiping='1'] .msg-jcard__swipe { opacity: 1; }
}
@media (pointer: fine) { .msg-jcard__swipe { display: none; } }

/* ── Moment 6 · The empty state ───────────────────────────────
   The screen Cole sees MOST, if triage works. "Nothing needs you" is the
   normal condition, so it has to feel like a result rather than a blank
   panel — a composed statement, plus the count of what Jarvis handled on his
   behalf so the quiet is evidently earned. */
.msg-jcard-empty {
  display: grid;
  place-items: center;
  gap: 10px;
  text-align: center;
  padding: 64px 24px;
  color: var(--text-muted);
}
.msg-jcard-empty__mark {
  width: 34px;
  height: 34px;
  border: 1px solid var(--border-hover);
  border-radius: 50%;
  display: grid;
  place-items: center;
  color: var(--text-dim);
  font-size: 15px;
}
.msg-jcard-empty__title {
  font-size: 16px;
  color: var(--text);
  letter-spacing: -0.01em;
}
.msg-jcard-empty__sub {
  font-size: 12.5px;
  color: var(--text-dim);
  max-width: 34ch;
  line-height: 1.5;
}

/* ── Dark ─────────────────────────────────────────────────────
   The tokens already flip under [data-theme="dark"]; only the two places
   that lean on a light-surface assumption need saying explicitly. */
[data-theme='dark'] .msg-jcard { background: var(--surface); }
[data-theme='dark'] .msg-jcard__btn--primary {
  background: var(--accent);
  border-color: var(--accent);
  color: #12100e;
}
[data-theme='dark'] .msg-jcard__btn--primary:hover {
  background: var(--accent-strong);
  border-color: var(--accent-strong);
  color: #fff;
}

/* ── Reduced motion ───────────────────────────────────────────
   Honoured in 29 places already in this codebase, and internal-dashboard
   accessibility is a standing priority. Every animation above degrades to an
   instant state change — the STATES all still work, they just stop moving. */
@media (prefers-reduced-motion: reduce) {
  .msg-jcard,
  .msg-jcard__btn,
  .msg-jcard__more,
  .msg-jcard__disclose::after {
    transition: none !important;
  }
  .msg-jcard--arriving,
  .msg-jcard--settled { animation: none !important; }
  .msg-jcard__thinking span { animation: none !important; opacity: 0.6; }
  .msg-jcard__btn[aria-busy='true']::after { animation: none !important; }
  @media (hover: hover) { .msg-jcard:hover { transform: none; } }
}

/* ── Narrow ───────────────────────────────────────────────────
   Mobile-first is a house rule and the PWA is where this gets read. Nothing
   here may ever scroll the page sideways. */
@media (max-width: 560px) {
  .msg-jcard { padding: 15px 16px 14px; border-radius: var(--radius, 10px); }
  .msg-jcard__ask { font-size: 15.5px; }
  /* TWO rows, not three. `flex: 1 1 auto` on every child gave each one its own
     full-width line and made a single decision 272px tall — measured at 375px,
     which is where this actually gets read. The primary takes its own row
     because it is the one thing a thumb should not have to aim for; the two
     secondaries share the next. */
  .msg-jcard__btn--primary { flex: 1 0 100%; }
  .msg-jcard__btn--quiet { flex: 0 1 auto; }
  .msg-jcard__disclose { margin-left: auto; flex: 0 0 auto; }
}

/* ── Needs-you vs handled (2026-08-03) ─────────────────────────────
   Cole: "items that have not been responded to should be highlighted a diff
   color, and i should be able to filter by read/addressed vs not addressed."
   The lifecycle has been in `jarvis_inbox.status` since mig 901; nothing read
   it, so a card dealt with three days ago looked exactly like one that landed
   a minute ago.

   OPEN is the loud state, not handled — an ambient system should draw the eye
   to what is still owed, and go quiet the moment it isn't. `--settled` already
   folds a card to its "✓ Handled" line, so `data-state` only has to carry the
   colour. */
.msg-jcard[data-state='raised'],
.msg-jcard[data-state='new'] {
  border-color: color-mix(in srgb, var(--accent) 38%, var(--border));
  background: color-mix(in srgb, var(--accent) 4%, var(--surface));
}
.msg-jcard[data-state='raised']::before,
.msg-jcard[data-state='new']::before {
  background: var(--accent);
  opacity: 1;
  width: 3px;
}
/* Handled recedes: no ring, no tint, and the left edge drops back to a hairline
   so a scrolled-back thread reads as history rather than a queue. */
.msg-jcard--settled {
  border-color: var(--border);
  background: var(--surface);
}
.msg-jcard--settled::before { background: var(--text-dim); opacity: 0.35; width: 2px; }
@media (hover: hover) {
  .msg-jcard--settled:hover { box-shadow: var(--shadow-sm); transform: none; }
}

/* The filter, in the #jarvis conversation header. */
.msg-jfilter {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 2px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface-2);
  margin-left: 8px;
}
.msg-jfilter-btn {
  font: inherit;
  font-size: 11.5px;
  font-weight: 600;
  line-height: 1;
  padding: 5px 10px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--text-dim);
  cursor: pointer;
  white-space: nowrap;
  transition: background 160ms var(--ease-standard), color 160ms var(--ease-standard);
}
.msg-jfilter-btn:hover { color: var(--text); }
.msg-jfilter-btn.is-on { background: var(--surface); color: var(--text); box-shadow: var(--shadow-sm); }
.msg-jfilter-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
@media (pointer: coarse) { .msg-jfilter-btn { padding: 8px 12px; } }

/* Filtered out — the whole reply row, so no empty Jarvis bubble is left behind. */
.msg-reply--jfiltered { display: none !important; }
