/* Text Animations */
.animate {
  font-size: 50px;
  margin: 100px 0 0;
  border-bottom: 2px solid #ccc;
}

.animate span {
  display: inline-block;
}

/* Animation Delays */
.animate span:nth-of-type(2) { animation-delay: .05s; }
.animate span:nth-of-type(3) { animation-delay: .1s; }
.animate span:nth-of-type(4) { animation-delay: .15s; }
.animate span:nth-of-type(5) { animation-delay: .2s; }
.animate span:nth-of-type(6) { animation-delay: .25s; }
.animate span:nth-of-type(7) { animation-delay: .3s; }
.animate span:nth-of-type(8) { animation-delay: .35s; }
.animate span:nth-of-type(9) { animation-delay: .4s; }
.animate span:nth-of-type(10) { animation-delay: .45s; }
.animate span:nth-of-type(11) { animation-delay: .5s; }
.animate span:nth-of-type(12) { animation-delay: .55s; }
.animate span:nth-of-type(13) { animation-delay: .6s; }
.animate span:nth-of-type(14) { animation-delay: .65s; }
.animate span:nth-of-type(15) { animation-delay: .7s; }
.animate span:nth-of-type(16) { animation-delay: .75s; }
.animate span:nth-of-type(17) { animation-delay: .8s; }
.animate span:nth-of-type(18) { animation-delay: .85s; }
.animate span:nth-of-type(19) { animation-delay: .9s; }
.animate span:nth-of-type(20) { animation-delay: .95s; }

/* Animation One */
.one span {
  color: #24a8e6;
  opacity: 0;
  transform: translate(-150px, -50px) rotate(-180deg) scale(3);
  animation: revolveScale .4s forwards;
}

@keyframes revolveScale {
  60% {
    transform: translate(20px, 20px) rotate(30deg) scale(.3);
  }
  100% {
    transform: translate(0) rotate(0) scale(1);
    opacity: 1;
  }
}

/* Animation Two */
.two span {
  color: #a5cb21;
  opacity: 0;
  transform: translate(200px, -100px) scale(2);
  animation: ballDrop .3s forwards;
}

@keyframes ballDrop {
  60% {
    transform: translate(0, 20px) rotate(-180deg) scale(.5);
  }
  100% {
    transform: translate(0) rotate(0deg) scale(1);
    opacity: 1;
  }
}

/* Newton Cradle */
.pendulum {
  position: absolute;
  width: 220px;
  height: 180px;
  background-color: #f8c6cf;
  top: 50%;
  left: 50%;
  border-radius: 5%;
  align-items: center;
  border-top: 15px solid #eee7d5;
  transform: translate(-50%, -50%);
}

.pendulum_box {
  display: flex;
  padding: 120px 0 0 10px;
  position: absolute;
  flex: 1;
}

.ball {
  height: 40px;
  width: 40px;
  border-radius: 50%;
  background-color: #455681;
  position: relative;
  transform-origin: 50% -300%;
}

.ball::before {
  content: '';
  width: 2px;
  height: 120px;
  background-color: #fffeff;
  left: 18px;
  top: -120px;
  position: absolute;
}

.ball.first {
  animation: firstball .9s alternate ease-in infinite;
}

@keyframes firstball {
  0% { transform: rotate(35deg); }
  50% { transform: rotate(0deg); }
}

.ball.last {
  animation: lastball .9s alternate ease-out infinite;
}

@keyframes lastball {
  50% { transform: rotate(0deg); }
  100% { transform: rotate(-35deg); }
}

/* Sisyphus Loader */
#loader {
  position: absolute;
  top: 50%;
  left: 50%;
  margin-top: -2.7em;
  margin-left: -2.7em;
  width: 5.4em;
  height: 5.4em;
  background-color: #404456;
}

#hill {
  position: absolute;
  width: 7.1em;
  height: 7.1em;
  top: 1.7em;
  left: 1.7em;
  background-color: transparent;
  border-left: .25em solid whitesmoke;
  transform: rotate(45deg);
}

#hill:after {
  content: '';
  position: absolute;
  width: 7.1em;
  height: 7.1em;
  left: 0;
  background-color: #404456;
}

#box {
  position: absolute;
  left: 0;
  bottom: -.1em;
  width: 1em;
  height: 1em;
  background-color: transparent;
  border: .25em solid whitesmoke;
  border-radius: 15%;
  transform: translate(0, -1em) rotate(-45deg);
  animation: push 2.5s cubic-bezier(.79, 0, .47, .97) infinite;
}

