/*
 * Motion.
 *
 * Kept apart from the layout stylesheet because it is a different kind of
 * decision. Everything here is about how the interface feels while it changes,
 * and none of it is needed for the interface to work — which is exactly why it
 * should be possible to read it, judge it, or delete it in one place.
 *
 * The rules it follows:
 *
 *   1. Nothing longer than 400ms. A transition the person has to wait for is a
 *      transition that has become a delay.
 *   2. Motion explains where something came from, or it does not happen. A panel
 *      that fades in from nothing tells you nothing; one that rises 8px tells
 *      you it belongs to the thing you just clicked.
 *   3. Everything is off under `prefers-reduced-motion`. Not shortened —
 *      removed. The interface still has to work for somebody who gets motion
 *      sick, and it does, because none of this carries meaning on its own.
 */

/* ------------------------------------------------------------- baseline */

/*
 * A single transition for everything clickable.
 *
 * Declared once here rather than repeated on forty selectors. The properties
 * are the ones that read as responsiveness — colour, border, shadow, position —
 * and deliberately not `all`, which would also animate layout and turn an
 * innocent style change into a reflow every frame.
 */
button,
a,
summary,
input,
select,
.project-btn,
.conv,
.material,
.example,
.version,
.site-card,
.step,
.acct-link,
.ad-btn,
.lp-btn,
.auth-tab {
  transition:
    color 0.18s var(--ease),
    background-color 0.18s var(--ease),
    border-color 0.18s var(--ease),
    box-shadow 0.22s var(--ease),
    transform 0.18s var(--ease-out),
    opacity 0.18s var(--ease);
}

/* --------------------------------------------------------- entrance */

/*
 * Panels arrive in order, from the outside in.
 *
 * The stagger is 40ms, which is below the threshold where a sequence reads as
 * slow, and enough that the eye follows the order rather than seeing three
 * things appear at once.
 */
@keyframes riseIn {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0); }
}

@keyframes slideInLeft {
  from { opacity: 0; transform: translateX(-14px); }
  to   { opacity: 1; transform: translateX(0); }
}

@keyframes slideInRight {
  from { opacity: 0; transform: translateX(14px); }
  to   { opacity: 1; transform: translateX(0); }
}

@keyframes popIn {
  from { opacity: 0; transform: scale(0.97) translateY(6px); }
  to   { opacity: 1; transform: scale(1) translateY(0); }
}

/* Set by app.js once the first paint is done, so the animation does not run on
   every re-render — only on the load that a person actually watches. */
.booted .top { animation: riseIn 0.4s var(--ease-out) both; }
.booted .col-left { animation: slideInLeft 0.45s var(--ease-out) 0.05s both; }
.booted .col-right { animation: slideInRight 0.45s var(--ease-out) 0.1s both; }
.booted .composer { animation: riseIn 0.45s var(--ease-out) 0.18s both; }
.booted .task-bar { animation: riseIn 0.4s var(--ease-out) 0.12s both; }

/* ----------------------------------------------------- task switching */

/*
 * Switching tasks fades the conversation rather than replacing it instantly.
 *
 * Without this the whole message column changes between one frame and the next,
 * which reads as the page having reloaded. A 180ms fade out and in keeps the
 * context of "I am still in the same place, looking at something else".
 */
#messages {
  transition: opacity 0.16s var(--ease), transform 0.16s var(--ease);
}

#messages.switching {
  opacity: 0;
  transform: translateY(6px);
}

/* ------------------------------------------------------- micro-interactions */

/* Buttons press in. Two pixels is enough to feel like a physical object and
   small enough that it does not move the layout around it. */
button:active:not(:disabled),
.lp-btn:active,
.ad-btn:active:not(:disabled) {
  transform: translateY(1px) scale(0.985);
}

/* Cards lift toward the pointer. */
.example:hover,
.material:hover,
.version:hover,
.site-card:hover,
.lp-card:hover,
.ad-stat:hover {
  transform: translateY(-2px);
}

.ad-stat { transition: transform 0.2s var(--ease-out), box-shadow 0.2s var(--ease); }
.ad-stat:hover { box-shadow: var(--shadow); }

/* The task list slides its active item to meet the pointer. */
.conv { position: relative; }
.conv::before {
  content: '';
  position: absolute;
  left: 0;
  top: 50%;
  width: 3px;
  height: 0;
  border-radius: 0 3px 3px 0;
  background: linear-gradient(180deg, var(--cyan-bright), var(--violet-bright));
  transform: translateY(-50%);
  transition: height 0.22s var(--ease-out);
}

.conv:hover::before { height: 40%; }
.conv.on::before { height: 68%; }

/* ----------------------------------------------------------------- focus */

