AI CSS Animations

File Tree Changes

A project file tree as the agent edits: limiter.ts is created and slides in, then each touched file glows while it is written and settles with an A or M badge and its +/- line counts.

Also known as: file explorer animation, changed files tree, git status file tree.

acme-web

HTML

<div class="aia-file-tree-changes" role="status" aria-label="Agent editing project files">
  <div class="aia-file-tree-changes-head">
    <span>acme-web</span>
    <span class="aia-file-tree-changes-count" aria-hidden="true"></span>
  </div>
  <ul class="aia-file-tree-changes-tree" aria-hidden="true">
    <li class="aia-file-tree-changes-dir"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m7 9.5 5 5 5-5"/></svg><span>src</span></li>
    <li class="aia-file-tree-changes-dir"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m7 9.5 5 5 5-5"/></svg><span>lib</span></li>
    <li class="aia-file-tree-changes-file"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5"/></svg><span>auth.ts</span><b>M</b><em><ins>+12</ins> <del>-3</del></em></li>
    <li class="aia-file-tree-changes-file aia-file-tree-changes-new"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5"/></svg><span>limiter.ts</span><b>A</b><em><ins>+48</ins></em></li>
    <li class="aia-file-tree-changes-dir"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m7 9.5 5 5 5-5"/></svg><span>routes</span></li>
    <li class="aia-file-tree-changes-file"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5"/></svg><span>login.ts</span><b>M</b><em><ins>+4</ins> <del>-1</del></em></li>
    <li class="aia-file-tree-changes-dir"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m7 9.5 5 5 5-5"/></svg><span>tests</span></li>
    <li class="aia-file-tree-changes-file aia-file-tree-changes-new"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5"/></svg><span>limiter.test.ts</span><b>A</b><em><ins>+36</ins></em></li>
  </ul>
</div>

CSS