@keyframes push {
  0% { transform: translate(0, -1em) rotate(-45deg); }
  5% { transform: translate(0, -1em) rotate(-50deg); }
  20% { transform: translate(1em, -2em) rotate(47deg); }
  25% { transform: translate(1em, -2em) rotate(45deg); }
  30% { transform: translate(1em, -2em) rotate(40deg); }
  45% { transform: translate(2em, -3em) rotate(137deg); }
  50% { transform: translate(2em, -3em) rotate(135deg); }
  55% { transform: translate(2em, -3em) rotate(130deg); }
  70% { transform: translate(3em, -4em) rotate(217deg); }
  75% { transform: translate(3em, -4em) rotate(220deg); }
  100% { transform: translate(0, -1em) rotate(-225deg); }
}

/* Scroll Animations */
.fade-in {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 0.6s ease-out, transform 0.6s ease-out;
}

.fade-in.visible {
  opacity: 1;
  transform: translateY(0);
}

.slide-in-left {
  opacity: 0;
  transform: translateX(-50px);
  transition: opacity 0.6s ease-out, transform 0.6s ease-out;
}

.slide-in-left.visible {
  opacity: 1;
  transform: translateX(0);
}

.slide-in-right {
  opacity: 0;
  transform: translateX(50px);
  transition: opacity 0.6s ease-out, transform 0.6s ease-out;
}

.slide-in-right.visible {
  opacity: 1;
  transform: translateX(0);
} 

