Berikut adalah script lengkap untuk membangun website resmi Organisasi Karang Taruna dengan Jekyll, Material Design 3, SEO-friendly, performa tinggi, dan mudah dikelola.
📁 Struktur Proyek
karang-taruna-web/
├── _config.yml
├── Gemfile
├── index.html
├── 404.html
├── robots.txt
├── _data/
│ ├── navigation.yml
│ ├── pengurus.yml
│ ├── program_kerja.yml
│ └── social.yml
├── _layouts/
│ ├── default.html
│ ├── post.html
│ ├── page.html
│ └── archive.html
├── _includes/
│ ├── head.html
│ ├── header.html
│ ├── footer.html
│ ├── breadcrumb.html
│ ├── article-card.html
│ ├── share.html
│ ├── comments.html
│ ├── search-modal.html
│ ├── skeleton.html
│ └── schema.html
├── _sass/
│ ├── _variables.scss
│ ├── _base.scss
│ ├── _layout.scss
│ ├── _components.scss
│ ├── _md3.scss
│ └── _markdown.scss
├── _pages/
│ ├── about.md
│ ├── struktur.md
│ ├── program-kerja.md
│ ├── kegiatan.md
│ ├── galeri.md
│ ├── dokumen.md
│ ├── kontak.md
│ ├── pengumuman.md
│ ├── agenda.md
│ ├── kategori.md
│ ├── tags.md
│ ├── arsip.md
│ └── search.md
├── _posts/
│ └── 2026-06-30-selamat-datang.md
├── assets/
│ ├── css/style.scss
│ ├── js/main.js
│ └── img/
└── feed.xml
1️⃣ _config.yml
title: Karang Taruna Desa Sejahtera
description: Portal resmi Karang Taruna Desa Sejahtera — Pemuda Berkarya, Desa Berjaya.
url: https://karangtaruna-sejahtera.github.io
baseurl: ""
lang: id-ID
timezone: Asia/Jakarta
# Author & Organization
author: Karang Taruna Desa Sejahtera
email: karangtaruna.sejahtera@gmail.com
logo: /assets/img/logo.png
# Social
social:
facebook: https://facebook.com/karangtaruna.sejahtera
instagram: https://instagram.com/karangtaruna.sejahtera
youtube: https://youtube.com/@karangtaruna.sejahtera
twitter: https://twitter.com/karangtaruna
whatsapp: https://wa.me/6281234567890
# Build
markdown: kramdown
highlighter: rouge
permalink: /berita/:year/:month/:day/:title/
paginate: 6
paginate_path: "/berita/page:num/"
kramdown:
input: GFM
syntax_highlighter: rouge
hard_wrap: false
auto_ids: true
toc_levels: 1..3
plugins:
- jekyll-paginate
- jekyll-sitemap
- jekyll-feed
- jekyll-seo-tag
- jekyll-gist
# Exclude
exclude:
- Gemfile
- Gemfile.lock
- README.md
- node_modules
- vendor
# Defaults
defaults:
- scope:
path: ""
type: posts
values:
layout: post
author: "Redaksi Karang Taruna"
comments: true
- scope:
path: "_pages"
values:
layout: page
# Collections
collections:
pages:
output: true
permalink: /:path/
# Giscus
giscus:
repo: karangtaruna-sejahtera/web
repo_id: "R_xxxxx"
category: "Announcements"
category_id: "DIC_xxxxx"
mapping: pathname
reactions_enabled: 1
emit_metadata: 0
theme: preferred_color_scheme
lang: id
# SEO
twitter:
username: karangtaruna
facebook:
publisher: https://facebook.com/karangtaruna.sejahtera
2️⃣ Gemfile
source "https://rubygems.org"
gem "github-pages", group: :jekyll_plugins
gem "jekyll-paginate"
gem "jekyll-sitemap"
gem "jekyll-feed"
gem "jekyll-seo-tag"
gem "jekyll-gist"
gem "webrick"
3️⃣ _data/navigation.yml
main:
- title: Beranda
url: /
icon: home
- title: Profil
url: /about/
icon: info
- title: Struktur
url: /struktur/
icon: account_tree
- title: Program Kerja
url: /program-kerja/
icon: task_alt
- title: Berita
url: /berita/
icon: newspaper
- title: Kegiatan
url: /kegiatan/
icon: event
- title: Galeri
url: /galeri/
icon: photo_library
- title: Dokumen
url: /dokumen/
icon: folder
- title: Kontak
url: /kontak/
icon: mail
4️⃣ _data/pengurus.yml
- nama: Ahmad Fauzi, S.Sos.
jabatan: Ketua
foto: /assets/img/pengurus/ketua.jpg
bidang: Pimpinan Umum
- nama: Siti Nurhaliza
jabatan: Sekretaris
foto: /assets/img/pengurus/sekretaris.jpg
bidang: Administrasi
- nama: Budi Santoso
jabatan: Bendahara
foto: /assets/img/pengurus/bendahara.jpg
bidang: Keuangan
- nama: Dewi Lestari
jabatan: Koordinator Bidang Kepemudaan
foto: /assets/img/pengurus/kepemudaan.jpg
bidang: Kepemudaan & Olahraga
- nama: Rudi Hartono
jabatan: Koordinator Bidang Sosial
foto: /assets/img/pengurus/sosial.jpg
bidang: Kesejahteraan Sosial
- nama: Maya Anggraini
jabatan: Koordinator Bidang Seni & Budaya
foto: /assets/img/pengurus/seni.jpg
bidang: Seni & Budaya
5️⃣ _data/program_kerja.yml
bidang:
- nama: Kepemudaan & Olahraga
icon: sports_soccer
program:
- nama: Turnamen Antar RT
deskripsi: Kompetisi olahraga tahunan antar RT.
- nama: Pelatihan Kepemimpinan Pemuda
deskripsi: Workshop kepemimpinan untuk pemuda desa.
- nama: Kesejahteraan Sosial
icon: volunteer_activism
program:
- nama: Bakti Sosial Bulanan
deskripsi: Kegiatan sosial rutin setiap bulan.
- nama: Santunan Anak Yatim
deskripsi: Pemberian santunan di hari besar keagamaan.
- nama: Seni & Budaya
icon: palette
program:
- nama: Festival Budaya Desa
deskripsi: Pementasan seni budaya lokal.
- nama: Sanggar Seni Remaja
deskripsi: Wadah pengembangan bakat seni.
- nama: Lingkungan Hidup
icon: eco
program:
- nama: Kerja Bakti Lingkungan
deskripsi: Pembersihan lingkungan mingguan.
- nama: Bank Sampah
deskripsi: Program daur ulang sampah.
6️⃣ _data/social.yml
- name: Facebook
icon: facebook
url: https://facebook.com/karangtaruna.sejahtera
color: "#1877F2"
- name: Instagram
icon: instagram
url: https://instagram.com/karangtaruna.sejahtera
color: "#E4405F"
- name: YouTube
icon: youtube
url: https://youtube.com/@karangtaruna.sejahtera
color: "#FF0000"
- name: WhatsApp
icon: whatsapp
url: https://wa.me/6281234567890
color: "#25D366"
- name: Email
icon: mail
url: mailto:karangtaruna.sejahtera@gmail.com
color: "#0F9D58"
7️⃣ _layouts/default.html
<!DOCTYPE html>
<html lang="{{ site.lang | default: 'id-ID' }}" data-theme="light">
<head>
{% include head.html %}
</head>
<body class="md3-body">
<a class="skip-link" href="#main">Lewati ke konten</a>
{% include header.html %}
<div id="reading-progress" class="reading-progress" aria-hidden="true"></div>
<main id="main" class="md3-main">
{% include breadcrumb.html %}
{{ content }}
</main>
{% include footer.html %}
{% include search-modal.html %}
<button id="back-to-top" class="fab" aria-label="Kembali ke atas" title="Kembali ke atas">
<span class="material-symbols-rounded">arrow_upward</span>
</button>
<script src="{{ '/assets/js/main.js' | relative_url }}" defer></script>
</body>
</html>
8️⃣ _layouts/post.html
---
layout: default
---
{% assign words = page.content | number_of_words %}
{% assign read_time = words | divided_by: 200 | plus: 1 %}
<article class="post md3-surface-1" itemscope itemtype="https://schema.org/Article">
<header class="post-header">
{% if page.image %}
<img class="post-hero" src="{{ page.image | relative_url }}" alt="{{ page.title }}" loading="eager" itemprop="image">
{% endif %}
<div class="post-meta-container">
{% if page.categories.size > 0 %}
<div class="post-categories">
{% for cat in page.categories %}
<a href="{{ '/kategori/' | append: cat | slugify | append: '/' | relative_url }}" class="md3-chip">{{ cat }}</a>
{% endfor %}
</div>
{% endif %}
<h1 class="post-title" itemprop="headline">{{ page.title }}</h1>
<div class="post-meta" itemprop="author" itemscope itemtype="https://schema.org/Person">
<span class="material-symbols-rounded">person</span>
<span itemprop="name">{{ page.author | default: site.author }}</span>
<span class="material-symbols-rounded">calendar_today</span>
<time datetime="{{ page.date | date_to_xmlschema }}" itemprop="datePublished">
{{ page.date | date: "%d %B %Y" }}
</time>
<span class="material-symbols-rounded">schedule</span>
<span>{{ read_time }} menit baca</span>
</div>
</div>
</header>
<div class="post-content md3-typography" itemprop="articleBody">
{{ content }}
</div>
{% if page.tags.size > 0 %}
<footer class="post-tags">
<span class="material-symbols-rounded">sell</span>
{% for tag in page.tags %}
<a href="{{ '/tags/' | append: tag | slugify | append: '/' | relative_url }}" class="md3-chip">#{{ tag }}</a>
{% endfor %}
</footer>
{% endif %}
{% include share.html %}
{% include comments.html %}
</article>
{% include schema.html %}
9️⃣ _layouts/page.html
---
layout: default
---
<article class="page md3-surface-1">
<header class="page-header">
<h1 class="page-title">{{ page.title }}</h1>
{% if page.description %}<p class="page-subtitle">{{ page.description }}</p>{% endif %}
</header>
<div class="page-content md3-typography">
{{ content }}
</div>
</article>
🔟 _layouts/archive.html
---
layout: default
---
<div class="archive">
<header class="page-header">
<h1 class="page-title">{{ page.title }}</h1>
{% if page.description %}<p class="page-subtitle">{{ page.description }}</p>{% endif %}
</header>
<div class="filter-bar">
<div class="md3-tabs" role="tablist">
<a href="{{ '/kategori/' | relative_url }}" class="md3-tab {% if page.layout_type == 'kategori' %}active{% endif %}">Kategori</a>
<a href="{{ '/tags/' | relative_url }}" class="md3-tab {% if page.layout_type == 'tags' %}active{% endif %}">Tag</a>
<a href="{{ '/arsip/' | relative_url }}" class="md3-tab {% if page.layout_type == 'arsip' %}active{% endif %}">Arsip</a>
</div>
</div>
<div class="archive-content">
{{ content }}
</div>
</div>
1️⃣1️⃣ _includes/head.html
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="theme-color" content="#2E7D32" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#0F1B15" media="(prefers-color-scheme: dark)">
<title>{% if page.title %}{{ page.title }} — {{ site.title }}{% else %}{{ site.title }} — {{ site.description }}{% endif %}</title>
<meta name="description" content="{{ page.description | default: site.description | strip_html | truncate: 160 }}">
<meta name="author" content="{{ page.author | default: site.author }}">
<link rel="canonical" href="{{ page.url | absolute_url }}">
<!-- SEO -->
{% seo %}
<!-- Preconnect & Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin>
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preconnect" href="https://giscus.app" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@24,400,1,0&display=swap" rel="stylesheet">
<!-- Feeds & Sitemap -->
<link rel="alternate" type="application/rss+xml" title="{{ site.title }}" href="{{ '/feed.xml' | absolute_url }}">
<link rel="sitemap" type="application/xml" href="{{ '/sitemap.xml' | absolute_url }}">
<!-- Icons -->
<link rel="icon" type="image/png" href="{{ '/assets/img/favicon.png' | relative_url }}">
<link rel="apple-touch-icon" href="{{ '/assets/img/apple-touch-icon.png' | relative_url }}">
<!-- Stylesheet -->
<link rel="stylesheet" href="{{ '/assets/css/style.css' | relative_url }}">
<!-- JSON-LD Organization -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Organization",
"name": {{ site.title | jsonify }},
"url": {{ site.url | jsonify }},
"logo": {{ site.logo | absolute_url | jsonify }},
"description": {{ site.description | jsonify }},
"email": {{ site.email | jsonify }},
"sameAs": [
{{ site.social.facebook | jsonify }},
{{ site.social.instagram | jsonify }},
{{ site.social.youtube | jsonify }}
]
}
</script>
1️⃣2️⃣ _includes/header.html
<header class="md3-app-bar" role="banner">
<div class="app-bar-inner">
<button class="icon-btn menu-toggle" aria-label="Buka menu" aria-expanded="false" aria-controls="primary-nav">
<span class="material-symbols-rounded">menu</span>
</button>
<a href="{{ '/' | relative_url }}" class="brand" aria-label="{{ site.title }}">
<img src="{{ site.logo | relative_url }}" alt="" width="36" height="36" loading="eager">
<span class="brand-text">{{ site.title }}</span>
</a>
<nav id="primary-nav" class="primary-nav" aria-label="Navigasi utama">
<ul class="nav-list">
{% for item in site.data.navigation.main %}
<li>
<a href="{{ item.url | relative_url }}" class="nav-link {% if page.url == item.url %}active{% endif %}">
<span class="material-symbols-rounded">{{ item.icon }}</span>
{{ item.title }}
</a>
</li>
{% endfor %}
</ul>
</nav>
<div class="app-bar-actions">
<button class="icon-btn" id="search-toggle" aria-label="Cari artikel" title="Cari (Ctrl+K)">
<span class="material-symbols-rounded">search</span>
</button>
<button class="icon-btn" id="theme-toggle" aria-label="Ganti tema" title="Ganti tema">
<span class="material-symbols-rounded theme-icon">dark_mode</span>
</button>
</div>
</div>
</header>
1️⃣3️⃣ _includes/footer.html
<footer class="md3-footer" role="contentinfo">
<div class="footer-grid">
<div class="footer-col">
<h3 class="footer-title">{{ site.title }}</h3>
<p class="footer-about">{{ site.description }}</p>
<div class="social-links">
{% for s in site.data.social %}
<a href="{{ s.url }}" class="social-icon" aria-label="{{ s.name }}" target="_blank" rel="noopener">
<span class="material-symbols-rounded">{{ s.icon }}</span>
</a>
{% endfor %}
</div>
</div>
<div class="footer-col">
<h4>Tautan Cepat</h4>
<ul class="footer-links">
{% for item in site.data.navigation.main limit: 6 %}
<li><a href="{{ item.url | relative_url }}">{{ item.title }}</a></li>
{% endfor %}
</ul>
</div>
<div class="footer-col">
<h4>Kontak</h4>
<ul class="footer-contact">
<li><span class="material-symbols-rounded">location_on</span> Jl. Pemuda No. 17, Desa Sejahtera</li>
<li><span class="material-symbols-rounded">mail</span> {{ site.email }}</li>
<li><span class="material-symbols-rounded">call</span> +62 812-3456-7890</li>
</ul>
</div>
<div class="footer-col">
<h4>Langganan</h4>
<p>Dapatkan informasi terbaru langsung di email Anda.</p>
<a href="{{ '/feed.xml' | relative_url }}" class="md3-button outlined">
<span class="material-symbols-rounded">rss_feed</span> RSS Feed
</a>
</div>
</div>
<div class="footer-bottom">
<p>© {{ 'now' | date: '%Y' }} {{ site.title }}. Seluruh hak cipta dilindungi.</p>
<p>Dibangun dengan ❤️ menggunakan <a href="https://jekyllrb.com" target="_blank" rel="noopener">Jekyll</a> & <a href="https://pages.github.com" target="_blank" rel="noopener">GitHub Pages</a>.</p>
</div>
</footer>
1️⃣4️⃣ _includes/breadcrumb.html
{% unless page.url == '/' %}
<nav class="breadcrumb" aria-label="Breadcrumb" itemscope itemtype="https://schema.org/BreadcrumbList">
<ol>
<li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
<a itemprop="item" href="{{ '/' | relative_url }}"><span itemprop="name">Beranda</span></a>
<meta itemprop="position" content="1">
</li>
{% if page.categories.size > 0 and page.layout == 'post' %}
<li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
<a itemprop="item" href="{{ '/kategori/' | append: page.categories.first | slugify | append: '/' | relative_url }}">
<span itemprop="name">{{ page.categories.first }}</span>
</a>
<meta itemprop="position" content="2">
</li>
{% endif %}
<li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem" aria-current="page">
<span itemprop="name">{{ page.title }}</span>
<meta itemprop="position" content="3">
</li>
</ol>
</nav>
{% endunless %}
1️⃣5️⃣ _includes/article-card.html
<article class="article-card md3-surface-1" itemscope itemtype="https://schema.org/Article">
<a href="{{ include.post.url | relative_url }}" class="card-media" aria-hidden="true" tabindex="-1">
{% if include.post.image %}
<img src="{{ include.post.image | relative_url }}" alt="{{ include.post.title }}" loading="lazy" decoding="async" itemprop="image">
{% else %}
<div class="card-placeholder">
<span class="material-symbols-rounded">article</span>
</div>
{% endif %}
</a>
<div class="card-body">
{% if include.post.categories.size > 0 %}
<div class="card-chips">
{% for cat in include.post.categories limit: 2 %}
<a href="{{ '/kategori/' | append: cat | slugify | append: '/' | relative_url }}" class="md3-chip small">{{ cat }}</a>
{% endfor %}
</div>
{% endif %}
<h3 class="card-title" itemprop="headline">
<a href="{{ include.post.url | relative_url }}" itemprop="url">{{ include.post.title }}</a>
</h3>
<p class="card-excerpt" itemprop="description">{{ include.post.excerpt | strip_html | truncate: 140 }}</p>
<div class="card-meta">
<time datetime="{{ include.post.date | date_to_xmlschema }}" itemprop="datePublished">
{{ include.post.date | date: "%d %b %Y" }}
</time>
<span>·</span>
<span>{{ include.post.content | number_of_words | divided_by: 200 | plus: 1 }} menit baca</span>
</div>
</div>
</article>
1️⃣6️⃣ _includes/share.html
<div class="share-bar" aria-label="Bagikan artikel">
<span class="share-label">Bagikan:</span>
{% assign post_url = page.url | absolute_url %}
{% assign post_title = page.title | url_encode %}
<a class="icon-btn share-btn" href="https://www.facebook.com/sharer/sharer.php?u={{ post_url }}" target="_blank" rel="noopener" aria-label="Facebook" title="Facebook">
<span class="material-symbols-rounded">facebook</span>
</a>
<a class="icon-btn share-btn" href="https://twitter.com/intent/tweet?url={{ post_url }}&text={{ post_title }}" target="_blank" rel="noopener" aria-label="Twitter" title="Twitter">
<span class="material-symbols-rounded">close</span>
</a>
<a class="icon-btn share-btn" href="https://wa.me/?text={{ post_title }}%20{{ post_url }}" target="_blank" rel="noopener" aria-label="WhatsApp" title="WhatsApp">
<span class="material-symbols-rounded">chat</span>
</a>
<a class="icon-btn share-btn" href="https://t.me/share/url?url={{ post_url }}&text={{ post_title }}" target="_blank" rel="noopener" aria-label="Telegram" title="Telegram">
<span class="material-symbols-rounded">send</span>
</a>
<button class="icon-btn share-btn" id="copy-link" aria-label="Salin tautan" title="Salin tautan">
<span class="material-symbols-rounded">content_copy</span>
</button>
</div>
1️⃣7️⃣ _includes/comments.html
{% if page.comments != false and site.giscus.repo %}
<section class="comments" aria-label="Komentar">
<h2 class="comments-title">
<span class="material-symbols-rounded">forum</span> Diskusi
</h2>
<div class="giscus-wrapper md3-surface-2"></div>
<script src="https://giscus.app/client.js"
data-repo="{{ site.giscus.repo }}"
data-repo-id="{{ site.giscus.repo_id }}"
data-category="{{ site.giscus.category }}"
data-category-id="{{ site.giscus.category_id }}"
data-mapping="{{ site.giscus.mapping }}"
data-reactions-enabled="{{ site.giscus.reactions_enabled }}"
data-emit-metadata="{{ site.giscus.emit_metadata }}"
data-theme="{{ site.giscus.theme }}"
data-lang="id"
crossorigin="anonymous"
async>
</script>
</section>
{% endif %}
1️⃣8️⃣ _includes/search-modal.html
<div id="search-modal" class="search-modal" role="dialog" aria-modal="true" aria-labelledby="search-title" hidden>
<div class="search-dialog md3-surface-2">
<div class="search-input-wrap">
<span class="material-symbols-rounded">search</span>
<input id="search-input" type="search" placeholder="Cari artikel, kategori, atau tag..." autocomplete="off" aria-label="Pencarian">
<kbd>ESC</kbd>
</div>
<div id="search-results" class="search-results" role="listbox"></div>
<div class="search-footer">
<span><kbd>↑↓</kbd> Navigasi</span>
<span><kbd>↵</kbd> Buka</span>
<span><kbd>ESC</kbd> Tutup</span>
</div>
</div>
</div>
1️⃣9️⃣ _includes/skeleton.html
<div class="skeleton-grid">
{% for i in (1..6) %}
<div class="skeleton-card md3-surface-1" aria-hidden="true">
<div class="skeleton-img skeleton-shimmer"></div>
<div class="skeleton-body">
<div class="skeleton-line short skeleton-shimmer"></div>
<div class="skeleton-line skeleton-shimmer"></div>
<div class="skeleton-line skeleton-shimmer"></div>
<div class="skeleton-line medium skeleton-shimmer"></div>
</div>
</div>
{% endfor %}
</div>
2️⃣0️⃣ _includes/schema.html
{% if page.layout == 'post' %}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"headline": {{ page.title | jsonify }},
"description": {{ page.description | default: page.excerpt | strip_html | truncate: 160 | jsonify }},
"image": {{ page.image | default: site.logo | absolute_url | jsonify }},
"datePublished": {{ page.date | date_to_xmlschema | jsonify }},
"dateModified": {{ page.last_modified_at | default: page.date | date_to_xmlschema | jsonify }},
"author": {
"@type": "Person",
"name": {{ page.author | default: site.author | jsonify }}
},
"publisher": {
"@type": "Organization",
"name": {{ site.title | jsonify }},
"logo": {
"@type": "ImageObject",
"url": {{ site.logo | absolute_url | jsonify }}
}
},
"mainEntityOfPage": {
"@type": "WebPage",
"@id": {{ page.url | absolute_url | jsonify }}
}
}
</script>
{% endif %}
2️⃣1️⃣ index.html (Beranda dengan Infinite Scroll)
---
layout: default
title: Beranda
description: Portal resmi Karang Taruna Desa Sejahtera — berita, kegiatan, program kerja, dan informasi pemuda.
---
<section class="hero md3-surface-1">
<div class="hero-inner">
<span class="hero-badge md3-chip">
<span class="material-symbols-rounded">eco</span> Pemuda Berkarya, Desa Berjaya
</span>
<h1 class="hero-title">Selamat Datang di <span class="text-gradient">Karang Taruna</span></h1>
<p class="hero-subtitle">Organisasi sosial wadah pengembangan generasi muda yang tumbuh atas dasar kesadaran dan tanggung jawab bersama di Desa Sejahtera.</p>
<div class="hero-actions">
<a href="{{ '/about/' | relative_url }}" class="md3-button filled">
<span class="material-symbols-rounded">info</span> Tentang Kami
</a>
<a href="{{ '/program-kerja/' | relative_url }}" class="md3-button tonal">
<span class="material-symbols-rounded">task_alt</span> Program Kerja
</a>
</div>
</div>
</section>
<section class="section" aria-labelledby="latest-title">
<div class="section-header">
<h2 id="latest-title" class="section-title">
<span class="material-symbols-rounded">newspaper</span> Berita Terbaru
</h2>
<a href="{{ '/berita/' | relative_url }}" class="md3-button text">Lihat Semua <span class="material-symbols-rounded">arrow_forward</span></a>
</div>
<div id="articles-grid" class="articles-grid">
{% include skeleton.html %}
</div>
<div id="infinite-sentinel" class="infinite-sentinel" aria-hidden="true"></div>
<div id="load-status" class="load-status" hidden>
<span class="material-symbols-rounded spin">progress_activity</span> Memuat artikel...
</div>
</section>
<section class="section features">
<h2 class="section-title center">
<span class="material-symbols-rounded">hub</span> Jelajahi Portal Kami
</h2>
<div class="features-grid">
{% for item in site.data.navigation.main offset:1 limit:8 %}
<a href="{{ item.url | relative_url }}" class="feature-card md3-surface-1">
<span class="material-symbols-rounded feature-icon">{{ item.icon }}</span>
<h3>{{ item.title }}</h3>
<p>Menuju halaman {{ item.title | downcase }}.</p>
</a>
{% endfor %}
</div>
</section>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebSite",
"name": {{ site.title | jsonify }},
"url": {{ site.url | jsonify }},
"potentialAction": {
"@type": "SearchAction",
"target": "{{ site.url }}/search/?q={query}",
"query-input": "required name=query"
}
}
</script>
2️⃣2️⃣ 404.html
---
layout: default
title: Halaman Tidak Ditemukan
permalink: /404.html
---
<section class="error-page md3-surface-1">
<div class="error-content">
<span class="error-icon material-symbols-rounded">forest</span>
<h1>404</h1>
<h2>Oops! Halaman yang Anda cari tidak ada</h2>
<p>Mungkin halaman telah dipindahkan atau URL yang Anda ketik salah. Mari kembali ke jalur yang benar.</p>
<div class="error-actions">
<a href="{{ '/' | relative_url }}" class="md3-button filled">
<span class="material-symbols-rounded">home</span> Kembali ke Beranda
</a>
<button class="md3-button tonal" id="open-search-404">
<span class="material-symbols-rounded">search</span> Cari Artikel
</button>
</div>
</div>
</section>
<script>
document.getElementById('open-search-404')?.addEventListener('click', () => {
document.getElementById('search-toggle')?.click();
});
</script>
2️⃣3️⃣ _pages/about.md
---
layout: page
title: Profil Organisasi
permalink: /about/
description: Mengenal lebih dekat Karang Taruna Desa Sejahtera — sejarah, visi, misi, dan nilai-nilai organisasi.
image: /assets/img/about-hero.jpg
---
## 🌱 Sejarah Singkat
Karang Taruna Desa Sejahtera didirikan pada tanggal **17 Agustus 1995** oleh sekelompok pemuda yang peduli terhadap perkembangan desa. Berawal dari kegiatan rutin arisan pemuda, organisasi ini resmi berdiri sebagai wadah pengembangan generasi muda desa sesuai amanat **Permensos RI No. 25 Tahun 2019** tentang Karang Taruna.
Selama lebih dari tiga dekade, Karang Taruna Desa Sejahtera telah menjadi motor penggerak berbagai program kepemudaan, sosial, dan pemberdayaan masyarakat.
## 👁️ Visi
> *"Terwujudnya generasi muda yang beriman, bertakwa, berakhlak mulia, sehat, cerdas, kreatif, mandiri, dan peduli sosial serta berjiwa nasionalis."*
## 🎯 Misi
1. Menumbuhkan kesadaran dan tanggung jawab sosial generasi muda.
2. Meningkatkan kualitas sumber daya pemuda melalui pelatihan dan pendidikan.
3. Mengembangkan potensi pemuda di bidang sosial, ekonomi, seni, dan olahraga.
4. Mempererat tali silaturahmi antarwarga dan pemuda desa.
5. Melestarikan budaya lokal dan menjaga kelestarian lingkungan.
## 💎 Nilai-Nilai Organisasi
- **Gotong Royong** — bekerja bersama untuk kebaikan bersama.
- **Integritas** — jujur, transparan, dan akuntabel.
- **Inovasi** — kreatif dan adaptif terhadap perubahan.
- **Inklusivitas** — terbuka untuk seluruh pemuda tanpa terkecuali.
- **Keberlanjutan** — berorientasi pada masa depan yang lebih baik.
## 🏆 Prestasi
- Juara 1 Karang Taruna Berprestasi Tingkat Kabupaten (2023)
- Juara 2 Lomba Karya Pemuda Tingkat Provinsi (2024)
- Penghargaan Desa Peduli Pemuda dari Kemensos (2025)
2️⃣4️⃣ _pages/struktur.md
---
layout: page
title: Struktur Pengurus
permalink: /struktur/
description: Susunan pengurus Karang Taruna Desa Sejahtera periode 2025–2028.
---
<div class="pengurus-grid">
{% for p in site.data.pengurus %}
<div class="pengurus-card md3-surface-1">
<div class="pengurus-photo">
<img src="{{ p.foto | relative_url }}" alt="{{ p.nama }}" loading="lazy" decoding="async"
onerror="this.onerror=null;this.src='https://api.dicebear.com/7.x/initials/svg?seed={{ p.nama | url_encode }}&backgroundColor=2E7D32';">
</div>
<div class="pengurus-info">
<h3>{{ p.nama }}</h3>
<p class="jabatan">{{ p.jabatan }}</p>
<span class="md3-chip small">{{ p.bidang }}</span>
</div>
</div>
{% endfor %}
</div>
2️⃣5️⃣ _pages/program-kerja.md
---
layout: page
title: Program Kerja
permalink: /program-kerja/
description: Program kerja Karang Taruna berdasarkan bidang kepengurusan.
---
<div class="program-container">
{% for bidang in site.data.program_kerja.bidang %}
<section class="program-bidang md3-surface-1">
<header class="program-header">
<span class="material-symbols-rounded program-icon">{{ bidang.icon }}</span>
<h2>{{ bidang.nama }}</h2>
</header>
<ul class="program-list">
{% for prog in bidang.program %}
<li class="program-item">
<span class="material-symbols-rounded">check_circle</span>
<div>
<h4>{{ prog.nama }}</h4>
<p>{{ prog.deskripsi }}</p>
</div>
</li>
{% endfor %}
</ul>
</section>
{% endfor %}
</div>
2️⃣6️⃣ _pages/kegiatan.md
---
layout: page
title: Kegiatan
permalink: /kegiatan/
description: Daftar kegiatan dan agenda Karang Taruna Desa Sejahtera.
---
## 📅 Agenda Mendatang
<div class="agenda-list">
<article class="agenda-card md3-surface-1">
<div class="agenda-date">
<span class="day">15</span>
<span class="month">Jul</span>
</div>
<div class="agenda-content">
<h3>Kerja Bakti Lingkungan</h3>
<p><span class="material-symbols-rounded">schedule</span> 07:00 – 11:00 WIB</p>
<p><span class="material-symbols-rounded">location_on</span> Balai Desa</p>
</div>
</article>
<article class="agenda-card md3-surface-1">
<div class="agenda-date">
<span class="day">22</span>
<span class="month">Jul</span>
</div>
<div class="agenda-content">
<h3>Pelatihan Digital Marketing</h3>
<p><span class="material-symbols-rounded">schedule</span> 09:00 – 15:00 WIB</p>
<p><span class="material-symbols-rounded">location_on</span> Aula Karang Taruna</p>
</div>
</article>
</div>
## 📰 Kegiatan Terbaru
<div class="articles-grid">
{% assign kegiatan = site.categories.kegiatan | slice: 0, 6 %}
{% for post in kegiatan %}
{% include article-card.html post=post %}
{% endfor %}
</div>
2️⃣7️⃣ _pages/galeri.md
---
layout: page
title: Galeri
permalink: /galeri/
description: Dokumentasi kegiatan Karang Taruna Desa Sejahtera.
---
<div class="gallery-grid">
{% for i in (1..12) %}
<figure class="gallery-item md3-surface-1">
<img src="https://picsum.photos/seed/kt{{ i }}/600/400" alt="Dokumentasi kegiatan {{ i }}" loading="lazy" decoding="async">
<figcaption>Kegiatan #{{ i }}</figcaption>
</figure>
{% endfor %}
</div>
2️⃣8️⃣ _pages/dokumen.md
---
layout: page
title: Dokumen
permalink: /dokumen/
description: Unduh dokumen resmi, AD/ART, laporan, dan formulir Karang Taruna.
---
<div class="docs-grid">
<a href="/assets/docs/ad-art.pdf" class="doc-card md3-surface-1" download>
<span class="material-symbols-rounded doc-icon pdf">picture_as_pdf</span>
<div>
<h3>AD/ART Karang Taruna</h3>
<p>Anggaran Dasar & Anggaran Rumah Tangga</p>
<small>PDF · 2.4 MB</small>
</div>
</a>
<a href="/assets/docs/laporan-2025.pdf" class="doc-card md3-surface-1" download>
<span class="material-symbols-rounded doc-icon pdf">picture_as_pdf</span>
<div>
<h3>Laporan Tahunan 2025</h3>
<p>Laporan pertanggungjawaban pengurus</p>
<small>PDF · 5.1 MB</small>
</div>
</a>
<a href="/assets/docs/formulir-anggota.docx" class="doc-card md3-surface-1" download>
<span class="material-symbols-rounded doc-icon doc">description</span>
<div>
<h3>Formulir Pendaftaran Anggota</h3>
<p>Formulir keanggotaan baru</p>
<small>DOCX · 120 KB</small>
</div>
</a>
<a href="/assets/docs/proker-2026.xlsx" class="doc-card md3-surface-1" download>
<span class="material-symbols-rounded doc-icon xls">table_view</span>
<div>
<h3>Program Kerja 2026</h3>
<p>Rencana kerja tahunan</p>
<small>XLSX · 340 KB</small>
</div>
</a>
</div>
2️⃣9️⃣ _pages/kontak.md
---
layout: page
title: Kontak
permalink: /kontak/
description: Hubungi kami untuk informasi, kerja sama, atau pertanyaan.
---
<div class="contact-grid">
<div class="contact-info md3-surface-1">
<h2>Mari Terhubung</h2>
<p>Kami terbuka untuk diskusi, kerja sama, maupun pertanyaan seputar kegiatan Karang Taruna.</p>
<ul class="contact-list">
<li><span class="material-symbols-rounded">location_on</span><div><strong>Alamat</strong><br>Jl. Pemuda No. 17, Desa Sejahtera, Kec. Makmur, Kab. Sejahtera 12345</div></li>
<li><span class="material-symbols-rounded">mail</span><div><strong>Email</strong><br><a href="mailto:{{ site.email }}">{{ site.email }}</a></div></li>
<li><span class="material-symbols-rounded">call</span><div><strong>Telepon</strong><br>+62 812-3456-7890</div></li>
<li><span class="material-symbols-rounded">schedule</span><div><strong>Jam Operasional</strong><br>Senin – Sabtu, 09:00 – 17:00 WIB</div></li>
</ul>
</div>
<form class="contact-form md3-surface-1" action="https://formspree.io/f/your_form_id" method="POST">
<h2>Kirim Pesan</h2>
<div class="form-field">
<label for="nama">Nama Lengkap</label>
<input type="text" id="nama" name="nama" required>
</div>
<div class="form-field">
<label for="email">Email</label>
<input type="email" id="email" name="email" required>
</div>
<div class="form-field">
<label for="subjek">Subjek</label>
<input type="text" id="subjek" name="subjek" required>
</div>
<div class="form-field">
<label for="pesan">Pesan</label>
<textarea id="pesan" name="pesan" rows="5" required></textarea>
</div>
<button type="submit" class="md3-button filled">
<span class="material-symbols-rounded">send</span> Kirim Pesan
</button>
</form>
</div>
<div class="map-container md3-surface-1">
<iframe
src="https://www.openstreetmap.org/export/embed.html?bbox=106.8,-6.2,106.9,-6.1&layer=mapnik"
loading="lazy"
title="Peta Lokasi Karang Taruna"
referrerpolicy="no-referrer-when-downgrade">
</iframe>
</div>
3️⃣0️⃣ _pages/kategori.md
---
layout: archive
title: Kategori
permalink: /kategori/
layout_type: kategori
---
<div class="kategori-grid">
{% assign cats = site.categories | sort %}
{% for cat in cats %}
{% assign name = cat | first %}
{% assign posts = cat | last %}
<a href="{{ '/kategori/' | append: name | slugify | append: '/' | relative_url }}" class="kategori-card md3-surface-1">
<span class="material-symbols-rounded">folder</span>
<h3>{{ name }}</h3>
<span class="count">{{ posts.size }} artikel</span>
</a>
{% endfor %}
</div>
3️⃣1️⃣ _pages/tags.md
---
layout: archive
title: Tag
permalink: /tags/
layout_type: tags
---
<div class="tags-cloud">
{% assign tags = site.tags | sort %}
{% for tag in tags %}
{% assign name = tag | first %}
{% assign posts = tag | last %}
<a href="{{ '/tags/' | append: name | slugify | append: '/' | relative_url }}" class="md3-chip">
#{{ name }} <span class="count">({{ posts.size }})</span>
</a>
{% endfor %}
</div>
3️⃣2️⃣ _pages/arsip.md
---
layout: archive
title: Arsip
permalink: /arsip/
layout_type: arsip
---
{% assign postsByYear = site.posts | group_by_exp: "post", "post.date | date: '%Y'" %}
{% for year in postsByYear %}
<section class="archive-year">
<h2 class="year-title"><span class="material-symbols-rounded">calendar_month</span> {{ year.name }}</h2>
<ul class="archive-list">
{% for post in year.items %}
<li class="archive-item">
<time datetime="{{ post.date | date_to_xmlschema }}">{{ post.date | date: "%d %b" }}</time>
<a href="{{ post.url | relative_url }}">{{ post.title }}</a>
{% if post.categories.size > 0 %}
<span class="md3-chip small">{{ post.categories.first }}</span>
{% endif %}
</li>
{% endfor %}
</ul>
</section>
{% endfor %}
3️⃣3️⃣ _pages/search.md
---
layout: page
title: Pencarian
permalink: /search/
sitemap: false
---
<div class="search-page">
<div class="search-input-wrap big">
<span class="material-symbols-rounded">search</span>
<input id="page-search-input" type="search" placeholder="Ketik kata kunci..." autocomplete="off">
</div>
<div id="page-search-results" class="search-results"></div>
</div>
3️⃣4️⃣ _pages/pengumuman.md & _pages/agenda.md
---
layout: page
title: Pengumuman
permalink: /pengumuman/
---
<div class="articles-grid">
{% for post in site.categories.pengumuman limit: 10 %}
{% include article-card.html post=post %}
{% endfor %}
</div>
---
layout: page
title: Agenda
permalink: /agenda/
---
<div class="articles-grid">
{% for post in site.categories.agenda limit: 10 %}
{% include article-card.html post=post %}
{% endfor %}
</div>
3️⃣5️⃣ berita/index.html (Halaman Berita dengan Pagination)
---
layout: default
title: Berita
permalink: /berita/
pagination:
enabled: true
---
<section class="section">
<header class="page-header">
<h1 class="page-title">Berita & Artikel</h1>
<p class="page-subtitle">Informasi terbaru seputar kegiatan Karang Taruna.</p>
</header>
<div class="filter-bar">
<div class="md3-tabs">
<a href="{{ '/kategori/' | relative_url }}" class="md3-tab">Kategori</a>
<a href="{{ '/tags/' | relative_url }}" class="md3-tab">Tag</a>
<a href="{{ '/arsip/' | relative_url }}" class="md3-tab">Arsip</a>
</div>
</div>
<div class="articles-grid">
{% for post in paginator.posts %}
{% include article-card.html post=post %}
{% endfor %}
</div>
<nav class="pagination" aria-label="Paginasi">
{% if paginator.previous_page %}
<a href="{{ paginator.previous_page_path | relative_url }}" class="md3-button outlined">← Sebelumnya</a>
{% endif %}
<span class="page-info">Halaman {{ paginator.page }} dari {{ paginator.total_pages }}</span>
{% if paginator.next_page %}
<a href="{{ paginator.next_page_path | relative_url }}" class="md3-button outlined">Selanjutnya →</a>
{% endif %}
</nav>
</section>
⚠️ Catatan: Karena
jekyll-paginatetidak mendukung custom collection, gunakan pluginjekyll-paginate-v2jika ingin pagination di/berita/. Untuk kompatibilitas GitHub Pages, alternatifnya gunakan infinite scroll di beranda dan tampilkan semua di/berita/atau buat pagination manual.
Versi sederhana tanpa plugin tambahan — ganti bagian pagination di atas dengan:
<div class="articles-grid">
{% for post in site.posts %}
{% include article-card.html post=post %}
{% endfor %}
</div>
3️⃣6️⃣ _posts/2026-06-30-selamat-datang.md (Contoh Artikel)
---
layout: post
title: "Selamat Datang di Portal Resmi Karang Taruna"
date: 2026-06-30 08:00:00 +0700
categories: [Pengumuman, Berita]
tags: [karang-taruna, pemuda, desa-sejahtera]
image: /assets/img/posts/welcome.jpg
description: "Portal resmi Karang Taruna Desa Sejahtera kini hadir untuk melayani informasi kegiatan, program kerja, dan berita pemuda."
---
Selamat datang di portal resmi **Karang Taruna Desa Sejahtera**! 🎉
Website ini dibangun sebagai wadah informasi resmi organisasi, mencakup berita, kegiatan, program kerja, hingga dokumen-dokumen penting.
## ✨ Fitur Website
- 📰 Berita dan artikel terbaru
- 📅 Agenda kegiatan
- 📊 Struktur pengurus
- 📁 Download dokumen
- 💬 Diskusi via Giscus
## 📝 Contoh Sintaks Markdown
### Teks & Format
Ini **bold**, ini *italic*, ini `inline code`, dan ini ~~strikethrough~~.
> "Pemuda adalah harapan bangsa." — Kutipan inspiratif
### Daftar
- [x] Mendirikan website
- [x] Menyusun program kerja
- [ ] Melaksanakan kegiatan perdana
### Tabel
| Bidang | Koordinator | Status |
|----------------|-------------|---------|
| Kepemudaan | Dewi | Aktif |
| Sosial | Rudi | Aktif |
| Seni & Budaya | Maya | Aktif |
### Code Block
```javascript
// Contoh kode dengan syntax highlighting
function sapa(nama) {
return `Halo, ${nama}! Selamat datang di Karang Taruna.`;
}
console.log(sapa("Pemuda"));
Gambar
Video Embed
Checklist
- Website live
- Tambah galeri foto
- Integrasi newsletter
Terima kasih telah mengunjungi portal kami. Mari berkarya bersama! 💚
---
## 3️⃣7️⃣ `assets/css/style.scss`
```scss
---
---
@import "variables";
@import "base";
@import "md3";
@import "layout";
@import "components";
@import "markdown";
3️⃣8️⃣ _sass/_variables.scss
// MD3 Color Tokens — Green/Teal Nature Theme
:root,
[data-theme="light"] {
--md-primary: #2E7D32;
--md-on-primary: #FFFFFF;
--md-primary-container: #C8E6C9;
--md-on-primary-container: #1B5E20;
--md-secondary: #00897B;
--md-on-secondary: #FFFFFF;
--md-secondary-container: #B2DFDB;
--md-on-secondary-container: #004D40;
--md-tertiary: #689F38;
--md-tertiary-container: #DCEDC8;
--md-surface: #F8FBF5;
--md-surface-1: #FFFFFF;
--md-surface-2: #F1F7EC;
--md-surface-3: #E8F0E2;
--md-surface-variant: #DCE5D6;
--md-on-surface: #1A1C19;
--md-on-surface-variant: #44483E;
--md-outline: #74796D;
--md-outline-variant: #C4C8BA;
--md-error: #BA1A1A;
--md-shadow: rgba(0, 0, 0, 0.08);
--md-scrim: rgba(0, 0, 0, 0.4);
--md-elevation-1: 0 1px 3px rgba(0,0,0,.08), 0 1px 2px rgba(0,0,0,.04);
--md-elevation-2: 0 3px 6px rgba(0,0,0,.08), 0 2px 4px rgba(0,0,0,.04);
--md-elevation-3: 0 8px 20px rgba(0,0,0,.10), 0 4px 8px rgba(0,0,0,.05);
--md-radius-sm: 8px;
--md-radius-md: 12px;
--md-radius-lg: 16px;
--md-radius-xl: 28px;
--font-sans: 'Plus Jakarta Sans', system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
--container: 1200px;
--gutter: 1.5rem;
}
[data-theme="dark"] {
--md-primary: #81C784;
--md-on-primary: #0A3810;
--md-primary-container: #1B5E20;
--md-on-primary-container: #C8E6C9;
--md-secondary: #4DB6AC;
--md-on-secondary: #00332C;
--md-secondary-container: #004D40;
--md-on-secondary-container: #B2DFDB;
--md-surface: #0F1B15;
--md-surface-1: #15201A;
--md-surface-2: #1A2820;
--md-surface-3: #203028;
--md-surface-variant: #2A3A30;
--md-on-surface: #E3E3DC;
--md-on-surface-variant: #C4C8BA;
--md-outline: #8E9386;
--md-outline-variant: #44483E;
--md-shadow: rgba(0, 0, 0, 0.4);
--md-elevation-1: 0 1px 3px rgba(0,0,0,.3), 0 1px 2px rgba(0,0,0,.2);
--md-elevation-2: 0 3px 6px rgba(0,0,0,.35), 0 2px 4px rgba(0,0,0,.25);
--md-elevation-3: 0 8px 20px rgba(0,0,0,.5), 0 4px 8px rgba(0,0,0,.3);
}
3️⃣9️⃣ _sass/_base.scss
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }
body {
margin: 0;
font-family: var(--font-sans);
font-size: 16px;
line-height: 1.6;
color: var(--md-on-surface);
background: var(--md-surface);
transition: background .3s, color .3s;
-webkit-font-smoothing: antialiased;
}
img, video, iframe { max-width: 100%; height: auto; display: block; }
a { color: var(--md-primary); text-decoration: none; transition: color .2s; }
a:hover { color: var(--md-on-primary-container); }
.skip-link {
position: absolute; left: -9999px; top: 0;
background: var(--md-primary); color: var(--md-on-primary);
padding: .75rem 1rem; z-index: 9999; border-radius: 0 0 var(--md-radius-sm) 0;
&:focus { left: 0; }
}
::selection { background: var(--md-primary); color: var(--md-on-primary); }
:focus-visible {
outline: 3px solid var(--md-primary);
outline-offset: 2px;
border-radius: var(--md-radius-sm);
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}
4️⃣0️⃣ _sass/_md3.scss
// MD3 Buttons
.md3-button {
display: inline-flex; align-items: center; gap: .5rem;
padding: .75rem 1.5rem; border-radius: var(--md-radius-xl);
font-weight: 600; font-size: .95rem; border: none; cursor: pointer;
transition: all .2s; font-family: inherit; text-decoration: none;
.material-symbols-rounded { font-size: 1.1rem; }
&.filled {
background: var(--md-primary); color: var(--md-on-primary);
box-shadow: var(--md-elevation-1);
&:hover { box-shadow: var(--md-elevation-2); filter: brightness(1.05); }
}
&.tonal {
background: var(--md-secondary-container); color: var(--md-on-secondary-container);
&:hover { filter: brightness(.95); }
}
&.outlined {
background: transparent; color: var(--md-primary);
border: 1px solid var(--md-outline);
&:hover { background: var(--md-surface-2); }
}
&.text {
background: transparent; color: var(--md-primary); padding: .5rem 1rem;
&:hover { background: var(--md-surface-2); }
}
}
// MD3 Chip
.md3-chip {
display: inline-flex; align-items: center; gap: .25rem;
padding: .35rem .85rem; border-radius: var(--md-radius-xl);
background: var(--md-surface-2); color: var(--md-on-surface);
font-size: .8rem; font-weight: 500; border: 1px solid var(--md-outline-variant);
transition: all .2s;
&.small { padding: .2rem .6rem; font-size: .75rem; }
&:hover { background: var(--md-primary-container); color: var(--md-on-primary-container); border-color: transparent; }
}
// MD3 Surface
.md3-surface-1 { background: var(--md-surface-1); box-shadow: var(--md-elevation-1); border-radius: var(--md-radius-lg); }
.md3-surface-2 { background: var(--md-surface-2); border-radius: var(--md-radius-lg); }
// Icon Button
.icon-btn {
width: 40px; height: 40px; border-radius: 50%;
display: inline-flex; align-items: center; justify-content: center;
background: transparent; border: none; cursor: pointer;
color: var(--md-on-surface-variant); transition: background .2s;
&:hover { background: var(--md-surface-2); }
}
// Tabs
.md3-tabs { display: flex; gap: .5rem; flex-wrap: wrap; }
.md3-tab {
padding: .6rem 1.2rem; border-radius: var(--md-radius-xl);
color: var(--md-on-surface-variant); font-weight: 500;
border: 1px solid transparent; transition: all .2s;
&.active, &:hover {
background: var(--md-primary-container);
color: var(--md-on-primary-container);
}
}
// Text gradient
.text-gradient {
background: linear-gradient(135deg, var(--md-primary), var(--md-secondary));
-webkit-background-clip: text; background-clip: text;
-webkit-text-fill-color: transparent;
}
.spin { animation: spin 1s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
4️⃣1️⃣ _sass/_layout.scss
// App Bar
.md3-app-bar {
position: sticky; top: 0; z-index: 100;
background: var(--md-surface-1);
border-bottom: 1px solid var(--md-outline-variant);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
}
.app-bar-inner {
max-width: var(--container); margin: 0 auto;
display: flex; align-items: center; gap: 1rem;
padding: .75rem var(--gutter);
}
.brand {
display: flex; align-items: center; gap: .75rem;
color: var(--md-on-surface); font-weight: 700;
img { border-radius: 50%; }
.brand-text { font-size: 1.05rem; }
}
.primary-nav { flex: 1; display: flex; justify-content: center; }
.nav-list { display: flex; gap: .25rem; list-style: none; margin: 0; padding: 0; }
.nav-link {
display: inline-flex; align-items: center; gap: .35rem;
padding: .5rem .9rem; border-radius: var(--md-radius-xl);
color: var(--md-on-surface-variant); font-weight: 500; font-size: .9rem;
transition: all .2s;
.material-symbols-rounded { font-size: 1.1rem; }
&:hover, &.active {
background: var(--md-primary-container);
color: var(--md-on-primary-container);
}
}
.app-bar-actions { display: flex; gap: .25rem; }
.menu-toggle { display: none; }
@media (max-width: 960px) {
.menu-toggle { display: inline-flex; }
.primary-nav {
position: fixed; top: 64px; left: 0; right: 0; bottom: 0;
background: var(--md-surface-1); padding: 1rem;
transform: translateX(-100%); transition: transform .3s;
overflow-y: auto; justify-content: flex-start;
&.open { transform: translateX(0); }
}
.nav-list { flex-direction: column; width: 100%; }
.nav-link { padding: .9rem 1rem; border-radius: var(--md-radius-md); }
.brand-text { display: none; }
}
// Main
.md3-main {
max-width: var(--container); margin: 0 auto;
padding: 1.5rem var(--gutter) 4rem;
min-height: calc(100vh - 200px);
}
// Breadcrumb
.breadcrumb {
margin-bottom: 1.5rem; font-size: .85rem;
ol { display: flex; flex-wrap: wrap; gap: .5rem; list-style: none; margin: 0; padding: 0; }
li { display: flex; align-items: center; gap: .5rem; color: var(--md-on-surface-variant);
&::after { content: '/'; color: var(--md-outline); }
&:last-child::after { content: ''; }
&[aria-current] { color: var(--md-primary); font-weight: 600; }
}
}
// Hero
.hero {
padding: 4rem var(--gutter); margin-bottom: 3rem;
background: linear-gradient(135deg, var(--md-primary-container), var(--md-secondary-container));
border-radius: var(--md-radius-xl);
position: relative; overflow: hidden;
&::before {
content: ''; position: absolute; inset: 0;
background-image: radial-gradient(circle at 20% 30%, rgba(255,255,255,.3), transparent 50%),
radial-gradient(circle at 80% 70%, rgba(46,125,50,.15), transparent 50%);
}
}
.hero-inner { position: relative; max-width: 720px; }
.hero-badge { margin-bottom: 1rem; }
.hero-title { font-size: clamp(1.8rem, 4vw, 3rem); font-weight: 800; line-height: 1.15; margin: 0 0 1rem; }
.hero-subtitle { font-size: 1.1rem; color: var(--md-on-surface-variant); margin: 0 0 2rem; }
.hero-actions { display: flex; flex-wrap: wrap; gap: .75rem; }
// Section
.section { margin-bottom: 4rem; }
.section-header {
display: flex; justify-content: space-between; align-items: center;
margin-bottom: 1.5rem; flex-wrap: wrap; gap: 1rem;
}
.section-title {
display: flex; align-items: center; gap: .5rem;
font-size: 1.5rem; font-weight: 700; margin: 0;
&.center { justify-content: center; margin-bottom: 2rem; }
.material-symbols-rounded { color: var(--md-primary); }
}
// Articles Grid
.articles-grid {
display: grid; gap: 1.5rem;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
}
// Features
.features-grid {
display: grid; gap: 1rem;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
}
.feature-card {
padding: 1.5rem; text-align: center; color: var(--md-on-surface);
transition: all .25s;
&:hover { transform: translateY(-4px); box-shadow: var(--md-elevation-2); }
.feature-icon {
font-size: 2.5rem; color: var(--md-primary);
background: var(--md-primary-container);
width: 64px; height: 64px; border-radius: 50%;
display: inline-flex; align-items: center; justify-content: center;
margin-bottom: 1rem;
}
h3 { margin: 0 0 .5rem; font-size: 1.05rem; }
p { margin: 0; font-size: .85rem; color: var(--md-on-surface-variant); }
}
// Footer
.md3-footer {
background: var(--md-surface-1); border-top: 1px solid var(--md-outline-variant);
padding: 3rem var(--gutter) 1.5rem; margin-top: 4rem;
}
.footer-grid {
max-width: var(--container); margin: 0 auto;
display: grid; gap: 2rem;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}
.footer-col h3, .footer-col h4 { margin-top: 0; }
.footer-title { color: var(--md-primary); }
.footer-about { color: var(--md-on-surface-variant); font-size: .9rem; }
.social-links { display: flex; gap: .5rem; margin-top: 1rem; }
.social-icon {
width: 40px; height: 40px; border-radius: 50%;
background: var(--md-surface-2); color: var(--md-on-surface);
display: inline-flex; align-items: center; justify-content: center;
transition: all .2s;
&:hover { background: var(--md-primary); color: var(--md-on-primary); transform: translateY(-2px); }
}
.footer-links, .footer-contact { list-style: none; padding: 0; margin: 0; }
.footer-links li, .footer-contact li { margin-bottom: .5rem; font-size: .9rem; }
.footer-contact li { display: flex; gap: .5rem; align-items: flex-start; }
.footer-bottom {
max-width: var(--container); margin: 2rem auto 0; padding-top: 1.5rem;
border-top: 1px solid var(--md-outline-variant);
text-align: center; font-size: .85rem; color: var(--md-on-surface-variant);
p { margin: .25rem 0; }
}
// FAB
.fab {
position: fixed; bottom: 1.5rem; right: 1.5rem;
width: 56px; height: 56px; border-radius: 50%;
background: var(--md-primary); color: var(--md-on-primary);
border: none; cursor: pointer; box-shadow: var(--md-elevation-3);
display: flex; align-items: center; justify-content: center;
opacity: 0; transform: translateY(20px) scale(.8); pointer-events: none;
transition: all .3s; z-index: 90;
&.visible { opacity: 1; transform: translateY(0) scale(1); pointer-events: auto; }
&:hover { filter: brightness(1.1); }
}
// Reading Progress
.reading-progress {
position: fixed; top: 0; left: 0; height: 3px;
background: linear-gradient(90deg, var(--md-primary), var(--md-secondary));
width: 0; z-index: 1000; transition: width .1s;
}
4️⃣2️⃣ _sass/_components.scss
// Article Card
.article-card {
overflow: hidden; display: flex; flex-direction: column;
transition: all .25s;
&:hover { transform: translateY(-4px); box-shadow: var(--md-elevation-2); }
.card-media {
aspect-ratio: 16/10; overflow: hidden; background: var(--md-surface-2);
img { width: 100%; height: 100%; object-fit: cover; transition: transform .4s; }
}
&:hover .card-media img { transform: scale(1.05); }
.card-placeholder {
width: 100%; height: 100%;
display: flex; align-items: center; justify-content: center;
color: var(--md-outline);
.material-symbols-rounded { font-size: 3rem; }
}
.card-body { padding: 1.25rem; display: flex; flex-direction: column; gap: .75rem; flex: 1; }
.card-chips { display: flex; gap: .35rem; flex-wrap: wrap; }
.card-title {
margin: 0; font-size: 1.1rem; font-weight: 700; line-height: 1.35;
a { color: var(--md-on-surface);
&:hover { color: var(--md-primary); }
}
}
.card-excerpt {
margin: 0; color: var(--md-on-surface-variant); font-size: .9rem;
display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
.card-meta {
margin-top: auto; display: flex; gap: .5rem; align-items: center;
font-size: .8rem; color: var(--md-on-surface-variant);
}
}
// Post
.post-header { margin-bottom: 2rem; }
.post-hero {
width: 100%; aspect-ratio: 21/9; object-fit: cover;
border-radius: var(--md-radius-lg); margin-bottom: 1.5rem;
}
.post-meta-container { padding: 0 1rem; }
.post-title { font-size: clamp(1.5rem, 3.5vw, 2.25rem); line-height: 1.2; margin: .5rem 0; }
.post-meta {
display: flex; flex-wrap: wrap; gap: .75rem; align-items: center;
color: var(--md-on-surface-variant); font-size: .9rem;
.material-symbols-rounded { font-size: 1rem; }
}
.post-content { padding: 1rem; }
.post-tags {
padding: 1.5rem; margin-top: 2rem;
display: flex; flex-wrap: wrap; gap: .5rem; align-items: center;
border-top: 1px solid var(--md-outline-variant);
}
// Share
.share-bar {
display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
padding: 1rem; margin: 2rem 0;
background: var(--md-surface-2); border-radius: var(--md-radius-lg);
}
.share-label { font-weight: 600; margin-right: .5rem; }
.share-btn { color: var(--md-on-surface); }
// Comments
.comments { margin-top: 3rem; padding-top: 2rem; border-top: 1px solid var(--md-outline-variant); }
.comments-title { display: flex; align-items: center; gap: .5rem; }
.giscus-wrapper { margin-top: 1rem; padding: 1rem; }
// Page
.page-header { margin-bottom: 2rem; }
.page-title { font-size: clamp(1.75rem, 4vw, 2.5rem); margin: 0 0 .5rem; }
.page-subtitle { color: var(--md-on-surface-variant); margin: 0; font-size: 1.05rem; }
.page-content { padding: 1.5rem; }
// Pengurus
.pengurus-grid {
display: grid; gap: 1.5rem;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
}
.pengurus-card {
text-align: center; padding: 1.5rem; transition: transform .25s;
&:hover { transform: translateY(-4px); }
.pengurus-photo {
width: 140px; height: 140px; border-radius: 50%; margin: 0 auto 1rem;
overflow: hidden; background: var(--md-primary-container);
img { width: 100%; height: 100%; object-fit: cover; }
}
h3 { margin: 0 0 .25rem; font-size: 1.05rem; }
.jabatan { color: var(--md-primary); font-weight: 600; margin: 0 0 .5rem; font-size: .9rem; }
}
// Program
.program-container { display: grid; gap: 1.5rem; }
.program-bidang { padding: 1.5rem; }
.program-header {
display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem;
.program-icon {
font-size: 2rem; color: var(--md-on-primary);
background: var(--md-primary); padding: .5rem; border-radius: 50%;
}
h2 { margin: 0; }
}
.program-list { list-style: none; padding: 0; margin: 0; }
.program-item {
display: flex; gap: 1rem; padding: .75rem 0;
border-bottom: 1px solid var(--md-outline-variant);
&:last-child { border-bottom: none; }
.material-symbols-rounded { color: var(--md-primary); flex-shrink: 0; }
h4 { margin: 0 0 .25rem; }
p { margin: 0; font-size: .9rem; color: var(--md-on-surface-variant); }
}
// Gallery
.gallery-grid {
display: grid; gap: 1rem;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
}
.gallery-item {
overflow: hidden; border-radius: var(--md-radius-md);
img { aspect-ratio: 1; object-fit: cover; transition: transform .4s; }
&:hover img { transform: scale(1.08); }
figcaption { padding: .75rem; font-size: .85rem; text-align: center; }
}
// Documents
.docs-grid {
display: grid; gap: 1rem;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
}
.doc-card {
display: flex; gap: 1rem; padding: 1.25rem;
color: var(--md-on-surface); transition: all .25s;
&:hover { transform: translateY(-2px); box-shadow: var(--md-elevation-2); }
.doc-icon { font-size: 2.5rem; flex-shrink: 0;
&.pdf { color: #D32F2F; }
&.doc { color: #1976D2; }
&.xls { color: #388E3C; }
}
h3 { margin: 0 0 .25rem; font-size: 1rem; }
p { margin: 0 0 .25rem; font-size: .85rem; color: var(--md-on-surface-variant); }
small { color: var(--md-outline); }
}
// Contact
.contact-grid {
display: grid; gap: 1.5rem;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
margin-bottom: 2rem;
}
.contact-info, .contact-form { padding: 2rem; }
.contact-list { list-style: none; padding: 0; }
.contact-list li {
display: flex; gap: 1rem; margin-bottom: 1.25rem;
.material-symbols-rounded {
color: var(--md-primary); background: var(--md-primary-container);
width: 44px; height: 44px; border-radius: 50%;
display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}
}
.form-field { margin-bottom: 1rem;
label { display: block; font-weight: 500; margin-bottom: .35rem; font-size: .9rem; }
input, textarea {
width: 100%; padding: .75rem 1rem; border-radius: var(--md-radius-md);
border: 1px solid var(--md-outline-variant); background: var(--md-surface);
color: var(--md-on-surface); font-family: inherit; font-size: .95rem;
transition: border-color .2s;
&:focus { outline: none; border-color: var(--md-primary); box-shadow: 0 0 0 3px var(--md-primary-container); }
}
}
.map-container {
padding: .5rem; border-radius: var(--md-radius-lg); overflow: hidden;
iframe { width: 100%; height: 400px; border: 0; border-radius: var(--md-radius-md); }
}
// Agenda
.agenda-list { display: grid; gap: 1rem; margin-bottom: 2rem; }
.agenda-card {
display: flex; gap: 1rem; padding: 1rem; align-items: center;
.agenda-date {
background: var(--md-primary); color: var(--md-on-primary);
padding: .75rem 1rem; border-radius: var(--md-radius-md);
text-align: center; min-width: 70px;
.day { display: block; font-size: 1.5rem; font-weight: 800; line-height: 1; }
.month { font-size: .8rem; text-transform: uppercase; }
}
h3 { margin: 0 0 .25rem; }
p { margin: 0; font-size: .85rem; color: var(--md-on-surface-variant);
display: flex; align-items: center; gap: .25rem;
.material-symbols-rounded { font-size: 1rem; }
}
}
// Kategori
.kategori-grid {
display: grid; gap: 1rem;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}
.kategori-card {
padding: 1.5rem; text-align: center; color: var(--md-on-surface);
transition: all .25s;
&:hover { transform: translateY(-3px); background: var(--md-primary-container); }
.material-symbols-rounded { font-size: 2rem; color: var(--md-primary); margin-bottom: .5rem; }
h3 { margin: .25rem 0; }
.count { color: var(--md-on-surface-variant); font-size: .85rem; }
}
.tags-cloud { display: flex; flex-wrap: wrap; gap: .5rem; }
.tags-cloud .count { opacity: .7; font-size: .75rem; }
// Archive
.archive-year { margin-bottom: 2rem; }
.year-title {
display: flex; align-items: center; gap: .5rem;
color: var(--md-primary); border-bottom: 2px solid var(--md-primary-container);
padding-bottom: .5rem;
}
.archive-list { list-style: none; padding: 0; }
.archive-item {
display: flex; gap: 1rem; align-items: center; padding: .75rem 0;
border-bottom: 1px solid var(--md-outline-variant);
time { color: var(--md-on-surface-variant); font-size: .85rem; min-width: 60px; }
a { flex: 1; color: var(--md-on-surface); font-weight: 500; }
}
// Filter
.filter-bar { margin-bottom: 1.5rem; }
// Pagination
.pagination {
display: flex; justify-content: center; align-items: center;
gap: 1rem; margin-top: 2rem; flex-wrap: wrap;
.page-info { color: var(--md-on-surface-variant); font-size: .9rem; }
}
// Search Modal
.search-modal {
position: fixed; inset: 0; background: var(--md-scrim);
z-index: 1000; display: flex; align-items: flex-start; justify-content: center;
padding: 10vh 1rem;
&[hidden] { display: none; }
}
.search-dialog {
width: 100%; max-width: 600px; padding: 1rem;
box-shadow: var(--md-elevation-3);
}
.search-input-wrap {
display: flex; align-items: center; gap: .75rem;
padding: .75rem 1rem; border-radius: var(--md-radius-md);
background: var(--md-surface-3);
input {
flex: 1; border: none; background: transparent; outline: none;
font-size: 1rem; color: var(--md-on-surface); font-family: inherit;
}
kbd {
font-size: .7rem; padding: .2rem .4rem; border-radius: 4px;
background: var(--md-surface-1); border: 1px solid var(--md-outline-variant);
color: var(--md-on-surface-variant);
}
&.big { padding: 1rem 1.25rem; font-size: 1.1rem; }
}
.search-results {
margin-top: 1rem; max-height: 50vh; overflow-y: auto;
.search-item {
display: block; padding: .75rem 1rem; border-radius: var(--md-radius-md);
color: var(--md-on-surface); transition: background .15s;
&:hover, &.active { background: var(--md-surface-3); }
h4 { margin: 0 0 .25rem; font-size: .95rem; }
p { margin: 0; font-size: .8rem; color: var(--md-on-surface-variant); }
}
.no-results { padding: 2rem; text-align: center; color: var(--md-on-surface-variant); }
}
.search-footer {
display: flex; gap: 1rem; padding-top: .75rem; margin-top: .75rem;
border-top: 1px solid var(--md-outline-variant);
font-size: .75rem; color: var(--md-on-surface-variant);
kbd { padding: .1rem .35rem; border-radius: 3px; background: var(--md-surface-3); margin-right: .25rem; }
}
// Skeleton
.skeleton-grid {
display: grid; gap: 1.5rem;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
}
.skeleton-card { overflow: hidden; }
.skeleton-img { aspect-ratio: 16/10; background: var(--md-surface-2); }
.skeleton-body { padding: 1.25rem; display: flex; flex-direction: column; gap: .6rem; }
.skeleton-line { height: 12px; background: var(--md-surface-2); border-radius: 4px;
&.short { width: 40%; }
&.medium { width: 75%; }
}
.skeleton-shimmer {
background: linear-gradient(90deg, var(--md-surface-2) 25%, var(--md-surface-3) 50%, var(--md-surface-2) 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
@keyframes shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
// 404
.error-page {
padding: 4rem 2rem; text-align: center;
.error-icon { font-size: 5rem; color: var(--md-primary); }
h1 { font-size: 5rem; margin: 0; color: var(--md-primary); font-weight: 800; }
h2 { margin: .5rem 0; }
p { color: var(--md-on-surface-variant); max-width: 500px; margin: 1rem auto; }
.error-actions { display: flex; gap: .75rem; justify-content: center; flex-wrap: wrap; margin-top: 2rem; }
}
// Infinite scroll sentinel
.infinite-sentinel { height: 1px; }
.load-status {
text-align: center; padding: 2rem; color: var(--md-on-surface-variant);
display: flex; align-items: center; justify-content: center; gap: .5rem;
}
4️⃣3️⃣ _sass/_markdown.scss
.md3-typography {
font-size: 1.05rem; line-height: 1.75; color: var(--md-on-surface);
h1, h2, h3, h4, h5, h6 {
margin: 2rem 0 1rem; font-weight: 700; line-height: 1.3;
scroll-margin-top: 80px;
}
h1 { font-size: 2rem; }
h2 { font-size: 1.6rem; border-bottom: 2px solid var(--md-primary-container); padding-bottom: .35rem; }
h3 { font-size: 1.3rem; color: var(--md-primary); }
h4 { font-size: 1.1rem; }
p { margin: 1rem 0; }
a { text-decoration: underline; text-underline-offset: 3px;
&:hover { color: var(--md-secondary); }
}
strong { font-weight: 700; color: var(--md-on-surface); }
em { font-style: italic; }
del { text-decoration: line-through; color: var(--md-on-surface-variant); }
blockquote {
margin: 1.5rem 0; padding: 1rem 1.5rem;
border-left: 4px solid var(--md-primary);
background: var(--md-surface-2); border-radius: 0 var(--md-radius-md) var(--md-radius-md) 0;
color: var(--md-on-surface-variant); font-style: italic;
p { margin: .5rem 0; }
}
ul, ol { padding-left: 1.5rem; margin: 1rem 0; }
li { margin: .4rem 0; }
li::marker { color: var(--md-primary); }
// Task list
ul.task-list { list-style: none; padding-left: 0; }
ul.task-list li { display: flex; align-items: flex-start; gap: .5rem; }
ul.task-list input[type="checkbox"] {
accent-color: var(--md-primary);
width: 18px; height: 18px; margin-top: .3rem;
}
code {
background: var(--md-surface-2); color: var(--md-primary);
padding: .15rem .4rem; border-radius: 4px;
font-family: 'JetBrains Mono', ui-monospace, Menlo, monospace;
font-size: .9em;
}
pre {
background: var(--md-surface-2); padding: 1.25rem;
border-radius: var(--md-radius-md); overflow-x: auto;
border: 1px solid var(--md-outline-variant);
code { background: transparent; color: var(--md-on-surface); padding: 0; font-size: .88rem; }
}
// Rouge syntax highlighting
.highlight { @extend pre; }
.hll { background: var(--md-primary-container); }
.c, .cm, .c1, .cs { color: #6A9955; font-style: italic; } // comment
.k, .kd, .kn, .kp, .kr, .kt { color: #C586C0; } // keyword
.s, .sb, .sc, .sd, .s2, .se, .sh, .si, .sx, .sr, .s1, .ss { color: #CE9178; } // string
.na, .nb, .nc, .nd, .ne, .nf, .ni, .nl, .no, .nt, .nv { color: #4EC9B0; } // name
.m, .mf, .mh, .mi, .mo { color: #B5CEA8; } // number
.o, .ow { color: #D4D4D4; } // operator
.p { color: var(--md-on-surface); }
table {
width: 100%; border-collapse: collapse; margin: 1.5rem 0;
background: var(--md-surface-1); border-radius: var(--md-radius-md);
overflow: hidden; box-shadow: var(--md-elevation-1);
}
th, td { padding: .75rem 1rem; text-align: left; border-bottom: 1px solid var(--md-outline-variant); }
th { background: var(--md-primary-container); color: var(--md-on-primary-container); font-weight: 600; }
tr:last-child td { border-bottom: none; }
tr:hover td { background: var(--md-surface-2); }
img, video, iframe {
border-radius: var(--md-radius-md); margin: 1.5rem auto;
box-shadow: var(--md-elevation-1);
}
img { max-width: 100%; height: auto; }
hr {
border: none; height: 2px;
background: linear-gradient(90deg, transparent, var(--md-outline-variant), transparent);
margin: 2.5rem 0;
}
// Details/Summary
details {
background: var(--md-surface-2); padding: 1rem 1.25rem;
border-radius: var(--md-radius-md); margin: 1rem 0;
summary { cursor: pointer; font-weight: 600; color: var(--md-primary); }
&[open] summary { margin-bottom: .75rem; }
}
mark {
background: var(--md-primary-container); color: var(--md-on-primary-container);
padding: .1rem .3rem; border-radius: 3px;
}
sup, sub { font-size: .75em; }
}
4️⃣4️⃣ assets/js/main.js
/* =========================================================
KARANG TARUNA — Main JavaScript
Features: Theme, Menu, Search, Infinite Scroll, Back to Top,
Reading Progress, Copy Link, Lazy Load
========================================================= */
(() => {
'use strict';
// ---------- THEME ----------
const THEME_KEY = 'kt-theme';
const themeToggle = document.getElementById('theme-toggle');
const themeIcon = themeToggle?.querySelector('.theme-icon');
const applyTheme = (t) => {
document.documentElement.setAttribute('data-theme', t);
if (themeIcon) themeIcon.textContent = t === 'dark' ? 'light_mode' : 'dark_mode';
localStorage.setItem(THEME_KEY, t);
};
const savedTheme = localStorage.getItem(THEME_KEY);
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
applyTheme(savedTheme || (prefersDark ? 'dark' : 'light'));
themeToggle?.addEventListener('click', () => {
const current = document.documentElement.getAttribute('data-theme');
applyTheme(current === 'dark' ? 'light' : 'dark');
});
// ---------- MOBILE MENU ----------
const menuToggle = document.querySelector('.menu-toggle');
const primaryNav = document.getElementById('primary-nav');
menuToggle?.addEventListener('click', () => {
const expanded = primaryNav.classList.toggle('open');
menuToggle.setAttribute('aria-expanded', expanded);
});
document.addEventListener('click', (e) => {
if (!primaryNav?.contains(e.target) && !menuToggle?.contains(e.target)) {
primaryNav?.classList.remove('open');
menuToggle?.setAttribute('aria-expanded', 'false');
}
});
// ---------- BACK TO TOP ----------
const btt = document.getElementById('back-to-top');
const onScroll = () => {
if (window.scrollY > 400) btt?.classList.add('visible');
else btt?.classList.remove('visible');
updateProgress();
};
window.addEventListener('scroll', onScroll, { passive: true });
btt?.addEventListener('click', () => window.scrollTo({ top: 0, behavior: 'smooth' }));
// ---------- READING PROGRESS ----------
const progress = document.getElementById('reading-progress');
const updateProgress = () => {
if (!progress) return;
const docH = document.documentElement.scrollHeight - window.innerHeight;
const pct = docH > 0 ? (window.scrollY / docH) * 100 : 0;
progress.style.width = pct + '%';
};
// ---------- SEARCH ----------
const searchModal = document.getElementById('search-modal');
const searchInput = document.getElementById('search-input');
const searchResults = document.getElementById('search-results');
const searchToggle = document.getElementById('search-toggle');
let searchIndex = null;
const openSearch = () => {
searchModal?.removeAttribute('hidden');
setTimeout(() => searchInput?.focus(), 50);
loadSearchIndex();
};
const closeSearch = () => {
searchModal?.setAttribute('hidden', '');
if (searchInput) searchInput.value = '';
if (searchResults) searchResults.innerHTML = '';
};
searchToggle?.addEventListener('click', openSearch);
searchModal?.addEventListener('click', (e) => { if (e.target === searchModal) closeSearch(); });
document.addEventListener('keydown', (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'k') { e.preventDefault(); openSearch(); }
if (e.key === 'Escape') closeSearch();
});
const loadSearchIndex = async () => {
if (searchIndex) return;
try {
const res = await fetch('/search.json');
searchIndex = await res.json();
} catch (err) {
console.warn('Search index not found', err);
}
};
searchInput?.addEventListener('input', (e) => {
const q = e.target.value.trim().toLowerCase();
if (!searchIndex || q.length < 2) {
searchResults.innerHTML = '';
return;
}
const results = searchIndex.filter(p =>
p.title.toLowerCase().includes(q) ||
(p.content || '').toLowerCase().includes(q) ||
(p.categories || []).some(c => c.toLowerCase().includes(q)) ||
(p.tags || []).some(t => t.toLowerCase().includes(q))
).slice(0, 10);
if (results.length === 0) {
searchResults.innerHTML = '<div class="no-results">Tidak ada hasil untuk <strong>' + escapeHtml(q) + '</strong></div>';
return;
}
searchResults.innerHTML = results.map(p => `
<a class="search-item" href="${p.url}" role="option">
<h4>${highlight(p.title, q)}</h4>
<p>${highlight((p.content || '').slice(0, 120), q)}...</p>
</a>
`).join('');
});
const escapeHtml = (s) => s.replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
const highlight = (text, q) => {
if (!q) return escapeHtml(text);
const re = new RegExp('(' + q.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + ')', 'gi');
return escapeHtml(text).replace(re, '<mark>$1</mark>');
};
// ---------- INFINITE SCROLL (Beranda) ----------
const grid = document.getElementById('articles-grid');
const sentinel = document.getElementById('infinite-sentinel');
const loadStatus = document.getElementById('load-status');
if (grid && sentinel && window.location.pathname === '/') {
// Load posts from JSON
const BATCH = 6;
let allPosts = [];
let currentPage = 0;
let loading = false;
const renderCard = (p) => {
const cats = (p.categories || []).slice(0, 2).map(c =>
`<a href="/kategori/${slugify(c)}/" class="md3-chip small">${c}</a>`).join('');
const excerpt = (p.content || '').replace(/<[^>]+>/g, '').slice(0, 140);
const words = (p.content || '').replace(/<[^>]+>/g, '').split(/\s+/).length;
const readTime = Math.max(1, Math.floor(words / 200));
const date = new Date(p.date).toLocaleDateString('id-ID', { day: '2-digit', month: 'short', year: 'numeric' });
const media = p.image
? `<img src="${p.image}" alt="${escapeHtml(p.title)}" loading="lazy" decoding="async">`
: `<div class="card-placeholder"><span class="material-symbols-rounded">article</span></div>`;
return `
<article class="article-card md3-surface-1">
<a href="${p.url}" class="card-media" aria-hidden="true" tabindex="-1">${media}</a>
<div class="card-body">
${cats ? `<div class="card-chips">${cats}</div>` : ''}
<h3 class="card-title"><a href="${p.url}">${escapeHtml(p.title)}</a></h3>
<p class="card-excerpt">${escapeHtml(excerpt)}...</p>
<div class="card-meta"><time>${date}</time><span>·</span><span>${readTime} menit baca</span></div>
</div>
</article>`;
};
const slugify = (s) => s.toString().toLowerCase().replace(/\s+/g, '-').replace(/[^\w-]/g, '');
const loadBatch = () => {
if (loading || currentPage * BATCH >= allPosts.length) return;
loading = true;
loadStatus?.removeAttribute('hidden');
setTimeout(() => {
const batch = allPosts.slice(currentPage * BATCH, (currentPage + 1) * BATCH);
batch.forEach(p => grid.insertAdjacentHTML('beforeend', renderCard(p)));
currentPage++;
loading = false;
loadStatus?.setAttribute('hidden', '');
if (currentPage * BATCH >= allPosts.length) observer.disconnect();
}, 300);
};
fetch('/search.json').then(r => r.json()).then(posts => {
allPosts = posts;
grid.innerHTML = ''; // remove skeleton
loadBatch();
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) loadBatch();
}, { rootMargin: '200px' });
observer.observe(sentinel);
}).catch(() => {
grid.innerHTML = '<p>Belum ada artikel.</p>';
});
}
// ---------- COPY LINK ----------
document.getElementById('copy-link')?.addEventListener('click', async (e) => {
try {
await navigator.clipboard.writeText(window.location.href);
const icon = e.currentTarget.querySelector('.material-symbols-rounded');
const original = icon.textContent;
icon.textContent = 'check';
setTimeout(() => icon.textContent = original, 1500);
} catch {}
});
// ---------- LAZY LOAD IMAGES ----------
if ('loading' in HTMLImageElement.prototype) {
document.querySelectorAll('img[loading="lazy"]').forEach(img => {
if (img.dataset.src) { img.src = img.dataset.src; }
});
} else {
const io = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (e.isIntersecting) {
const img = e.target;
if (img.dataset.src) img.src = img.dataset.src;
io.unobserve(img);
}
});
});
document.querySelectorAll('img[data-src]').forEach(img => io.observe(img));
}
// ---------- SMOOTH ANCHOR ----------
document.querySelectorAll('a[href^="#"]').forEach(a => {
a.addEventListener('click', (e) => {
const target = document.querySelector(a.getAttribute('href'));
if (target) { e.preventDefault(); target.scrollIntoView({ behavior: 'smooth', block: 'start' }); }
});
});
})();
4️⃣5️⃣ feed.xml
---
layout: none
---
<?xml version="1.0" encoding="UTF-8"?>
{% feed %}
Jika plugin
jekyll-feedaktif, file ini otomatis digenerate. Jika tidak, gunakan template di bawah:
---
layout: none
permalink: /feed.xml
---
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>{{ site.title | xml_escape }}</title>
<description>{{ site.description | xml_escape }}</description>
<link>{{ site.url }}{{ site.baseurl }}/</link>
<atom:link href="{{ '/feed.xml' | absolute_url }}" rel="self" type="application/rss+xml"/>
<lastBuildDate>{{ site.time | date_to_rfc822 }}</lastBuildDate>
{% for post in site.posts limit:20 %}
<item>
<title>{{ post.title | xml_escape }}</title>
<description>{{ post.content | xml_escape }}</description>
<pubDate>{{ post.date | date_to_rfc822 }}</pubDate>
<link>{{ post.url | absolute_url }}</link>
<guid isPermaLink="true">{{ post.url | absolute_url }}</guid>
</item>
{% endfor %}
</channel>
</rss>
4️⃣6️⃣ robots.txt
---
layout: none
permalink: /robots.txt
---
User-agent: *
Allow: /
Sitemap: {{ site.url | append: site.baseurl }}/sitemap.xml
4️⃣7️⃣ search.json (Endpoint Pencarian)
---
layout: none
permalink: /search.json
---
[
{% for post in site.posts %}
{
"title": {{ post.title | jsonify }},
"url": {{ post.url | relative_url | jsonify }},
"date": {{ post.date | date_to_xmlschema | jsonify }},
"categories": {{ post.categories | jsonify }},
"tags": {{ post.tags | jsonify }},
"image": {{ post.image | default: '' | relative_url | jsonify }},
"content": {{ post.content | strip_html | normalize_whitespace | truncate: 400 | jsonify }}
}{% unless forloop.last %},{% endunless %}
{% endfor %}
]
🚀 Cara Deploy ke GitHub Pages
1. Buat Repository GitHub
git init
git add .
git commit -m "Initial commit: Website Karang Taruna"
git branch -M main
git remote add origin https://github.com/USERNAME/USERNAME.github.io.git
git push -u origin main
2. Aktifkan GitHub Pages
- Buka Settings → Pages
- Source: Deploy from a branch
- Branch:
main/ root
3. Konfigurasi Giscus
- Kunjungi giscus.app
- Install aplikasi Giscus di repository
- Salin
repo_iddancategory_idke_config.yml
4. Tambahkan Logo & Gambar
/assets/img/logo.png(logo organisasi)/assets/img/favicon.png/assets/img/pengurus/*.jpg/assets/img/posts/*.jpg
5. Menulis Artikel Baru
Buat file di _posts/ dengan format:
YYYY-MM-DD-judul-artikel.md
Front matter wajib:
---
layout: post
title: "Judul Artikel"
date: 2026-06-30 10:00:00 +0700
categories: [Kategori1, Kategori2]
tags: [tag1, tag2]
image: /assets/img/posts/cover.jpg
description: "Ringkasan artikel untuk SEO."
---
✅ Fitur yang Sudah Terpenuhi
| Fitur | Status |
|---|---|
| Material Design 3 (Green/Teal/Nature) | ✅ |
| SEO (OG, Twitter, JSON-LD, Sitemap, RSS) | ✅ |
| Markdown lengkap + syntax highlighting | ✅ |
| Infinite Scroll di beranda | ✅ |
| Dark/Light mode + localStorage | ✅ |
| Search instan (Ctrl+K) | ✅ |
| Breadcrumb navigation | ✅ |
| Reading time + progress bar | ✅ |
| Back to Top FAB | ✅ |
| Skeleton loading | ✅ |
| Giscus comments | ✅ |
| Share ke sosial media | ✅ |
| Lazy loading gambar | ✅ |
| Responsive super mobile-friendly | ✅ |
| Halaman 404 menarik | ✅ |
| Profil, Struktur, Proker, Galeri, Dokumen, Kontak | ✅ |
| Kategori, Tag, Arsip | ✅ |
| Peta lokasi + form kontak | ✅ |
| Core Web Vitals optimized | ✅ |
💡 Tips Pengembangan
- Tambah kategori baru — cukup buat posting dengan
categories: [NamaKategori], halaman kategori otomatis terbuat. - Ganti warna tema — edit
_sass/_variables.scss. - Ganti komentar — ubah konfigurasi Giscus di
_config.ymlatau ganti ke Disqus/Utterances. - Form kontak — ganti
actiondi_pages/kontak.mddengan Formspree atau Getform. - Analytics — tambahkan Google Analytics / Plausible di
_includes/head.html.
Selamat membangun portal Karang Taruna yang modern, cepat, dan bermanfaat! 🌿💚
Posting Komentar