/* BonBonBot Console: what it says about the thing you just did.
   ===================================================================
   The behaviour and the reasons for it are in src/toast.ts and
   src/toast-view.ts. This file is only what a toast looks like.

   The whole border carries the tone, never a bar down one edge
   (docs/10, and the rail is the one thing that is never done here).
   One custom property, --cbc-toast-tone, is what the border, the mark and
   the draining hairline all read, so a tone is one line to add. */

.cb-toast-stack {
  position: fixed;
  /* The launcher keeps the corner; toasts start on the first free line above
     it (--cbc-dock-lift, console-theme.css). Written as the shared property
     rather than as this file's own number, which is how this and the save sign
     came to disagree by eight pixels. */
  inset-block-end: var(--cbc-dock-lift);
  inset-inline-end: var(--cbc-dock-inset);
  z-index: var(--cbc-z-toast);
  display: grid;
  gap: var(--cb-space-lg);
  justify-items: end;
  inline-size: min(var(--cb-width-xs), calc(100% - var(--cb-space-7xl)));
  /* The column is mostly empty air above the toasts. Without this it swallows
     every click on the right-hand third of whatever screen is open. */
  pointer-events: none;
}
.cb-toasts {
  /* The corner belongs to the stack. A region is a row inside it, so that the
     polite one and the loud one sit in one column instead of on top of each
     other. */
  position: static;
  display: grid;
  gap: var(--cb-space-lg);
  justify-items: end;
  inline-size: 100%;
}
/* An empty region still makes a grid row, and a row still takes a gap either
   side of it, so a stack holding nothing but an error floated a notch away
   from the corner that everything else sits in. */
.cb-toasts:empty {
  display: none;
}

/* ---- Sharing the corner with the layout sign ----
   views/layout-store.ts puts a small pill in this same corner while a
   dashboard arrangement is saving. It is not a toast and should not become
   one: it fires on every card somebody drags, and a full toast per drag would
   be a console shouting about furniture.

   So the two coexist, and the stack is the one that moves. The sign keeps the
   corner it has always had, and the toasts start above it for as long as it is
   there, which is a second or two. Lifting the sign instead would mean
   guessing the height of a stack that changes as toasts arrive and leave. */
body:has(.cb-saver:not([hidden])) .cb-toast-stack {
  inset-block-end: calc(var(--cbc-dock-lift) + var(--cb-space-6xl));
}

/* ---- One toast ----
   Newest nearest the corner, so the one that just arrived is where the eye
   already is, and the oldest (the next to go) is the one at the top: it leaves
   without moving anything below it. */
.cb-toast {
  --cbc-toast-tone: var(--cbc-line-strong);
  pointer-events: auto;
  position: relative;
  inline-size: 100%;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: var(--cb-space-lg);
  align-items: start;
  padding: var(--cb-space-lg) var(--cb-space-xl);
  border: 1px solid var(--cbc-toast-tone);
  border-radius: var(--cb-radius-2xl);
  background: var(--cbc-raised);
  color: var(--cbc-ink);
  box-shadow: var(--cbc-shadow);
  /* The track sits on the bottom edge and has to take the corners with it. */
  overflow: hidden;
  /* Horizontal is the dismiss gesture; vertical still scrolls the page. */
  touch-action: pan-y;
  /* The drag is `translate` and the animations are `transform`, which are two
     properties rather than one fight over the same one: they compose, so a
     toast thrown to the edge carries on out from where the finger left it
     instead of springing back to the start of the exit. */
  translate: calc(var(--cbc-toast-drag, 0) * 100%) 0;
  transition: translate var(--cb-motion-fast) var(--cbc-spring);
  /* backwards, not both. Nothing should hold a finished animation's last frame
     in front of the rules underneath it. */
  animation: cbc-toast-in var(--cb-motion-base) var(--cbc-spring) backwards;
}
.cb-toast[data-tone="success"] {
  --cbc-toast-tone: var(--cbc-good);
}
.cb-toast[data-tone="warning"] {
  --cbc-toast-tone: var(--cbc-warn);
}
.cb-toast[data-tone="error"] {
  --cbc-toast-tone: var(--cbc-poor);
}
.cb-toast[data-tone="progress"] {
  --cbc-toast-tone: var(--cbc-accent);
}
/* Nothing follows the finger with a transition on it: the toast has to sit
   under the pointer exactly, or the drag feels like it is being argued with. */
.cb-toast[data-dragging] {
  transition: none;
  cursor: grabbing;
}
.cb-toast[data-leaving] {
  pointer-events: none;
  animation: cbc-toast-out var(--cb-motion-fast) var(--cbc-spring) both;
}

.cb-toast-mark {
  color: var(--cbc-toast-tone);
  /* On the first line of the title rather than in the middle of the card: a
     mark centred against two lines of text points at the gap between them. */
  margin-block-start: var(--cb-space-xxs);
}
.cb-toast-mark .cb-icon {
  inline-size: var(--cb-space-xl);
  block-size: var(--cb-space-xl);
}
/* linear, and that is the one easing exemption in this console: a spinner is a
   continuous rotation with no start and no end to shape, so a spring makes it
   lurch once per turn. The DURATION is a token like every other. */