/* Vroom Vroom Animation */
body { 
  background: var(--background-color);
}
.loop-wrapper {
  margin: 0 auto;
  position: relative;
  display: block;
  width: 100%;
  height: 125px;
  overflow: hidden;
  border-bottom: 3px solid var(--scene-road, #000);
  color: var(--text-on-primary);
  margin-bottom: 0;
  background: transparent;
}

/*
  The mountain range.

  It is an SVG of triangles now. It used to be six box-shadow spreads on a 2px element rotated
  130deg, and a box-shadow spread is a square: every peak was a diamond, and the snow caps
  added on top of them were smaller diamonds that hung off the side of the rock rather than
  capping it. Three attempts to place those caps by arithmetic all produced the same wrong
  shape, because the shape was wrong before it was placed.

  Rock and snow are painted into the picture, not taken from theme colours: the range was
  var(--primary-color) once, which made it the same flat blue as a button.

  Since 2026-10-26 each theme may bring its own scene (owner ruling): --scene-far swaps this
  range for another picture, --roadside-art and --roadside-art-far the trees, --scene-hill-a
  and -b the hill, --scene-rock the rock and --scene-road the road line. A replacement for the
  range must be a seamless 2000x120 tile, for the reason given below. Every fallback is
  today's scene, so a theme that sets none of them gets exactly this one.
*/
.mountain {
  position: absolute;
  left: 0;
  bottom: -6px;
  /* One strip wide plus one whole tile. The range is a repeating tile travelling left, so the
     element has to carry a spare tile off the right edge for the next one to come from, and
     the animation has to move by exactly one tile width. Any other distance puts a jump in
     the skyline once per loop. It was a single 2000px image sliding 5350px, which left the
     strip with no mountain at all for seven seconds in every twenty. */
  width: calc(100% + 2000px);
  /* 120, not 150. .loop-wrapper is 125px with overflow hidden and this sits 6px below its
     floor, so anything taller than 119px has its peaks cut off square. */
  height: 120px;
  background: var(--scene-far, url(../images/mountains.svg)) repeat-x left bottom;
  background-size: 2000px 120px;
  /* 7.5s for 2000px is the same 267px/s the range travelled before, which is a little under
     half the speed of the trees. The range is the far distance and has to read that way. */
  animation: mtn 7.5s linear infinite;
  opacity: 1;
  will-change: transform;
}

.hill {
  position: absolute;
  right: -900px;
  bottom: -50px;
  width: 400px;
  border-radius: 50%;
  height: 20px;
  /* Grass, so a dark green rather than the theme's accent, unless a theme's scene says
     otherwise (dust on the moon, paper on Paper). */
  box-shadow:
    0 0 0 50px var(--scene-hill-a, #24512f),
    -20px 0 0 20px var(--scene-hill-a, #24512f),
    -90px 0 0 50px var(--scene-hill-a, #24512f),
    250px 0 0 50px var(--scene-hill-b, #1d4527),
    290px 0 0 50px var(--scene-hill-b, #1d4527),
    620px 0 0 50px var(--scene-hill-a, #24512f);
  animation: hill 4s 2s linear infinite;
  opacity: 1;
  will-change: transform;
}

.tree {
  position: absolute;
  height: var(--roadside-height, 100px);
  width: var(--roadside-width, 35px);
  bottom: 0;
  background: var(--roadside-art, url(../images/tree.svg)) no-repeat center bottom / contain;
  /* No hue-rotate. It turned the whole image one hue, trunk included, which is
     why the trunks were the same green as the needles. tree.svg carries its own
     colours: brown trunk, three greens for the tiers. */
  opacity: 1;
  will-change: transform;
}

/* The third tree runs on its own slower loop; a theme may stand something else there. */
.tree + .tree + .tree {
  background-image: var(--roadside-art-far, var(--roadside-art, url(../images/tree.svg)));
}

/* A stone at the roadside. It was 2% by 2% of the strip, a sliver 2.5px tall that read as
   nothing at all. */
.rock {
  height: 9px;
  width: 18px;
  bottom: 0;
  border-radius: 50% 50% 3px 3px;
  position: absolute;
  background: var(--scene-rock, #636363);
  opacity: 1;
  will-change: transform;
}
.truck, .wheels {
  transition: all ease;
  width: 85px;
  margin-right: -60px;
  bottom: 0px;
  right: 50%;
  position: absolute;
}
/*
  The truck takes a different livery on every theme; since 2026-10-26 the rest of the scene
  may change with it (see .mountain). A grey truck was the deadest thing on the page.

  One painted livery per theme in assets/images/truck-<theme>.svg, rather than one file
  filtered once per theme. A CSS hue-rotate moves every colour in a file by the same angle, so
  cab, cargo box, glass and chassis stay locked to each other and every theme got the same
  flat truck in another shade. Each livery paints its nine parts separately, so a theme
  changes which truck drives past rather than which filter is over it. --truck-art lives in
  each theme's own token block in portfolio-v2.css, beside all its other colours; a theme
  without one falls back to the professional truck rather than to a grey silhouette.

  A theme can go further and bring a whole vehicle of its own: --wheels-art and --wheels-filter
  swap the wheels, and --wind-colour the wind lines behind it. Every fallback below is the
  painted truck's, so a theme that sets none of them gets exactly that truck.
*/
.truck {
  background: var(--truck-art, url(../images/truck-professional.svg)) no-repeat;
  background-size: contain;
  height: 60px;
}
.truck:before {
  content: " ";
  position: absolute;
  width: 25px;
  box-shadow:
    -30px 28px 0 1.5px var(--wind-colour, #fff),
     -35px 18px 0 1.5px var(--wind-colour, #fff);
}
/* Rubber is rubber. The painted trucks' wheels stay dark and take no hue from the theme. */
.wheels {
  background: var(--wheels-art, url(../images/wheels.svg)) no-repeat;
  height: 15px;
  margin-bottom: 0;
  filter: var(--wheels-filter, brightness(0.42) saturate(0.2));
}

/* Each tree on its own loop, so three pass rather than one. These were :nth-child(2) and (3),
   but the trees are the 3rd to 5th children of the strip, so two of them shared one loop and
   stood exactly on top of each other. Counting from the first tree does not depend on what
   comes before it. */
.tree  { animation: tree 3s 0.000s linear infinite; }
.tree + .tree  { animation: tree2 2s 0.150s linear infinite; }
.tree + .tree + .tree  { animation: tree3 8s 0.050s linear infinite; }
.rock  { animation: rock 4s 0.530s linear infinite; }
.truck  { animation: truck 4s 0.080s ease infinite; }
.wheels  { animation: truck 4s 0.001s ease infinite; }
.truck:before { animation: wind 1.5s 0.000s ease infinite; }

@keyframes tree {
  0%   { transform: translate(2000px); opacity: 1; }
  100% { transform: translate(-50px); opacity: 1; }
}

@keyframes tree2 {
  0%   { transform: translate(2000px); opacity: 1; }
  100% { transform: translate(-50px); opacity: 1; }
}

@keyframes tree3 {
  0%   { transform: translate(2000px); opacity: 1; }
  100% { transform: translate(-50px); opacity: 1; }
}

@keyframes rock {
  0%   { transform: translate(1350px); opacity: 1; }
  100% { transform: translate(-50px); opacity: 1; }
}

@keyframes truck {
  0%   { }
  6%   { transform: translateY(0px); }
  7%   { transform: translateY(-6px); }
  9%   { transform: translateY(0px); }
  10%   { transform: translateY(-1px); }
  11%   { transform: translateY(0px); }
  100%   { }
}
@keyframes wind {
  0%   {  }
  50%   { transform: translateY(3px) }
  100%   { }
}
/* Exactly one tile of mountains.svg. See .mountain. */
@keyframes mtn {
  0%   { transform: translateX(0); }
  100% { transform: translateX(-2000px); }
}
@keyframes hill {
  0%   { transform: translate(1350px); opacity: 1; }
  100% { transform: translate(-4000px); opacity: 1; }
}