/*
 * A ring that grows.
 *
 * The browser default is a hard outline that appears at full size instantly.
 * This one animates in over 180ms, which sounds like nothing and is the
 * difference between "the field is focused" registering and not.
 */
@keyframes ringIn {
  from { box-shadow: 0 0 0 0 rgba(14, 116, 144, 0.34); }
  to   { box-shadow: 0 0 0 3px rgba(14, 116, 144, 0.15); }
}

input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  animation: ringIn 0.18s var(--ease-out);
}

/* ------------------------------------------------------------------ ripple */

/*
 * A click sends a ring out from where the pointer actually was.
 *
 * The position is set by app.js through two custom properties, so the effect
 * originates under the cursor rather than at the centre of the element — which
 * is the whole difference between a button that responds and a button with a
 * decoration on it.
 */
.ripple {
  position: relative;
  overflow: hidden;
}

/* The one rule that has to win over everything in the layout sheet.
 *
 * `.ghost`, `.primary` and the rest each carried their own
 * `transition: 0.2s var(--ease)`, which is shorthand for `all` — so they both
 * overrode the baseline above and animated layout properties frame by frame.
 * Those are gone now, but this stays last and specific so a new component
 * cannot quietly reintroduce the problem. */
button, a, summary, input, select, textarea,
[class*="btn"], [class*="ghost"], [class*="primary"], [class*="mini"],
.conv, .example, .material, .version, .site-card, .step, .panel, .stat, .ad-stat,
.composer, .modal-box, .done, .welcome, .task-bar, .project-btn, .provider, .result {
  transition:
    color 0.18s var(--ease),
    background-color 0.18s var(--ease),
    border-color 0.18s var(--ease),
    box-shadow 0.22s var(--ease),
    transform 0.18s var(--ease-out),
    opacity 0.18s var(--ease);
}

.ripple > .ripple-wave {
  position: absolute;
  left: var(--rx, 50%);
  top: var(--ry, 50%);
  width: 8px;
  height: 8px;
  margin: -4px 0 0 -4px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.22;
  pointer-events: none;
  animation: rippleOut 0.55s var(--ease-out) forwards;
}

@keyframes rippleOut {
  from { transform: scale(1); opacity: 0.22; }
  to   { transform: scale(34); opacity: 0; }
}

/* The primary button is filled, so its ripple needs to be white rather than
   the button's own colour, or it disappears into the background. */
.primary.ripple > .ripple-wave,
.send.ripple > .ripple-wave { background: #ffffff; opacity: 0.3; }

/* ------------------------------------------------------------------ modal */

/*
 * Dialogs scale up from just under full size.
 *
 * Coming from 97% rather than from nothing keeps the sense that the dialog was
 * already there and grew, which is what makes a modal feel attached to the page
 * instead of pasted over it.
 */
.modal:not([hidden]) .modal-box {
  animation: popIn 0.24s var(--ease-out) both;
}

.modal:not([hidden]) {
  animation: fadeIn 0.18s var(--ease) both;
}

@keyframes fadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* ------------------------------------------------------------- new messages */

/* Messages already rose in; this adds the small settle at the end that makes
   the arrival feel like it landed rather than stopped. */
@keyframes msgIn {
  from { opacity: 0; transform: translateY(8px); }
  60%  { opacity: 1; }
  to   { opacity: 1; transform: translateY(0); }
}

/* --------------------------------------------------------------- work bar */

/* The sweep across the working bar is the one piece of motion that carries
   information: it runs only while something is happening. */
@keyframes sweep {
  from { transform: translateX(-100%); }
  to   { transform: translateX(340%); }
}

/* ------------------------------------------------------------ scrollbars */

/* A visible scrollbar trough on a white panel is a hard grey stripe. This keeps
   it out of the way until the pointer is over the area. */
.scroll-area { scrollbar-width: thin; scrollbar-color: transparent transparent; }
.scroll-area:hover { scrollbar-color: rgba(15, 23, 42, 0.22) transparent; }

.scroll-area::-webkit-scrollbar { width: 10px; height: 10px; }
.scroll-area::-webkit-scrollbar-track { background: transparent; }
.scroll-area::-webkit-scrollbar-thumb {
  background: transparent;
  border-radius: 6px;
  border: 3px solid transparent;
  background-clip: content-box;
}
.scroll-area:hover::-webkit-scrollbar-thumb { background: rgba(15, 23, 42, 0.2); background-clip: content-box; }

/* ------------------------------------------------------------ reduced motion */

/*
 * Everything off, not shortened.
 *
 * A person who has asked their system for less motion is not asking for faster
 * motion. None of this carries meaning on its own — the interface says the same
 * things standing still — so removing it costs nothing and removes the problem
 * completely.
 */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }

  .ripple > .ripple-wave { display: none; }
}

/* Smooth scrolling, but only for people who have not opted out. */
html { scroll-behavior: smooth; }
