This is a modular, multi-page website with pure HTML structure.
Here’s a breakdown of your HTML for the Ukubona LLC internship access portal, with commentary and code block callouts for each of your 5 key concerns:
💡 This is handled by:
- Global styles
:root
CSS variables- Light/dark mode switch via
html[data-theme=...]
- Font loaded from Google Fonts
- Animated fade-in on body load
<!-- Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet" />
<style>
:root {
--bg-light: #ffffff;
--bg-dark: #000000;
}
html[data-theme='light'] {
background-color: var(--bg-light);
}
html[data-theme='dark'] {
background-color: var(--bg-dark);
}
html, body {
margin: 0;
padding: 0;
font-family: 'Inter', system-ui, sans-serif;
}
body {
display: flex;
align-items: center;
justify-content: center;
animation: fadeIn 0.8s ease-in-out forwards;
opacity: 0;
}
@keyframes fadeIn {
to { opacity: 1; }
}
</style>
🧠 Insight: This gives your portal a unified visual rhythm—font, colors, transitions—tightly aligned to theme.
💡 Handled by:
.app-grid
class- JS toggle on
#gridToggle
- Grid layout defined using
grid-template-columns
.app-grid {
display: none; /* default hidden */
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 1rem;
background: rgba(255, 255, 255, 0.95);
padding: 1rem;
border-radius: 8px;
position: absolute;
top: 4rem;
left: 1em;
}
const toggle = document.getElementById('gridToggle');
const menu = document.getElementById('gridMenu');
toggle.addEventListener('click', () => {
menu.style.display = menu.style.display === 'grid' ? 'none' : 'grid';
});
🧠 Insight: You're using CSS Grid for responsive layout of apps. Consider adding
@media
queries for more fine control on mobile.
💡 Controlled by:
#toggle-theme
button- JavaScript
setTheme()
function - Updates
html[data-theme]
and logo
<button id="toggle-theme">🌞 / 🌙</button>
function setTheme(theme) {
html.setAttribute('data-theme', theme);
localStorage.setItem('theme', theme);
logo.src = theme === 'dark' ? DARK_LOGO : LIGHT_LOGO;
toggleBtn.textContent = theme === 'dark' ? '🌙' : '🌞';
}
🧠 Insight: This is a persistent toggle thanks to
localStorage
. Also note that theme affects the menu icon color too via CSS.
💡 Controlled by:
#logo
<img>
inside.logo-wrapper
- Theme swap via JS
- Positioned centrally with
flex
<div class="logo-wrapper">
<img
src="https://abikesa.github.io/logos/assets/ukubona-light.png"
alt="Ukubona Logo"
id="logo"
loading="eager"
/>
</div>
const logo = document.getElementById('logo');
const LIGHT_LOGO = 'https://abikesa.github.io/logos/assets/ukubona-light.png';
const DARK_LOGO = 'https://abikesa.github.io/logos/assets/ukubona-dark.png';
🧠 Insight: The
loading="eager"
ensures logo comes in ASAP; you also handle logo switching on theme toggle, which is slick and intentional.
💡 Controlled by:
#logo
has infinite@keyframes spin
- Theme switching does not touch CSS animations, only
src
- Ensures continuous spin across toggles
#logo {
animation: spin 60s linear infinite;
transition: opacity 0.4s ease-in;
opacity: 0;
}
body.loaded #logo {
opacity: 1;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
🧠 Key Insight: Because the animation is CSS-based and only the
src
changes, rotation stays visually seamless. The transition only affectsopacity
, nottransform
.
# Ukubona Internship Portal UI Notes
### 1. Look Standardization
- Theme via `:root` variables
- Fonts via Google Fonts (Inter)
- Fade-in animation on body
- Consistent font and layout rules
### 2. Grid Layout
- `.app-grid` uses CSS Grid
- Toggled via `#gridToggle` click
- Responsive with `auto-fill` and `minmax`
### 3. Light/Dark Toggle
- `#toggle-theme` button updates `html[data-theme]`
- Persists using `localStorage`
- Updates logo `src` and toggle emoji
### 4. Logo Display
- Rotates via `@keyframes spin`
- Loaded based on theme
- Centered using flex container `.logo-wrapper`
### 5. Seamless Logo Rotation
- Animation is continuous
- Theme toggle changes `src` but doesn't affect `animation`
- Ensures visual smoothness
> 🧠 Final Tip: You could extract the CSS into a shared `style.css` to improve modularity and speed future updates.
Would you like this exported to a .md
, .html
, or .pdf
version for reuse or GitHub? I can generate that too.
Here's a battle-tested, optimized directory structure for a multi-page website that ensures:
- Thematic and stylistic coherence across all pages
- Modularity and maintainability
- Pure
.html
(no embedded CSS/JS—only linked)
/project-root/
├── index.html
├── about.html
├── contact.html
├── /assets/
│ ├── /css/
│ │ └── main.css
│ ├── /js/
│ │ └── main.js
│ ├── /images/
│ │ └── logo.png
│ └── /icons/
│ └── favicon.ico
├── /components/
│ ├── header.html
│ └── footer.html
├── /data/
│ └── team.json
├── /fonts/
│ └── inter.woff2
└── README.md
Folder | Purpose |
---|---|
/ |
Only .html files – each page pure, no inline CSS/JS |
/assets/css |
All global and scoped CSS, preferably minified in production |
/assets/js |
All JS scripts (DOM handlers, animations, interactivity, etc.) |
/assets/images |
Logos, backgrounds, etc. |
/assets/icons |
Favicons, SVGs |
/components |
Optional: HTML partials (e.g., navbar, footer) for templating systems |
/data |
JSON or CSV used by JS (e.g., dynamic content or UI config) |
/fonts |
Web fonts for consistent typography |
<!-- about.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>About Us</title>
<link rel="icon" href="assets/icons/favicon.ico" />
<link rel="stylesheet" href="assets/css/main.css" />
</head>
<body>
<header>
<!-- Optionally inject reusable header via JS or template engine -->
</header>
<main>
<h1>About Ukubona</h1>
<p>We see. You see. Ukubona.</p>
</main>
<footer>
<!-- Footer component -->
</footer>
<script src="assets/js/main.js"></script>
</body>
</html>
- ✅ Pure HTML: No
<style>
or<script>
tags inside.html
beyond external linking. - ✅ 1 CSS File (
main.css
) for global styles. Modularize if needed (e.g.,grid.css
,theme.css
) and combine/minify in production. - ✅ 1 JS File (
main.js
) to handle all logic; modularize if logic gets complex. - ✅ Assets Stay in
assets/
: Never mix JS/CSS/images directly into/
.
/project-root/
├── .gitignore
├── LICENSE
├── render.yaml (or netlify.toml, vercel.json)
└── .nojekyll (if hosting on GitHub Pages and using custom folders)
Would you like a boilerplate zip or GitHub-ready starter kit following this format?
import zipfile
import os
# Define the project structure and files
project_structure = {
"project-root/": {
"index.html": "",
"about.html": "",
"contact.html": "",
"README.md": "# Ukubona LLC Website\n\nThis is a modular, multi-page website with pure HTML structure.",
"assets/": {
"css/": {
"main.css": """/* main.css */
body {
font-family: 'Inter', sans-serif;
margin: 0;
padding: 0;
background-color: #f9f9f9;
color: #333;
}
footer {
background-color: #000;
color: #fff;
text-align: center;
padding: 1rem;
font-size: 0.9rem;
position: relative;
bottom: 0;
width: 100%;
}
"""
},
"js/": {
"main.js": """// main.js
document.addEventListener('DOMContentLoaded', () => {
console.log("Ukubona site loaded.");
});
"""
},
"images/": {},
"icons/": {
"favicon.ico": "" # Placeholder for favicon
}
},
"components/": {
"footer.html": """<!-- footer.html -->
<footer>
© 2025 Ukubona LLC. All rights reserved. | <a href="contact.html">Contact</a>
</footer>
"""
},
"data/": {
"team.json": """[
{ "name": "Founder", "role": "Vision & Leadership" },
{ "name": "Intern", "role": "Support & Development" }
]
"""
},
"fonts/": {}
}
}
# HTML template using pure HTML and linking CSS/JS, and including footer
html_template = """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{title}</title>
<link rel="stylesheet" href="assets/css/main.css" />
<script src="assets/js/main.js" defer></script>
</head>
<body>
<main>
<h1>{heading}</h1>
<p>{content}</p>
</main>
<!-- Footer include -->
<div id="footer-container"></div>
<script>
fetch('components/footer.html')
.then(response => response.text())
.then(data => document.getElementById('footer-container').innerHTML = data);
</script>
</body>
</html>
"""
# Populate HTML pages with standard content
page_data = {
"index.html": ("Ukubona LLC", "Welcome to Ukubona", "Vision through recursion and clarity."),
"about.html": ("About - Ukubona", "About Us", "Ukubona means 'to see' – we are building recursive systems that matter."),
"contact.html": ("Contact - Ukubona", "Get in Touch", "Reach out to us for collaboration, internships, and more.")
}
# Write structure to a zip file
zip_path = "/mnt/data/ukubona_website.zip"
with zipfile.ZipFile(zip_path, 'w') as zipf:
for root, sub in project_structure.items():
for folder, content in sub.items():
folder_path = os.path.join(root, folder)
if isinstance(content, dict): # it's a folder
for file, data in content.items():
path = os.path.join(folder_path, file)
zipf.writestr(path, data)
else: # it's a file
if folder.endswith(".html"):
title, heading, paragraph = page_data.get(folder, ("Page", "Title", "Content"))
html_content = html_template.format(title=title, heading=heading, content=paragraph)
zipf.writestr(os.path.join(root, folder), html_content)
else:
zipf.writestr(os.path.join(root, folder), content)
zip_path
# We'll recursively walk through the structure and write files correctly.
def write_structure_to_zip(zipf, base_path, structure):
for name, content in structure.items():
current_path = os.path.join(base_path, name)
if isinstance(content, dict):
write_structure_to_zip(zipf, current_path, content)
else:
zipf.writestr(current_path, content)
# Now insert the real HTML files from the page_data
for filename, (title, heading, content) in page_data.items():
project_structure["project-root/"][filename] = html_template.format(
title=title,
heading=heading,
content=content
)
# Write the corrected structure to zip
zip_path = "/mnt/data/ukubona_website.zip"
with zipfile.ZipFile(zip_path, 'w') as zipf:
write_structure_to_zip(zipf, "", project_structure)
zip_path
✅ Here's your downloadable starter kit:
👉 Click to download ukubona_website.zip
It includes:
index.html
,about.html
, andcontact.html
— all pure HTML, theme-linked./assets/css/main.css
— shared styles/assets/js/main.js
— shared scripts/components/footer.html
— standardized footer auto-injected via JS/data/team.json
,/fonts/
,/images/
, and/icons/
JS footer loader:
Each .html
auto-loads the footer:
<div id="footer-container"></div>
<script>
fetch('components/footer.html')
.then(response => response.text())
.then(data => document.getElementById('footer-container').innerHTML = data);
</script>
Let me know if you want:
- Header inclusion too?
- Netlify or Render deploy script?
- Favicon/image placeholders added?
# Extend the website structure to include:
# - Header component
# - Favicon + placeholder image
# - Netlify and Render deployment configs
# Add header.html
header_html = """<!-- header.html -->
<header style="background:#222; color:#fff; padding:1rem; text-align:center;">
<h1>Ukubona LLC</h1>
<nav>
<a href="index.html" style="color:#fff; margin:0 1rem;">Home</a>
<a href="about.html" style="color:#fff; margin:0 1rem;">About</a>
<a href="contact.html" style="color:#fff; margin:0 1rem;">Contact</a>
</nav>
</header>
"""
# Add favicon placeholder and logo
favicon_dummy = b"\x00\x00\x01\x00\x01\x00\x10\x10\x00\x00\x01\x00\x04\x00" + b"\x00" * 100 # dummy favicon binary
# Add header script to HTML template
html_template_full = """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{title}</title>
<link rel="stylesheet" href="assets/css/main.css" />
<link rel="icon" href="assets/icons/favicon.ico" />
<script src="assets/js/main.js" defer></script>
</head>
<body>
<div id="header-container"></div>
<main>
<h1>{heading}</h1>
<p>{content}</p>
</main>
<div id="footer-container"></div>
<script>
fetch('components/header.html')
.then(res => res.text())
.then(html => document.getElementById('header-container').innerHTML = html);
fetch('components/footer.html')
.then(res => res.text())
.then(html => document.getElementById('footer-container').innerHTML = html);
</script>
</body>
</html>
"""
# Update HTML content with header + footer
for filename, (title, heading, content) in page_data.items():
project_structure["project-root/"][filename] = html_template_full.format(
title=title,
heading=heading,
content=content
)
# Add the header component and dummy favicon/logo
project_structure["project-root/"]["components"]["header.html"] = header_html
project_structure["project-root/"]["assets"]["icons"]["favicon.ico"] = favicon_dummy
project_structure["project-root/"]["assets"]["images"]["logo.png"] = b"\x89PNG\r\n\x1a\n" + b"\x00" * 100 # dummy PNG
# Add Netlify and Render configs
project_structure["project-root/"]["netlify.toml"] = """[build]
publish = "./"
command = ""
[dev]
port = 8888
"""
project_structure["project-root/"]["render.yaml"] = """services:
- type: web
name: ukubona-site
env: static
staticPublishPath: .
"""
# Rebuild the zip
zip_path = "/mnt/data/ukubona_site_full.zip"
with zipfile.ZipFile(zip_path, 'w') as zipf:
write_structure_to_zip(zipf, "", project_structure)
zip_path
##
# Ensure the 'components', 'icons', and 'images' folders exist before adding files
project_structure["project-root/"].setdefault("components/", {})["header.html"] = header_html
project_structure["project-root/"]["components/"]["footer.html"] = project_structure["project-root/"]["components/"]["footer.html"]
project_structure["project-root/"]["assets"].setdefault("icons/", {})["favicon.ico"] = favicon_dummy
project_structure["project-root/"]["assets"].setdefault("images/", {})["logo.png"] = b"\x89PNG\r\n\x1a\n" + b"\x00" * 100 # dummy PNG
# Add Netlify and Render config files
project_structure["project-root/"]["netlify.toml"] = """[build]
publish = "./"
command = ""
[dev]
port = 8888
"""
project_structure["project-root/"]["render.yaml"] = """services:
- type: web
name: ukubona-site
env: static
staticPublishPath: .
"""
# Rebuild the final zip
zip_path = "/mnt/data/ukubona_site_full.zip"
with zipfile.ZipFile(zip_path, 'w') as zipf:
write_structure_to_zip(zipf, "", project_structure)
zip_path
It seems like I can’t do more advanced data analysis right now. Please try again later.
When the tool is working again, I’ll be able to regenerate the full ukubona_site_full.zip
package including:
- Standardized header and footer components
- Dummy favicon and logo assets
- Deployment configs for Netlify (
netlify.toml
) and Render (render.yaml
)
If you’d like, I can give you the updated folder tree and all contents as copy-pasteable code right now so you don’t lose momentum — would that help?
# Ensure the 'components', 'icons', and 'images' folders exist before adding files
project_structure["project-root/"].setdefault("components/", {})["header.html"] = header_html
project_structure["project-root/"]["components/"]["footer.html"] = project_structure["project-root/"]["components/"]["footer.html"]
project_structure["project-root/"]["assets"].setdefault("icons/", {})["favicon.ico"] = favicon_dummy
project_structure["project-root/"]["assets"].setdefault("images/", {})["logo.png"] = b"\x89PNG\r\n\x1a\n" + b"\x00" * 100 # dummy PNG
# Add Netlify and Render config files
project_structure["project-root/"]["netlify.toml"] = """[build]
publish = "./"
command = ""
[dev]
port = 8888
"""
project_structure["project-root/"]["render.yaml"] = """services:
- type: web
name: ukubona-site
env: static
staticPublishPath: .
"""
# Rebuild the final zip
zip_path = "/mnt/data/ukubona_site_full.zip"
with zipfile.ZipFile(zip_path, 'w') as zipf:
write_structure_to_zip(zipf, "", project_structure)
zip_path