.cb-toast-spin {
  animation: cbc-spin var(--cbc-loop) linear infinite;
}

.cb-toast-words {
  min-inline-size: 0;
}
.cb-toast-line {
  display: flex;
  align-items: baseline;
  gap: var(--cb-space-sm);
}
.cb-toast-title {
  min-inline-size: 0;
  font-size: var(--cb-type-sm-size);
  line-height: var(--cb-type-sm-line-height);
  font-weight: var(--cb-font-weight-medium);
  /* Clipped rather than allowed to grow: a stack of toasts is a corner of the
     screen, not a column of paragraphs. The whole of a clipped line is on its
     own tooltip, so nothing is lost, only folded away. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 1;
  line-clamp: 1;
  overflow: hidden;
}
/* A title with nothing under it may have the line the body would have used. */
.cb-toast-title[data-lines="2"] {
  -webkit-line-clamp: 2;
  line-clamp: 2;
}
.cb-toast-count {
  flex: none;
  font-size: var(--cb-type-xs-size);
  font-variant-numeric: tabular-nums;
  color: var(--cbc-ink-faint);
}
.cb-toast-body {
  margin-block-start: var(--cb-space-xxs);
  font-size: var(--cb-type-xs-size);
  line-height: var(--cb-type-xs-line-height);
  color: var(--cbc-ink-muted);
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
}
/* A declared display beats the browser's own rule for [hidden], so a toast with
   nothing to say underneath it still carried an empty paragraph and its margin
   until this said otherwise. */
.cb-toast-body[hidden] {
  display: none;
}
.cb-toast-doing {
  margin-block-start: var(--cb-space-md);
  /* Pulled back to the text above it: a quiet button carries a field's padding,
     which would otherwise indent the label past the words it belongs to. */
  margin-inline-start: calc(var(--cb-space-xl) * -1);
}

/* The x. Quiet until the pointer is on the toast, because a toast that arrives
   already showing its own dismiss button is a toast asking to be dealt with. */
.cb-toast-shut {
  display: grid;
  place-items: center;
  padding: var(--cb-space-xs);
  border: 0;
  border-radius: var(--cb-radius-sm);
  background: none;
  color: var(--cbc-ink-faint);
  cursor: pointer;
  opacity: 0;
  transition:
    opacity var(--cb-motion-fast) var(--cbc-spring),
    color var(--cb-motion-fast) var(--cbc-spring);
}
.cb-toast:hover .cb-toast-shut,
.cb-toast:focus-within .cb-toast-shut,
.cb-toast-shut:focus-visible {
  opacity: 1;
}
.cb-toast-shut:hover {
  color: var(--cbc-ink);
  background: var(--cbc-hover);
}
.cb-toast-shut .cb-icon {
  inline-size: var(--cb-space-lg);
  block-size: var(--cb-space-lg);
}

/* ---- The countdown ----
   A hairline draining along the bottom edge, so a toast leaving is never a
   surprise. It is the tone's own colour: the same fact, said in the same
   place, rather than a second colour to learn. */
.cb-toast-track {
  position: absolute;
  inset-block-end: 0;
  inset-inline: 0;
  block-size: 2px;
  background: var(--cbc-line);
}
.cb-toast-fill {
  display: block;
  block-size: 100%;
  background: var(--cbc-toast-tone);
  transform-origin: left;
  transform: scaleX(var(--cbc-toast-left, 1));
}

.cb-toast-more {
  font-size: var(--cb-type-xs-size);
  line-height: var(--cb-type-xs-line-height);
  color: var(--cbc-ink-faint);
  padding-inline-end: var(--cb-space-lg);
}

/* One entrance, from the corner the stack lives in, and a shorter exit in the
   same direction. Nothing scales and nothing bounces: it arrives and settles. */
@keyframes cbc-toast-in {
  from {
    opacity: 0;
    transform: translate3d(var(--cb-space-2xl), 0, 0);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
@keyframes cbc-toast-out {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
    transform: translate3d(var(--cb-space-2xl), 0, 0);
  }
}

/* Somebody who has asked their system to stop animating things gets the
   working page. console-theme.css already flattens every duration on the page
   to a millisecond, which is what turns the slide into an appearance; the
   animations are left in place rather than removed because the exit is what
   tells the engine the element can go, and an animation that never runs never
   ends. What is taken away here is the drain, since a hairline crawling for
   nine seconds is the animation somebody asked not to see, and the toast
   leaves at the same moment either way. */
@media (prefers-reduced-motion: reduce) {
  .cb-toast-track {
    display: none;
  }
  .cb-toast-shut {
    opacity: 1;
  }
}

/* 768px mirrors tokens.breakpoint.md */
@media (max-width: 768px) {
  .cb-toast-stack {
    inset-block-end: var(--cb-space-xl);
    inset-inline: var(--cb-space-xl);
    inline-size: auto;
  }
  /* Nothing to hover with, so the x is simply there, and big enough to hit. */
  .cb-toast-shut {
    opacity: 1;
    padding: var(--cb-space-md);
  }
}
