Почему margin ведет себя так?
Маржины складываются, вот и всё. То есть например у верхнего блока был нижний маржин 20, а у нижнего блока верхний 10, то сам маржин между ними будет 10. Это то, как работает маржин.
Про схлопывание отступов можно почитать на MDN, например. Ваш случай - "Родительский и первый/последний дочерние элементы". Потом можно пойти в спецификацию Не забудьте, что у body тоже по умолчанию тоже есть ненулевой margin