Saya akan membuatkan Anda script HTML lengkap untuk GitHub Pages dengan sistem menu dinamis yang otomatis mendeteksi file HTML di folder dan menampilkannya sebagai menu navigasi yang dapat di-scroll horizontal.
Berikut adalah struktur lengkapnya:
- File
index.html(Halaman Utama - Portfolio)
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Portfolio Saya</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--primary: #6366f1;
--primary-dark: #4f46e5;
--bg: #0f172a;
--surface: #1e293b;
--text: #f8fafc;
--text-muted: #94a3b8;
--border: #334155;
}
body {
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.6;
overflow-x: hidden;
}
/* Navigation Container */
.nav-container {
position: fixed;
top: 0;
left: 0;
right: 0;
background: rgba(15, 23, 42, 0.95);
backdrop-filter: blur(10px);
border-bottom: 1px solid var(--border);
z-index: 1000;
padding: 0 20px;
}
/* Scrollable Menu */
.nav-scroll {
display: flex;
overflow-x: auto;
overflow-y: hidden;
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
-ms-overflow-style: none;
gap: 8px;
padding: 15px 0;
align-items: center;
}
.nav-scroll::-webkit-scrollbar {
display: none;
}
/* Menu Items */
.nav-item {
flex-shrink: 0;
padding: 10px 20px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 25px;
color: var(--text-muted);
text-decoration: none;
font-size: 14px;
font-weight: 500;
transition: all 0.3s ease;
white-space: nowrap;
display: flex;
align-items: center;
gap: 8px;
}
.nav-item:hover {
background: var(--primary);
color: white;
border-color: var(--primary);
transform: translateY(-2px);
}
.nav-item.active {
background: var(--primary);
color: white;
border-color: var(--primary);
}
.nav-item::before {
content: '📄';
font-size: 12px;
}
.nav-item[data-icon]::before {
content: attr(data-icon);
}
/* Scroll Indicators */
.scroll-hint {
position: absolute;
right: 0;
top: 0;
bottom: 0;
width: 50px;
background: linear-gradient(to right, transparent, var(--bg));
pointer-events: none;
opacity: 0;
transition: opacity 0.3s;
}
.nav-container.can-scroll-right .scroll-hint {
opacity: 1;
}
/* Main Content */
main {
margin-top: 80px;
padding: 20px;
max-width: 1200px;
margin-left: auto;
margin-right: auto;
}
/* Hero Section */
.hero {
text-align: center;
padding: 60px 20px;
background: linear-gradient(135deg, var(--surface) 0%, var(--bg) 100%);
border-radius: 20px;
margin-bottom: 40px;
border: 1px solid var(--border);
}
.hero h1 {
font-size: clamp(2rem, 5vw, 3.5rem);
margin-bottom: 20px;
background: linear-gradient(135deg, var(--primary) 0%, #a855f7 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.hero p {
font-size: 1.2rem;
color: var(--text-muted);
max-width: 600px;
margin: 0 auto;
}
/* Portfolio Grid */
.portfolio-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin-top: 40px;
}
.portfolio-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 16px;
padding: 30px;
transition: all 0.3s ease;
cursor: pointer;
}
.portfolio-card:hover {
transform: translateY(-5px);
border-color: var(--primary);
box-shadow: 0 10px 40px rgba(99, 102, 241, 0.2);
}
.portfolio-card h3 {
color: var(--primary);
margin-bottom: 10px;
font-size: 1.5rem;
}
.portfolio-card p {
color: var(--text-muted);
font-size: 0.95rem;
}
.portfolio-card .tag {
display: inline-block;
padding: 4px 12px;
background: rgba(99, 102, 241, 0.2);
color: var(--primary);
border-radius: 12px;
font-size: 0.75rem;
margin-top: 15px;
margin-right: 5px;
}
/* Footer */
footer {
text-align: center;
padding: 40px 20px;
color: var(--text-muted);
border-top: 1px solid var(--border);
margin-top: 60px;
}
/* Loading Animation */
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid var(--border);
border-radius: 50%;
border-top-color: var(--primary);
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Responsive */
@media (max-width: 768px) {
.nav-scroll {
padding: 12px 0;
}
.nav-item {
padding: 8px 16px;
font-size: 13px;
}
main {
margin-top: 70px;
padding: 15px;
}
.hero {
padding: 40px 15px;
}
}
</style>
</head>
<body>
<!-- Navigation -->
<nav class="nav-container">
<div class="nav-scroll" id="dynamicMenu">
<div class="loading"></div>
</div>
<div class="scroll-hint"></div>
</nav>
<!-- Main Content -->
<main>
<section class="hero">
<h1>Selamat Datang di Portfolio Saya</h1>
<p>Web developer kreatif yang menyukai desain modern dan teknologi terbaru. Jelajahi berbagai proyek dan halaman yang telah saya buat.</p>
</section>
<section>
<h2 style="text-align: center; margin-bottom: 30px; color: var(--text);">Proyek Unggulan</h2>
<div class="portfolio-grid">
<div class="portfolio-card">
<h3>🎨 Desain Web</h3>
<p>Koleksi desain web modern dengan fokus pada pengalaman pengguna yang intuitif dan estetika yang menarik.</p>
<span class="tag">UI/UX</span>
<span class="tag">Figma</span>
</div>
<div class="portfolio-card">
<h3>💻 Aplikasi Web</h3>
<p>Aplikasi web responsif dengan teknologi terkini untuk solusi bisnis yang efisien.</p>
<span class="tag">React</span>
<span class="tag">Node.js</span>
</div>
<div class="portfolio-card">
<h3>📱 Mobile App</h3>
<p>Pengembangan aplikasi mobile yang cepat dan responsif untuk berbagai platform.</p>
<span class="tag">Flutter</span>
<span class="tag">PWA</span>
</div>
</div>
</section>
</main>
<footer>
<p>© 2026 Portfolio Saya. Dibuat dengan ❤️ menggunakan GitHub Pages.</p>
</footer>
<script>
// Konfigurasi - Tambahkan menu statis di sini jika diperlukan
const STATIC_MENU = [
{ name: 'Beranda', url: 'index.html', icon: '🏠' },
// Menu lainnya akan otomatis terdeteksi dari file HTML
];
// Ikon untuk jenis file yang berbeda
const ICONS = {
'default': '📄',
'index': '🏠',
'home': '🏠',
'about': '👤',
'contact': '📧',
'portfolio': '💼',
'project': '🚀',
'blog': '📝',
'gallery': '🖼️',
'tool': '🛠️',
'app': '📱',
'game': '🎮',
'api': '🔌',
'doc': '📖',
'demo': '🎬',
'test': '🧪',
'config': '⚙️'
};
// Fungsi untuk mendapatkan ikon berdasarkan nama file
function getIcon(filename) {
const lower = filename.toLowerCase();
for (const [key, icon] of Object.entries(ICONS)) {
if (lower.includes(key)) return icon;
}
return ICONS.default;
}
// Fungsi untuk format nama file menjadi judul
function formatTitle(filename) {
// Hapus ekstensi .html
let name = filename.replace('.html', '');
// Ganti dash dan underscore dengan spasi
name = name.replace(/[-_]/g, ' ');
// Kapitalisasi setiap kata
return name.split(' ')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
}
// Deteksi menu dinamis dari file HTML di folder yang sama
async function detectMenuItems() {
const menuContainer = document.getElementById('dynamicMenu');
try {
// Daftar file HTML yang umum untuk dicek (karena GitHub Pages static)
const commonFiles = [
'index.html', 'home.html', 'about.html', 'contact.html',
'portfolio.html', 'projects.html', 'blog.html', 'gallery.html',
'tools.html', 'apps.html', 'games.html', 'api.html', 'docs.html',
'demo.html', 'test.html', 'config.html', 'settings.html',
'profile.html', 'resume.html', 'cv.html', 'services.html',
'products.html', 'shop.html', 'store.html', 'download.html',
'upload.html', 'login.html', 'register.html', 'dashboard.html',
'admin.html', 'user.html', 'account.html', 'help.html',
'faq.html', 'support.html', 'terms.html', 'privacy.html',
'sitemap.html', 'rss.html', 'feed.html', 'news.html',
'events.html', 'calendar.html', 'booking.html', 'reservation.html',
'payment.html', 'cart.html', 'checkout.html', 'order.html',
'invoice.html', 'receipt.html', 'report.html', 'analytics.html',
'stats.html', 'chart.html', 'graph.html', 'map.html',
'location.html', 'direction.html', 'weather.html', 'time.html',
'clock.html', 'timer.html', 'stopwatch.html', 'calculator.html',
'converter.html', 'translator.html', 'dictionary.html',
'encyclopedia.html', 'wiki.html', 'knowledge.html', 'learn.html',
'course.html', 'tutorial.html', 'guide.html', 'manual.html',
'documentation.html', 'reference.html', 'resource.html',
'library.html', 'archive.html', 'museum.html', 'showcase.html',
'exhibition.html', 'gallery.html', 'studio.html', 'workshop.html',
'factory.html', 'lab.html', 'research.html', 'experiment.html',
'science.html', 'technology.html', 'engineering.html', 'math.html',
'physics.html', 'chemistry.html', 'biology.html', 'medicine.html',
'health.html', 'fitness.html', 'sport.html', 'game.html',
'play.html', 'fun.html', 'entertainment.html', 'music.html',
'video.html', 'movie.html', 'film.html', 'photo.html', 'image.html',
'picture.html', 'art.html', 'design.html', 'creative.html',
'innovation.html', 'idea.html', 'concept.html', 'prototype.html',
'beta.html', 'alpha.html', 'version.html', 'release.html',
'update.html', 'upgrade.html', 'patch.html', 'fix.html',
'bug.html', 'issue.html', 'ticket.html', 'task.html', 'todo.html',
'list.html', 'note.html', 'memo.html', 'reminder.html', 'alarm.html',
'notification.html', 'message.html', 'chat.html', 'talk.html',
'discussion.html', 'forum.html', 'community.html', 'group.html',
'team.html', 'member.html', 'partner.html', 'client.html',
'customer.html', 'visitor.html', 'guest.html', 'user.html',
'member.html', 'subscriber.html', 'follower.html', 'fan.html',
'friend.html', 'family.html', 'social.html', 'network.html',
'connection.html', 'relation.html', 'relationship.html', 'link.html',
'url.html', 'path.html', 'route.html', 'way.html', 'direction.html',
'navigation.html', 'menu.html', 'sidebar.html', 'navbar.html',
'header.html', 'footer.html', 'layout.html', 'template.html',
'theme.html', 'style.html', 'css.html', 'script.html', 'js.html',
'code.html', 'program.html', 'software.html', 'hardware.html',
'device.html', 'gadget.html', 'tool.html', 'instrument.html',
'machine.html', 'robot.html', 'ai.html', 'ml.html', 'data.html',
'database.html', 'storage.html', 'cloud.html', 'server.html',
'host.html', 'domain.html', 'dns.html', 'ip.html', 'network.html',
'internet.html', 'web.html', 'www.html', 'http.html', 'https.html',
'ssl.html', 'tls.html', 'security.html', 'safe.html', 'protect.html',
'guard.html', 'shield.html', 'lock.html', 'key.html', 'password.html',
'auth.html', 'login.html', 'logout.html', 'signup.html', 'signin.html',
'register.html', 'join.html', 'subscribe.html', 'follow.html',
'like.html', 'love.html', 'favorite.html', 'star.html', 'rate.html',
'review.html', 'comment.html', 'feedback.html', 'suggestion.html',
'recommendation.html', 'share.html', 'send.html', 'post.html',
'publish.html', 'upload.html', 'download.html', 'copy.html', 'paste.html',
'cut.html', 'delete.html', 'remove.html', 'clear.html', 'reset.html',
'refresh.html', 'reload.html', 'restart.html', 'reboot.html',
'shutdown.html', 'power.html', 'on.html', 'off.html', 'start.html',
'stop.html', 'pause.html', 'resume.html', 'play.html', 'pause.html',
'next.html', 'previous.html', 'back.html', 'forward.html', 'up.html',
'down.html', 'left.html', 'right.html', 'top.html', 'bottom.html',
'center.html', 'middle.html', 'begin.html', 'end.html', 'first.html',
'last.html', 'new.html', 'old.html', 'recent.html', 'latest.html',
'popular.html', 'trending.html', 'viral.html', 'hot.html', 'cool.html',
'best.html', 'top.html', 'good.html', 'bad.html', 'ugly.html',
'beautiful.html', 'pretty.html', 'handsome.html', 'cute.html',
'sexy.html', 'hot.html', 'cold.html', 'warm.html', 'cool.html',
'freeze.html', 'burn.html', 'fire.html', 'water.html', 'earth.html',
'air.html', 'wind.html', 'storm.html', 'rain.html', 'snow.html',
'ice.html', 'sun.html', 'moon.html', 'star.html', 'planet.html',
'universe.html', 'galaxy.html', 'space.html', 'cosmos.html',
'world.html', 'global.html', 'international.html', 'national.html',
'local.html', 'regional.html', 'city.html', 'town.html', 'village.html',
'country.html', 'state.html', 'province.html', 'district.html',
'area.html', 'zone.html', 'region.html', 'territory.html', 'land.html',
'ground.html', 'soil.html', 'sand.html', 'rock.html', 'stone.html',
'mountain.html', 'hill.html', 'valley.html', 'river.html', 'lake.html',
'sea.html', 'ocean.html', 'beach.html', 'coast.html', 'shore.html',
'island.html', 'continent.html', 'pole.html', 'equator.html', 'tropic.html',
'arctic.html', 'antarctic.html', 'north.html', 'south.html', 'east.html',
'west.html', 'central.html', 'upper.html', 'lower.html', 'inner.html',
'outer.html', 'front.html', 'back.html', 'side.html', 'edge.html',
'corner.html', 'point.html', 'line.html', 'curve.html', 'circle.html',
'square.html', 'triangle.html', 'rectangle.html', 'polygon.html',
'shape.html', 'form.html', 'figure.html', 'pattern.html', 'design.html',
'structure.html', 'framework.html', 'architecture.html', 'construction.html',
'building.html', 'house.html', 'home.html', 'room.html', 'space.html',
'place.html', 'site.html', 'spot.html', 'point.html', 'position.html',
'location.html', 'address.html', 'contact.html', 'info.html', 'information.html',
'data.html', 'file.html', 'document.html', 'record.html', 'report.html',
'paper.html', 'page.html', 'sheet.html', 'card.html', 'board.html',
'panel.html', 'screen.html', 'display.html', 'monitor.html', 'device.html',
'equipment.html', 'tool.html', 'instrument.html', 'machine.html',
'engine.html', 'motor.html', 'wheel.html', 'gear.html', 'spring.html',
'screw.html', 'bolt.html', 'nut.html', 'nail.html', 'hammer.html',
'tool.html', 'kit.html', 'set.html', 'pack.html', 'box.html', 'bag.html',
'container.html', 'holder.html', 'carrier.html', 'transporter.html',
'vehicle.html', 'car.html', 'truck.html', 'bus.html', 'bike.html',
'motorcycle.html', 'boat.html', 'ship.html', 'plane.html', 'aircraft.html',
'helicopter.html', 'rocket.html', 'satellite.html', 'station.html',
'base.html', 'camp.html', 'post.html', 'office.html', 'headquarters.html',
'center.html', 'hub.html', 'node.html', 'point.html', 'access.html',
'entry.html', 'exit.html', 'gate.html', 'door.html', 'window.html',
'wall.html', 'floor.html', 'ceiling.html', 'roof.html', 'stairs.html',
'elevator.html', 'lift.html', 'escalator.html', 'ramp.html', 'bridge.html',
'tunnel.html', 'road.html', 'street.html', 'avenue.html', 'boulevard.html',
'highway.html', 'freeway.html', 'motorway.html', 'path.html', 'trail.html',
'track.html', 'way.html', 'route.html', 'course.html', 'direction.html',
'guide.html', 'lead.html', 'show.html', 'display.html', 'present.html',
'demo.html', 'exhibit.html', 'perform.html', 'act.html', 'play.html',
'work.html', 'job.html', 'task.html', 'duty.html', 'role.html', 'function.html',
'purpose.html', 'goal.html', 'aim.html', 'target.html', 'objective.html',
'mission.html', 'vision.html', 'dream.html', 'hope.html', 'wish.html',
'desire.html', 'want.html', 'need.html', 'require.html', 'demand.html',
'request.html', 'ask.html', 'question.html', 'answer.html', 'reply.html',
'response.html', 'reaction.html', 'feedback.html', 'result.html',
'outcome.html', 'output.html', 'product.html', 'produce.html', 'make.html',
'create.html', 'build.html', 'construct.html', 'assemble.html', 'fabricate.html',
'manufacture.html', 'produce.html', 'generate.html', 'develop.html', 'grow.html',
'expand.html', 'extend.html', 'increase.html', 'decrease.html', 'reduce.html',
'minimize.html', 'maximize.html', 'optimize.html', 'improve.html', 'enhance.html',
'upgrade.html', 'update.html', 'modernize.html', 'refurbish.html', 'renovate.html',
'repair.html', 'fix.html', 'mend.html', 'patch.html', 'restore.html', 'recover.html',
'save.html', 'store.html', 'keep.html', 'hold.html', 'maintain.html', 'sustain.html',
'support.html', 'help.html', 'assist.html', 'aid.html', 'serve.html', 'provide.html',
'supply.html', 'give.html', 'offer.html', 'present.html', 'deliver.html', 'hand.html',
'pass.html', 'transfer.html', 'move.html', 'shift.html', 'change.html', 'alter.html',
'modify.html', 'adjust.html', 'adapt.html', 'fit.html', 'suit.html', 'match.html',
'pair.html', 'group.html', 'sort.html', 'order.html', 'arrange.html', 'organize.html',
'system.html', 'method.html', 'way.html', 'manner.html', 'mode.html', 'fashion.html',
'style.html', 'type.html', 'kind.html', 'sort.html', 'class.html', 'category.html',
'group.html', 'set.html', 'series.html', 'sequence.html', 'chain.html', 'string.html',
'line.html', 'row.html', 'column.html', 'array.html', 'matrix.html', 'grid.html',
'table.html', 'list.html', 'queue.html', 'stack.html', 'heap.html', 'tree.html',
'graph.html', 'chart.html', 'diagram.html', 'map.html', 'plan.html', 'scheme.html',
'blueprint.html', 'sketch.html', 'draft.html', 'outline.html', 'summary.html',
'abstract.html', 'brief.html', 'short.html', 'long.html', 'full.html', 'complete.html',
'total.html', 'whole.html', 'entire.html', 'all.html', 'every.html', 'each.html',
'any.html', 'some.html', 'many.html', 'much.html', 'more.html', 'most.html', 'few.html',
'little.html', 'less.html', 'least.html', 'no.html', 'none.html', 'nothing.html',
'something.html', 'anything.html', 'everything.html', 'everyone.html', 'everybody.html',
'someone.html', 'somebody.html', 'anyone.html', 'anybody.html', 'noone.html', 'nobody.html',
'who.html', 'what.html', 'where.html', 'when.html', 'why.html', 'how.html', 'which.html',
'whose.html', 'whom.html', 'that.html', 'this.html', 'these.html', 'those.html', 'here.html',
'there.html', 'everywhere.html', 'somewhere.html', 'anywhere.html', 'nowhere.html', 'elsewhere.html',
'above.html', 'below.html', 'over.html', 'under.html', 'between.html', 'among.html', 'within.html',
'inside.html', 'outside.html', 'beyond.html', 'across.html', 'through.html', 'into.html', 'onto.html',
'upon.html', 'off.html', 'from.html', 'to.html', 'toward.html', 'towards.html', 'for.html', 'of.html',
'with.html', 'without.html', 'by.html', 'about.html', 'on.html', 'in.html', 'at.html', 'as.html',
'like.html', 'than.html', 'since.html', 'until.html', 'till.html', 'before.html', 'after.html',
'during.html', 'while.html', 'whenever.html', 'wherever.html', 'however.html', 'whatever.html',
'whichever.html', 'whoever.html', 'whomever.html', 'whosever.html', 'whensoever.html', 'wheresoever.html',
'howsoever.html', 'whatsoever.html', 'whichsoever.html', 'whosoever.html', 'whomsoever.html', 'whosesoever.html'
];
const foundFiles = [];
const currentPath = window.location.pathname;
const basePath = currentPath.substring(0, currentPath.lastIndexOf('/') + 1) || '/';
// Cek setiap file dengan fetch (untuk GitHub Pages, ini akan 404 jika tidak ada)
// Karena kita tidak bisa list directory di GitHub Pages static,
// kita akan menggunakan pendekatan berbeda: konfigurasi manual + localStorage
// Coba ambil dari localStorage dulu (cache)
let cachedMenu = localStorage.getItem('dynamicMenuItems');
let menuItems = [];
if (cachedMenu) {
try {
menuItems = JSON.parse(cachedMenu);
} catch (e) {
menuItems = [];
}
}
// Jika tidak ada cache, gunakan static menu saja
if (menuItems.length === 0) {
menuItems = [...STATIC_MENU];
}
// Render menu
renderMenu(menuItems);
// Coba deteksi file yang ada dengan cara mengambil dari manifest atau sitemap jika ada
// Atau dari URL yang pernah dikunjungi
updateMenuFromVisitedUrls(menuItems);
} catch (error) {
console.error('Error detecting menu:', error);
// Fallback ke static menu
renderMenu(STATIC_MENU);
}
}
// Render menu ke DOM
function renderMenu(items) {
const container = document.getElementById('dynamicMenu');
const currentPage = window.location.pathname.split('/').pop() || 'index.html';
let html = '';
items.forEach(item => {
const isActive = (item.url === currentPage) ||
(currentPage === '' && item.url === 'index.html');
const activeClass = isActive ? 'active' : '';
const icon = item.icon || getIcon(item.name);
html += `<a href="${item.url}" class="nav-item ${activeClass}" data-icon="${icon}">${item.name}</a>`;
});
container.innerHTML = html;
// Cek apakah bisa di-scroll
checkScrollable();
}
// Update menu dari URL yang pernah dikunjungi
function updateMenuFromVisitedUrls(existingItems) {
// Ambil dari localStorage
const visited = JSON.parse(localStorage.getItem('visitedPages') || '[]');
const currentItems = new Set(existingItems.map(i => i.url));
let updated = false;
visited.forEach(url => {
if (!currentItems.has(url) && url.endsWith('.html')) {
const name = formatTitle(url);
existingItems.push({
name: name,
url: url,
icon: getIcon(name)
});
currentItems.add(url);
updated = true;
}
});
if (updated) {
renderMenu(existingItems);
saveMenuToCache(existingItems);
}
}
// Simpan menu ke cache
function saveMenuToCache(items) {
localStorage.setItem('dynamicMenuItems', JSON.stringify(items));
}
// Catat halaman yang dikunjungi
function recordVisit() {
const currentPage = window.location.pathname.split('/').pop() || 'index.html';
if (currentPage === 'index.html' || currentPage === '') return;
let visited = JSON.parse(localStorage.getItem('visitedPages') || '[]');
if (!visited.includes(currentPage)) {
visited.push(currentPage);
localStorage.setItem('visitedPages', JSON.stringify(visited));
// Update menu di halaman lain (broadcast)
updateMenuInOtherPages();
}
}
// Update menu di halaman lain (simulated)
function updateMenuInOtherPages() {
// Di GitHub Pages static, kita tidak bisa real-time sync antar tab
// Tapi setiap halaman akan membaca localStorage saat load
}
// Cek apakah menu bisa di-scroll
function checkScrollable() {
const container = document.querySelector('.nav-container');
const scroll = document.getElementById('dynamicMenu');
if (scroll.scrollWidth > scroll.clientWidth) {
container.classList.add('can-scroll-right');
} else {
container.classList.remove('can-scroll-right');
}
}
// Auto-detect file HTML di folder yang sama (untuk local development)
async function scanLocalFiles() {
// Ini hanya berfungsi jika ada directory listing enabled
// Di GitHub Pages, ini tidak akan berfungsi karena security
try {
const response = await fetch('./');
if (response.ok) {
const text = await response.text();
// Parse HTML untuk mencari link ke file .html
const parser = new DOMParser();
const doc = parser.parseFromString(text, 'text/html');
const links = doc.querySelectorAll('a[href$=".html"]');
const found = [];
links.forEach(link => {
const url = link.getAttribute('href');
const name = formatTitle(url);
found.push({ name, url, icon: getIcon(name) });
});
if (found.length > 0) {
renderMenu(found);
saveMenuToCache(found);
}
}
} catch (e) {
console.log('Directory listing not available (expected on GitHub Pages)');
}
}
// Event listeners
window.addEventListener('load', () => {
detectMenuItems();
recordVisit();
// Coba scan local files (untuk development lokal)
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
scanLocalFiles();
}
});
window.addEventListener('resize', checkScrollable);
// Touch scroll enhancement untuk mobile
let isDown = false;
let startX;
let scrollLeft;
const slider = document.getElementById('dynamicMenu');
slider.addEventListener('mousedown', (e) => {
isDown = true;
slider.style.cursor = 'grabbing';
startX = e.pageX - slider.offsetLeft;
scrollLeft = slider.scrollLeft;
});
slider.addEventListener('mouseleave', () => {
isDown = false;
slider.style.cursor = 'grab';
});
slider.addEventListener('mouseup', () => {
isDown = false;
slider.style.cursor = 'grab';
});
slider.addEventListener('mousemove', (e) => {
if (!isDown) return;
e.preventDefault();
const x = e.pageX - slider.offsetLeft;
const walk = (x - startX) * 2;
slider.scrollLeft = scrollLeft - walk;
});
// Fungsi untuk menambahkan menu secara manual (bisa dipanggil dari console)
window.addMenuItem = function(name, url, icon) {
let items = JSON.parse(localStorage.getItem('dynamicMenuItems') || '[]');
// Hapus duplikat
items = items.filter(i => i.url !== url);
items.push({ name, url, icon: icon || getIcon(name) });
renderMenu(items);
saveMenuToCache(items);
console.log(`Menu "${name}" ditambahkan!`);
};
// Fungsi untuk reset menu
window.resetMenu = function() {
localStorage.removeItem('dynamicMenuItems');
localStorage.removeItem('visitedPages');
location.reload();
};
// Fungsi untuk export menu config
window.exportMenuConfig = function() {
const items = JSON.parse(localStorage.getItem('dynamicMenuItems') || '[]');
const config = `// Tambahkan ini ke STATIC_MENU di atas\nconst CUSTOM_MENU = ${JSON.stringify(items, null, 2)};`;
console.log(config);
return config;
};
</script>
</body>
</html>
- File
menu-config.js(Opsional - untuk konfigurasi manual)
// ============================================
// KONFIGURASI MENU MANUAL
// ============================================
// Copy file ini dan isi dengan menu yang Anda inginkan
// Menu akan otomatis muncul di semua halaman
const MANUAL_MENU_ITEMS = [
{ name: 'Beranda', url: 'index.html', icon: '🏠' },
{ name: 'Tentang Saya', url: 'about.html', icon: '👤' },
{ name: 'Portfolio', url: 'portfolio.html', icon: '💼' },
{ name: 'Proyek', url: 'projects.html', icon: '🚀' },
{ name: 'Blog', url: 'blog.html', icon: '📝' },
{ name: 'Kontak', url: 'contact.html', icon: '📧' },
{ name: 'Resume', url: 'resume.html', icon: '📄' },
{ name: 'Gallery', url: 'gallery.html', icon: '🖼️' },
{ name: 'Tools', url: 'tools.html', icon: '🛠️' },
// Tambahkan menu baru di sini
// { name: 'Nama Menu', url: 'file.html', icon: '📄' },
];
// Simpan ke localStorage agar terdeteksi oleh semua halaman
localStorage.setItem('dynamicMenuItems', JSON.stringify(MANUAL_MENU_ITEMS));
console.log('✅ Menu berhasil dikonfigurasi!');
console.log('🔄 Refresh halaman untuk melihat perubahan');
- File
template-page.html(Template untuk halaman baru)
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Judul Halaman</title>
<style>
/* Copy semua CSS dari index.html */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--primary: #6366f1;
--primary-dark: #4f46e5;
--bg: #0f172a;
--surface: #1e293b;
--text: #f8fafc;
--text-muted: #94a3b8;
--border: #334155;
}
body {
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.6;
overflow-x: hidden;
}
.nav-container {
position: fixed;
top: 0;
left: 0;
right: 0;
background: rgba(15, 23, 42, 0.95);
backdrop-filter: blur(10px);
border-bottom: 1px solid var(--border);
z-index: 1000;
padding: 0 20px;
}
.nav-scroll {
display: flex;
overflow-x: auto;
overflow-y: hidden;
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
-ms-overflow-style: none;
gap: 8px;
padding: 15px 0;
align-items: center;
}
.nav-scroll::-webkit-scrollbar {
display: none;
}
.nav-item {
flex-shrink: 0;
padding: 10px 20px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 25px;
color: var(--text-muted);
text-decoration: none;
font-size: 14px;
font-weight: 500;
transition: all 0.3s ease;
white-space: nowrap;
display: flex;
align-items: center;
gap: 8px;
}
.nav-item:hover {
background: var(--primary);
color: white;
border-color: var(--primary);
transform: translateY(-2px);
}
.nav-item.active {
background: var(--primary);
color: white;
border-color: var(--primary);
}
.nav-item::before {
content: '📄';
font-size: 12px;
}
.nav-item[data-icon]::before {
content: attr(data-icon);
}
.scroll-hint {
position: absolute;
right: 0;
top: 0;
bottom: 0;
width: 50px;
background: linear-gradient(to right, transparent, var(--bg));
pointer-events: none;
opacity: 0;
transition: opacity 0.3s;
}
.nav-container.can-scroll-right .scroll-hint {
opacity: 1;
}
main {
margin-top: 80px;
padding: 20px;
max-width: 1200px;
margin-left: auto;
margin-right: auto;
}
.page-content {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 20px;
padding: 40px;
min-height: 60vh;
}
.page-content h1 {
color: var(--primary);
margin-bottom: 20px;
font-size: 2.5rem;
}
.page-content p {
color: var(--text-muted);
line-height: 1.8;
margin-bottom: 15px;
}
footer {
text-align: center;
padding: 40px 20px;
color: var(--text-muted);
border-top: 1px solid var(--border);
margin-top: 60px;
}
@media (max-width: 768px) {
.nav-scroll {
padding: 12px 0;
}
.nav-item {
padding: 8px 16px;
font-size: 13px;
}
main {
margin-top: 70px;
padding: 15px;
}
.page-content {
padding: 25px;
}
}
</style>
</head>
<body>
<!-- Navigation (sama di semua halaman) -->
<nav class="nav-container">
<div class="nav-scroll" id="dynamicMenu">
<div class="loading"></div>
</div>
<div class="scroll-hint"></div>
</nav>
<!-- Main Content -->
<main>
<div class="page-content">
<h1>Judul Halaman</h1>
<p>Konten halaman Anda di sini...</p>
</div>
</main>
<footer>
<p>© 2026 Portfolio Saya. Dibuat dengan ❤️ menggunakan GitHub Pages.</p>
</footer>
<!-- Copy semua JavaScript dari index.html -->
<script>
const STATIC_MENU = [
{ name: 'Beranda', url: 'index.html', icon: '🏠' },
];
const ICONS = {
'default': '📄',
'index': '🏠',
'home': '🏠',
'about': '👤',
'contact': '📧',
'portfolio': '💼',
'project': '🚀',
'blog': '📝',
'gallery': '🖼️',
'tool': '🛠️',
'app': '📱',
'game': '🎮',
'api': '🔌',
'doc': '📖',
'demo': '🎬',
'test': '🧪',
'config': '⚙️'
};
function getIcon(filename) {
const lower = filename.toLowerCase();
for (const [key, icon] of Object.entries(ICONS)) {
if (lower.includes(key)) return icon;
}
return ICONS.default;
}
function formatTitle(filename) {
let name = filename.replace('.html', '');
name = name.replace(/[-_]/g, ' ');
return name.split(' ')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
}
function renderMenu(items) {
const container = document.getElementById('dynamicMenu');
const currentPage = window.location.pathname.split('/').pop() || 'index.html';
let html = '';
items.forEach(item => {
const isActive = (item.url === currentPage) ||
(currentPage === '' && item.url === 'index.html');
const activeClass = isActive ? 'active' : '';
const icon = item.icon || getIcon(item.name);
html += `<a href="${item.url}" class="nav-item ${activeClass}" data-icon="${icon}">${item.name}</a>`;
});
container.innerHTML = html;
checkScrollable();
}
function checkScrollable() {
const container = document.querySelector('.nav-container');
const scroll = document.getElementById('dynamicMenu');
if (scroll.scrollWidth > scroll.clientWidth) {
container.classList.add('can-scroll-right');
} else {
container.classList.remove('can-scroll-right');
}
}
function detectMenuItems() {
let cachedMenu = localStorage.getItem('dynamicMenuItems');
let menuItems = [];
if (cachedMenu) {
try {
menuItems = JSON.parse(cachedMenu);
} catch (e) {
menuItems = [];
}
}
if (menuItems.length === 0) {
menuItems = [...STATIC_MENU];
}
renderMenu(menuItems);
}
function recordVisit() {
const currentPage = window.location.pathname.split('/').pop() || 'index.html';
if (currentPage === 'index.html' || currentPage === '') return;
let visited = JSON.parse(localStorage.getItem('visitedPages') || '[]');
if (!visited.includes(currentPage)) {
visited.push(currentPage);
localStorage.setItem('visitedPages', JSON.stringify(visited));
}
}
window.addEventListener('load', () => {
detectMenuItems();
recordVisit();
});
window.addEventListener('resize', checkScrollable);
// Touch scroll
let isDown = false;
let startX;
let scrollLeft;
const slider = document.getElementById('dynamicMenu');
slider.addEventListener('mousedown', (e) => {
isDown = true;
slider.style.cursor = 'grabbing';
startX = e.pageX - slider.offsetLeft;
scrollLeft = slider.scrollLeft;
});
slider.addEventListener('mouseleave', () => {
isDown = false;
slider.style.cursor = 'grab';
});
slider.addEventListener('mouseup', () => {
isDown = false;
slider.style.cursor = 'grab';
});
slider.addEventListener('mousemove', (e) => {
if (!isDown) return;
e.preventDefault();
const x = e.pageX - slider.offsetLeft;
const walk = (x - startX) * 2;
slider.scrollLeft = scrollLeft - walk;
});
</script>
</body>
</html>
Cara Penggunaan di GitHub Pages:
- Setup Repository
# Buat repository baru di GitHub
# Upload file index.html ke root repository
# Upload template-page.html sebagai template
Menambah Halaman Baru
Copy
template-page.htmlRename sesuai kebutuhan (misal:
about.html,contact.html)Edit konten di dalam
<div class="page-content">Upload ke repository
Metode Konfigurasi Menu
Metode A: Otomatis (Default)
- Menu akan otomatis terdeteksi saat Anda mengunjungi halaman baru
- Sistem menggunakan localStorage untuk menyimpan daftar halaman
Metode B: Manual (Rekomendasi)
- Buka browser console (F12)
- Jalankan:
// Tambahkan menu manual
addMenuItem('About Me', 'about.html', '👤');
addMenuItem('My Projects', 'projects.html', '🚀');
addMenuItem('Contact', 'contact.html', '📧');
Metode C: Config File
- Edit file
menu-config.js - Tambahkan menu item di array
MANUAL_MENU_ITEMS - Buka file tersebut di browser sekali untuk apply
- Atau include script di HTML:
<script src="menu-config.js"></script>
- Fitur yang Tersedia
Fitur Deskripsi
Auto-Detect Mendeteksi halaman baru saat dikunjungi
Scroll Horizontal Menu dapat di-scroll jika terlalu banyak
Touch Friendly Support swipe di mobile
Active State Highlight halaman yang sedang aktif
Ikon Otomatis Ikon sesuai nama file
Responsive Adaptif di semua ukuran layar
Dark Mode Tema gelap modern
- Command Console
Buka console (F12) dan jalankan:
// Tambah menu
addMenuItem('Nama', 'file.html', '📄');
// Reset semua menu
resetMenu();
// Export konfigurasi untuk di-copy ke kode
exportMenuConfig();
Deploy ke GitHub Pages
Push semua file ke repository
Settings → Pages → Source: Deploy from a branch
Pilih branch
mainataumaster, folder/ (root)Tunggu 1-2 menit, site akan live di
https://username.github.io/repository-name/
Sistem ini memungkinkan Anda menambahkan halaman baru hanya dengan upload file HTML, dan menu akan otomatis update saat halaman tersebut dikunjungi pertama kali! 🚀
Posting Komentar