diff options
| author | Himanshu Sardana <himanshusardana2005@gmail.com> | 2026-04-04 21:01:01 +0000 |
|---|---|---|
| committer | Himanshu Sardana <himanshusardana2005@gmail.com> | 2026-04-04 21:01:01 +0000 |
| commit | f32213675b7af3f706baf0257c3e4379799c926b (patch) | |
| tree | 73704ba06b7c10274fcd687b0028e152fcdb37d9 /themes | |
| parent | 7a5c0d55c421f42bdcad32d42a0e84a6d932c852 (diff) | |
feat: add new theme "magical"
Diffstat (limited to 'themes')
| -rw-r--r-- | themes/magical/home.html | 352 | ||||
| -rw-r--r-- | themes/magical/layout.html | 775 |
2 files changed, 1127 insertions, 0 deletions
diff --git a/themes/magical/home.html b/themes/magical/home.html new file mode 100644 index 0000000..bb0e936 --- /dev/null +++ b/themes/magical/home.html @@ -0,0 +1,352 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <title>{{ .SiteTitle }}</title> + <link rel="preconnect" href="https://fonts.googleapis.com" /> + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> + <link + href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" + rel="stylesheet" + /> + <style> + :root { + --bg: #fafafa; + --bg-elevated: #ffffff; + --text: #0a0a0a; + --text-secondary: #525252; + --text-muted: #737373; + --border: #e5e5e5; + --accent: #6366f1; + --accent-light: #e0e7ff; + --sans: + "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + --mono: "JetBrains Mono", "Fira Code", monospace; + --max-width: 720px; + --ease: cubic-bezier(0.16, 1, 0.3, 1); + } + + *, + *::before, + *::after { + box-sizing: border-box; + margin: 0; + padding: 0; + } + + html { + scroll-behavior: smooth; + } + + body { + font-family: var(--sans); + background: var(--bg); + color: var(--text); + line-height: 1.6; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + min-height: 100vh; + } + + /* ── Layout ── */ + .page { + max-width: var(--max-width); + margin: 0 auto; + padding: 0 2rem; + } + + /* ── Nav ── */ + nav { + display: flex; + justify-content: flex-end; + align-items: center; + padding: 2rem 0 0; + gap: 2rem; + } + + nav a { + font-family: var(--mono); + font-size: 0.68rem; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--text-muted); + text-decoration: none; + transition: color 120ms ease; + } + + nav a:hover { + color: var(--text); + } + + /* ── Hero ── */ + .hero { + padding: 5rem 0 4rem; + border-bottom: 1px solid var(--border); + } + + .hero-name { + font-size: clamp(2.2rem, 6vw, 3.5rem); + font-weight: 700; + line-height: 1.1; + letter-spacing: -0.03em; + margin-bottom: 1rem; + color: var(--text); + animation: fadeUp 0.6s var(--ease) both; + } + + .hero-role { + font-family: var(--mono); + font-size: 0.7rem; + letter-spacing: 0.1em; + text-transform: uppercase; + color: var(--text-muted); + margin-bottom: 1.25rem; + animation: fadeUp 0.6s 0.08s var(--ease) both; + } + + .hero-bio { + font-size: 1rem; + line-height: 1.7; + color: var(--text-secondary); + max-width: 480px; + animation: fadeUp 0.6s 0.15s var(--ease) both; + } + + /* ── Writing Header ── */ + .writing-header { + display: flex; + align-items: center; + gap: 1rem; + padding: 2.5rem 0 1.5rem; + } + + .writing-label { + font-family: var(--mono); + font-size: 0.65rem; + letter-spacing: 0.12em; + text-transform: uppercase; + color: var(--text-muted); + white-space: nowrap; + } + + .writing-rule { + flex: 1; + height: 1px; + background: var(--border); + } + + /* ── Post List ── */ + .post-list { + list-style: none; + margin: 0; + padding: 0; + } + + .post-item { + display: flex; + align-items: baseline; + gap: 2rem; + padding: 1.25rem 0; + text-decoration: none; + color: inherit; + border-bottom: 1px solid var(--border); + transition: opacity 140ms ease; + animation: fadeUp 0.5s var(--ease) both; + } + + .post-item:hover { + opacity: 0.6; + } + + .post-date { + font-family: var(--mono); + font-size: 0.65rem; + letter-spacing: 0.05em; + color: var(--text-muted); + white-space: nowrap; + flex-shrink: 0; + padding-top: 0.15em; + min-width: 6rem; + } + + .post-right { + flex: 1; + min-width: 0; + } + + .post-title { + font-size: 1.1rem; + font-weight: 500; + color: var(--text); + line-height: 1.4; + display: block; + } + + .post-tags { + display: flex; + gap: 0.35rem; + margin-top: 0.4rem; + flex-wrap: wrap; + } + + .pill { + display: inline-block; + font-family: var(--mono); + font-size: 0.58rem; + letter-spacing: 0.06em; + text-transform: uppercase; + padding: 0.15rem 0.5rem; + background: transparent; + border: 1px solid var(--border); + border-radius: 100px; + color: var(--text-muted); + } + + /* ── Empty State ── */ + .empty { + font-family: var(--mono); + font-size: 0.75rem; + color: var(--text-muted); + letter-spacing: 0.06em; + padding: 2.5rem 0; + } + + /* ── Footer ── */ + footer { + padding: 2.5rem 0 3rem; + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 0.8rem; + font-family: var(--mono); + font-size: 0.65rem; + letter-spacing: 0.05em; + color: var(--text-muted); + } + + footer a { + color: inherit; + text-decoration: underline; + text-underline-offset: 3px; + text-decoration-color: var(--border); + transition: color 120ms ease; + } + + footer a:hover { + color: var(--text); + } + + /* ── Animations ── */ + @keyframes fadeUp { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } + } + + .post-item:nth-child(1) { + animation-delay: 0.2s; + } + .post-item:nth-child(2) { + animation-delay: 0.27s; + } + .post-item:nth-child(3) { + animation-delay: 0.34s; + } + .post-item:nth-child(4) { + animation-delay: 0.41s; + } + .post-item:nth-child(5) { + animation-delay: 0.48s; + } + .post-item:nth-child(6) { + animation-delay: 0.55s; + } + + /* ── Mobile ── */ + @media (max-width: 500px) { + .page { + padding: 0 1.25rem; + } + .hero { + padding: 3.5rem 0 3rem; + } + .post-item { + flex-direction: column; + gap: 0.3rem; + } + .post-date { + min-width: unset; + } + footer { + flex-direction: column; + } + } + + :focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; + border-radius: 4px; + } + </style> + </head> + <body> + <div class="page"> + <nav> + <a href="#">About</a> + <a href="#">Contact</a> + </nav> + + <header class="hero"> + <h1 class="hero-name">{{ .AuthorName }}</h1> + <p class="hero-role">{{ .AuthorRole }}</p> + <p class="hero-bio">{{ .AuthorBio }}</p> + </header> + + <main> + <div class="writing-header"> + <span class="writing-label">Writing</span> + <span class="writing-rule"></span> + </div> + + {{ if .Posts }} + <ul class="post-list"> + {{ range .Posts }} + <li> + <a class="post-item" href="/{{ .Slug }}/"> + <span class="post-date">{{ .Date }}</span> + <span class="post-right"> + <span class="post-title">{{ .Title }}</span> + {{ if .Tags }} + <span class="post-tags"> + {{ range .Tags }} + <span class="pill">{{ . }}</span> + {{ end }} + </span> + {{ end }} + </span> + </a> + </li> + {{ end }} + </ul> + {{ else }} + <p class="empty">No posts yet.</p> + {{ end }} + </main> + + <footer> + <span>© {{ .Year }} {{ .AuthorName }}</span> + <span + >Built with + <a href="https://github.com/HimanshuSardana/kite">kite</a></span + > + </footer> + </div> + </body> +</html> diff --git a/themes/magical/layout.html b/themes/magical/layout.html new file mode 100644 index 0000000..bce6818 --- /dev/null +++ b/themes/magical/layout.html @@ -0,0 +1,775 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <meta name="description" content="{{ .Title }}" /> + <title>{{ .Title }}</title> + + <!-- Google Fonts: Playfair Display (Serif) + Inter/Geist (Sans) --> + <link rel="preconnect" href="https://fonts.googleapis.com" /> + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> + <link + href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400..800;1,400..800&family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" + rel="stylesheet" + /> + + <!-- Prism for Syntax Highlighting --> + <link + rel="stylesheet" + href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism-tomorrow.min.css" + /> + <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js"></script> + <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-go.min.js"></script> + <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-javascript.min.js"></script> + <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-typescript.min.js"></script> + <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-css.min.js"></script> + <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-bash.min.js"></script> + <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/plugins/autoloader/prism-autoloader.min.js"></script> + + <style> + /* ── Modern Dark Theme — Editorial + Micro-interactions ── */ + + :root { + --bg: #09090b; /* Deep zinc */ + --bg-elevated: rgba(24, 24, 27, 0.65); /* Glassmorphic base */ + --bg-subtle: rgba(39, 39, 42, 0.5); + --bg-hover: rgba(63, 63, 70, 0.4); + + --text: #fafafa; + --text-secondary: #a1a1aa; + --text-muted: #71717a; + + --border: rgba(63, 63, 70, 0.4); + --border-light: rgba(82, 82, 91, 0.5); + + --accent: #818cf8; /* Indigo accent */ + --accent-glow: rgba(129, 140, 248, 0.4); + --accent-dim: rgba(129, 140, 248, 0.08); + --code-bg: #111113; + + --serif: "Playfair Display", Georgia, serif; + --sans: "Inter", -apple-system, BlinkMacSystemFont, sans-serif; + --mono: "JetBrains Mono", monospace; + + --max-width: 700px; + --ease-spring: cubic-bezier( + 0.25, + 1.2, + 0.5, + 1 + ); /* Bouncy micro-interaction */ + --ease-smooth: cubic-bezier(0.16, 1, 0.3, 1); + } + + /* ── Base ── */ + *, + *::before, + *::after { + box-sizing: border-box; + margin: 0; + padding: 0; + } + html { + scroll-behavior: smooth; + } + + body { + font-family: var(--sans); + background: var(--bg); + color: var(--text); + line-height: 1.8; + font-size: 16px; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + overflow-x: hidden; + } + + /* ── Initial Load Animation ── */ + @keyframes fadeInSlideUp { + 0% { + opacity: 0; + transform: translateY(20px); + } + 100% { + opacity: 1; + transform: translateY(0); + } + } + + /* ── Article Layout ── */ + .article { + max-width: var(--max-width); + margin: 0 auto; + padding: 0 2rem 8rem; + animation: fadeInSlideUp 0.8s var(--ease-smooth) forwards; + } + + /* ── Article Title (h1) ── */ + h1 { + font-family: var(--serif); + font-size: clamp(2.2rem, 5vw, 3.5rem); + font-weight: 600; + line-height: 1.15; + letter-spacing: -0.02em; + margin-bottom: 2.5rem; + color: var(--text); + } + + /* ════════════════════════════════════════ + TOC — Sticky dropdown pill (Glassmorphism) + ════════════════════════════════════════ */ + + .toc-toggle { + display: none !important; + } + .toc-overlay { + display: none !important; + } + + .toc { + position: sticky; + top: 1.5rem; + display: flex; + justify-content: center; + align-items: top; + z-index: 50; + width: 100%; + max-width: 100%; + background: transparent; + margin-bottom: 3.5rem; + } + + /* Pill trigger */ + .toc-pill { + display: inline-flex; + align-items: center; + gap: 0.6rem; + background: var(--bg-elevated); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border: 1px solid var(--border); + border-radius: 999px; + padding: 0.5rem 1rem 0.5rem 0.75rem; + cursor: pointer; + user-select: none; + max-width: 100%; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2); + transition: + transform 0.3s var(--ease-spring), + border-color 0.3s var(--ease-smooth), + box-shadow 0.3s var(--ease-smooth), + background 0.3s var(--ease-smooth); + } + + /* Pill Hover & Active Micro-interactions */ + .toc-pill:hover { + border-color: var(--border-light); + background: rgba(39, 39, 42, 0.7); + transform: translateY(-2px); + box-shadow: + 0 8px 30px rgba(0, 0, 0, 0.4), + 0 0 0 1px rgba(129, 140, 248, 0.2); + } + + .toc-pill:active { + transform: translateY(1px) scale(0.97); + } + + /* Spinner */ + .toc-progress { + width: 16px; + height: 16px; + flex-shrink: 0; + overflow: visible; + transform: rotate( + -90deg + ); /* Reset handled in JS too, but good for initial paint */ + } + .toc-progress-track { + fill: none; + stroke: var(--border); + stroke-width: 2.5; + } + .toc-progress-fill { + fill: none; + stroke: var(--accent); + stroke-width: 2.5; + stroke-linecap: round; + transition: stroke-dashoffset 0.2s var(--ease-smooth); + filter: drop-shadow(0 0 2px var(--accent-glow)); + } + + .toc-current { + font-size: 0.85rem; + font-weight: 500; + color: var(--text-secondary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 32ch; + transition: color 0.3s ease; + } + + .toc-pill:hover .toc-current { + color: var(--text); + } + + .toc-chevron { + margin-left: 0.2rem; + flex-shrink: 0; + color: var(--text-muted); + transition: + transform 0.4s var(--ease-spring), + color 0.3s ease; + width: 14px; + height: 14px; + } + + .toc-pill:hover .toc-chevron { + color: var(--text-secondary); + } + + .toc[open] .toc-chevron, + .toc.open .toc-chevron { + transform: rotate(180deg); + } + + /* Dropdown panel */ + .toc-panel { + position: absolute; + top: calc(100% + 0.75rem); + left: 50%; + transform: translateX(-50%) translateY(-10px) scale(0.95); + min-width: 300px; + max-width: min(420px, calc(100vw - 2rem)); + background: rgba(24, 24, 27, 0.85); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + border: 1px solid var(--border-light); + border-radius: 16px; + padding: 0.75rem; + box-shadow: + 0 10px 40px rgba(0, 0, 0, 0.6), + 0 0 0 1px rgba(255, 255, 255, 0.02); + opacity: 0; + pointer-events: none; + transition: + opacity 0.3s var(--ease-smooth), + transform 0.4s var(--ease-spring); + } + + .toc-panel-header { + display: flex; + align-items: flex-start; + gap: 0.7rem; + padding: 0.5rem 0.5rem 1rem; + border-bottom: 1px solid var(--border); + margin-bottom: 0.75rem; + } + + .toc-panel-title { + font-family: var(--serif); + font-size: 1rem; + font-weight: 500; + color: var(--text); + line-height: 1.3; + letter-spacing: 0.01em; + } + + .toc[open] .toc-panel, + .toc.open .toc-panel { + opacity: 1; + transform: translateX(-50%) translateY(0) scale(1); + pointer-events: auto; + } + + .toc-list { + list-style: none; + } + .toc-item { + margin: 2px 0; + } + + .toc-link { + display: flex; + align-items: center; + gap: 0.55rem; + padding: 0.5rem 0.75rem; + font-size: 0.85rem; + color: var(--text-secondary); + text-decoration: none; + border-radius: 10px; + transition: all 0.25s var(--ease-smooth); + line-height: 1.4; + } + + /* Hover Micro-interaction on List */ + .toc-link:hover { + background: var(--bg-hover); + color: var(--text); + transform: translateX(4px); + } + + .toc-link.active { + color: var(--text); + background: var(--bg-subtle); + font-weight: 500; + } + + .toc-link.active::before { + content: ""; + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--accent); + box-shadow: 0 0 8px var(--accent-glow); + flex-shrink: 0; + } + + .toc-level-3 .toc-link { + padding-left: 1.75rem; + font-size: 0.8rem; + color: var(--text-muted); + } + .toc-level-4 .toc-link { + padding-left: 2.5rem; + font-size: 0.75rem; + color: var(--text-muted); + } + + /* ════════════════════════════════════════ + Content Typography & Playfair Display + ════════════════════════════════════════ */ + + .content h2 { + font-family: var(--serif); + font-size: 2rem; + font-weight: 500; + margin-top: 4rem; + margin-bottom: 1.25rem; + letter-spacing: -0.01em; + color: var(--text); + padding-bottom: 0.75rem; + border-bottom: 1px solid var(--border); + } + + .content h3 { + font-family: var(--serif); + font-size: 1.5rem; + font-weight: 500; + margin-top: 3rem; + margin-bottom: 1rem; + color: var(--text); + } + + .content h4 { + font-family: var(--serif); + font-size: 1.2rem; + font-weight: 600; + margin-top: 2rem; + margin-bottom: 0.75rem; + color: var(--text-secondary); + } + + .content p { + margin-bottom: 1.5rem; + color: var(--text-secondary); + font-size: 1.05rem; + font-weight: 300; + } + + /* Inline Link Hover Animation */ + .content a { + color: var(--text); + font-weight: 500; + text-decoration: none; + background-image: linear-gradient(var(--accent), var(--accent)); + background-size: 0% 1px; + background-position: 0 100%; + background-repeat: no-repeat; + transition: + background-size 0.3s var(--ease-smooth), + color 0.3s var(--ease-smooth); + padding-bottom: 1px; + } + + .content a:hover { + background-size: 100% 1px; + color: var(--accent); + } + + .content strong { + font-weight: 600; + color: var(--text); + } + .content em { + font-style: italic; + color: var(--text-secondary); + } + + .content ul, + .content ol { + margin-bottom: 1.5rem; + padding-left: 0; + color: var(--text-secondary); + font-size: 1.05rem; + font-weight: 300; + list-style: none; + } + + .content ul li, + .content ol li { + position: relative; + padding-left: 1.75rem; + margin-bottom: 0.6rem; + line-height: 1.7; + } + + .content ul li::before { + content: ""; + position: absolute; + left: 0.25rem; + top: 0.65rem; + width: 5px; + height: 5px; + border-radius: 50%; + background: var(--accent); + opacity: 0.8; + } + + .content ol { + counter-reset: ol-counter; + } + .content ol li { + counter-increment: ol-counter; + } + .content ol li::before { + content: counter(ol-counter) "."; + position: absolute; + left: 0; + color: var(--accent); + font-size: 0.85em; + font-weight: 600; + font-family: var(--mono); + top: 0.1em; + background: transparent; + width: auto; + height: auto; + } + + .content code { + font-family: var(--mono); + font-size: 0.85em; + background: var(--bg-subtle); + border: 1px solid var(--border); + padding: 0.2em 0.4em; + border-radius: 6px; + color: #c4b5fd; + } + + .content pre { + margin: 2rem 0; + border-radius: 12px; + overflow: hidden; + border: 1px solid var(--border); + background: var(--code-bg); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4); + transition: + border-color 0.3s ease, + box-shadow 0.3s ease; + } + + .content pre:hover { + border-color: var(--border-light); + box-shadow: 0 8px 30px rgba(0, 0, 0, 0.6); + } + + .content pre code { + font-family: var(--mono); + font-size: 0.85rem; + line-height: 1.7; + background: transparent; + color: #e2e8f0; + padding: 1.5rem; + border: none; + display: block; + overflow-x: auto; + } + + .content blockquote { + position: relative; + padding: 1.5rem 2rem; + margin: 2.5rem 0; + background: var(--accent-dim); + border-left: 3px solid var(--accent); + border-radius: 0 12px 12px 0; + } + + /* Playfair emphasis for blockquotes */ + .content blockquote p { + font-family: var(--serif); + font-size: 1.25rem; + color: var(--text); + font-style: italic; + line-height: 1.6; + margin-bottom: 0; + } + + .content hr { + border: none; + height: 1px; + background: linear-gradient( + 90deg, + transparent, + var(--border), + transparent + ); + margin: 3.5rem 0; + } + + .content img { + max-width: 100%; + border-radius: 12px; + margin: 2.5rem 0; + border: 1px solid var(--border); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); + transition: + transform 0.4s var(--ease-spring), + box-shadow 0.4s var(--ease-smooth); + } + + .content img:hover { + transform: scale(1.01); + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5); + } + + :focus-visible { + outline: 2px solid var(--accent); + outline-offset: 4px; + border-radius: 6px; + } + + @media (max-width: 640px) { + .article { + padding: 0 1.25rem 5rem; + } + h1 { + font-size: 2rem; + margin-bottom: 2rem; + } + .content h2 { + font-size: 1.6rem; + margin-top: 3rem; + } + .content h3 { + font-size: 1.3rem; + } + .toc-current { + max-width: 20ch; + } + .toc-panel { + max-width: calc(100vw - 2.5rem); + } + .content blockquote { + padding: 1.2rem 1.5rem; + } + } + </style> + </head> + <body> + <main class="article"> + <div class="toc" id="toc"> + <!-- Pill trigger --> + <div + class="toc-pill" + id="toc-pill" + role="button" + aria-expanded="false" + aria-controls="toc-panel" + tabindex="0" + > + <!-- Section progress ring --> + <svg class="toc-progress" viewBox="0 0 16 16" aria-hidden="true"> + <circle class="toc-progress-track" cx="8" cy="8" r="6" /> + <circle + class="toc-progress-fill" + cx="8" + cy="8" + r="6" + id="toc-progress-fill" + /> + </svg> + <span class="toc-current" id="toc-current" + >{{ (index .TOC 0).Text }}</span + > + <svg + class="toc-chevron" + viewBox="0 0 16 16" + fill="none" + stroke="currentColor" + stroke-width="2" + stroke-linecap="round" + stroke-linejoin="round" + aria-hidden="true" + > + <polyline points="4 6 8 10 12 6" /> + </svg> + </div> + + <!-- Dropdown panel --> + <div class="toc-panel" id="toc-panel" role="listbox"> + <div class="toc-panel-header"> + <svg + class="toc-progress" + viewBox="0 0 16 16" + aria-hidden="true" + style="margin-top: 3px; flex-shrink: 0" + > + <circle class="toc-progress-track" cx="8" cy="8" r="6" /> + <circle + class="toc-progress-fill" + cx="8" + cy="8" + r="6" + id="toc-progress-fill-panel" + /> + </svg> + <span class="toc-panel-title">{{ .Title }}</span> + </div> + + <ul class="toc-list"> + {{ range .TOC }} + <li class="toc-item toc-level-{{ .Level }}"> + <a class="toc-link" href="#{{ .ID }}">{{ .Text }}</a> + </li> + {{ end }} + </ul> + </div> + </div> + + <h1>{{ .Title }}</h1> + <div class="content">{{ .Content }}</div> + </main> + + <script> + (function () { + const toc = document.getElementById("toc"); + const pill = document.getElementById("toc-pill"); + const panel = document.getElementById("toc-panel"); + const currentLabel = document.getElementById("toc-current"); + const fillPill = document.getElementById("toc-progress-fill"); + const fillPanel = document.getElementById("toc-progress-fill-panel"); + + if (!toc || !pill) return; + + // ── Circle geometry ── + const CIRCUMFERENCE = 2 * Math.PI * 6; + + [fillPill, fillPanel].forEach((el) => { + if (!el) return; + el.style.strokeDasharray = CIRCUMFERENCE; + el.style.strokeDashoffset = CIRCUMFERENCE; + el.style.transform = "rotate(-90deg)"; + el.style.transformOrigin = "50% 50%"; + }); + + function setProgress(ratio) { + const offset = CIRCUMFERENCE * (1 - Math.min(1, Math.max(0, ratio))); + [fillPill, fillPanel].forEach((el) => { + if (el) el.style.strokeDashoffset = offset; + }); + } + + // ── Toggle open/close ── + function openToc() { + toc.classList.add("open"); + pill.setAttribute("aria-expanded", "true"); + } + function closeToc() { + toc.classList.remove("open"); + pill.setAttribute("aria-expanded", "false"); + } + function toggle() { + toc.classList.contains("open") ? closeToc() : openToc(); + } + + pill.addEventListener("click", toggle); + pill.addEventListener("keydown", (e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + toggle(); + } + }); + + document.addEventListener("click", (e) => { + if (!toc.contains(e.target)) closeToc(); + }); + + panel.querySelectorAll(".toc-link").forEach((link) => { + link.addEventListener("click", () => closeToc()); + }); + + // ── Section tracking ── + const links = Array.from(panel.querySelectorAll(".toc-link")); + if (!links.length) return; + + const ids = links.map((a) => + decodeURIComponent(a.getAttribute("href").slice(1)), + ); + const headings = ids + .map((id) => document.getElementById(id)) + .filter(Boolean); + + let activeIndex = 0; + + function getActiveIndex() { + const scrollY = window.scrollY + window.innerHeight * 0.25; + let idx = 0; + for (let i = 0; i < headings.length; i++) { + if ( + headings[i].getBoundingClientRect().top + window.scrollY <= + scrollY + ) + idx = i; + else break; + } + return idx; + } + + function getSectionProgress(idx) { + const sectionTop = + headings[idx].getBoundingClientRect().top + window.scrollY; + const nextTop = + idx + 1 < headings.length + ? headings[idx + 1].getBoundingClientRect().top + window.scrollY + : document.documentElement.scrollHeight; + const sectionHeight = nextTop - sectionTop; + const scrolled = + window.scrollY + window.innerHeight * 0.25 - sectionTop; + return scrolled / sectionHeight; + } + + function update() { + activeIndex = getActiveIndex(); + const progress = getSectionProgress(activeIndex); + + setProgress(progress); + + links.forEach((link, i) => { + const on = i === activeIndex; + link.classList.toggle("active", on); + if (on && currentLabel) + currentLabel.textContent = link.textContent.trim(); + }); + } + + window.addEventListener("scroll", update, { passive: true }); + window.addEventListener("resize", update, { passive: true }); + + // Initial call + setTimeout(update, 100); + })(); + </script> + </body> +</html> |
