/* ═══════════════════════════════════════════════════════════════════════════════
   DISPLACEMENT — how things make room for each other.

   When a panel opens or a "Saved" note appears, everything below it moves. Done
   with `display:none` → `block` that move is a jump: the page is one height on
   one frame and another height on the next, and the eye reads it as a glitch
   rather than as a thing arriving.

   Two techniques, and the choice between them is not stylistic:

     .ciab-reveal   grid-template-rows 0fr → 1fr. Animates to the element's OWN
                    height, whatever that turns out to be, so nothing has to guess
                    a max-height. Use it wherever the content's height is unknown
                    or variable.

     max-height     for one short line of status text, where the ceiling is known
                    and a wrapper element would be markup added purely to animate.

   The old .hw-savenote did the second with a hard 34px, which silently clips a
   two-line message. Everything here is either measured or generous.
   ═══════════════════════════════════════════════════════════════════════════════ */

/* ── A panel that pushes the page down ────────────────────────────────────────
   Requires ONE wrapper element around the content:
     <div class="ciab-reveal"><div> …anything… </div></div>
   and .open toggled on the outer. The inner needs min-height:0 or grid refuses
   to shrink it below its content, which is the whole mechanism. */
.ciab-reveal{ display:grid;grid-template-rows:0fr;opacity:0;
  transition:grid-template-rows .3s cubic-bezier(.2,.8,.2,1),
             opacity .2s ease,
             margin .3s cubic-bezier(.2,.8,.2,1); }
.ciab-reveal > *{ overflow:hidden;min-height:0; }
.ciab-reveal.open{ grid-template-rows:1fr;opacity:1; }

/* ── Status text that appears in place ────────────────────────────────────────
   ":not(:empty)" is the trigger, which matters: every call site already sets
   .textContent and clears it again, so this needs no JavaScript and no wrapper.
   Setting text expands it; clearing text collapses it; both push what follows
   instead of snapping it.

   The ceiling is 4em rather than one line's worth. A save error can be a
   sentence, and a message clipped mid-word to preserve an animation is a worse
   outcome than the animation ending slightly early. */
.status,.hw-msg,.hw-savenote,.cx-msg{
  display:block;max-height:0;opacity:0;overflow:hidden;
  transition:max-height .28s cubic-bezier(.2,.8,.2,1),opacity .2s ease,
             margin-top .28s cubic-bezier(.2,.8,.2,1); }
.status:not(:empty),.hw-msg:not(:empty),.hw-savenote:not(:empty),.cx-msg:not(:empty){
  max-height:4em;opacity:1; }

/* Inline status — the ones that sit ON a row beside a control rather than under
   a card — must stay inline or they break the row onto two lines. They fade
   rather than displace, because there is nothing below them to push. */
.hw-head .hw-msg,.hw-headact .hw-msg,.save-bar .hw-msg,.set-row .hw-msg{
  display:inline;max-height:none;overflow:visible; }

/* ── Cards and rows arriving in a list ────────────────────────────────────────
   Used by lists that paint asynchronously, so a row that lands late eases in
   instead of appearing mid-scroll. */
.ciab-fade-in{ animation:ciabFadeIn .24s cubic-bezier(.2,.8,.2,1) both; }
@keyframes ciabFadeIn{ from{ opacity:0;transform:translateY(-4px) } to{ opacity:1;transform:none } }

@media (prefers-reduced-motion:reduce){
  .ciab-reveal,.status,.hw-msg,.hw-savenote,.cx-msg{ transition:none; }
  .ciab-fade-in{ animation:none; }
}
