Jeremy Threlfall
This small interactive project uses HTML, CSS, and JavaScript to create a rotating navigation menu. It listens for clicks on open and close buttons to toggle a CSS class that triggers smooth transitions and transforms. The main content and navigation container rotate gracefully, creating a visually engaging effect while keeping the JavaScript simple and focused on toggling CSS classes.
In the HTML the main container div with the class container holds the page’s main content including headings paragraphs and an image. This content serves as filler to show the rotation effect but is not part of the navigation functionality itself (ellipsis here to declutter the code). Outside the container is a circle-container div that contains two buttons inside a circle. These buttons with ids open and close control toggling the navigation menu. Finally the nav element holds a list of navigation items each styled with a custom property for positioning. When activated the container and circle-container rotate moving the main content aside to reveal the navigation menu sliding in. The buttons trigger this rotation by toggling classes in JavaScript enabling the smooth animated effect.
<div class="container">
			<div class="content">
				...
			</div>
		</div>
		<div class="circle-container">
			<div class="circle">
				<button id="close">
					<i class="fas fa-times"></i>
				</button>
				<button id="open">
					<i class="fas fa-bars"></i>
				</button>
			</div>
		</div>
		<nav>
			<ul>
				<li style="--i: 0"><i class="fas fa-home"></i> Home</li>
				<li style="--i: 1"><i class="fas fa-user-alt"></i> About</li>
				<li style="--i: 2"><i class="fas fa-envelope"></i> Contact</li>
			</ul>
		</nav>The styling of this project leans heavily on CSS variables to keep colors and animation timings centralized and easy to update. Variables like --color-bg and --color-accent define the main background and highlight colors, while --transition-speed and –-transition-timing control the pacing of all animations, making it simple to tweak the feel of the rotation and sliding effects without hunting through the code.
The .container forms the main content area with a clean white background (--color-bg), and is set to rotate around its top-left corner using transform-origin: top left. The rotation is triggered by toggling the .show-nav class, which smoothly rotates the container by -20 degrees. This transform is animated with the transition defined by the CSS variables, ensuring a polished, fluid motion when opening or closing the navigation.
Adjacent to the container is the .circle-container holding the navigation toggle buttons styled inside a circular shape with the accent color (--color-accent). The .circle-container itself rotates a further -90 degrees when .show-nav is active on the container, creating a layered, dynamic visual effect that complements the main content rotation.
The navigation menu (nav ul li) uses a clever system to slide the list items in from off-screen using the transform property with a calculated translateX value. Each list item defines a custom property –i in the HTML that acts as its index. This index is then used in the CSS calculation translateX(calc(-100% - (50% * var(–i)))) to offset each item progressively further to the left when the menu is closed. When the .show-nav class is active, the transform is reset to zero, causing the menu items to slide smoothly into view in a staggered manner. This approach keeps the CSS DRY and scalable so that adding more menu items simply requires setting the correct –i value without needing additional CSS rules.
Transitions on the list items are timed with ease-in curves and a consistent speed to maintain a smooth, natural feel as the navigation slides in and out. Additionally, margin-left is calculated based on the same index variable, subtly spacing each menu item horizontally, which helps visually separate them when expanded.
Other content elements like .content small, .content p, and .content h3 use the CSS variables for color, maintaining a consistent and accessible color palette across the page. The content area is constrained to a max width for readability and centered with automatic margins.
:root {
	--transition-speed: 0.5s;
	--transition-timing: linear;
	--color-bg: #fafafa;
	--color-md-gray: #555;
	--color-dark: #333;
	--color-darker: #222;
	--color-white: #fff;
	--color-accent: #ff7979;
}
/* Basic CSS Reset */
...
.container {
	background-color: var(--color-bg);
	transform-origin: top left;
	transition: transform var(--transition-speed) var(--transition-timing);
	width: 100vw;
	min-height: 100vh;
	padding: 50px;
}
.container.show-nav {
	transform: rotate(-20deg);
}
.circle-container {
	position: fixed;
	top: -100px;
	left: -100px;
}
.circle {
	background-color: var(--color-accent);
	height: 200px;
	width: 200px;
	border-radius: 50%;
	position: relative;
	transition: transform var(--transition-speed) var(--transition-timing);
}
.container.show-nav + .circle-container {
	transform: rotate(-90deg);
}
button {
	position: absolute;
	top: 50%;
	left: 50%;
	height: 100px;
	background: transparent;
	border: 0;
	font-size: 26px;
	color: var(--color-white);
	cursor: pointer;
}
.circle button#open {
	left: 60%;
}
.circle button#close {
	top: 60%;
	transform: rotate(90deg);
	transform-origin: top left;
}
.container.show-nav ~ nav li {
	transform: translateX(0);
	transition-delay: 0.3s;
}
nav {
	position: fixed;
	bottom: 40px;
	left: 0;
	z-index: 100;
}
nav ul {
	list-style-type: none;
	padding-left: 30px;
}
nav ul li {
	--i: 0; /* Set individually in HTML */
	text-transform: uppercase;
	color: var(--color-white);
	margin: 40px 0;
	margin-left: calc(15px * var(--i));
	transform: translateX(calc(-100% - (50% * var(--i))));
	transition: transform 0.4s ease-in;
}
nav ul li i {
	font-size: 20px;
	margin-right: 10px;
}
.content {
	max-width: 1000px;
	margin: 50px auto;
}
.content small {
	color: var(--color-md-gray);
	font-style: italic;
}
.content p {
	color: var(--color-dark);
	line-height: 1.5;
	margin: 20px 0 0;
}
.content h3 {
	margin: 10px 0 10px;
}The script selects the container and the open and close buttons. It listens for click events on these buttons to toggle the show-nav class on the container, triggering the CSS rotation and sliding animations. This minimal JavaScript keeps the logic simple and focused solely on toggling CSS classes, allowing the smooth transitions and transforms to handle the visual effects.
const container = document.querySelector('.container');
document
	.getElementById('open')
	.addEventListener('click', () => container.classList.add('show-nav'));
document
	.getElementById('close')
	.addEventListener('click', () => container.classList.remove('show-nav'));Overall, the project demonstrates how effective use of CSS variables, calculated transforms, and carefully timed transitions can create an engaging interactive navigation experience with minimal JavaScript. The rotation and sliding animations are smooth and visually appealing while the code remains easy to maintain and extend.
Jeremy is a Fullstack Developer. Originally from the United States, he currently resides in Taiwan, and works freelance remotely.
Portfolio