@clex, the breathing LED is a bit of an illusion in that it gets updated in the “background” firmware. The Core runs one big loop comprised of the background loop and the user loop() function. So, when the user loop() function ends, it passes control to the background loop and so on.
To do a breathing LED, you need to run code in loop() that changes the color of an LED over a defined time, using millis() for example to do the timing. Each time loop() runs, you check that a certain number of millis() has gone by and then run the code that updates the LED color.
unsigned long update = millis();
if ((millis() - update) > 1000) // if 1000ms or 1sec has elapsed
update = millis(); //reset the timer
// CODE TO UPDATE THE LED
Notice that the code to update the LED only runs when the correct time has elapsed so it is “non-blocking” since loop() continues to run. By setting up different timers you can run different code at different intervals.