GitHub Auto - Membuat website sederhana landing page dengan upload file html otomatis

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:

  1. 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>&copy; 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>
  1. 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');
  1. 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>&copy; 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:

  1. Setup Repository
# Buat repository baru di GitHub
# Upload file index.html ke root repository
# Upload template-page.html sebagai template
  1. Menambah Halaman Baru

  2. Copy template-page.html

  3. Rename sesuai kebutuhan (misal: about.html, contact.html)

  4. Edit konten di dalam <div class="page-content">

  5. Upload ke repository

  6. 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)

  1. Buka browser console (F12)
  2. Jalankan:
// Tambahkan menu manual
addMenuItem('About Me', 'about.html', '👤');
addMenuItem('My Projects', 'projects.html', '🚀');
addMenuItem('Contact', 'contact.html', '📧');

Metode C: Config File

  1. Edit file menu-config.js
  2. Tambahkan menu item di array MANUAL_MENU_ITEMS
  3. Buka file tersebut di browser sekali untuk apply
  4. Atau include script di HTML:
<script src="menu-config.js"></script>
  1. 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

  1. 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();
  1. Deploy ke GitHub Pages

  2. Push semua file ke repository

  3. Settings → Pages → Source: Deploy from a branch

  4. Pilih branch main atau master, folder / (root)

  5. 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! 🚀

Lebih lamaTerbaru

Posting Komentar