Branch-by-abstraction and feature-flags/feature-toggles are the typical techniques allowing development of any combinations of features in various degrees of readiness in the same branch. For a proper CI/CD approach that should be the
master branch. Each feature being developed can be tested independently by turning the respective flag on or off. "Releasing" the feature in that branch simply means that the respective flag is enabled in the source code
To be effective, all the relevant combinations of the feature flags' values should be subjected to the respective CI/CD QA verifications in this branch. The complication, of course, is the number of such combinations which can cause cost/time "explosions" in these verifications, especially in large scale projects with many features.
Grouping multiple feature flags under a single "super-flag" is an effective method of reducing the number of such relevant combinations. Turning on (in the code base) these super-flags are often time-driven according to a release schedule. Which can be either periodical or based on certain specified/seasonal time/dates, it doesn't really matter (as long the respective features meet the expected quality level by that time, of course, but that's a different discussion).
Turning on the super-flags is all that's needed for the software products that are continuously deployed. But for the products which can't be continuously deployed (like OSes or most of the embedded software products) release branches in which only the super flag(s) (or directly feature flags) applicable to the respective releases are enabled is the typical way to keep the release time/costs under control. They can also serve as the CI/CD branch vehicles for subsequent hot fixes, service packs and/or minor releases.
Release branches would be pulled off the main/master branch (or parent major release branch) whenever the quality level is good enough for the very next release and trouble to support the delta to the 2nd next release remains acceptable (as soon as the branch for the next release is pulled the parent branch can diverge, heading towards the 2nd next release).
And no, release branches aren't (necessarily) evil: they are not feature branches and they can completely avoid any massive branch sync/reparent/merge operation, thus remaining true to the CI principles.