@property --aia-file-tree-changes-t {
  syntax: "<number>";
  inherits: true;
  initial-value: 0;
}
@property --aia-file-tree-changes-n {
  syntax: "<integer>";
  inherits: false;
  initial-value: 0;
}
.aia-file-tree-changes {
  --c: var(--aia-color, #7c5cff);
  font-size: var(--aia-size, 16px);
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
  line-height: 1.4;
  position: relative;
  box-sizing: border-box;
  padding: 0.9em 1em;
  border-radius: 0.9em;
  background: color-mix(in srgb, currentColor 3%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, currentColor 11%, transparent), 0 0.1em 0.2em color-mix(in srgb, currentColor 4%, transparent), 0 0.9em 2em -1.1em rgb(0 0 0 / 0.3);
  --d: 8.5s;
  --t: var(--aia-file-tree-changes-t);
  --add: color-mix(in srgb, #16a34a 78%, currentColor);
  --mod: color-mix(in srgb, #d97706 84%, currentColor);
  --row: 1.5em;
  width: 18em;
  padding: 0.7em 0.55em 0.6em;
  animation: aia-file-tree-changes-clock var(--d) cubic-bezier(0.45, 0, 0.3, 1) infinite;
}
.aia-file-tree-changes-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin: 0 0.45em 0.35em;
  color: color-mix(in srgb, currentColor 55%, transparent);
  font-size: 0.7em;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.aia-file-tree-changes-count {
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  --aia-file-tree-changes-n: calc(clamp(0, (var(--t) - 1.7) * 6, 1) + clamp(0, (var(--t) - 2.7) * 6, 1) + clamp(0, (var(--t) - 3.7) * 6, 1) + clamp(0, (var(--t) - 4.7) * 6, 1));
  counter-reset: n var(--aia-file-tree-changes-n);
  animation: aia-file-tree-changes-fade var(--d) linear infinite;
}
.aia-file-tree-changes-count::before { content: counter(n) " changed"; }
.aia-file-tree-changes-tree {
  height: calc(var(--row) * 8);
  margin: 0;
  padding: 0;
  list-style: none;
  font: 0.78em ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;
  animation: aia-file-tree-changes-fade var(--d) linear infinite;
}
.aia-file-tree-changes-tree li {
  --lvl: 0;
  --at: 9;
  --in: 1;
  --e: clamp(0, min(var(--t) - var(--at), var(--at) + 0.7 - var(--t)) * 6, 1);
  --k: clamp(0, (var(--t) - var(--at) - 0.72) * 6, 1);
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.4em;
  height: calc(var(--row) * var(--in));
  padding: 0 0.6em 0 calc(0.6em + var(--lvl) * 1.1em);
  overflow: hidden;
  border-radius: 0.4em;
  white-space: nowrap;
  opacity: var(--in);
}
.aia-file-tree-changes-tree li:nth-child(2),
.aia-file-tree-changes-tree li:nth-child(5),
.aia-file-tree-changes-tree li:nth-child(8) { --lvl: 1; }
.aia-file-tree-changes-tree li:nth-child(3),
.aia-file-tree-changes-tree li:nth-child(4),
.aia-file-tree-changes-tree li:nth-child(6) { --lvl: 2; }
.aia-file-tree-changes-tree li:nth-child(3) { --at: 2; }
.aia-file-tree-changes-tree li:nth-child(4) { --at: 1; }
.aia-file-tree-changes-tree li:nth-child(6) { --at: 3; }
.aia-file-tree-changes-tree li:nth-child(8) { --at: 4; }
.aia-file-tree-changes-tree li.aia-file-tree-changes-new {
  --in: clamp(0, (var(--t) - var(--at) + 0.15) * 5, 1);
  --kc: var(--add);
}
/* the write glow on the file being edited */
.aia-file-tree-changes-tree li::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(90deg, color-mix(in srgb, var(--c) 22%, transparent), color-mix(in srgb, var(--c) 6%, transparent));
  box-shadow: inset 0.15em 0 0 var(--c);
  opacity: var(--e);
}
.aia-file-tree-changes-tree li > * { position: relative; }
.aia-file-tree-changes-tree svg {
  flex: none;
  width: 1.05em;
  height: 1.05em;
  color: color-mix(in srgb, currentColor 45%, transparent);
}
.aia-file-tree-changes-dir span { color: color-mix(in srgb, currentColor 75%, transparent); }
.aia-file-tree-changes-file {
  --kc: var(--mod);
}
.aia-file-tree-changes-file span {
  margin-right: auto;
  color: color-mix(in srgb, var(--kc) calc(var(--k) * 90%), color-mix(in srgb, currentColor 70%, transparent));
}
.aia-file-tree-changes-file b,
.aia-file-tree-changes-file em {
  opacity: var(--k);
  transform: scale(calc(0.6 + var(--k) * 0.4));
}
.aia-file-tree-changes-file em {
  font-style: normal;
  font-size: 0.92em;
}
.aia-file-tree-changes-file ins {
  color: var(--add);
  text-decoration: none;
}
.aia-file-tree-changes-file del {
  color: color-mix(in srgb, #dc2626 80%, currentColor);
  text-decoration: none;
}
.aia-file-tree-changes-file b {
  order: 1;
  width: 1.1em;
  color: var(--kc);
  font-weight: 700;
  text-align: right;
}
/* the pencil pulse beside the file while it is written */
.aia-file-tree-changes-file::after {
  content: "";
  position: absolute;
  right: 0.75em;
  top: calc(50% - 0.25em);
  width: 0.5em;
  height: 0.5em;
  border-radius: 50%;
  background: var(--c);
  opacity: var(--e);
  animation: aia-file-tree-changes-pulse 0.7s ease-in-out infinite alternate;
}
@keyframes aia-file-tree-changes-clock {
  0%, 4% { --aia-file-tree-changes-t: 0.4; }
  9%, 16% { --aia-file-tree-changes-t: 1.35; }
  21% { --aia-file-tree-changes-t: 2; }
  25%, 32% { --aia-file-tree-changes-t: 2.35; }
  37% { --aia-file-tree-changes-t: 3; }
  41%, 48% { --aia-file-tree-changes-t: 3.35; }
  53% { --aia-file-tree-changes-t: 4; }
  57%, 64% { --aia-file-tree-changes-t: 4.35; }
  70%, 97% { --aia-file-tree-changes-t: 4.9; }
  97.5%, 100% { --aia-file-tree-changes-t: 0.4; }
}
@keyframes aia-file-tree-changes-fade {
  0%, 92% { opacity: 1; }
  95.5%, 97.5% { opacity: 0; }
  100% { opacity: 1; }
}
@keyframes aia-file-tree-changes-pulse {
  from { transform: scale(0.6); }
  to { transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .aia-file-tree-changes,
  .aia-file-tree-changes-count,
  .aia-file-tree-changes-tree {
    animation: none;
    --aia-file-tree-changes-t: 4.9;
  }
  .aia-file-tree-changes-file::after { animation: none; }
}

@media (prefers-reduced-motion: reduce) {
  .aia-file-tree-changes, .aia-file-tree-changes *, .aia-file-tree-changes::before, .aia-file-tree-changes::after, .aia-file-tree-changes *::before, .aia-file-tree-changes *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

Customizing this snippet

  • Color: set --aia-color on the element or a parent. Currently previewing #3b82f6.
  • Base size: set --aia-size. Every other dimension is in em, so the whole thing scales together. Currently previewing 16px.
  • Speed: change the duration on the transition or animation shorthand. Staggered delays are relative, so they keep their rhythm.
  • Reduced motion: the rule at the end of the CSS turns the motion off for people who asked their OS for less of it. Keep it.

Similar ai animations