Interpolation

value.interpolate() maps an input range onto an output range, linearly between the points you list, and returns a node usable anywhere a value goes. Outputs may be numbers, colors or strings with units such as '90deg'. Beyond the input range the mapping continues (extrapolate: 'extend', the default); 'clamp' holds the end value. A collapsing header derives four styles from one scroll offset:

src/anim/Interp.tsx: four outputs from one input (excerpt)TSX
  const scrollY = useAnimatedValue(0);
  const height = scrollY.interpolate({ inputRange: [0, 120], outputRange: [180, 60],
    extrapolate: 'clamp' });                       // stays within 60..180
  const fade = scrollY.interpolate({ inputRange: [0, 60, 120], outputRange: [1, 1, 0] });
  const tilt = scrollY.interpolate({ inputRange: [0, 120], outputRange: ['0deg', '90deg'] });
  const tint = scrollY.interpolate({ inputRange: [0, 120],
    outputRange: ['#1565c0', '#0b1320'] });
  // ...listeners copy height, opacity, rotation and color into `row` as h, o, r, c; then:
    for (const y of [-40, 0, 60, 90, 120, 200]) {
      scrollY.setValue(y);                         // JS-driven: listeners fire at once
      console.log(`y=${y}`.padEnd(6), JSON.stringify(row));
    }
Output
 LOG  y=-40  {"h":180,"o":1,"c":"rgba(24, 128, 245, 1)","r":"-30deg"}
 LOG  y=0    {"h":180,"o":1,"c":"rgba(21, 101, 192, 1)","r":"0deg"}
 LOG  y=60   {"h":120,"o":1,"c":"rgba(16, 60, 112, 1)","r":"45deg"}
 LOG  y=90   {"h":90,"o":0.5,"c":"rgba(14, 40, 72, 1)","r":"67.5deg"}
 LOG  y=120  {"h":60,"o":0,"c":"rgba(11, 19, 32, 1)","r":"90deg"}
 LOG  y=200  {"h":60,"o":-1.3333333333333335,"c":"rgba(4, -36, -75, 1)","r":"150deg"}

The clamped height stops at 60, but at y=200 the unclamped outputs run off the end: opacity -1.33, a color with negative channels, a 150-degree tilt. Overscroll produces exactly such inputs, so clamp every output with a natural limit.