Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 78 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,78 @@
# js-project-business-site
# De-News 🚀

**Live Site:** [**fartist.xyz**](https://fartist.xyz)

De-News is a modern, single-page news portal focused on Web3, blockchain, and the world of decentralization. This project was built to fulfill the requirements of the _Frontend Development with JavaScript_ course.

####🤔 So, What's a "Fartist"?

You might’ve noticed the domain is fartist.xyz, while the project itself is called De-News. What’s up with that?

It all began with a new kind of mixed feeling—let’s call it the Fartist Syndrome. Think of it as imposter syndrome 2.0: that strange mix of awe and doubt you get when powerful tools make you feel like a creative genius, even while you’re still figuring things out.

"Fartist" (a playful twist on fake artist—with a double meaning, of course 😏) is a reminder to lean into that feeling. It’s about experimenting, breaking boundaries, and finding joy in the messy, unexpected process of creating.

In a decentralized world that’s constantly shifting, being a Fartist means embracing that surge of creativity—pushing boundaries, having fun with the process, and letting every experiment spark a breath of fresh innovation. 😄

## ✨ Features

- **Fully Responsive:** A mobile-first layout ensures a great experience on any device.
- **Dark/Light Theme:** A theme toggle with `localStorage` to remember the user's preference.
- **Interactive UI:** Smooth CSS animations, a slide-in mobile menu, and a functional sign-in modal.
- **Accessibility Ready:** Designed with ARIA attributes and keyboard navigation in mind.

## 🛠️ Technology Stack

- **HTML5:** Semantic and well-structured markup.
- **CSS3:** Styled with Flexbox, CSS Grid, and custom properties for theming.
- **Vanilla JavaScript:** All interactivity is powered by simple, framework-free JavaScript.

## 🎓 Course Project Details

### ✅ Assessment Criteria Checklist

#### Navigation & Layout

- [x] Responsive navigation bar
- [x] Card/article grid system
- [x] Combined usage of CSS Grid and Flexbox
- [x] Responsive header with hero image/video (3D)

#### Interactive Elements

- [x] A form including at least 3 different input types:
- [x] Text input
- [x] Password
- [x] Radio button set
- [x] Checkbox set
- [x] Select/dropdown
- [x] Submit button
- [x] Form configuration: POST method to `https://httpbin.org/anything`
- [x] Responsive hamburger menu:
- [x] Visible on mobile devices
- [x] Interactive functionality via DOM manipulation

#### Deployment

- [x] Successful deployment on Netlify: [Fartist.xyz](https://fartist.xyz)

#### Stretch Goals (VG – Pass with Distinction)

- [x] Box shadow on cards (Polaroid effect)
- [x] Cards aligned to the centre of the page
- [x] Input focus effect (`:focus` with glow/border change)
- [x] Form validation added
- [x] Hamburger icon changes when expanded/collapsed
- [x] CSS animations (slide-in effect on menu)
- [x] JavaScript toggle for dark mode/light mode

<br>

### 💻 How Criteria Are Met

- **Responsive Navigation:** Built with Flexbox to adapt the layout between desktop and mobile.
- **Grid System:** CSS Grid structures the main article layout, with Flexbox for content alignment within cards.
- **Form:** Includes all required input types and posts to `httpbin.org` for testing.
- **Hamburger Menu:** Appears on mobile and uses JavaScript to toggle its state, complete with animations and icon changes.
- **JavaScript Functionality:** All interactivity is handled by `javascript.js`. The code is written in a simple procedural style to manage the theme, menu, and modal, using `localStorage` to persist the theme choice.

Binary file added images/cbdcdevelopment.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/crosschainbridges.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/cryptoregulation.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/daogovernance.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/defitvl100b.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/ethereum2staking.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/greenblockchain.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/layer2growth.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/nftutilitytokens.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/virtualrealestate.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/web3gaming.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/zeroknowledgeproofs.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
530 changes: 530 additions & 0 deletions index.html

Large diffs are not rendered by default.

253 changes: 253 additions & 0 deletions javascript.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@

// Keep this 'use strict' here. It helps me catch silly mistakes.
'use strict';

// --- MY GLOBAL VARIABLES ---
// Grabbing all the elements I need to work with right at the top.

// Theme Toggle Elements
const themeToggleButton = document.getElementById('theme-toggle');
const themeIcon = document.getElementById('theme-icon');
const body = document.body;

// Mobile Navigation Elements
const menuToggleCheckbox = document.getElementById('menu-toggle');
const mobileNav = document.querySelector('.nav-mobile');
const hamburgerButton = document.querySelector('.hamburger');
const hamburgerLabel = document.getElementById('hamburger-label');

// Modal Elements
const modalOpenButton = document.getElementById('header-auth-modal-btn');
const mobileModalOpenButton = document.getElementById('mobile-auth-modal-btn');
const authModal = document.getElementById('header-auth-modal');
const modalCloseButton = document.getElementById('header-auth-modal-close');

// --- MY CONFIG & ICONS ---
// Storing constants and SVG strings here so I don't have to repeat them.
const THEME_STORAGE_KEY = 'theme';
const LIGHT_THEME = 'light';
const DARK_THEME = 'dark';

const SVG_ICONS = {
MOON: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>`,
SUN: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="12" r="5" stroke="currentColor" stroke-width="2"/>
<line x1="12" y1="1" x2="12" y2="3" stroke="currentColor" stroke-width="2"/>
<line x1="12" y1="21" x2="12" y2="23" stroke="currentColor" stroke-width="2"/>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" stroke="currentColor" stroke-width="2"/>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" stroke="currentColor" stroke-width="2"/>
<line x1="1" y1="12" x2="3" y2="12" stroke="currentColor" stroke-width="2"/>
<line x1="21" y1="12" x2="23" y2="12" stroke="currentColor" stroke-width="2"/>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" stroke="currentColor" stroke-width="2"/>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" stroke="currentColor" stroke-width="2"/>
</svg>`
};


// --- THEME TOGGLE FUNCTIONS ---

/**
* Get the theme I saved in localStorage.
* Default to dark mode if nothing's there.
* This is the part that makes the theme choice stick around.
*/
function getSavedTheme() {
return localStorage.getItem(THEME_STORAGE_KEY) || DARK_THEME;
}

/**
* This function does all the theme-switching work:
* - Toggles the 'light-mode' class on the body.
* - Swaps the sun/moon icon.
* - Updates the aria-label for screen readers.
* - Saves the choice back to localStorage.
*/
function applyTheme(theme) {
const isLight = theme === LIGHT_THEME;

body.classList.toggle('light-mode', isLight);
themeIcon.innerHTML = isLight ? SVG_ICONS.MOON : SVG_ICONS.SUN;

const label = isLight ? 'Switch to dark mode' : 'Switch to light mode';
themeToggleButton.setAttribute('aria-label', label);

localStorage.setItem(THEME_STORAGE_KEY, theme);
}

/**
* The main handler for the theme toggle button click.
*/
function handleThemeToggle() {
const currentTheme = body.classList.contains('light-mode') ? LIGHT_THEME : DARK_THEME;
const newTheme = currentTheme === LIGHT_THEME ? DARK_THEME : LIGHT_THEME;
applyTheme(newTheme);
}


// --- MOBILE NAVIGATION FUNCTIONS ---

/**
* Just a quick check to see if the mobile nav is open.
*/
function isMobileNavOpen() {
return mobileNav.classList.contains('open');
}

/**
* Opens the mobile nav and handles the ARIA attributes.
*/
function openMobileNav() {
mobileNav.classList.add('open');
hamburgerButton.classList.add('active');
hamburgerButton.setAttribute('aria-expanded', 'true');
mobileNav.setAttribute('aria-hidden', 'false');
}

/**
* Closes the mobile nav and resets the ARIA attributes.
*/
function closeMobileNav() {
mobileNav.classList.remove('open');
hamburgerButton.classList.remove('active');
hamburgerButton.setAttribute('aria-expanded', 'false');
mobileNav.setAttribute('aria-hidden', 'true');
}

/**
* This runs when I click the hamburger button.
*/
function handleMobileNavToggle(event) {
// I need to prevent the default link behavior here.
event.preventDefault();
if (isMobileNavOpen()) {
closeMobileNav();
} else {
openMobileNav();
}
}


// --- AUTH MODAL FUNCTIONS ---

/**
* Shows the sign-in modal.
*/
function openAuthModal() {
authModal.classList.remove('hide');
authModal.style.display = 'flex';
authModal.setAttribute('aria-hidden', 'false');

// Move the cursor to the first input, it's better for UX.
const firstInput = authModal.querySelector('input');
if (firstInput) {
firstInput.focus();
}
}

/**
* Hides the sign-in modal.
*/
function closeAuthModal() {
authModal.classList.add('hide');
authModal.style.display = 'none';
authModal.setAttribute('aria-hidden', 'true');
// Put focus back on the button that opened it. Good for keyboard navigation.
modalOpenButton.focus();
}


// --- GLOBAL EVENT HANDLERS ---

/**
* This handles clicks anywhere on the page.
* I use it to close the mobile menu if I click outside of it.
*/
function handleDocumentClick(event) {
const isClickInsideNav = mobileNav.contains(event.target) || hamburgerButton.contains(event.target);
if (!isClickInsideNav && isMobileNavOpen()) {
closeMobileNav();
}
}

/**
* This handles key presses.
* Mainly for closing the menu or modal with the 'Escape' key.
*/
function handleDocumentKeyDown(event) {
if (event.key === 'Escape') {
if (isMobileNavOpen()) {
closeMobileNav();
hamburgerButton.focus();
}
if (!authModal.classList.contains('hide')) {
closeAuthModal();
}
}
}


// --- INITIALIZATION ---

/**
* My main function to kick everything off once the page is ready.
* I'll set up all my event listeners in here.
*/
function initializeApp() {
// --- Set up Theme Toggle ---
if (themeToggleButton) {
const initialTheme = getSavedTheme();
applyTheme(initialTheme);
themeToggleButton.addEventListener('click', handleThemeToggle);
}

// --- Set up Mobile Navigation ---
if (hamburgerButton && mobileNav) {
// Hiding the checkbox since I'm controlling the menu with JS.
menuToggleCheckbox.style.display = 'none';
hamburgerButton.addEventListener('click', handleMobileNavToggle);

// Make sure the menu closes when I click a link inside it.
const navLinks = mobileNav.querySelectorAll('.nav-link');
navLinks.forEach(link => {
link.addEventListener('click', closeMobileNav);
});
}

// --- Set up Auth Modal ---
if (modalOpenButton && authModal && modalCloseButton) {
modalOpenButton.addEventListener('click', openAuthModal);
modalCloseButton.addEventListener('click', closeAuthModal);

// Don't forget the mobile button.
if (mobileModalOpenButton) {
mobileModalOpenButton.addEventListener('click', openAuthModal);
}

// This is for closing the modal by clicking the background overlay.
authModal.addEventListener('click', (event) => {
if (event.target === authModal) {
closeAuthModal();
}
});
}

// --- Set up Global Listeners ---
// For closing things with outside clicks or the Escape key.
document.addEventListener('click', handleDocumentClick);
document.addEventListener('keydown', handleDocumentKeyDown);

console.log('De-News app (Simplified Version) initialized successfully');
}

// --- START THE APP ---
// I have to wait for the HTML to be fully loaded before I can find elements.
// 'DOMContentLoaded' is the event that tells me the page is ready.
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeApp);
} else {
// If the page is already loaded, just run the app.
initializeApp();
}

Loading