commit 53c0532f602f7a7101d84cb30960f1af4ac12dcb Author: karim Date: Tue May 26 11:23:18 2026 +0200 Initial commit: DOSSIER Hugo website diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..29ad6d8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# Hugo build output +/public/ +/resources/_gen/ +.hugo_build.lock + +# macOS +.DS_Store + +# Editor +.vscode/ +.idea/ + +# Backups +*.bak diff --git a/assets/css/custom.css b/assets/css/custom.css new file mode 100644 index 0000000..aea0758 --- /dev/null +++ b/assets/css/custom.css @@ -0,0 +1,683 @@ +/* ───────────────────────────────────────────────────────────── + DOSSIER — Theme-Overrides für Hextra + Petrol-Grün auf dunklem Grund (Architektur-Studio-Ästhetik) + ───────────────────────────────────────────────────────────── */ + +@import url('https://fonts.googleapis.com/css2?family=Archivo+Black&family=Inter:wght@300;400;500;600;700&family=Playfair+Display:ital,wght@0,400;0,700;1,400&display=swap'); + +/* Krungthep — DOSSIER-Display-Font (Mac-System-Font lokal gebundelt) */ +@font-face { + font-family: 'Krungthep'; + src: url('/fonts/Krungthep.ttf') format('truetype'); + font-weight: 400; + font-style: normal; + font-display: swap; +} + +/* — Primary-HSL: Petrol #5fa896 — */ +:root { + --primary-hue: 165deg; + --primary-saturation: 28%; + --primary-lightness: 51%; + + /* DOSSIER-Palette */ + --dossier-bg: #0e1413; + --dossier-surface: #161d1c; + --dossier-surface2: #1b2422; + --dossier-dark: #0a100f; + --dossier-dark2: #1f2826; + --dossier-accent: #5fa896; + --dossier-accent-2: #4a8a7c; + --dossier-accent-3: #2f5d54; + --dossier-text: #e6e8e6; + --dossier-text-2: #b4bcb8; + --dossier-text-3: #828a86; + --dossier-text-4: #5a625e; + --dossier-border: #232b29; + --dossier-border-2: #2e3633; +} + +.dark { + --primary-hue: 165deg; + --primary-saturation: 38%; + --primary-lightness: 51%; + --color-dark: var(--dossier-bg); +} + +/* — Body & Backgrounds — */ +.dark body { + background: var(--dossier-bg); + color: var(--dossier-text); + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif; +} + +.dark ::selection { + background: rgba(95, 168, 150, 0.30); + color: #fff; +} + +/* — Typografie — Headings serifig, Body monospaced — */ +.dark .hextra-toc, +.dark .content, +.dark .prose { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif; +} + +.dark h1, .dark h2, .dark h3, .dark h4, +.hextra-home h1, .hextra-home h2, .hextra-home h3, +.hx\:text-2xl, .hx\:text-3xl, .hx\:text-4xl, .hx\:text-5xl, .hx\:text-6xl { + font-family: 'Playfair Display', Georgia, serif !important; + font-weight: 700 !important; + letter-spacing: -0.01em; +} + +/* Navbar-Logo: DOSSIER in Krungthep/Archivo Black — nur navbar! */ +.hextra-navbar-title, +nav .hextra-navbar-title, +nav .hextra-max-navbar-width .hx\:font-bold { + font-family: Krungthep, 'Archivo Black', sans-serif !important; + letter-spacing: -0.02em !important; + font-weight: 900 !important; +} + +/* — Navbar dunkel mit Border — */ +.dark .nav-container { + background: rgba(14, 20, 19, 0.85) !important; + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border-bottom: 1px solid var(--dossier-border) !important; +} + +.dark .nav-container-blur { + background: transparent !important; +} + +/* — Sidebar — */ +.dark aside.sidebar-container, +.dark .sidebar-container { + background: var(--dossier-bg) !important; + border-right: 1px solid var(--dossier-border); +} + +.dark .sidebar-container a { + color: var(--dossier-text-2); +} + +.dark .sidebar-container a:hover { + color: var(--dossier-accent); + background: var(--dossier-surface); +} + +.dark .sidebar-active-item, +.dark .sidebar-container .sidebar-active-item { + background: rgba(95, 168, 150, 0.12) !important; + color: var(--dossier-accent) !important; + border-color: rgba(95, 168, 150, 0.20) !important; +} + +/* — Links — */ +.dark a { + transition: color 0.15s; +} + +.dark .content a, +.dark .prose a { + color: var(--dossier-accent); + text-decoration: none; +} + +.dark .content a:hover, +.dark .prose a:hover { + color: #6db5a4; + text-decoration: underline; +} + +/* Hextra-Card-Links (-Shortcode) sollen NIE unterstrichen werden */ +.hextra-card, +.hextra-card:hover, +.hextra-card:focus, +.hextra-card:active, +.hextra-card *, +.hextra-card:hover * { + text-decoration: none !important; +} + +a.hextra-card:hover, +a.hextra-card:focus { + outline: none; + text-decoration: none !important; +} + +/* — Buttons / Hero-Button — */ +.hextra-hero-button { + background: var(--dossier-accent) !important; + color: #0a1715 !important; + border-radius: 20px !important; + padding: 13px 28px !important; + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif !important; + font-size: 11px !important; + font-weight: 500 !important; + letter-spacing: 0.07em !important; + text-transform: uppercase !important; + box-shadow: 0 6px 18px rgba(95, 168, 150, 0.22); + transition: all 0.18s; +} + +.hextra-hero-button:hover { + background: #6db5a4 !important; + box-shadow: 0 10px 26px rgba(95, 168, 150, 0.32); + transform: translateY(-1px); +} + +/* — DOSSIER Hero-Buttons — eigene Pills im RAPPORT-Layout-Pattern — */ +.dossier-hero-actions { + display: flex; + gap: 18px; + align-items: center; + justify-content: center; + flex-wrap: wrap; + margin-top: 8px; + margin-bottom: 8px; +} + +.dossier-btn { + display: inline-flex; + align-items: center; + gap: 9px; + border-radius: 999px; + padding: 14px 30px; + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif; + font-size: 12px; + font-weight: 500; + letter-spacing: 0.07em; + text-transform: uppercase; + text-decoration: none !important; + cursor: pointer; + transition: transform 0.18s ease, box-shadow 0.18s ease, background 0.18s ease, border-color 0.18s ease; + user-select: none; + white-space: nowrap; +} + +/* Primary — Petrol-Akzent mit Tiefe */ +.dossier-btn-primary { + background: var(--dossier-accent); + color: #0a1715 !important; + border: 1px solid var(--dossier-accent); + box-shadow: + 0 1px 0 rgba(255,255,255,0.10) inset, + 0 2px 4px rgba(0,0,0,0.30), + 0 8px 20px rgba(0,0,0,0.40), + 0 16px 40px rgba(95,168,150,0.22); +} + +.dossier-btn-primary:hover { + background: #6db5a4; + border-color: #6db5a4; + color: #0a1715 !important; + transform: translateY(-2px); + box-shadow: + 0 1px 0 rgba(255,255,255,0.10) inset, + 0 4px 8px rgba(0,0,0,0.36), + 0 14px 28px rgba(0,0,0,0.42), + 0 24px 56px rgba(95,168,150,0.32); +} + +.dossier-btn-primary:active { + transform: translateY(0); + box-shadow: + 0 1px 0 rgba(255,255,255,0.10) inset, + 0 2px 4px rgba(0,0,0,0.30), + 0 4px 12px rgba(0,0,0,0.36); +} + +/* Secondary — Outline */ +.dossier-btn-secondary { + background: transparent; + color: var(--dossier-text-2) !important; + border: 1.5px solid var(--dossier-border-2); + box-shadow: 0 2px 6px rgba(0,0,0,0.20); +} + +.dossier-btn-secondary:hover { + background: rgba(255,255,255,0.04); + border-color: var(--dossier-text-3); + color: var(--dossier-text) !important; + transform: translateY(-2px); + box-shadow: + 0 6px 14px rgba(0,0,0,0.28), + 0 12px 28px rgba(0,0,0,0.20); +} + +.dossier-btn-secondary:active { + transform: translateY(0); +} + +/* — Hero-Badge — */ +.hextra-badge { + background: var(--dossier-surface) !important; + border: 1px solid var(--dossier-border-2) !important; + border-radius: 20px !important; + padding: 5px 14px !important; + font-size: 10px !important; + letter-spacing: 0.12em !important; + color: var(--dossier-text-3) !important; + text-transform: uppercase !important; + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif !important; +} + +/* — Feature-Cards — */ +.hextra-feature-card { + background: var(--dossier-surface) !important; + border: 1px solid var(--dossier-border) !important; + border-radius: 14px !important; + transition: box-shadow 0.2s, transform 0.2s, border-color 0.2s !important; +} + +.hextra-feature-card:hover { + border-color: var(--dossier-border-2) !important; + transform: translateY(-2px); + box-shadow: 0 10px 32px rgba(0, 0, 0, 0.40), 0 2px 8px rgba(0, 0, 0, 0.28) !important; +} + +.hextra-feature-card h3 { + font-family: 'Playfair Display', serif !important; + color: var(--dossier-text) !important; + font-weight: 700 !important; +} + +.hextra-feature-card p { + color: var(--dossier-text-3) !important; + font-size: 12px !important; + line-height: 1.8 !important; +} + +/* — Generic Cards (Hextra shortcode) — */ +.dark .hextra-card { + background: var(--dossier-surface); + border: 1px solid var(--dossier-border); + border-radius: 12px; + transition: border-color 0.2s, transform 0.2s; +} + +.dark .hextra-card:hover { + border-color: var(--dossier-accent-2); + transform: translateY(-1px); +} + +/* — Code-Blocks — nur innerer pre/highlight bekommt Box, äusserer Wrapper bleibt transparent — */ +.dark .hextra-code-block { + background: transparent !important; + border: none !important; +} + +.dark .hextra-code-block pre, +.dark .hextra-code-block .highlight, +.dark .highlight pre, +.dark pre.chroma { + background: var(--dossier-dark) !important; + border: 1px solid var(--dossier-border) !important; + border-radius: 12px !important; +} + +/* Doppelten Border vermeiden wenn pre & highlight beide gestyled werden */ +.dark .hextra-code-block .highlight { + background: transparent !important; + border: none !important; + border-radius: 0 !important; +} + +.dark code, .dark pre, .dark pre code, .dark kbd, .dark samp, .dark tt { + font-family: ui-monospace, 'SF Mono', Menlo, Monaco, Consolas, 'Liberation Mono', monospace !important; +} + +.dark code { + color: var(--dossier-accent) !important; + background: rgba(95, 168, 150, 0.08) !important; + padding: 2px 6px; + border-radius: 4px; + font-size: 0.9em; +} + +.dark pre code { + color: var(--dossier-text-2) !important; + background: transparent !important; + padding: 0; +} + +/* — Callouts — */ +.dark .hextra-callout { + background: var(--dossier-surface) !important; + border-color: var(--dossier-border-2) !important; +} + +/* — Footer — */ +.dark .hextra-footer, +.dark footer { + background: var(--dossier-dark); + border-top: 1px solid var(--dossier-border-2); + color: var(--dossier-text-4); +} + +.dark .hextra-footer a { + color: var(--dossier-text-3); +} + +.dark .hextra-footer a:hover { + color: var(--dossier-accent); +} + +/* — TOC — */ +.dark .hextra-toc a { + color: var(--dossier-text-3); +} + +.dark .hextra-toc a:hover, +.dark .hextra-toc .active { + color: var(--dossier-accent) !important; +} + +/* Hextra-Sticky-Bottom-Fades (TOC "Nach oben" + Sidebar-Footer) — */ +/* der hardcoded #111-Fade passt nicht zum petrol-dunklen DOSSIER-Bg */ +.hextra-toc div:has(> #backToTop), +[data-toggle-animation] { + background: var(--dossier-bg) !important; + box-shadow: none !important; + border-top-color: var(--dossier-border) !important; +} + +/* — Search — */ +.dark .hextra-search-wrapper input { + background: var(--dossier-surface) !important; + border: 1px solid var(--dossier-border) !important; + color: var(--dossier-text) !important; +} + +.dark .hextra-search-wrapper input:focus { + border-color: var(--dossier-accent-2) !important; +} + +/* — Tabellen — */ +.dark table { + border-color: var(--dossier-border) !important; +} + +.dark thead { + background: var(--dossier-surface) !important; +} + +.dark th, .dark td { + border-color: var(--dossier-border) !important; +} + +/* — Hero-Section Hintergrund-Glow — */ +.hextra-home::before, +body.hextra-home::before { + content: ""; + position: fixed; + top: 10%; + left: 50%; + transform: translateX(-50%); + width: 720px; + height: 720px; + background: radial-gradient(circle, rgba(95, 168, 150, 0.08) 0%, transparent 60%); + pointer-events: none; + z-index: -1; +} + +/* — DOSSIER-Logo-Karte (für hero) — Pill-Form — */ +.dossier-logo-card { + background: var(--dossier-surface); + border: 1px solid var(--dossier-border-2); + border-radius: 999px; + padding: 28px 64px 26px; + display: inline-block; + box-shadow: 6px 0 20px rgba(0, 0, 0, 0.40), 0 6px 16px rgba(0, 0, 0, 0.28); + text-align: center; + margin: 0 auto 32px; +} + +.dossier-logo-text { + font-family: Krungthep, 'Archivo Black', sans-serif; + font-size: 42px; + letter-spacing: -0.02em; + color: var(--dossier-text); + line-height: 0.95; +} + +.dossier-logo-sub { + font-size: 9px; + letter-spacing: 0.15em; + color: var(--dossier-accent); + text-transform: uppercase; + margin-top: 8px; + font-weight: 500; +} + +/* — Hero-Meta-Pillen — */ +.dossier-meta { + display: flex; + gap: 6px; + align-items: center; + justify-content: center; + flex-wrap: wrap; + margin-top: 32px; + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif; +} + +.dossier-meta-item { + font-size: 10px; + letter-spacing: 0.08em; + color: var(--dossier-text-4); + text-transform: uppercase; + padding: 0 10px; + border-right: 1px solid var(--dossier-border); +} + +.dossier-meta-item:last-child { + border-right: none; +} + +/* — Status-Badges (in Arbeit / Geplant / Stabil) — */ +.dossier-status { + display: inline-block; + font-size: 9px; + letter-spacing: 0.1em; + text-transform: uppercase; + padding: 2px 9px; + border-radius: 10px; + margin-bottom: 8px; + font-weight: 500; + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif; +} + +.dossier-status.active { + background: rgba(95, 168, 150, 0.12); + color: var(--dossier-accent); + border: 1px solid rgba(95, 168, 150, 0.20); +} + +.dossier-status.planned { + background: rgba(255, 255, 255, 0.04); + color: var(--dossier-text-3); + border: 1px solid var(--dossier-border-2); +} + +.dossier-status.stable { + background: rgba(95, 168, 150, 0.20); + color: #e6e8e6; + border: 1px solid var(--dossier-accent-2); +} + +/* — Stack-Bar (am Footer) — */ +.dossier-stack-bar { + padding: 20px 0; + border-top: 1px solid var(--dossier-border); + display: flex; + align-items: center; + gap: 20px; + flex-wrap: wrap; + margin-top: 32px; + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif; +} + +.dossier-stack-label { + font-size: 10px; + letter-spacing: 0.12em; + text-transform: uppercase; + color: var(--dossier-text-4); + flex-shrink: 0; +} + +.dossier-stack-items { + display: flex; + gap: 8px; + flex-wrap: wrap; +} + +.dossier-stack-item { + font-size: 10px; + letter-spacing: 0.06em; + color: var(--dossier-text-3); + background: var(--dossier-surface); + border: 1px solid var(--dossier-border); + border-radius: 6px; + padding: 4px 10px; +} + +/* — Reduce default content max-width slightly for monospace lines — */ +.content article { + max-width: 100%; +} + +/* ───────────────────────────────────────────────────────────── + HOME-HERO — zentriertes Layout wie auf der alten Website + ───────────────────────────────────────────────────────────── */ + +/* Inhalt der home Page zentrieren (override hextra-home.html) */ +.hextra-home { + align-items: center !important; + text-align: center; + max-width: 100%; +} + +/* Subtitle zentriert, schmaler max-width, Playfair Display wie alt */ +.hextra-home > p, +.hextra-home .not-prose.hx\:text-xl { + font-family: 'Playfair Display', serif !important; + font-size: clamp(16px, 2.2vw, 24px) !important; + font-weight: 400 !important; + color: var(--dossier-text-2) !important; + max-width: 560px !important; + margin-left: auto !important; + margin-right: auto !important; + line-height: 1.55 !important; + text-align: center; +} + +/* Buttons immer in einer zentrierten Zeile */ +.hextra-home > div.hx\:flex, +.hextra-home > .hx\:flex { + justify-content: center; + align-items: center; +} + +/* Hero-Button matches DOSSIER design */ +.hextra-home .hextra-hero-button, +a.hextra-hero-button { + background: var(--dossier-accent); + color: #0a1715 !important; + border-radius: 20px; + padding: 13px 28px; + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif !important; + font-size: 11px !important; + font-weight: 500 !important; + letter-spacing: 0.07em !important; + text-transform: uppercase !important; + box-shadow: 0 6px 18px rgba(95, 168, 150, 0.22); + transition: all 0.18s; + display: inline-flex; + align-items: center; + gap: 9px; +} + +/* Feature-Grid wieder linksbündig (text in cards) */ +.hextra-home .hextra-feature-grid, +.hextra-home .dossier-stack-bar { + text-align: left; + width: 100%; +} + +/* Cards & Stack-Bar Inhalte: text wieder normal ausrichten */ +.hextra-home .hextra-feature-card * { + text-align: left; +} + +.dossier-stack-bar { + justify-content: flex-start; + text-align: left; +} + +/* Section-Heading & Eyebrow vor dem Feature-Grid auch zentrieren */ +.hextra-home h2, +.hextra-home > div > h2 { + text-align: center; + margin-left: auto; + margin-right: auto; +} + +/* DOSSIER-Logo bleibt zentriert */ +.hextra-home .dossier-logo-card { + margin-left: auto; + margin-right: auto; + display: block; +} + +/* Hero-Badge zentrieren */ +.hextra-home .hextra-badge, +.hextra-home > p:has(.hextra-badge) { + margin-left: auto; + margin-right: auto; + display: inline-flex !important; +} + +/* Hero-Background-Glow — sanfter Petrol-Halo */ +body:has(.hextra-home)::before { + content: ""; + position: fixed; + top: 5%; + left: 50%; + transform: translateX(-50%); + width: 720px; + height: 720px; + background: radial-gradient(circle, rgba(95, 168, 150, 0.10) 0%, transparent 60%); + pointer-events: none; + z-index: 0; +} + +/* DOSSIER-Logo grösser und expressiver */ +.dossier-logo-text { + font-family: 'Krungthep', 'Archivo Black', sans-serif !important; + font-size: 48px; + letter-spacing: -0.02em; + color: var(--dossier-text); + line-height: 0.95; + font-weight: 400; +} + +/* Navbar-Logo — DOSSIER-Schriftzug in Krungthep, etwas grösser */ +.hextra-navbar-title, +nav a[href="/"] span, +nav [class*="font-bold"] { + font-family: 'Krungthep', 'Archivo Black', sans-serif !important; + letter-spacing: -0.02em !important; + font-weight: 400 !important; + font-size: 23px !important; + line-height: 1 !important; +} + +/* DOSSIER-Meta-Pillen zentriert */ +.dossier-meta { + justify-content: center; + text-align: center; +} + diff --git a/content/_index.md b/content/_index.md new file mode 100644 index 0000000..c13d05d --- /dev/null +++ b/content/_index.md @@ -0,0 +1,143 @@ +--- +title: DOSSIER +layout: hextra-home +toc: false +--- + +{{< hextra/hero-badge >}} +
+ Pre-Release 0.1.0 · Aktiv in Entwicklung +{{< /hextra/hero-badge >}} + +
+
+
DOSSIER
+
Rhino 8 Plugin · Teil von OpenBureau
+
+
+ +
+{{< hextra/hero-subtitle >}} + Ein Design-Studio für Rhino — smarte Bauteile, Schnitte, Plan-Vorlagen, Materialien, Symbole und SIA-416-Räume. Modell-Qualität und Planabgabe direkt aus 3D, ohne BIM-Cloud. +{{< /hextra/hero-subtitle >}} +
+ + + +
+ AGPL-3.0 + Rhino 8+ + macOS & Windows + CPython 3.9 + Open Source +
+ +
+

ANSATZ

+

Aus Rhino ein vollständiges Design-Studio machen

+

Rhino ist stark in der Geometrie — Dossier ergänzt das, was bis zur Planabgabe fehlt: smarte Bauteile, Schnitte und Ansichten, projektweite Material- und Symbol-Library, SIA-Räume, Layout-Set und Beschriftung. Kein BIM-Datenbank-Overhead, keine Cloud — die `.3dm` bleibt die einzige Quelle.

+
+ +{{< hextra/feature-grid >}} + + {{< hextra/feature-card + title="Smart-Elemente" + subtitle="Wände, Decken, Dächer, Öffnungen, Treppen und Tragwerk als parametrische Bauteile mit Source ↔ Volume Pattern. Wand v1 mit Polyline-Achse, Chain-Anchor und nativen Rhino-Grips — Cmd+Z über alle Joints stabil." + style="background: radial-gradient(ellipse at 50% 80%,rgba(95,168,150,0.10),hsla(0,0%,100%,0));" + >}} + + {{< hextra/feature-card + title="Geschosse & Ebenen" + subtitle="Multi-Geschoss-Clipping mit Top-View-Z-Guard und Snap-Bar pro Geschoss. Layer-Hierarchie (10_GRUNDRISSE/EG/WÄNDE, …) wird automatisch aufgebaut und mit den Smart-Elementen verknüpft." + style="background: radial-gradient(ellipse at 50% 80%,rgba(95,168,150,0.08),hsla(0,0%,100%,0));" + >}} + + {{< hextra/feature-card + title="Schnitte & Ansichten" + subtitle="Schnitt-Perspektive mit voller Section-Style-API — Hatch-Pattern für Schnittflächen, Hidden-Line-Removal, Schnittlinien-Stil pro Layer. Anlass für die CPython-3-Migration." + style="background: radial-gradient(ellipse at 50% 80%,rgba(95,168,150,0.12),hsla(0,0%,100%,0));" + >}} + + {{< hextra/feature-card + title="Project-Settings (5 Tabs)" + subtitle="Zentraler Dialog für Voreinstellungen + Projektdaten, Materialien (PBR + Texturen), Linientypen, Schraffuren und Symbole. Daten leben im .3dm — keine externen Konfigs." + style="background: radial-gradient(ellipse at 50% 80%,rgba(95,168,150,0.10),hsla(0,0%,100%,0));" + >}} + + {{< hextra/feature-card + title="Material-Library" + subtitle="ArchiCAD-style List/Detail mit Auto-Regen, Materialvorschau und Material/Ebene-Separation. Linetypes per .lin- und Hatches per .pat-Import direkt ins Projekt." + >}} + + {{< hextra/feature-card + title="Symbol-Library" + subtitle="2D+3D Pair-Files mit Satellite-Picker. Multi-Format-Import (.3dm/.dwg/.obj/.fbx/.dae/.stl), automatische Base64-PNG-Thumbnails und CRUD direkt aus Rhino." + >}} + + {{< hextra/feature-card + title="SIA-416 Flächen" + subtitle="Räume mit HNF / NNF / FF / VF-Kategorisierung. Im Standardmodus transparent, im Flächenmodus farbliche Überlagerung. CSV-Export und aggregierte Schemata direkt aus der Klassifikation." + >}} + + {{< hextra/feature-card + title="Layouts & PDF-Export" + subtitle="Plan-Sets aus Named Views, Titelblock-Vorlagen mit Bürodaten aus dem Projekt, Stapelexport als Vektor-PDF oder PNG. Benennung und Reihenfolge folgen dem Planverzeichnis." + >}} + + {{< hextra/feature-card + title="Massstab & Display-Modes" + subtitle="Viewport-Skala 1:N mit Auto-DPI über CoreGraphics, PlotWeight-Synchronisation und Display-Mode-Kopplung an Ausschnitte. Display-Mode Dossier-Plan (Hidden-Line, weisser Hintergrund, Section-Hatch) in aktiver Entwicklung." + >}} + + {{< hextra/feature-card + title="Gestaltung & Overrides" + subtitle="Regelbasierte Overrides (Bedingung → Aktion), Plot-Sync für Farbe / Lineweight / Linetype / Hatch und Preset-Verwaltung cross-doc." + >}} + + {{< hextra/feature-card + title="Ausschnitte & Kamera" + subtitle="Viewport-Snapshots (Kamera + Display + Layer-Sichtbarkeit) als wiederherstellbare States. Verknüpfung an Massstab und Layout." + >}} + + {{< hextra/feature-card + title="Swisstopo & OSM" + subtitle="Schweizer Landeskarten-Tiles und OSM-Daten als georeferenzierter Hintergrund. Adress-Prefill aus Projektdaten, Terrain-Import als Höhenmodell und m.ü.M-Bezug für die Situation." + >}} + + {{< hextra/feature-card + title="DOSSIER-Text Editor" + subtitle="WYSIWYG Rich-Text Editor für TextEntities mit RTF-Export. Fett / Italic / Underline / Strike, Tab-Stops und Multi-Font-Support im Modellraum." + >}} + + {{< hextra/feature-card + title="Dimensionen" + subtitle="Bemassungs-Panel für Wand-Dicken, Geschoss-Höhen und Öffnungs-Masse. Konsistente Stile, automatische Aktualisierung bei Modell-Änderungen." + >}} + + {{< hextra/feature-card + title="Werkzeuge" + subtitle="Batch-Operationen für wiederkehrende Aufgaben — Layer-Bereinigung, Section-Style-Reset, Joint-Cache-Clear, Material-Index-Refresh." + >}} + + {{< hextra/feature-card + title="Tauri-Launcher" + subtitle="Standalone-App für Projekt-Verwaltung, Settings-Sync, Auto-Updates und Window-Layouts. System-Tray, file-based IPC zu Rhino — beide Apps laufen unabhängig voneinander." + icon="sparkles" + >}} + +{{< /hextra/feature-grid >}} + +
+ Aufgebaut auf +
+ RhinoCommon + CPython 3.9 + React + Vite + Eto.Forms WebView + Tauri 2 + AGPL-3.0 +
+
diff --git a/content/docs/_index.md b/content/docs/_index.md new file mode 100644 index 0000000..bc1cad7 --- /dev/null +++ b/content/docs/_index.md @@ -0,0 +1,38 @@ +--- +title: Dokumentation +linkTitle: Dokumentation +next: docs/erste-schritte +weight: 1 +sidebar: + open: true +--- + +Willkommen zur DOSSIER-Dokumentation. DOSSIER ist ein **Rhino 8 Plugin** für architektonisches Entwerfen mit smarten Bauteilen — Teil von **OpenBureau**. + +{{< cards >}} + {{< card link="erste-schritte" title="Erste Schritte" icon="play" subtitle="Installation, Setup, erster Workflow." >}} + {{< card link="../features" title="Features" icon="cube" subtitle="Alle Panels und ihre Funktionen." >}} + {{< card link="../launcher" title="Launcher" icon="desktop-computer" subtitle="Standalone-App für Projekte und Settings." >}} + {{< card link="architektur" title="Architektur" icon="template" subtitle="Module, Bridge-Pattern, Sticky-Storage." >}} + {{< card link="roadmap" title="Roadmap" icon="map" subtitle="Erledigt, in Arbeit, geplant." >}} + {{< card link="../faq" title="FAQ" icon="question-mark-circle" subtitle="Häufige Fragen und Antworten." >}} +{{< /cards >}} + +## Auf einen Blick + +Dossier bündelt den gesamten architektonischen Workflow in Rhino: + +- **Modellieren** mit smarten Bauteilen (Source ↔ Volume Pattern, Wand v1 mit Polyline-Achse) +- **Strukturieren** über Geschosse, Multi-Geschoss-Clipping und eine automatische Layer-Hierarchie +- **Schneiden & Anschauen** mit der vollen Section-Style-API und Schnitt-Perspektive +- **Material- & Symbol-verwalten** projektzentral aus den Project-Settings (PBR-Texturen, 2D+3D-Symbol-Pairs) +- **Auswerten** nach SIA 416 (HNF / NNF / FF / VF) +- **Darstellen** mit Massstab, Display-Modes und Overrides +- **Beschriften** mit Bemassung, Raumstempeln und Rich-Text +- **Ausgeben** als Plan-Set in PDF oder PNG + +## Laufzeit + +DOSSIER läuft als **CPython 3.9** über Rhinos neuen Python-3-Engine. Die UI wird in einem React-WebView-Panel über Rhinos Eto.Forms-Layer eingebettet (inline via `LoadHtml`). + +Lese die [Architektur-Übersicht](architektur) für Details zum Bridge-Pattern und den Sticky-Storage-Konventionen. diff --git a/content/docs/architektur.md b/content/docs/architektur.md new file mode 100644 index 0000000..e70a169 --- /dev/null +++ b/content/docs/architektur.md @@ -0,0 +1,142 @@ +--- +title: Architektur +linkTitle: Architektur +weight: 2 +--- + +DOSSIER ist als **Plugin-Verbund** aufgebaut: jedes Feature lebt in einem eigenen Modul, alle teilen sich ein gemeinsames Bridge-Pattern für die React-↔-Python-Kommunikation. + +## Module-Map + +| Modul | LOC | Rolle | +|----------------------|------:|----------------------------------------------------------------| +| `panel_base.py` | 697 | Fundament: BaseBridge, WebView-IO, Panel-Registration, Icons | +| `rhinopanel.py` | 798 | EBENEN — Zeichnungsebenen, Layer-Hierarchie, Presets | +| `elemente.py` | 7'244 | ELEMENTE — Wände, Decken, Öffnungen, Treppen, Tragwerk, Räume | +| `gestaltung.py` | 1'635 | GESTALTUNG — Selektions-Attribute (Farbe, Lineweight, Hatch) | +| `oberleiste.py` | 981 | OBERLEISTE — Top-Bar, Display, Massstab, Snaps, Settings | +| `massstab.py` | 1'096 | MASSSTAB — Viewport 1:N, Auto-DPI, PlotWeight | +| `overrides.py` | 797 | Engine — regelbasierte Overrides (Bedingung → Aktion) | +| `overrides_panel.py` | 226 | UI für Overrides-Engine | +| `ausschnitte.py` | 708 | AUSSCHNITTE — Viewport-Snapshots (Kamera + Display + Layer) | +| `dimensionen.py` | 613 | DIMENSIONEN — Bemassung (Wand-Dicken, Geschoss-Höhen, …) | +| `layouts.py` | 749 | LAYOUTS — Plan-Editor, Titelblock, PDF-Export | +| `werkzeuge.py` | 58 | WERKZEUGE — Quick-Tools (Batch) | +| `layer_builder.py` | 436 | Helper — Ebenen-Hierarchie aufbauen, Sublayer-Sync | +| `startup.py` | 136 | Init — liest `dossier.project.json`, lädt Module selektiv | + +## Tragende Patterns + +### Bridge-Pattern (Pflicht für jedes Panel) + +```python +class MyBridge(panel_base.BaseBridge): + def __init__(self): + panel_base.BaseBridge.__init__(self, "mymodule") + + def _on_ready(self): + self.send("STATE_SYNC", {...}) # WebView fertig geladen + + def handle(self, data): + t = data.get("type") + if t == "ACTION": self._do_action() + +def _bridge_factory(): + b = MyBridge() + _install_listeners(b) # Rhino-Events registrieren + return b + +panel_base.register_and_open( + "mymodule", "MY PANEL", PANEL_GUID_STR, + _bridge_factory, + icon_spec=("foundation", "#5fa896"), # Material-Icon + Petrol + min_size=(400, 300), +) +``` + +### React ↔ Python Kommunikation + +- **React → Python**: `document.title = "RHINOMSG::{json}"` — gepollt im Idle-Handler +- **Python → React**: `bridge.send(type, payload)` → `webview.ExecuteScript("window.onRhinoMessage(…)")` +- **Chunking**: Messages > 200 KB werden in `panel_base.handle_raw` automatisch gesplittet und reassembliert. Subklassen kümmern sich nicht drum. + +### Source ↔ Volume Pattern + +Jedes Smart-Element hat: + +1. eine **Source-Geometrie** (Achse / Outline / Punkt) — vom User editierbar +2. ein generiertes **Volume** (Brep) — automatisch regeneriert bei Source-Änderungen + +Beispiel Wand: Source = Achs-Linie, Volume = Brep mit Dicke × Höhe. + +### Sticky-Storage (Cross-Module-State) + +Konventionen für `sc.sticky`-Keys: + +- `"{modul}_bridge"` — Bridge-Instanz +- `"{modul}_listeners"` — Bool-Flag: Listener bereits registriert? +- `"_dossier_*"` — globale States (z.B. `_dossier_joints_cache`) +- `"{modul}_*_cache"` — Modul-Cache + +### Listener-Hookup (Idempotent) + +```python +def _install_listeners(bridge): + flag = "mymodule_listeners" + sc.sticky["mymodule_bridge"] = bridge + if sc.sticky.get(flag): return # Schon registriert + Rhino.RhinoApp.Idle += _on_idle + Rhino.RhinoDoc.ActiveDocumentChanged += _on_view_change + sc.sticky[flag] = True +``` + +## Datenhaltung + +- **Geschosse** in `doc.Strings["dossier_ebenen"]` als JSON +- **Smart-Elemente** als Rhino-Objekte mit UserStrings — `dossier_element_id`, `dossier_element_type`, … +- **Section-Styles** über `Rhino.DocObjects.SectionStyle()` + `layer.SetCustomSectionStyle()` +- **Settings**: `~/Library/Application Support/ch.gabrielevarano.Dossier/dossier_settings.json` + +Eine `.3dm`-Datei bleibt eine Datei — keine externen Datenbanken. + +## Layer-Hierarchie + +```text +10_GRUNDRISSE + └── EG + ├── 20_WAENDE + ├── 30_DECKEN + ├── 31_DAECHER + └── 40_TREPPEN + └── 1OG (gleiche Sublayer) +20_SCHNITTE +30_ANSICHTEN +00_RASTER · 01_VERMESSUNG · 40_SITUATION · 90_REFERENZEN · 99_KONSTRUKTION +``` + +## Cross-Module-Pfade + +| Sender → Empfänger | Trigger | Effekt | +|-----------------------------|-------------------------------|---------------------------------------------------------| +| `rhinopanel` → `elemente` | Apply von Ebenen-Struktur | `elemente_bridge._regenerate_all()` regeneriert Wände/Decken | +| `elemente` → `rhinopanel` | Wand/Decken-Delete | `ebenen_bridge_ref._send_state()` | +| `oberleiste` → `overrides` | Preset-Auswahl in Topbar | `overrides_bridge._send_state()` | +| `massstab` ↔ `ausschnitte` | Viewport-/Zoom-Wechsel | Bi-direktional Skala lesen / setzen | +| `gestaltung` ↔ `rhinopanel` | Hatch-Pattern auf Selektion | Pattern + Scale + Rotation-Signatur vergleichen | + +## Konventionen + +- **Python-Identifier ohne Umlaute** — `ue/oe/ae` statt `ü/ö/ä` in Code-Bezeichnern, Layer-Codes, UserString-Keys. UI-Strings dürfen Umlaute. +- **`LoadHtml`-inline** statt `file://`-URL — Rhinos WKWebView blockiert sonst ` + diff --git a/themes/hextra/docs/layouts/_shortcodes/new-feature.html b/themes/hextra/docs/layouts/_shortcodes/new-feature.html new file mode 100644 index 0000000..fadf7ec --- /dev/null +++ b/themes/hextra/docs/layouts/_shortcodes/new-feature.html @@ -0,0 +1,11 @@ +{{- $version := .Get "version" | default "" -}} +{{- $icon := .Get "icon" | default "" -}} +{{- $defaultLink := cond (eq $version "") "https://github.com/imfing/hextra/tree/main" (printf "https://github.com/imfing/hextra/releases/tag/%s" $version) -}} +{{- $link := .Get "link" | default $defaultLink -}} +{{- $content := cond (eq $version "") "New in main branch" (printf "New in %s" $version) -}} + +
+ + {{- partial "shortcodes/badge" (dict "content" $content "border" true "icon" $icon) -}} + +
diff --git a/themes/hextra/docs/static/casts/demo.cast b/themes/hextra/docs/static/casts/demo.cast new file mode 100644 index 0000000..1978901 --- /dev/null +++ b/themes/hextra/docs/static/casts/demo.cast @@ -0,0 +1,19 @@ +{"version": 2, "width": 80, "height": 24, "timestamp": 1640995200, "env": {"TERM": "xterm-256color", "SHELL": "/bin/bash"}, "title": "Demo Terminal Session"} +[0.0, "o", "Welcome to the demo!\r\n"] +[1.0, "o", "$ "] +[2.0, "o", "ls -la\r\n"] +[2.5, "o", "total 8\r\n"] +[2.6, "o", "drwxr-xr-x 2 user user 4096 Jan 1 12:00 .\r\n"] +[2.7, "o", "drwxr-xr-x 20 user user 4096 Jan 1 12:00 ..\r\n"] +[2.8, "o", "-rw-r--r-- 1 user user 0 Jan 1 12:00 demo.txt\r\n"] +[3.0, "o", "$ "] +[4.0, "o", "cat demo.txt\r\n"] +[4.5, "o", "Hello, this is a demo file!\r\n"] +[5.0, "o", "$ "] +[6.0, "o", "echo 'This is a test command'\r\n"] +[6.5, "o", "This is a test command\r\n"] +[7.0, "o", "$ "] +[8.0, "o", "pwd\r\n"] +[8.5, "o", "/home/user/demo\r\n"] +[9.0, "o", "$ "] +[10.0, "o", "exit\r\n"] diff --git a/themes/hextra/docs/static/favicon-dark.svg b/themes/hextra/docs/static/favicon-dark.svg new file mode 100644 index 0000000..3b49e35 --- /dev/null +++ b/themes/hextra/docs/static/favicon-dark.svg @@ -0,0 +1,13 @@ + + + diff --git a/themes/hextra/docs/static/images/card-image-unprocessed.jpg b/themes/hextra/docs/static/images/card-image-unprocessed.jpg new file mode 100644 index 0000000..6b46052 Binary files /dev/null and b/themes/hextra/docs/static/images/card-image-unprocessed.jpg differ diff --git a/themes/hextra/docs/static/images/hextra-doc.webp b/themes/hextra/docs/static/images/hextra-doc.webp new file mode 100644 index 0000000..2ef0c1d Binary files /dev/null and b/themes/hextra/docs/static/images/hextra-doc.webp differ diff --git a/themes/hextra/docs/static/images/hextra-markdown.webp b/themes/hextra/docs/static/images/hextra-markdown.webp new file mode 100644 index 0000000..1c322ba Binary files /dev/null and b/themes/hextra/docs/static/images/hextra-markdown.webp differ diff --git a/themes/hextra/docs/static/images/hextra-search.webp b/themes/hextra/docs/static/images/hextra-search.webp new file mode 100644 index 0000000..8e7ae1d Binary files /dev/null and b/themes/hextra/docs/static/images/hextra-search.webp differ diff --git a/themes/hextra/examples/README.md b/themes/hextra/examples/README.md new file mode 100644 index 0000000..3ae0b4f --- /dev/null +++ b/themes/hextra/examples/README.md @@ -0,0 +1,13 @@ +# Examples + +This folder will host boilerplate example sites demonstrating Hextra for different use-cases. + +Planned examples: +- docs/ (minimal docs boilerplate) +- blog/ (blog-centric setup) +- portfolio/ (personal site/portfolio) + +Each example will include: +- A ready-to-run Hugo site +- Recommended config and params +- Customizations and best practices diff --git a/themes/hextra/go.mod b/themes/hextra/go.mod new file mode 100644 index 0000000..6209398 --- /dev/null +++ b/themes/hextra/go.mod @@ -0,0 +1,3 @@ +module github.com/imfing/hextra + +go 1.21 diff --git a/themes/hextra/hugo.toml b/themes/hextra/hugo.toml new file mode 100644 index 0000000..42aa463 --- /dev/null +++ b/themes/hextra/hugo.toml @@ -0,0 +1,16 @@ +[module] + [module.hugoVersion] + min = '0.146.0' + +[outputFormats] + [outputFormats.llms] + name= 'llms' + baseName = 'llms' + mediaType = 'text/plain' + isPlainText = true + [outputFormats.markdown] + name = 'markdown' + baseName = 'index' + mediaType = 'text/markdown' + isPlainText = true + ugly = true diff --git a/themes/hextra/i18n/cs.yaml b/themes/hextra/i18n/cs.yaml new file mode 100644 index 0000000..9269882 --- /dev/null +++ b/themes/hextra/i18n/cs.yaml @@ -0,0 +1,43 @@ +# Accessibility labels (screen reader only) +closeBanner: "Zavřít banner" +menu: "Menu" +mermaidDiagram: "Diagram" +pdfViewer: "Prohlížeč PDF" +permalinkLabel: "Trvalý odkaz na tuto sekci" +playbackTime: "Doba přehrávání" +searchResults: "Výsledky vyhledávání" +skipToContent: "Přejít na obsah" +tableOfContents: "Obsah" +terminalRecording: "Záznam terminálu" +togglePageContextMenu: "Přepnout kontextové menu stránky" +toggleSection: "Přepnout sekci" + +# Accessibility live-region/status text +resultsFound: "%d výsledků nalezeno" + +# User-facing UI text +archives: "Archiv" +backToTop: "Zpět nahoru" +changeLanguage: "Změnit jazyk" +changeTheme: "Změnit vzhled" +copy: "Kopírovat" +copied: "Zkopírováno!" +copyAsMarkdown: "Kopírovat jako Markdown" +copyPage: "Kopírovat stránku" +copyCode: "Zkopírovat kód" +copyright: "© 2026 Hextra Project." +dark: "Tmavý" +editThisPage: "Upravit tuto stránku na GitHubu →" +lastUpdated: "Naposledy změněno" +light: "Světlý" +next: "Další" +noResultsFound: "Nebylo nic nalezeno." +onThisPage: "Na této stránce" +more: "Více" +poweredBy: "Powered by Hextra" +previous: "Předchozí" +readMore: "Přečíst víc →" +searchPlaceholder: "Hledat..." +system: "Systém" +tags: "Tagy" +viewAsMarkdown: "Zobrazit jako Markdown" diff --git a/themes/hextra/i18n/de.yaml b/themes/hextra/i18n/de.yaml new file mode 100644 index 0000000..ed0cf4d --- /dev/null +++ b/themes/hextra/i18n/de.yaml @@ -0,0 +1,43 @@ +# Accessibility labels (screen reader only) +closeBanner: "Banner schließen" +menu: "Menü" +mermaidDiagram: "Diagramm" +pdfViewer: "PDF-Betrachter" +permalinkLabel: "Permalink für diesen Abschnitt" +playbackTime: "Wiedergabezeit" +searchResults: "Suchergebnisse" +skipToContent: "Zum Inhalt springen" +tableOfContents: "Inhaltsverzeichnis" +terminalRecording: "Terminalaufzeichnung" +togglePageContextMenu: "Seitenkontextmenü umschalten" +toggleSection: "Abschnitt umschalten" + +# Accessibility live-region/status text +resultsFound: "%d Ergebnisse gefunden" + +# User-facing UI text +archives: "Archiv" +backToTop: "Nach oben" +changeLanguage: "Sprache ändern" +changeTheme: "Darstellung ändern" +copy: "Kopieren" +copied: "Kopiert!" +copyAsMarkdown: "Als Markdown kopieren" +copyPage: "Seite kopieren" +copyCode: "Code kopieren" +copyright: "© 2026 Hextra Project." +dark: "Dunkel" +editThisPage: "Diese Seite auf GitHub bearbeiten →" +lastUpdated: "Zuletzt aktualisiert am" +light: "Hell" +next: "Weiter" +noResultsFound: "Keine Ergebnisse gefunden." +onThisPage: "Auf dieser Seite" +more: "Mehr" +poweredBy: "Unterstützt durch Hextra" +previous: "Zurück" +readMore: "Mehr lesen →" +searchPlaceholder: "Suchen..." +system: "System" +tags: "Schlagwörter" +viewAsMarkdown: "Als Markdown anzeigen" diff --git a/themes/hextra/i18n/en.yaml b/themes/hextra/i18n/en.yaml new file mode 100644 index 0000000..c06ae37 --- /dev/null +++ b/themes/hextra/i18n/en.yaml @@ -0,0 +1,43 @@ +# Accessibility labels (screen reader only) +closeBanner: "Close banner" +menu: "Menu" +mermaidDiagram: "Diagram" +pdfViewer: "PDF viewer" +permalinkLabel: "Permalink for this section" +playbackTime: "Playback time" +searchResults: "Search results" +skipToContent: "Skip to content" +tableOfContents: "Table of contents" +terminalRecording: "Terminal recording" +togglePageContextMenu: "Toggle page context menu" +toggleSection: "Toggle section" + +# Accessibility live-region/status text +resultsFound: "%d results found" + +# User-facing UI text +archives: "Archives" +backToTop: "Scroll to top" +changeLanguage: "Change language" +changeTheme: "Change theme" +copy: "Copy" +copied: "Copied!" +copyAsMarkdown: "Copy as Markdown" +copyPage: "Copy Page" +copyCode: "Copy code" +copyright: "© 2026 Hextra Project." +dark: "Dark" +editThisPage: "Edit this page on GitHub →" +lastUpdated: "Last updated on" +light: "Light" +next: "Next" +noResultsFound: "No results found." +onThisPage: "On this page" +more: "More" +poweredBy: "Powered by Hextra" +previous: "Prev" +readMore: "Read more →" +searchPlaceholder: "Search..." +system: "System" +tags: "Tags" +viewAsMarkdown: "View as Markdown" diff --git a/themes/hextra/i18n/es.yaml b/themes/hextra/i18n/es.yaml new file mode 100644 index 0000000..62ab963 --- /dev/null +++ b/themes/hextra/i18n/es.yaml @@ -0,0 +1,43 @@ +# Accessibility labels (screen reader only) +closeBanner: "Cerrar banner" +menu: "Menú" +mermaidDiagram: "Diagrama" +pdfViewer: "Visor de PDF" +permalinkLabel: "Enlace permanente a esta sección" +playbackTime: "Tiempo de reproducción" +searchResults: "Resultados de búsqueda" +skipToContent: "Saltar al contenido" +tableOfContents: "Tabla de contenidos" +terminalRecording: "Grabación de terminal" +togglePageContextMenu: "Alternar menú contextual de la página" +toggleSection: "Alternar sección" + +# Accessibility live-region/status text +resultsFound: "%d resultados encontrados" + +# User-facing UI text +archives: "Archivos" +backToTop: "Subir al inicio" +changeLanguage: "Cambiar idioma" +changeTheme: "Cambiar tema" +copy: "Copiar" +copied: "¡Copiado!" +copyAsMarkdown: "Copiar como Markdown" +copyPage: "Copiar página" +copyCode: "Copiar código" +copyright: "© 2026 Proyecto Hextra." +dark: "Oscuro" +editThisPage: "Edita esta página en GitHub →" +lastUpdated: "Última actualización" +light: "Claro" +next: "Siguiente" +noResultsFound: "No hubo resultados." +onThisPage: "En esta página" +more: "Más" +poweredBy: "Con tecnología de Hextra" +previous: "Anterior" +readMore: "Leer más →" +searchPlaceholder: "Buscar..." +system: "Sistema" +tags: "Etiquetas" +viewAsMarkdown: "Ver como Markdown" diff --git a/themes/hextra/i18n/fa.yaml b/themes/hextra/i18n/fa.yaml new file mode 100644 index 0000000..95d71ea --- /dev/null +++ b/themes/hextra/i18n/fa.yaml @@ -0,0 +1,43 @@ +# Accessibility labels (screen reader only) +closeBanner: "بستن بنر" +menu: "منو" +mermaidDiagram: "نمودار" +pdfViewer: "نمایشگر PDF" +permalinkLabel: "پیوند ثابت به این بخش" +playbackTime: "زمان پخش" +searchResults: "نتایج جستجو" +skipToContent: "رفتن به محتوا" +tableOfContents: "فهرست مطالب" +terminalRecording: "ضبط ترمینال" +togglePageContextMenu: "تغییر وضعیت منوی زمینه صفحه" +toggleSection: "تغییر وضعیت بخش" + +# Accessibility live-region/status text +resultsFound: "%d نتیجه یافت شد" + +# User-facing UI text +archives: "آرشیو" +backToTop: "به بالا بروید" +changeLanguage: "تغییر زبان" +changeTheme: "تغییر تم" +copy: "کپی" +copied: "کپی شد!" +copyAsMarkdown: "کپی به عنوان Markdown" +copyPage: "کپی صفحه" +copyCode: "کپی کد" +copyright: "© ۲۰۲۴ پروژه هگزترا." +dark: "تیره" +editThisPage: "ویرایش این صفحه در گیت‌هاب ←" +lastUpdated: "آخرین به‌روزرسانی در" +light: "روشن" +next: "بعدی" +noResultsFound: "هیچ نتیجه‌ای پیدا نشد." +onThisPage: "در این صفحه" +more: "بیشتر" +poweredBy: "طراحی شده توسط هگزترا" +previous: "قبلی" +readMore: "ادامه مطلب ←" +searchPlaceholder: "جستجو..." +system: "سیستم" +tags: "برچسب‌ها" +viewAsMarkdown: "مشاهده به عنوان Markdown" diff --git a/themes/hextra/i18n/fr.yaml b/themes/hextra/i18n/fr.yaml new file mode 100644 index 0000000..1c97ade --- /dev/null +++ b/themes/hextra/i18n/fr.yaml @@ -0,0 +1,43 @@ +# Accessibility labels (screen reader only) +closeBanner: "Fermer la bannière" +menu: "Menu" +mermaidDiagram: "Diagramme" +pdfViewer: "Lecteur PDF" +permalinkLabel: "Lien permanent vers cette section" +playbackTime: "Temps de lecture" +searchResults: "Résultats de recherche" +skipToContent: "Aller au contenu" +tableOfContents: "Table des matières" +terminalRecording: "Enregistrement du terminal" +togglePageContextMenu: "Basculer le menu contextuel de la page" +toggleSection: "Basculer la section" + +# Accessibility live-region/status text +resultsFound: "%d résultats trouvés" + +# User-facing UI text +archives: "Archives" +backToTop: "Revenir en haut" +changeLanguage: "Changer la langue" +changeTheme: "Thème d'affichage" +copy: "Copier" +copied: "Copié !" +copyAsMarkdown: "Copier en Markdown" +copyPage: "Copier la page" +copyCode: "Copier le code" +copyright: "© 2026 Hextra Project." +dark: "Sombre" +editThisPage: "Modifier cette page sur GitHub →" +lastUpdated: "Dernière modification" +light: "Clair" +next: "Suivant" +noResultsFound: "Pas de résultats trouvés" +onThisPage: "Sur cette page" +more: "Plus" +poweredBy: "Propulsé par Hextra" +previous: "Précédent" +readMore: "Lire plus →" +searchPlaceholder: "Rechercher..." +system: "Système" +tags: "Étiquettes" +viewAsMarkdown: "Voir en Markdown" diff --git a/themes/hextra/i18n/he.yaml b/themes/hextra/i18n/he.yaml new file mode 100644 index 0000000..c149c87 --- /dev/null +++ b/themes/hextra/i18n/he.yaml @@ -0,0 +1,43 @@ +# Accessibility labels (screen reader only) +closeBanner: "סגור באנר" +menu: "תפריט" +mermaidDiagram: "תרשים" +pdfViewer: "מציג PDF" +permalinkLabel: "קישור קבוע לפסקה זו" +playbackTime: "זמן ניגון" +searchResults: "תוצאות חיפוש" +skipToContent: "דלג לתוכן" +tableOfContents: "תוכן עניינים" +terminalRecording: "הקלטת מסוף" +togglePageContextMenu: "הצג/הסתר תפריט ההקשר של הדף" +toggleSection: "הצג/הסתר מקטע" + +# Accessibility live-region/status text +resultsFound: "%d תוצאות נמצאו" + +# User-facing UI text +archives: "ארכיון" +backToTop: "גלול למעלה" +changeLanguage: "שנה שפה" +changeTheme: "שנה ערכת צבעים" +copy: "העתק" +copied: "הועתק!" +copyAsMarkdown: "העתק כ-Markdown" +copyPage: "העתק עמוד" +copyCode: "העתק קוד" +copyright: "© 2026 פרוייקט Hextra" +dark: "כהה" +editThisPage: "← ערוך עמוד זה בגיטהאב" +lastUpdated: "עודכן לאחרונה ב" +light: "בהיר" +next: "הבא" +noResultsFound: "לא נמצאו תוצאות." +onThisPage: "בעמוד זה" +more: "עוד" +poweredBy: "Hextra מופעל על-ידי" +previous: "הקודם" +readMore: "← קרא עוד" +searchPlaceholder: "חיפוש..." +system: "מערכת" +tags: "תגיות" +viewAsMarkdown: "הצג כ-Markdown" diff --git a/themes/hextra/i18n/it.yaml b/themes/hextra/i18n/it.yaml new file mode 100644 index 0000000..d56d053 --- /dev/null +++ b/themes/hextra/i18n/it.yaml @@ -0,0 +1,43 @@ +# Accessibility labels (screen reader only) +closeBanner: "Chiudi banner" +menu: "Menu" +mermaidDiagram: "Diagramma" +pdfViewer: "Visualizzatore PDF" +permalinkLabel: "Link permanente a questa sezione" +playbackTime: "Tempo di riproduzione" +searchResults: "Risultati della ricerca" +skipToContent: "Vai al contenuto" +tableOfContents: "Indice dei contenuti" +terminalRecording: "Registrazione del terminale" +togglePageContextMenu: "Attiva/disattiva il menu contestuale della pagina" +toggleSection: "Attiva/disattiva sezione" + +# Accessibility live-region/status text +resultsFound: "%d risultati trovati" + +# User-facing UI text +archives: "Archivi" +backToTop: "Torna all'inizio" +changeLanguage: "Cambia lingua" +changeTheme: "Cambia tema" +copy: "Copia" +copied: "Copiato!" +copyAsMarkdown: "Copia come Markdown" +copyPage: "Copia pagina" +copyCode: "Copia codice" +copyright: "© 2026 Hextra Project." +dark: "Scuro" +editThisPage: "Modifica questa pagina su GitHub →" +lastUpdated: "Ultimo aggiornamento il" +light: "Chiaro" +next: "Successivo" +noResultsFound: "Nessun risultato trovato." +onThisPage: "In questa pagina" +more: "Altro" +poweredBy: "Realizzato da Hextra" +previous: "Precedente" +readMore: "Per saperne di più →" +searchPlaceholder: "Cerca..." +system: "Sistema" +tags: "Etichette" +viewAsMarkdown: "Visualizza come Markdown" diff --git a/themes/hextra/i18n/ja.yaml b/themes/hextra/i18n/ja.yaml new file mode 100644 index 0000000..a67f97e --- /dev/null +++ b/themes/hextra/i18n/ja.yaml @@ -0,0 +1,43 @@ +# Accessibility labels (screen reader only) +closeBanner: "バナーを閉じる" +menu: "メニュー" +mermaidDiagram: "図" +pdfViewer: "PDFビューアー" +permalinkLabel: "このセクションへのパーマリンク" +playbackTime: "再生時間" +searchResults: "検索結果" +skipToContent: "コンテンツにスキップ" +tableOfContents: "目次" +terminalRecording: "ターミナル録画" +togglePageContextMenu: "ページコンテキストメニューの切り替え" +toggleSection: "セクションの切り替え" + +# Accessibility live-region/status text +resultsFound: "%d件の結果が見つかりました" + +# User-facing UI text +archives: "アーカイブ" +backToTop: "トップにスクロール" +changeLanguage: "言語を変更" +changeTheme: "テーマを変更" +copy: "コピー" +copied: "コピーしました!" +copyAsMarkdown: "Markdownとしてコピー" +copyPage: "ページをコピー" +copyCode: "コードをコピー" +copyright: "© 2026 Hextra プロジェクト。" +dark: "ダーク" +editThisPage: "このページをGitHubで編集 →" +lastUpdated: "最終更新日" +light: "ライト" +next: "次へ" +noResultsFound: "結果が見つかりませんでした。" +onThisPage: "このページの内容" +more: "その他" +poweredBy: "提供元 Hextra" +previous: "前へ" +readMore: "もっと読む →" +searchPlaceholder: "検索..." +system: "システム" +tags: "タグ" +viewAsMarkdown: "Markdownとして表示" diff --git a/themes/hextra/i18n/ko.yaml b/themes/hextra/i18n/ko.yaml new file mode 100644 index 0000000..273d949 --- /dev/null +++ b/themes/hextra/i18n/ko.yaml @@ -0,0 +1,43 @@ +# Accessibility labels (screen reader only) +closeBanner: "배너 닫기" +menu: "메뉴" +mermaidDiagram: "다이어그램" +pdfViewer: "PDF 뷰어" +permalinkLabel: "이 섹션에 대한 고유 링크" +playbackTime: "재생 시간" +searchResults: "검색 결과" +skipToContent: "본문으로 건너뛰기" +tableOfContents: "목차" +terminalRecording: "터미널 녹화" +togglePageContextMenu: "페이지 컨텍스트 메뉴 전환" +toggleSection: "섹션 전환" + +# Accessibility live-region/status text +resultsFound: "%d개의 결과를 찾았습니다" + +# User-facing UI text +archives: "아카이브" +backToTop: "맨위로 스크롤" +changeLanguage: "언어 변경" +changeTheme: "테마 변경" +copy: "복사" +copied: "복사됨!" +copyAsMarkdown: "Markdown으로 복사" +copyPage: "페이지 복사" +copyCode: "코드 복사" +copyright: "© 2026 Hextra Project." +dark: "어두운 테마" +editThisPage: "GitHub에서 편집하기 →" +lastUpdated: "마지막 수정 일자" +light: "밝은 테마" +next: "다음" +noResultsFound: "결과 없음" +onThisPage: "페이지 목차" +more: "더보기" +poweredBy: "Hextra로 제작됨" +previous: "이전" +readMore: "더보기 →" +searchPlaceholder: "검색..." +system: "시스템" +tags: "태그" +viewAsMarkdown: "Markdown으로 보기" diff --git a/themes/hextra/i18n/nb.yaml b/themes/hextra/i18n/nb.yaml new file mode 100644 index 0000000..23ed637 --- /dev/null +++ b/themes/hextra/i18n/nb.yaml @@ -0,0 +1,43 @@ +# Accessibility labels (screen reader only) +closeBanner: "Lukk banner" +menu: "Meny" +mermaidDiagram: "Diagram" +pdfViewer: "PDF-visning" +permalinkLabel: "Permanent lenke til denne seksjonen" +playbackTime: "Avspillingstid" +searchResults: "Søkeresultater" +skipToContent: "Hopp til innhold" +tableOfContents: "Innholdsfortegnelse" +terminalRecording: "Terminalopptak" +togglePageContextMenu: "Veksle sidekontekstmeny" +toggleSection: "Veksle seksjon" + +# Accessibility live-region/status text +resultsFound: "%d resultater funnet" + +# User-facing UI text +archives: "Arkiv" +backToTop: "Gå til toppen" +changeLanguage: "Endre språk" +changeTheme: "Endre tema" +copy: "Kopier" +copied: "Kopiert!" +copyAsMarkdown: "Kopier som Markdown" +copyPage: "Kopier side" +copyCode: "Kopier kode" +copyright: "© 2026 Hextra-prosjektet." +dark: "Mørk" +editThisPage: "Rediger denne siden på GitHub →" +lastUpdated: "Sist oppdatert" +light: "Lys" +next: "Neste" +noResultsFound: "Fant ingen treff." +onThisPage: "På denne siden" +more: "Mer" +poweredBy: "Powered by Hextra" +previous: "Forrige" +readMore: "Les mer →" +searchPlaceholder: "Søk..." +system: "System" +tags: "Stikkord" +viewAsMarkdown: "Vis som Markdown" diff --git a/themes/hextra/i18n/nl.yaml b/themes/hextra/i18n/nl.yaml new file mode 100644 index 0000000..cb7764c --- /dev/null +++ b/themes/hextra/i18n/nl.yaml @@ -0,0 +1,43 @@ +# Accessibility labels (screen reader only) +closeBanner: "Banner sluiten" +menu: "Menu" +mermaidDiagram: "Diagram" +pdfViewer: "PDF-weergave" +permalinkLabel: "Permanente link naar deze sectie" +playbackTime: "Afspeeltijd" +searchResults: "Zoekresultaten" +skipToContent: "Ga naar inhoud" +tableOfContents: "Inhoudsopgave" +terminalRecording: "Terminalopname" +togglePageContextMenu: "Paginacontextmenu wisselen" +toggleSection: "Sectie wisselen" + +# Accessibility live-region/status text +resultsFound: "%d resultaten gevonden" + +# User-facing UI text +archives: "Archief" +backToTop: "Terug naar boven" +changeLanguage: "Taal veranderen" +changeTheme: "Thema aanpassen" +copy: "Kopiëren" +copied: "Gekopieerd!" +copyAsMarkdown: "Kopieer als Markdown" +copyPage: "Pagina kopiëren" +copyCode: "Kopieer code" +copyright: "© 2026 Hextra Project." +dark: "Donker" +editThisPage: "Bewerk deze pagina op GitHub →" +lastUpdated: "Laatst bijgewerkt op" +light: "Licht" +next: "Volgende" +noResultsFound: "Geen resultaten gevonden." +onThisPage: "Op deze pagina" +more: "Meer" +poweredBy: "Mogelijk gemaakt door Hextra" +previous: "Vorige" +readMore: "Lees meer →" +searchPlaceholder: "Zoeken..." +system: "Systeem" +tags: "Labels" +viewAsMarkdown: "Bekijk als Markdown" diff --git a/themes/hextra/i18n/nn.yaml b/themes/hextra/i18n/nn.yaml new file mode 100644 index 0000000..12076cd --- /dev/null +++ b/themes/hextra/i18n/nn.yaml @@ -0,0 +1,43 @@ +# Accessibility labels (screen reader only) +closeBanner: "Lukk banner" +menu: "Meny" +mermaidDiagram: "Diagram" +pdfViewer: "PDF-vising" +permalinkLabel: "Permanent lenkje til denne seksjonen" +playbackTime: "Avspelingstid" +searchResults: "Søkjeresultat" +skipToContent: "Hopp til innhald" +tableOfContents: "Innhaldsliste" +terminalRecording: "Terminalopptak" +togglePageContextMenu: "Veksle sidekontekstmeny" +toggleSection: "Veksle seksjon" + +# Accessibility live-region/status text +resultsFound: "%d resultat funne" + +# User-facing UI text +archives: "Arkiv" +backToTop: "Gå til toppen" +changeLanguage: "Endre språk" +changeTheme: "Endre tema" +copy: "Kopier" +copied: "Kopiert!" +copyAsMarkdown: "Kopier som Markdown" +copyPage: "Kopier side" +copyCode: "Kopier kode" +copyright: "© 2026 Hextra-prosjektet." +dark: "Mørk" +editThisPage: "Rediger denne sida på GitHub →" +lastUpdated: "Sist oppdatert" +light: "Ljos" +next: "Neste" +noResultsFound: "Fann ingen treff." +onThisPage: "På denne sida" +more: "Meir" +poweredBy: "Powered by Hextra" +previous: "Førre" +readMore: "Les meir →" +searchPlaceholder: "Søk..." +system: "System" +tags: "Stikkord" +viewAsMarkdown: "Vis som Markdown" diff --git a/themes/hextra/i18n/pt.yaml b/themes/hextra/i18n/pt.yaml new file mode 100644 index 0000000..5ad9212 --- /dev/null +++ b/themes/hextra/i18n/pt.yaml @@ -0,0 +1,43 @@ +# Accessibility labels (screen reader only) +closeBanner: "Fechar banner" +menu: "Menu" +mermaidDiagram: "Diagrama" +pdfViewer: "Visualizador de PDF" +permalinkLabel: "Link permanente para esta secção" +playbackTime: "Tempo de reprodução" +searchResults: "Resultados da pesquisa" +skipToContent: "Saltar para o conteúdo" +tableOfContents: "Índice" +terminalRecording: "Gravação de terminal" +togglePageContextMenu: "Alternar menu de contexto da página" +toggleSection: "Alternar secção" + +# Accessibility live-region/status text +resultsFound: "%d resultados encontrados" + +# User-facing UI text +archives: "Arquivo" +backToTop: "Voltar ao topo" +changeLanguage: "Mudar a língua" +changeTheme: "Mudar tema" +copy: "Copiar" +copied: "Copiado!" +copyAsMarkdown: "Copiar como Markdown" +copyPage: "Copiar página" +copyCode: "Copiar código" +copyright: "© 2026 Projecto Hextra." +dark: "Escuro" +editThisPage: "Edita esta página no GitHub →" +lastUpdated: "Última modificação" +light: "Claro" +next: "Seguinte" +noResultsFound: "Nenhum resultado encontrado" +onThisPage: "Nesta página" +more: "Mais" +poweredBy: "Com a tecnologia de Hextra" +previous: "Anterior" +readMore: "Ler mais →" +searchPlaceholder: "Procurar..." +system: "Sistema" +tags: "Etiquetas" +viewAsMarkdown: "Ver como Markdown" diff --git a/themes/hextra/i18n/ro.yaml b/themes/hextra/i18n/ro.yaml new file mode 100644 index 0000000..d5c796c --- /dev/null +++ b/themes/hextra/i18n/ro.yaml @@ -0,0 +1,43 @@ +# Accessibility labels (screen reader only) +closeBanner: "Închide bannerul" +menu: "Meniu" +mermaidDiagram: "Diagramă" +pdfViewer: "Vizualizator PDF" +permalinkLabel: "Link permanent către această secțiune" +playbackTime: "Timp de redare" +searchResults: "Rezultatele căutării" +skipToContent: "Salt la conținut" +tableOfContents: "Cuprins" +terminalRecording: "Înregistrare terminal" +togglePageContextMenu: "Comutare meniu contextual pagină" +toggleSection: "Comutare secțiune" + +# Accessibility live-region/status text +resultsFound: "%d rezultate găsite" + +# User-facing UI text +archives: "Arhivă" +backToTop: "Înapoi sus" +changeLanguage: "Schimbă limba" +changeTheme: "Schimbă tema" +copy: "Copiază" +copied: "Copiat!" +copyAsMarkdown: "Copiază ca Markdown" +copyPage: "Copiază pagina" +copyCode: "Copiază codul" +copyright: "© 2026 Hextra Project." +dark: "Întuneric" +editThisPage: "Editați această pagină pe GitHub ←" +lastUpdated: "Ultima actualizare la" +light: "Lumină" +next: "Următor" +noResultsFound: "Nici un rezultat găsit." +onThisPage: "Pe această pagină" +more: "Mai mult" +poweredBy: "Susținut de Hextra" +previous: "Anterior" +readMore: "Citește mai mult ←" +searchPlaceholder: "Caută..." +system: "Sistem" +tags: "Etichete" +viewAsMarkdown: "Vizualizează ca Markdown" diff --git a/themes/hextra/i18n/ru.yaml b/themes/hextra/i18n/ru.yaml new file mode 100644 index 0000000..7572fd0 --- /dev/null +++ b/themes/hextra/i18n/ru.yaml @@ -0,0 +1,43 @@ +# Accessibility labels (screen reader only) +closeBanner: "Закрыть баннер" +menu: "Меню" +mermaidDiagram: "Диаграмма" +pdfViewer: "Просмотр PDF" +permalinkLabel: "Постоянная ссылка на этот раздел" +playbackTime: "Время воспроизведения" +searchResults: "Результаты поиска" +skipToContent: "Перейти к содержимому" +tableOfContents: "Содержание" +terminalRecording: "Запись терминала" +togglePageContextMenu: "Переключить контекстное меню страницы" +toggleSection: "Переключить раздел" + +# Accessibility live-region/status text +resultsFound: "%d результатов найдено" + +# User-facing UI text +archives: "Архив" +backToTop: "Прокрутить к началу" +changeLanguage: "Изменить язык" +changeTheme: "Изменить тему" +copy: "Копировать" +copied: "Скопировано!" +copyAsMarkdown: "Копировать как Markdown" +copyPage: "Копировать страницу" +copyCode: "Скопировать код" +copyright: "2026 Проект Hextra." +dark: "Темная" +editThisPage: "Отредактировать страницу на GitHub →" +lastUpdated: "Последнее обновление" +light: "Светлая" +next: "Далее" +noResultsFound: "Ничего не найдено." +onThisPage: "На этой странице" +more: "Ещё" +poweredBy: "При поддержке Hextra" +previous: "Назад" +readMore: "Читать далее →" +searchPlaceholder: "Поиск..." +system: "Система" +tags: "Теги" +viewAsMarkdown: "Просмотреть как Markdown" diff --git a/themes/hextra/i18n/sw.yaml b/themes/hextra/i18n/sw.yaml new file mode 100644 index 0000000..100a4dc --- /dev/null +++ b/themes/hextra/i18n/sw.yaml @@ -0,0 +1,43 @@ +# Accessibility labels (screen reader only) +closeBanner: "Funga bango" +menu: "Menyu" +mermaidDiagram: "Mchoro" +pdfViewer: "Kitazamaji cha PDF" +permalinkLabel: "Kiungo cha kudumu kwa sehemu hii" +playbackTime: "Muda wa kucheza" +searchResults: "Matokeo ya utafutaji" +skipToContent: "Ruka hadi maudhui" +tableOfContents: "Jedwali la yaliyomo" +terminalRecording: "Rekodi ya terminal" +togglePageContextMenu: "Badili menyu ya muktadha wa ukurasa" +toggleSection: "Badili sehemu" + +# Accessibility live-region/status text +resultsFound: "Matokeo %d yamepatikana" + +# User-facing UI text +archives: "Kumbukumbu" +backToTop: "Tembeza hadi juu" +changeLanguage: "Badilisha lugha" +changeTheme: "Badilisha mandhari" +copy: "Nakili" +copied: "Imenakiliwa!" +copyAsMarkdown: "Nakili kama Markdown" +copyPage: "Nakili ukurasa" +copyCode: "Nakili msimbo" +copyright: "© 2026 Hextra Project." +dark: "Meusi" +editThisPage: "Hariri ukurasa huu kwenye GitHub →" +lastUpdated: "Ilisasishwa mwisho" +light: "Meupe" +next: "Ifuatayo" +noResultsFound: "Hakuna matokeo yalipopatikana." +onThisPage: "Kwenye ukurasa huu" +more: "Zaidi" +poweredBy: "Inaendeshwa na Hextra" +previous: "Iliyotangulia" +readMore: "Soma zaidi →" +searchPlaceholder: "Tafuta..." +system: "Mfumo" +tags: "Lebo" +viewAsMarkdown: "Tazama kama Markdown" diff --git a/themes/hextra/i18n/uk.yaml b/themes/hextra/i18n/uk.yaml new file mode 100644 index 0000000..178d381 --- /dev/null +++ b/themes/hextra/i18n/uk.yaml @@ -0,0 +1,43 @@ +# Accessibility labels (screen reader only) +closeBanner: "Закрити банер" +menu: "Меню" +mermaidDiagram: "Діаграма" +pdfViewer: "Перегляд PDF" +permalinkLabel: "Постійне посилання на цей розділ" +playbackTime: "Час відтворення" +searchResults: "Результати пошуку" +skipToContent: "Перейти до вмісту" +tableOfContents: "Зміст" +terminalRecording: "Запис терміналу" +togglePageContextMenu: "Перемкнути контекстне меню сторінки" +toggleSection: "Перемкнути розділ" + +# Accessibility live-region/status text +resultsFound: "%d результатів знайдено" + +# User-facing UI text +archives: "Архів" +backToTop: "Прокрутити до початку" +changeLanguage: "Змінити мову" +changeTheme: "Змінити тему" +copy: "Копіювати" +copied: "Скопійовано!" +copyAsMarkdown: "Копіювати як Markdown" +copyPage: "Копіювати сторінку" +copyCode: "Скопіювати код" +copyright: "2026 Проєкт Hextra." +dark: "Темна" +editThisPage: "Редагувати цю сторінку на GitHub →" +lastUpdated: "Востаннє оновлено" +light: "Світла" +next: "Далі" +noResultsFound: "Не знайдено результатів" +onThisPage: "На цій сторінці" +more: "Ще" +poweredBy: "Працює завдяки Hextra" +previous: "Назад" +readMore: "Читати більше →" +searchPlaceholder: "Пошук..." +system: "Система" +tags: "Теги" +viewAsMarkdown: "Переглянути як Markdown" diff --git a/themes/hextra/i18n/vi.yaml b/themes/hextra/i18n/vi.yaml new file mode 100644 index 0000000..45f53df --- /dev/null +++ b/themes/hextra/i18n/vi.yaml @@ -0,0 +1,43 @@ +# Accessibility labels (screen reader only) +closeBanner: "Đóng biểu ngữ" +menu: "Menu" +mermaidDiagram: "Sơ đồ" +pdfViewer: "Trình xem PDF" +permalinkLabel: "Liên kết cố định đến phần này" +playbackTime: "Thời gian phát" +searchResults: "Kết quả tìm kiếm" +skipToContent: "Chuyển đến nội dung" +tableOfContents: "Mục lục" +terminalRecording: "Bản ghi terminal" +togglePageContextMenu: "Bật/tắt menu ngữ cảnh trang" +toggleSection: "Bật/tắt mục" + +# Accessibility live-region/status text +resultsFound: "%d kết quả được tìm thấy" + +# User-facing UI text +archives: "Lưu trữ" +backToTop: "Lướt lên đầu trang" +changeLanguage: "Đổi ngôn ngữ" +changeTheme: "Đổi chủ đề" +copy: "Sao chép" +copied: "Đã sao chép!" +copyAsMarkdown: "Sao chép dạng Markdown" +copyPage: "Sao chép trang" +copyCode: "Sao chép mã" +copyright: "© 2026 Hextra Project." +dark: "Tối" +editThisPage: "Sửa trang này trên GitHub →" +lastUpdated: "Lần cuối cập nhật lúc" +light: "Sáng" +next: "Tiếp" +noResultsFound: "Không tìm thấy kết quả." +onThisPage: "Ở trang này" +more: "Thêm" +poweredBy: "Chạy bởi Hextra" +previous: "Trước" +readMore: "Đọc thêm →" +searchPlaceholder: "Tìm kiếm..." +system: "Hệ thống" +tags: "Thẻ" +viewAsMarkdown: "Xem dạng Markdown" diff --git a/themes/hextra/i18n/zh-cn.yaml b/themes/hextra/i18n/zh-cn.yaml new file mode 100644 index 0000000..2fb23c5 --- /dev/null +++ b/themes/hextra/i18n/zh-cn.yaml @@ -0,0 +1,43 @@ +# Accessibility labels (screen reader only) +closeBanner: "关闭横幅" +menu: "菜单" +mermaidDiagram: "图表" +pdfViewer: "PDF 查看器" +permalinkLabel: "此章节的永久链接" +playbackTime: "播放时间" +searchResults: "搜索结果" +skipToContent: "跳至内容" +tableOfContents: "目录" +terminalRecording: "终端录像" +togglePageContextMenu: "切换页面上下文菜单" +toggleSection: "切换章节" + +# Accessibility live-region/status text +resultsFound: "找到 %d 个结果" + +# User-facing UI text +archives: "归档" +backToTop: "返回顶部" +changeLanguage: "切换语言" +changeTheme: "切换主题" +copy: "复制" +copied: "已复制!" +copyAsMarkdown: "复制为 Markdown" +copyPage: "复制页面" +copyCode: "复制代码" +copyright: "© 2026 Hextra Project." +dark: "深色" +editThisPage: "在 GitHub 上编辑此页 →" +lastUpdated: "最后更新于" +light: "浅色" +next: "下一页" +noResultsFound: "无结果" +onThisPage: "此页上" +more: "更多" +poweredBy: "由 Hextra 驱动" +previous: "上一页" +readMore: "更多 →" +searchPlaceholder: "搜索文档..." +system: "跟随系统" +tags: "标签" +viewAsMarkdown: "以 Markdown 查看" diff --git a/themes/hextra/i18n/zh-tw.yaml b/themes/hextra/i18n/zh-tw.yaml new file mode 100644 index 0000000..4c5b2c8 --- /dev/null +++ b/themes/hextra/i18n/zh-tw.yaml @@ -0,0 +1,43 @@ +# Accessibility labels (screen reader only) +closeBanner: "關閉橫幅" +menu: "選單" +mermaidDiagram: "圖表" +pdfViewer: "PDF 檢視器" +permalinkLabel: "此章節的永久連結" +playbackTime: "播放時間" +searchResults: "搜尋結果" +skipToContent: "跳至內容" +tableOfContents: "目錄" +terminalRecording: "終端機錄影" +togglePageContextMenu: "切換頁面內容選單" +toggleSection: "切換章節" + +# Accessibility live-region/status text +resultsFound: "找到 %d 個結果" + +# User-facing UI text +archives: "歸檔" +backToTop: "返回頂部" +changeLanguage: "切換語言" +changeTheme: "切換主題" +copy: "複製" +copied: "已複製!" +copyAsMarkdown: "複製為 Markdown" +copyPage: "複製頁面" +copyCode: "複製程式碼" +copyright: "© 2026 Hextra Project." +dark: "深色" +editThisPage: "在 GitHub 上編輯此頁 →" +lastUpdated: "最後更新於" +light: "淺色" +next: "下一頁" +noResultsFound: "無結果" +onThisPage: "此頁上" +more: "更多" +poweredBy: "由 Hextra 驅動" +previous: "上一頁" +readMore: "更多 →" +searchPlaceholder: "搜尋文檔..." +system: "系統" +tags: "標籤" +viewAsMarkdown: "以 Markdown 檢視" diff --git a/themes/hextra/images/screenshot.jpg b/themes/hextra/images/screenshot.jpg new file mode 100644 index 0000000..5dde712 Binary files /dev/null and b/themes/hextra/images/screenshot.jpg differ diff --git a/themes/hextra/images/tn.jpg b/themes/hextra/images/tn.jpg new file mode 100644 index 0000000..7e3a0d1 Binary files /dev/null and b/themes/hextra/images/tn.jpg differ diff --git a/themes/hextra/layouts/404.html b/themes/hextra/layouts/404.html new file mode 100644 index 0000000..902db64 --- /dev/null +++ b/themes/hextra/layouts/404.html @@ -0,0 +1,40 @@ + + + +
+ +

+ 404 +

+
+

This page could not be found.

+
+
+ + diff --git a/themes/hextra/layouts/_markup/render-blockquote-alert.html b/themes/hextra/layouts/_markup/render-blockquote-alert.html new file mode 100644 index 0000000..66518fc --- /dev/null +++ b/themes/hextra/layouts/_markup/render-blockquote-alert.html @@ -0,0 +1,9 @@ +{{- if not (in (slice "note" "tip" "important" "warning" "caution") .AlertType) -}} + {{- warnf "Alert type %s is not supported" .AlertType -}} +{{- end -}} + +{{- $content := .Text -}} +{{- $alertType := .AlertType -}} +{{- $alertTitle := .AlertTitle -}} + +{{- partial "components/github-style-alert.html" (dict "content" $content "alertType" $alertType "alertTitle" $alertTitle) -}} diff --git a/themes/hextra/layouts/_markup/render-blockquote-regular.html b/themes/hextra/layouts/_markup/render-blockquote-regular.html new file mode 100644 index 0000000..652cb51 --- /dev/null +++ b/themes/hextra/layouts/_markup/render-blockquote-regular.html @@ -0,0 +1,3 @@ +
+ {{ .Text }} +
diff --git a/themes/hextra/layouts/_markup/render-codeblock-mermaid.html b/themes/hextra/layouts/_markup/render-codeblock-mermaid.html new file mode 100644 index 0000000..c2e8942 --- /dev/null +++ b/themes/hextra/layouts/_markup/render-codeblock-mermaid.html @@ -0,0 +1,6 @@ +
+
+    {{ .Inner | htmlEscape | safeHTML }}
+  
+
+{{- .Page.Store.Set "hasMermaid" true -}} diff --git a/themes/hextra/layouts/_markup/render-codeblock.html b/themes/hextra/layouts/_markup/render-codeblock.html new file mode 100644 index 0000000..0d1c920 --- /dev/null +++ b/themes/hextra/layouts/_markup/render-codeblock.html @@ -0,0 +1,13 @@ +{{- $class := .Attributes.class | default "" -}} +{{- $filename := .Attributes.filename | default "" -}} +{{- $base_url := .Attributes.base_url | default "" -}} +{{- $lang := .Attributes.lang | default .Type -}} + + +
+ {{- partial "components/codeblock" (dict "filename" $filename "lang" $lang "base_url" $base_url "content" .Inner "options" .Options) -}} + + {{- if or (eq site.Params.highlight.copy.enable nil) (site.Params.highlight.copy.enable) -}} + {{- partialCached "components/codeblock-copy-button" (dict "filename" $filename) $filename -}} + {{- end -}} +
diff --git a/themes/hextra/layouts/_markup/render-heading.html b/themes/hextra/layouts/_markup/render-heading.html new file mode 100644 index 0000000..c28dfb1 --- /dev/null +++ b/themes/hextra/layouts/_markup/render-heading.html @@ -0,0 +1,8 @@ + + {{- .Text | safeHTML -}} + {{- if gt .Level 1 -}} + + + {{- end -}} + +{{- /* Drop trailing newlines */ -}} diff --git a/themes/hextra/layouts/_markup/render-image.html b/themes/hextra/layouts/_markup/render-image.html new file mode 100644 index 0000000..2d3c8cf --- /dev/null +++ b/themes/hextra/layouts/_markup/render-image.html @@ -0,0 +1,59 @@ +{{- $alt := .PlainText | safeHTML -}} +{{- $lazyLoading := .Page.Site.Params.enableImageLazyLoading | default true -}} +{{- $enableImageZoom := .Page.Site.Params.imageZoom.enable | default false -}} +{{- if not (eq .Page.Params.imageZoom nil) -}} + {{- $enableImageZoom = .Page.Params.imageZoom -}} +{{- end -}} +{{- $dest := .Destination -}} +{{- $url := urls.Parse $dest -}} + +{{- $isLocal := not $url.Scheme -}} +{{- $isPage := and (eq .Page.Kind "page") (not .Page.BundleType) -}} +{{- $startsWithSlash := hasPrefix $dest "/" -}} +{{- $startsWithRelative := hasPrefix $dest "../" -}} + +{{- if and $dest $isLocal -}} + {{- if $startsWithSlash -}} + {{- with or (.PageInner.Resources.Get $url.Path) (resources.Get $url.Path) -}} + {{/* Images under assets directory */}} + {{- $query := cond $url.RawQuery (printf "?%s" $url.RawQuery) "" -}} + {{- $fragment := cond $url.Fragment (printf "#%s" $url.Fragment) "" -}} + {{- $dest = printf "%s%s%s" .RelPermalink $query $fragment -}} + {{- else -}} + {{/* Images under static directory */}} + {{- $dest = (relURL (strings.TrimPrefix "/" $dest)) -}} + {{- end -}} + {{- else -}} + {{/* Resolve page bundle resource for multilingual permalink */}} + {{- with .PageInner.Resources.Get (strings.TrimPrefix "./" $url.Path) -}} + {{- $query := cond $url.RawQuery (printf "?%s" $url.RawQuery) "" -}} + {{- $fragment := cond $url.Fragment (printf "#%s" $url.Fragment) "" -}} + {{- $dest = printf "%s%s%s" .RelPermalink $query $fragment -}} + {{- else -}} + {{- if and $isPage (not $startsWithRelative) -}} + {{ $dest = (printf "../%s" $dest) }} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- $attributes := "" -}} +{{- range $key, $value := .Attributes -}} + {{- if $value -}} + {{- $pair := printf "%s=%q" $key ($value | transform.HTMLEscape) -}} + {{- $attributes = printf "%s %s" $attributes $pair -}} + {{- end -}} +{{- end -}} + +{{- if $enableImageZoom -}} + {{- .Page.Store.Set "hasImageZoom" true -}} +{{- end -}} + +{{- with .Title -}} +
+ {{ $alt }} +
{{ . }}
+
+{{- else -}} + {{ $alt }} +{{- end -}} diff --git a/themes/hextra/layouts/_markup/render-link.html b/themes/hextra/layouts/_markup/render-link.html new file mode 100644 index 0000000..a673882 --- /dev/null +++ b/themes/hextra/layouts/_markup/render-link.html @@ -0,0 +1,28 @@ +{{- $dest := .Destination -}} +{{- $url := urls.Parse $dest -}} + +{{- if and $dest (hasPrefix $dest "/") -}} + {{- with or (.PageInner.GetPage $url.Path) (.PageInner.Resources.Get $url.Path) (resources.Get $url.Path) -}} + {{- $query := cond $url.RawQuery (printf "?%s" $url.RawQuery) "" -}} + {{- $fragment := cond $url.Fragment (printf "#%s" $url.Fragment) "" -}} + {{- $dest = printf "%s%s%s" .RelPermalink $query $fragment -}} + {{- else -}} + {{- $hasBasePrefix := and (ne site.Home.RelPermalink "/") (hasPrefix $dest site.Home.RelPermalink) -}} + {{- if not $hasBasePrefix -}} + {{- $dest = (relURL (strings.TrimPrefix "/" $dest)) -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- with . -}} +{{- $isExternal := strings.HasPrefix .Destination "http" -}} + + {{- .Text | safeHTML -}} + {{- if and .Page.Site.Params.externalLinkDecoration $isExternal -}} + {{- partial "utils/icon.html" (dict "name" "arrow-up-right" "attributes" `class="hx:inline hx:rtl:rotate-270 hx:align-baseline" height="1em" aria-hidden="true"`) -}} + {{- end -}} + +{{- end -}} diff --git a/themes/hextra/layouts/_markup/render-passthrough.html b/themes/hextra/layouts/_markup/render-passthrough.html new file mode 100644 index 0000000..3c23c6a --- /dev/null +++ b/themes/hextra/layouts/_markup/render-passthrough.html @@ -0,0 +1,20 @@ +{{- $engine := site.Params.math.engine | default "katex" -}} +{{- if eq $engine "katex" -}} + {{- $opts := dict "output" "htmlAndMathml" "displayMode" (eq .Type "block") }} + {{- with try (transform.ToMath .Inner $opts) }} + {{- with .Err }} + {{ errorf "Unable to render mathematical markup to HTML using the transform.ToMath function. The KaTeX display engine threw the following error: %s: see %s." . $.Position }} + {{- else }} + {{- .Value }} + {{- $.Page.Store.Set "hasMath" true }} + {{- end }} + {{- end }} +{{- else -}} + {{/* MathJax - need to add delimiters back in */}} + {{- $.Page.Store.Set "hasMath" true }} + {{- if eq .Type "block" -}} + \[{{- .Inner -}}\] + {{- else -}} + \( {{- .Inner -}} \) + {{- end -}} +{{- end -}} diff --git a/themes/hextra/layouts/_partials/banner.html b/themes/hextra/layouts/_partials/banner.html new file mode 100644 index 0000000..d15b31c --- /dev/null +++ b/themes/hextra/layouts/_partials/banner.html @@ -0,0 +1,19 @@ +{{- if site.Params.banner }} +
+
+ {{- with partial "custom/banner.html" . -}} + {{- . -}} + {{- else -}} +
+ {{- site.Params.banner.message | default "🎉 Welcome! This is a banner message." | .RenderString -}} +
+ {{- end -}} + +
+
+{{- end -}} diff --git a/themes/hextra/layouts/_partials/breadcrumb.html b/themes/hextra/layouts/_partials/breadcrumb.html new file mode 100644 index 0000000..1f4612e --- /dev/null +++ b/themes/hextra/layouts/_partials/breadcrumb.html @@ -0,0 +1,17 @@ +{{- $page := .page -}} +{{- $enable := .enable -}} +{{- if (default $enable $page.Params.breadcrumbs) -}} +
+ {{- range $page.Ancestors.Reverse }} + {{- if not .IsHome }} + + {{- partial "utils/icon.html" (dict "name" "chevron-right" "attributes" "class=\"hx:w-3.5 hx:shrink-0 hx:rtl:-rotate-180\"") -}} + {{ end -}} + {{ end -}} +
+ {{- partial "utils/title" $page -}} +
+
+{{ end -}} diff --git a/themes/hextra/layouts/_partials/components/analytics/analytics.html b/themes/hextra/layouts/_partials/components/analytics/analytics.html new file mode 100644 index 0000000..6e56b52 --- /dev/null +++ b/themes/hextra/layouts/_partials/components/analytics/analytics.html @@ -0,0 +1,24 @@ +{{- if hugo.IsProduction -}} + + +{{- if .Site.Config.Services.GoogleAnalytics.ID }} + + {{ partial "google-analytics.html" . -}} +{{- end }} + + +{{- if .Site.Params.analytics.umami -}} + {{ partial "components/analytics/umami.html" . }} +{{- end }} + + +{{- if .Site.Params.analytics.matomo -}} + {{ partial "components/analytics/matomo.html" . }} +{{- end }} + + +{{- if .Site.Params.analytics.goatCounter -}} + {{ partial "components/analytics/goat-counter.html" . }} +{{- end -}} + +{{- end }} diff --git a/themes/hextra/layouts/_partials/components/analytics/goat-counter.html b/themes/hextra/layouts/_partials/components/analytics/goat-counter.html new file mode 100644 index 0000000..3399bb0 --- /dev/null +++ b/themes/hextra/layouts/_partials/components/analytics/goat-counter.html @@ -0,0 +1,17 @@ +{{- with .Site.Params.analytics.goatCounter -}} + {{- if not .code -}} + {{- errorf "Missing GoatCounter 'code' configuration. See https://imfing.github.io/hextra/versions/latest/docs/guide/configuration/#goatcounter-analytics" -}} + {{- end -}} + + +{{- end -}} \ No newline at end of file diff --git a/themes/hextra/layouts/_partials/components/analytics/google-analytics.html b/themes/hextra/layouts/_partials/components/analytics/google-analytics.html new file mode 100644 index 0000000..62e9d42 --- /dev/null +++ b/themes/hextra/layouts/_partials/components/analytics/google-analytics.html @@ -0,0 +1,13 @@ +{{- with site.Config.Services.GoogleAnalytics.ID }} + + + +{{ end -}} diff --git a/themes/hextra/layouts/_partials/components/analytics/matomo.html b/themes/hextra/layouts/_partials/components/analytics/matomo.html new file mode 100644 index 0000000..02b81b5 --- /dev/null +++ b/themes/hextra/layouts/_partials/components/analytics/matomo.html @@ -0,0 +1,31 @@ +{{- /* +Matomo Analytics. +https://developer.matomo.org/guides/tracking-javascript-guide +*/ -}} + +{{- with .Site.Params.analytics.matomo -}} + +{{- if not .serverURL }} + {{- errorf "Missing Matomo 'serverURL' configuration. See https://imfing.github.io/hextra/versions/latest/docs/guide/configuration/#matomo-analytics" -}} +{{- end -}} + +{{- if not .websiteID }} + {{- errorf "Missing Matomo 'websiteID' configuration. See https://imfing.github.io/hextra/versions/latest/docs/guide/configuration/#matomo-analytics" -}} +{{- end -}} + + + + + +{{- end -}} diff --git a/themes/hextra/layouts/_partials/components/analytics/umami.html b/themes/hextra/layouts/_partials/components/analytics/umami.html new file mode 100644 index 0000000..df073be --- /dev/null +++ b/themes/hextra/layouts/_partials/components/analytics/umami.html @@ -0,0 +1,57 @@ +{{- /* +Umami Analytics +https://umami.is/docs/tracker-configuration +*/ -}} + +{{- with .Site.Params.analytics.umami -}} + +{{- if not .serverURL }} + {{- errorf "Missing Umami 'serverURL' configuration. See https://imfing.github.io/hextra/versions/latest/docs/guide/configuration/#umami-analytics" -}} +{{- end -}} + +{{- if not .websiteID }} + {{- errorf "Missing Umami 'websiteID' configuration. See https://imfing.github.io/hextra/versions/latest/docs/guide/configuration/#umami-analytics" -}} +{{- end -}} + +{{- $attributes := newScratch -}} + +{{- $attributes.SetInMap "umami" "src" (printf "%s/%s" .serverURL (.scriptName | default "script.js")) -}} +{{- $attributes.SetInMap "umami" "data-website-id" .websiteID -}} + +{{- if .hostURL -}} + {{- /* https://umami.is/docs/tracker-configuration#data-host-url */ -}} + {{- $attributes.SetInMap "umami" "data-host-url" .hostURL -}} +{{- end -}} + +{{- if .autoTrack -}} + {{- /* https://umami.is/docs/tracker-configuration#data-auto-track */ -}} + {{- $attributes.SetInMap "umami" "data-auto-track" .autoTrack -}} +{{- end -}} + +{{- if .tag -}} + {{- /* https://umami.is/docs/tracker-configuration#data-tag */ -}} + {{- $attributes.SetInMap "umami" "data-tag" .tag -}} +{{- end -}} + +{{- if .excludeSearch -}} + {{- /* https://umami.is/docs/tracker-configuration#data-exclude-search */ -}} + {{- $attributes.SetInMap "umami" "data-exclude-search" .excludeSearch -}} +{{- end -}} + +{{- if .excludeHash -}} + {{- /* https://umami.is/docs/tracker-configuration#data-exclude-hash */ -}} + {{- $attributes.SetInMap "umami" "data-exclude-hash" .excludeHash -}} +{{- end -}} + +{{- if .doNotTrack -}} + {{- /* https://umami.is/docs/tracker-configuration#data-do-not-track */ -}} + {{- $attributes.SetInMap "umami" "data-do-not-track" .doNotTrack -}} +{{- end -}} + +{{- if .domains -}} + {{- /* https://umami.is/docs/tracker-configuration#data-domains */ -}} + {{- $attributes.SetInMap "umami" "data-domains" .domains -}} +{{- end -}} + + +{{- end -}} diff --git a/themes/hextra/layouts/_partials/components/blog-pager.html b/themes/hextra/layouts/_partials/components/blog-pager.html new file mode 100644 index 0000000..1b6b130 --- /dev/null +++ b/themes/hextra/layouts/_partials/components/blog-pager.html @@ -0,0 +1,39 @@ +{{/* + Blog pagination component for list pages (e.g., blog list, category list) + + Usage: {{ partial "components/blog-pager.html" $paginator }} + + Parameters: + - . (context): Hugo paginator object +*/}} + +{{- $paginator := . -}} +{{- $prevText := (T "previous") | default "Prev" -}} +{{- $nextText := (T "next") | default "Next" -}} +{{- $prevLabel := printf "%s %d/%d" $prevText (sub $paginator.PageNumber 1) $paginator.TotalPages -}} +{{- $nextLabel := printf "%s %d/%d" $nextText (add $paginator.PageNumber 1) $paginator.TotalPages -}} + +{{- if or $paginator.HasPrev $paginator.HasNext -}} + +{{- end -}} \ No newline at end of file diff --git a/themes/hextra/layouts/_partials/components/codeblock-copy-button.html b/themes/hextra/layouts/_partials/components/codeblock-copy-button.html new file mode 100644 index 0000000..78d0f0d --- /dev/null +++ b/themes/hextra/layouts/_partials/components/codeblock-copy-button.html @@ -0,0 +1,17 @@ +{{/* TODO: remove filename variable */}} +{{- $filename := .filename | default "" -}} +{{- $display := site.Params.highlight.copy.display | default "hover" -}} +{{- $copyCode := (T "copyCode") | default "Copy code" -}} + + +
+ +
diff --git a/themes/hextra/layouts/_partials/components/codeblock.html b/themes/hextra/layouts/_partials/components/codeblock.html new file mode 100644 index 0000000..cdee81f --- /dev/null +++ b/themes/hextra/layouts/_partials/components/codeblock.html @@ -0,0 +1,29 @@ +{{ $filename := .filename | default "" -}} +{{ $base_url := .base_url | default "" -}} +{{ $lang := .lang | default "" }} +{{ $content := .content }} +{{ $options := .options | default (dict) }} + +{{- if $filename -}} +
+ {{- if $base_url -}} + + {{- $base_url = strings.TrimSuffix "/" $base_url -}} + {{- $filename = strings.TrimPrefix "/" $filename -}} + {{- $file_url := urls.JoinPath $base_url $filename -}} + + + {{- $filename -}} + {{- partial "utils/icon" (dict "name" "external-link" "attributes" "height=1em") -}} + + {{- else -}} + {{- $filename -}} + {{- end -}} +
+{{- end -}} + +{{- if transform.CanHighlight $lang -}} +
{{- highlight $content $lang $options -}}
+{{- else -}} +
{{ $content }}
+{{- end -}} diff --git a/themes/hextra/layouts/_partials/components/comments.html b/themes/hextra/layouts/_partials/components/comments.html new file mode 100644 index 0000000..ac893a1 --- /dev/null +++ b/themes/hextra/layouts/_partials/components/comments.html @@ -0,0 +1,11 @@ +{{- $enableComments := site.Params.comments.enable | default false -}} + +{{ if not (eq .Params.comments nil) }} + {{ $enableComments = .Params.comments }} +{{ end }} + +{{- if $enableComments -}} + {{- if eq site.Params.comments.type "giscus" -}} + {{ partial "components/giscus.html" . }} + {{- end -}} +{{- end -}} diff --git a/themes/hextra/layouts/_partials/components/giscus.html b/themes/hextra/layouts/_partials/components/giscus.html new file mode 100644 index 0000000..973e50b --- /dev/null +++ b/themes/hextra/layouts/_partials/components/giscus.html @@ -0,0 +1,89 @@ +{{- $lang := site.Language.Lang | default `en` -}} +{{- if hasPrefix $lang "zh" -}} + {{- /* See: https://github.com/giscus/giscus/tree/main/locales */}} + {{- $lang = partial "utils/hugo-compat/language-locale.html" site.Language | default `zh-CN` -}} +{{- end -}} + +{{- with site.Params.comments.giscus -}} + + +
+{{- else -}} + {{ warnf "giscus is not configured" }} +{{- end -}} diff --git a/themes/hextra/layouts/_partials/components/github-style-alert.html b/themes/hextra/layouts/_partials/components/github-style-alert.html new file mode 100644 index 0000000..f03b261 --- /dev/null +++ b/themes/hextra/layouts/_partials/components/github-style-alert.html @@ -0,0 +1,53 @@ +{{- $content := .content -}} +{{- $alertType := .alertType -}} +{{- $alertTitle := .alertTitle -}} + +{{- $styles := newScratch -}} +{{- $styles.Set "default" (dict + "icon" "light-bulb" + "style" "hx:border-green-200 hx:bg-green-100 hx:text-green-900 hx:dark:border-green-200/30 hx:dark:bg-green-900/30 hx:dark:text-green-200" + ) +-}} +{{- $styles.Set "note" (dict + "icon" "information-circle" + "style" "hx:border-blue-200 hx:bg-blue-100 hx:text-blue-900 hx:dark:border-blue-200/30 hx:dark:bg-blue-900/30 hx:dark:text-blue-200" + ) +-}} +{{- $styles.Set "tip" (dict + "icon" "light-bulb" + "style" "hx:border-green-200 hx:bg-green-100 hx:text-green-900 hx:dark:border-green-200/30 hx:dark:bg-green-900/30 hx:dark:text-green-200" + ) +-}} +{{- $styles.Set "important" (dict + "icon" "information-circle" + "style" "hx:border-purple-200 hx:bg-purple-100 hx:text-purple-900 hx:dark:border-purple-200/30 hx:dark:bg-purple-900/30 hx:dark:text-purple-200" + ) +-}} +{{- $styles.Set "warning" (dict + "icon" "exclamation" + "style" "hx:border-amber-200 hx:bg-amber-100 hx:text-amber-900 hx:dark:border-amber-200/30 hx:dark:bg-amber-900/30 hx:dark:text-amber-200" + ) +-}} +{{- $styles.Set "caution" (dict + "icon" "exclamation-circle" + "style" "hx:border-red-200 hx:bg-red-100 hx:text-red-900 hx:dark:border-red-200/30 hx:dark:bg-red-900/30 hx:dark:text-red-200" + ) +-}} + +{{- $style := or ($styles.Get $alertType) ($styles.Get "default") -}} +{{- $title := or $alertTitle (or (i18n $alertType) (title $alertType)) -}} + +
+

+ {{- with $style.icon -}} + {{- partial "utils/icon.html" (dict "name" . "attributes" `height=16px class="hx:inline-block hx:align-middle hx:mr-2"`) -}} + {{- end -}} + {{- $title -}} +

+ +
+
+ {{- $content -}} +
+
+
diff --git a/themes/hextra/layouts/_partials/components/last-updated.html b/themes/hextra/layouts/_partials/components/last-updated.html new file mode 100644 index 0000000..f900c0b --- /dev/null +++ b/themes/hextra/layouts/_partials/components/last-updated.html @@ -0,0 +1,20 @@ +{{- $lastUpdated := (T "lastUpdated") | default "Last updated on" -}} +{{- $page := . -}} + +{{- if site.Params.displayUpdatedDate -}} + {{- with .Lastmod -}} + {{ $datetime := (time.Format "2006-01-02T15:04:05.000Z" .) }} +
+ {{ $lastUpdated }} + {{- if site.Params.displayUpdatedAuthor -}} + {{- with $page.GitInfo -}} + {{ print " • " .AuthorName | safeHTML }} + {{- end -}} + {{- end -}} +
+ {{- else -}} +
+ {{- end -}} +{{- else -}} +
+{{- end -}} diff --git a/themes/hextra/layouts/_partials/components/page-context-menu.html b/themes/hextra/layouts/_partials/components/page-context-menu.html new file mode 100644 index 0000000..3893d0e --- /dev/null +++ b/themes/hextra/layouts/_partials/components/page-context-menu.html @@ -0,0 +1,90 @@ +{{- $enableGlobal := site.Params.page.contextMenu.enable | default false -}} +{{- $enablePage := .Params.contextMenu -}} +{{- $enable := cond (ne $enablePage nil) $enablePage $enableGlobal -}} +{{- $customLinks := site.Params.page.contextMenu.links | default slice -}} + +{{- if $enable -}} + {{- with .OutputFormats.Get "markdown" -}} + {{- $markdownURL := .Permalink -}} + {{- $pageURL := $.Permalink -}} + {{- $pageTitle := $.Title -}} +
+
+ + +
+ +
+ {{- end -}} +{{- end -}} diff --git a/themes/hextra/layouts/_partials/components/pager.html b/themes/hextra/layouts/_partials/components/pager.html new file mode 100644 index 0000000..1adc43a --- /dev/null +++ b/themes/hextra/layouts/_partials/components/pager.html @@ -0,0 +1,53 @@ +{{/* Article navigation on the footer of the article */}} + +{{- $reversePagination := .Store.Get "reversePagination" | default false -}} + +{{- $prev := cond $reversePagination .PrevInSection .NextInSection -}} +{{- $next := cond $reversePagination .NextInSection .PrevInSection -}} + +{{- if eq .Params.prev false }} + {{- if $reversePagination }}{{ $next = false }}{{ else }}{{ $prev = false }}{{ end -}} +{{ else }} + {{- with .Params.prev -}} + {{- with $.Site.GetPage . -}} + {{- if $reversePagination }}{{ $next = . }}{{ else }}{{ $prev = . }}{{ end -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- if eq .Params.next false }} + {{- if $reversePagination }}{{ $prev = false }}{{ else }}{{ $next = false }}{{ end -}} +{{ else }} + {{- with .Params.next -}} + {{- with $.Site.GetPage . -}} + {{- if $reversePagination }}{{ $prev = . }}{{ else }}{{ $next = . }}{{ end -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- if or $prev $next -}} + +{{- end -}} diff --git a/themes/hextra/layouts/_partials/custom/banner.html b/themes/hextra/layouts/_partials/custom/banner.html new file mode 100644 index 0000000..e69de29 diff --git a/themes/hextra/layouts/_partials/custom/footer.html b/themes/hextra/layouts/_partials/custom/footer.html new file mode 100644 index 0000000..e69de29 diff --git a/themes/hextra/layouts/_partials/custom/head-end.html b/themes/hextra/layouts/_partials/custom/head-end.html new file mode 100644 index 0000000..e69de29 diff --git a/themes/hextra/layouts/_partials/custom/navbar-title.html b/themes/hextra/layouts/_partials/custom/navbar-title.html new file mode 100644 index 0000000..e69de29 diff --git a/themes/hextra/layouts/_partials/favicons.html b/themes/hextra/layouts/_partials/favicons.html new file mode 100644 index 0000000..78442d4 --- /dev/null +++ b/themes/hextra/layouts/_partials/favicons.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/themes/hextra/layouts/_partials/footer.html b/themes/hextra/layouts/_partials/footer.html new file mode 100644 index 0000000..bba76b5 --- /dev/null +++ b/themes/hextra/layouts/_partials/footer.html @@ -0,0 +1,44 @@ +{{- $enableFooterSwitches := .Store.Get "enableFooterSwitches" | default false -}} +{{- $displayThemeToggle := site.Params.theme.displayToggle | default true -}} +{{- $footerSwitchesVisible := and $enableFooterSwitches (or hugo.IsMultilingual $displayThemeToggle) -}} +{{- $copyrightSectionVisible := or (.Site.Params.footer.displayPoweredBy | default true) .Site.Params.footer.displayCopyright -}} + +{{- $copyright := (T "copyright") | default "© 2024 Hextra." -}} +{{- $poweredBy := (T "poweredBy") | default "Powered by Hextra" -}} + + +
+ {{- if $footerSwitchesVisible -}} + + {{- if or hugo.IsMultilingual $displayThemeToggle -}} +
+ {{- end -}} + {{- end -}} + + {{- if $copyrightSectionVisible -}} + + {{- end -}} +
+ +{{- define "theme-credit" -}} + + + {{- . | markdownify -}} + {{- if strings.Contains . "Hextra" -}} + {{- partial "utils/icon.html" (dict "name" "hextra" "attributes" `height=1em class="hx:inline-block hx:ltr:ml-1 hx:rtl:mr-1 hx:align-[-2.5px]"`) -}} + {{- end -}} + + +{{- end -}} diff --git a/themes/hextra/layouts/_partials/google-analytics.html b/themes/hextra/layouts/_partials/google-analytics.html new file mode 100644 index 0000000..16ab488 --- /dev/null +++ b/themes/hextra/layouts/_partials/google-analytics.html @@ -0,0 +1,2 @@ +{{- /* Only for compatibility. */ -}} +{{- partial "components/analytics/google-analytics.html" . -}} diff --git a/themes/hextra/layouts/_partials/head.html b/themes/hextra/layouts/_partials/head.html new file mode 100644 index 0000000..eb06bc5 --- /dev/null +++ b/themes/hextra/layouts/_partials/head.html @@ -0,0 +1,81 @@ + + + + {{- $noindex := .Params.noindex | default false -}} + {{ if and (hugo.IsProduction) (not $noindex) -}} + + {{ else -}} + + {{ end -}} + {{ partialCached "favicons.html" . -}} + + {{- if .IsHome -}} + {{ .Site.Title -}} + {{ else -}} + {{ with .Title }}{{ . }} – {{ end -}} + {{ .Site.Title -}} + {{ end -}} + + + + {{- with .Params.canonical -}} + + {{- else -}} + + {{- end -}} + + {{- partial "opengraph.html" . -}} + {{- partial "schema.html" . -}} + {{- partial "twitter_cards.html" . -}} + + {{- $mainCss := resources.Get "css/compiled/main.css" -}} + {{- $customCss := resources.Get "css/custom.css" -}} + {{- $variablesCss := resources.Get "css/variables.css" | resources.ExecuteAsTemplate "css/variables.css" . -}} + + {{- /* Production build */ -}} + {{- if hugo.IsProduction }} + {{- $styles := slice $variablesCss $mainCss $customCss | resources.Concat "css/compiled/main.css" | minify | fingerprint }} + + + + {{- /* Theme development mode (non-production + theme environment) */ -}} + {{- else if eq hugo.Environment "theme" }} + {{- $devStyles := resources.Get "css/styles.css" | postCSS (dict "inlineImports" true) }} + + + + + {{- /* User local development */ -}} + {{- else }} + {{- $styles := resources.Get "css/compiled/main.css" -}} + + + + {{- end }} + + {{ partial "components/analytics/analytics.html" . }} + + {{- $scriptsHead := slice -}} + {{- range resources.Match "js/head/*.js" -}} + {{ $scriptsHead = $scriptsHead | append (resources.ExecuteAsTemplate .Name $ .) }} + {{- end -}} + + {{- $scripts := $scriptsHead | resources.Concat "js/main-head.js" -}} + + {{- if hugo.IsProduction -}} + {{- $scripts = $scripts | minify | fingerprint -}} + {{- end -}} + + + + {{ $noop := .WordCount -}} + {{- $engine := site.Params.math.engine | default "katex" -}} + {{ if and (.Page.Store.Get "hasMath") (eq $engine "katex") -}} + {{ partialCached "scripts/katex.html" . -}} + {{ else if and (.Page.Store.Get "hasMath") (eq $engine "mathjax") -}} + {{ partialCached "scripts/mathjax.html" . -}} + {{ end -}} + + {{ partial "utils/page-width-override.html" . }} + {{ partial "custom/head-end.html" . -}} + diff --git a/themes/hextra/layouts/_partials/language-switch.html b/themes/hextra/layouts/_partials/language-switch.html new file mode 100644 index 0000000..4603615 --- /dev/null +++ b/themes/hextra/layouts/_partials/language-switch.html @@ -0,0 +1,58 @@ +{{- $page := .context -}} +{{- $iconName := .iconName | default "globe-alt" -}} +{{- $iconHeight := .iconHeight | default 12 -}} +{{- $location := .location -}} + +{{- $class := .class | default "hx:h-7 hx:px-2 hx:text-xs hx:text-gray-600 hx:transition-colors hx:dark:text-gray-400 hx:hover:bg-gray-100 hx:hover:text-gray-900 hx:dark:hover:bg-primary-100/5 hx:dark:hover:text-gray-50" -}} + +{{- $grow := .grow -}} +{{- $hideLabel := .hideLabel | default false -}} + +{{- $changeLanguage := (T "changeLanguage") | default "Change language" -}} +{{- $currentLanguageLang := site.Language.Lang -}} +{{- $currentLanguageLabel := partial "utils/hugo-compat/language-label.html" site.Language -}} + +{{- if hugo.IsMultilingual -}} +
+ + +
+{{- end -}} diff --git a/themes/hextra/layouts/_partials/navbar-link.html b/themes/hextra/layouts/_partials/navbar-link.html new file mode 100644 index 0000000..0633471 --- /dev/null +++ b/themes/hextra/layouts/_partials/navbar-link.html @@ -0,0 +1,88 @@ +{{- $currentPage := .currentPage -}} +{{- $link := .link -}} +{{- $item := .item -}} +{{- $icon := .icon -}} +{{- $external := .external -}} + +{{- $active := or ($currentPage.HasMenuCurrent "main" $item) ($currentPage.IsMenuCurrent "main" $item) -}} +{{- /* Additional check for section landing pages in multilingual sites (normalize trailing slashes) */ -}} +{{- if and (not $active) $link -}} + {{- $currentPath := strings.TrimSuffix "/" $currentPage.RelPermalink -}} + {{- $linkPath := strings.TrimSuffix "/" $link -}} + {{- if eq $currentPath $linkPath -}} + {{- $active = true -}} + {{- end -}} +{{- end -}} +{{- $activeClass := cond $active "hx:font-medium" "hx:text-gray-600 hx:hover:text-gray-800 hx:dark:text-gray-400 hx:dark:hover:text-gray-200" -}} + +{{- if $item.HasChildren -}} +{{- /* Dropdown menu for items with children */ -}} +
+ + +
+{{- else -}} +{{- /* Regular menu item without children */ -}} + + {{- if $icon -}} + + {{- partial "utils/icon" (dict "name" $icon "attributes" `height="1em" class="hx:inline-block"`) -}} + + {{- end -}} + + {{- or (T $item.Identifier) $item.Name | safeHTML -}} + + +{{- end -}} diff --git a/themes/hextra/layouts/_partials/navbar-title.html b/themes/hextra/layouts/_partials/navbar-title.html new file mode 100644 index 0000000..36a9b64 --- /dev/null +++ b/themes/hextra/layouts/_partials/navbar-title.html @@ -0,0 +1,16 @@ +{{- $logoPath := .Site.Params.navbar.logo.path | default "images/logo.svg" -}} +{{- $logoLink := .Site.Params.navbar.logo.link | default .Site.Home.RelPermalink -}} +{{- $logoWidth := .Site.Params.navbar.logo.width | default "20" -}} +{{- $logoHeight := .Site.Params.navbar.logo.height | default "20" -}} +{{- $logoDarkPath := .Site.Params.navbar.logo.dark | default $logoPath -}} + + + {{- $displayTitle := (.Site.Params.navbar.displayTitle | default true) }} + {{- if (.Site.Params.navbar.displayLogo | default true) }} + {{ cond $displayTitle `Logo` .Site.Title }} + {{ cond $displayTitle `Dark Logo` .Site.Title }} + {{- end }} + {{- if $displayTitle }} + {{- .Site.Title -}} + {{- end }} + diff --git a/themes/hextra/layouts/_partials/navbar.html b/themes/hextra/layouts/_partials/navbar.html new file mode 100644 index 0000000..5745230 --- /dev/null +++ b/themes/hextra/layouts/_partials/navbar.html @@ -0,0 +1,59 @@ +{{- $navWidth := "hx:max-w-[90rem]" -}} +{{- with .Site.Params.navbar.width -}} + {{ if eq . "normal" -}} + {{ $navWidth = "hx:max-w-screen-xl" -}} + {{ else if eq . "full" -}} + {{ $navWidth = "max-w-full" -}} + {{ end -}} +{{- end -}} + +{{- $page := . -}} +{{- $iconHeight := 24 -}} + +
+
+ + +
diff --git a/themes/hextra/layouts/_partials/opengraph.html b/themes/hextra/layouts/_partials/opengraph.html new file mode 100644 index 0000000..91b9d3f --- /dev/null +++ b/themes/hextra/layouts/_partials/opengraph.html @@ -0,0 +1,96 @@ +{{/* Adapted from https://github.com/gohugoio/hugo/blob/v0.149.0/docs/layouts/_partials/opengraph/opengraph.html */}} + + + + + + +{{- with $.Params.images -}} + {{- range first 6 . }} + {{- with $.Resources.GetMatch . }} + + + {{- else }} + + {{- $image := . -}} + {{- if hasPrefix $image "/" -}} + {{- $image = relURL (strings.TrimPrefix "/" $image) -}} + {{- end -}} + + {{- end }} + {{- end }} +{{- else -}} + {{- with $.Site.Params.images }} + {{- $image := index . 0 -}} + {{- if hasPrefix $image "/" -}} + {{- $image = relURL (strings.TrimPrefix "/" $image) -}} + {{- end -}} + + {{- end }} +{{- end -}} + + +{{- if .IsPage }} +{{- $iso8601 := "2006-01-02T15:04:05-07:00" -}} + +{{ with .PublishDate }} + +{{ end }} +{{ with .Lastmod }} + +{{ end }} +{{- end -}} + +{{- with .Params.audio }}{{ end }} +{{- with .Params.locale }} + +{{ end }} +{{- with .Site.Params.title }} + +{{ end }} +{{- with .Params.videos }} +{{- range . }} + +{{ end }} + +{{ end }} + +{{- /* If it is part of a series, link to related articles */}} +{{- $permalink := .Permalink }} +{{- $siteSeries := .Site.Taxonomies.series }} +{{ with .Params.series }} +{{- range $name := . }} +{{- $series := index $siteSeries ($name | urlize) }} +{{- range $page := first 6 $series.Pages }} +{{- if ne $page.Permalink $permalink }} + +{{ end }} +{{- end }} +{{ end }} + +{{ end }} + +{{- /* Facebook Page Admin ID for Domain Insights */}} +{{- with site.Params.social.facebook_admin }} + +{{ end }} diff --git a/themes/hextra/layouts/_partials/scripts.html b/themes/hextra/layouts/_partials/scripts.html new file mode 100644 index 0000000..abf6cd4 --- /dev/null +++ b/themes/hextra/layouts/_partials/scripts.html @@ -0,0 +1,20 @@ +{{/* Core scripts (theme, menu, tabs, etc.) */}} +{{- partial "scripts/core.html" . -}} + +{{/* Search */}} +{{- partial "scripts/search.html" . -}} + +{{/* Mermaid */}} +{{- if (.Store.Get "hasMermaid") -}} + {{- partial "scripts/mermaid.html" . -}} +{{- end -}} + +{{/* Asciinema */}} +{{- if (.Store.Get "hasAsciinema") -}} + {{- partial "scripts/asciinema.html" . -}} +{{- end -}} + +{{/* Medium Zoom */}} +{{- if (.Store.Get "hasImageZoom") -}} + {{- partial "scripts/medium-zoom.html" . -}} +{{- end -}} diff --git a/themes/hextra/layouts/_partials/scripts/asciinema.html b/themes/hextra/layouts/_partials/scripts/asciinema.html new file mode 100644 index 0000000..6db5a8b --- /dev/null +++ b/themes/hextra/layouts/_partials/scripts/asciinema.html @@ -0,0 +1,140 @@ +{{- /* Asciinema */ -}} + +{{- $asciinemaBase := "" -}} +{{- $useDefaultCdn := true -}} +{{- with site.Params.asciinema.base -}} + {{- $asciinemaBase = . -}} + {{- $useDefaultCdn = false -}} +{{- end -}} + +{{- $asciinemaJsAsset := "" -}} +{{- with site.Params.asciinema.js -}} + {{- $asciinemaJsAsset = . -}} +{{- end -}} + +{{- $asciinemaCssAsset := "" -}} +{{- with site.Params.asciinema.css -}} + {{- $asciinemaCssAsset = . -}} +{{- end -}} + +{{- /* If only js/css is set without base, use local asset loading */ -}} +{{- if and $useDefaultCdn (or (ne $asciinemaJsAsset "") (ne $asciinemaCssAsset "")) -}} + {{- $useDefaultCdn = false -}} +{{- end -}} + +{{- /* Set default CDN base if needed */ -}} +{{- if $useDefaultCdn -}} + {{- $asciinemaBase = "https://cdn.jsdelivr.net/npm/asciinema-player@latest/dist/bundle" -}} +{{- end -}} + +{{- $isRemoteBase := or (strings.HasPrefix $asciinemaBase "http://") (strings.HasPrefix $asciinemaBase "https://") -}} +{{- $minSuffix := cond hugo.IsProduction ".min" "" -}} + +{{- /* CSS retrieval: get raw CSS from either local asset or remote, then process */ -}} +{{- if $isRemoteBase -}} + {{- $cssPath := cond (ne $asciinemaCssAsset "") $asciinemaCssAsset "asciinema-player.css" -}} + {{- $asciinemaCssUrl := urls.JoinPath $asciinemaBase $cssPath -}} + {{- with try (resources.GetRemote $asciinemaCssUrl) -}} + {{- with .Err -}} + {{- errorf "Could not retrieve Asciinema css file from %s. Reason: %s." $asciinemaCssUrl . -}} + {{- else with .Value -}} + {{- with resources.Copy "css/asciinema-player.css" . -}} + {{- $asciinemaCss := . | fingerprint -}} + + {{- end -}} + {{- end -}} + {{- end -}} +{{- else if $asciinemaCssAsset -}} + {{- with resources.Get $asciinemaCssAsset -}} + {{- $asciinemaCss := . | fingerprint -}} + + {{- else -}} + {{- errorf "Asciinema css asset not found at %q" $asciinemaCssAsset -}} + {{- end -}} +{{- end -}} + +{{- /* JS retrieval: get raw JS from either local asset or remote, then process */ -}} +{{- if $isRemoteBase -}} + {{- $jsPath := cond (ne $asciinemaJsAsset "") $asciinemaJsAsset (printf "asciinema-player%s.js" $minSuffix) -}} + {{- $asciinemaJsUrl := urls.JoinPath $asciinemaBase $jsPath -}} + {{- with try (resources.GetRemote $asciinemaJsUrl) -}} + {{- with .Err -}} + {{- errorf "Could not retrieve Asciinema js file from %s. Reason: %s." $asciinemaJsUrl . -}} + {{- else with .Value -}} + {{- with resources.Copy (printf "js/asciinema-player%s.js" $minSuffix) . -}} + {{- $asciinemaJs := . | fingerprint -}} + + {{- end -}} + {{- end -}} + {{- end -}} +{{- else if $asciinemaJsAsset -}} + {{- with resources.Get $asciinemaJsAsset -}} + {{- $asciinemaJs := . | fingerprint -}} + + {{- else -}} + {{- errorf "Asciinema js asset not found at %q" $asciinemaJsAsset -}} + {{- end -}} +{{- end -}} + + diff --git a/themes/hextra/layouts/_partials/scripts/core.html b/themes/hextra/layouts/_partials/scripts/core.html new file mode 100644 index 0000000..c93c674 --- /dev/null +++ b/themes/hextra/layouts/_partials/scripts/core.html @@ -0,0 +1,10 @@ +{{- $scriptsBody := slice }} +{{- range resources.Match "js/core/*.js" -}} + {{ $scriptsBody = $scriptsBody | append (resources.ExecuteAsTemplate .Name $ .) }} +{{- end -}} + +{{- $scripts := $scriptsBody | resources.Concat "js/main.js" -}} +{{- if hugo.IsProduction -}} + {{- $scripts = $scripts | minify | fingerprint -}} +{{- end -}} + diff --git a/themes/hextra/layouts/_partials/scripts/katex.html b/themes/hextra/layouts/_partials/scripts/katex.html new file mode 100644 index 0000000..add36c0 --- /dev/null +++ b/themes/hextra/layouts/_partials/scripts/katex.html @@ -0,0 +1,92 @@ +{{- /* KaTeX CSS loader + + Behavior (driven by site.params.math.katex): + - base (remote URL) + optional css: + - Construct remote CSS URL: "{{ base }}/{{ css | default "katex[.min].css" }}". + - Fetch via resources.GetRemote, rewrite font URLs to "{{ base }}/fonts/...". + - Build and fingerprint; emit . + - base (local path or not set) + css (asset path): + - Read CSS from Hugo assets via resources.Get; DO NOT rewrite font URLs. + - Build and fingerprint; emit . + - base (local path) only (no css): + - Link directly to "{{ base }}/katex[.min].css" (no processing). + - Nothing set: + - Default to CDN latest base; same as remote path above. + + Additional: + - assets: optional list to publish extra assets. CSS/JS get tags with integrity (JS loads async). +*/ -}} +{{- $noop := .WordCount -}} + +{{- $katexBase := "" -}} +{{- with site.Params.math.katex.base -}} + {{- $katexBase = . -}} +{{- else -}} + {{- if not site.Params.math.katex.css -}} + {{- $katexBase = "https://cdn.jsdelivr.net/npm/katex@latest/dist" -}} + {{- end -}} +{{- end -}} + +{{- $katexCssAsset := "" -}} +{{- with site.Params.math.katex.css -}} + {{- $katexCssAsset = . -}} +{{- end -}} + +{{- $s := newScratch -}} +{{- $isRemoteBase := or (strings.HasPrefix $katexBase "http://") (strings.HasPrefix $katexBase "https://") -}} + +{{- /* CSS retrieval consolidated: get raw CSS from either local asset or remote, then process once */ -}} +{{- $minSuffix := cond hugo.IsProduction ".min" "" -}} +{{- if $isRemoteBase -}} + {{- $cssPath := cond (ne $katexCssAsset "") $katexCssAsset (printf "katex%s.css" $minSuffix) -}} + {{- $katexCssUrl := urls.JoinPath $katexBase $cssPath -}} + {{- with try (resources.GetRemote $katexCssUrl) -}} + {{- with .Err -}} + {{- errorf "Could not retrieve KaTeX css file from %s. Reason: %s." $katexCssUrl . -}} + {{- else with .Value -}} + {{- $s.Set "katexCssValue" .Content -}} + {{- end -}} + {{- end -}} +{{- else if $katexCssAsset -}} + {{- with resources.Get $katexCssAsset -}} + {{- $s.Set "katexCssValue" .Content -}} + {{- else -}} + {{- errorf "KaTeX css asset not found at %q" $katexCssAsset -}} + {{- end -}} +{{- end -}} + +{{- with $s.Get "katexCssValue" -}} + {{- $cssContent := . -}} + {{- if $isRemoteBase -}} + {{- $fontPattern := "url(fonts/" -}} + {{- $fontSub := printf "url(%s/" (urls.JoinPath $katexBase "fonts") -}} + {{- $cssContent = strings.Replace $cssContent $fontPattern $fontSub -}} + {{- end -}} + {{- with resources.FromString (printf "css/katex%s.css" $minSuffix) $cssContent -}} + {{- $css := . | fingerprint "sha512" -}} + + {{- end -}} +{{- else -}} + {{- if not $isRemoteBase -}} + {{- $cssPath := cond (ne $katexCssAsset "") $katexCssAsset (printf "katex%s.css" $minSuffix) -}} + + {{- end -}} +{{- end -}} + +{{- /* Optionally publish files (fonts, css, js, etc.) from assets and emit tags for css/js with integrity and crossorigin */ -}} +{{- with site.Params.math.katex.assets -}} + {{- range . -}} + {{- with resources.Get . -}} + {{- $name := .Name | lower -}} + {{- if strings.HasSuffix $name ".css" -}} + {{- $built := . | fingerprint "sha512" -}} + + {{- else if or (strings.HasSuffix $name ".js") (strings.HasSuffix $name ".mjs") -}} + {{- $built := . | fingerprint "sha512" -}} + + {{- else -}} + {{- .Publish -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/themes/hextra/layouts/_partials/scripts/mathjax.html b/themes/hextra/layouts/_partials/scripts/mathjax.html new file mode 100644 index 0000000..a05b543 --- /dev/null +++ b/themes/hextra/layouts/_partials/scripts/mathjax.html @@ -0,0 +1,20 @@ +{{/* MathJax */}} +{{ $mathjaxJsUrl := "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js" -}} + + diff --git a/themes/hextra/layouts/_partials/scripts/medium-zoom.html b/themes/hextra/layouts/_partials/scripts/medium-zoom.html new file mode 100644 index 0000000..c11925c --- /dev/null +++ b/themes/hextra/layouts/_partials/scripts/medium-zoom.html @@ -0,0 +1,85 @@ +{{- /* Medium Zoom */ -}} + +{{- $zoomBase := "" -}} +{{- $useDefaultCdn := true -}} +{{- with site.Params.imageZoom.base -}} + {{- $zoomBase = . -}} + {{- $useDefaultCdn = false -}} +{{- end -}} + +{{- $zoomJsAsset := "" -}} +{{- with site.Params.imageZoom.js -}} + {{- $zoomJsAsset = . -}} +{{- end -}} + +{{- /* If only js is set without base, use local asset loading */ -}} +{{- if and $useDefaultCdn (ne $zoomJsAsset "") -}} + {{- $useDefaultCdn = false -}} +{{- end -}} + +{{- /* Set default CDN base if needed */ -}} +{{- if $useDefaultCdn -}} + {{- $zoomBase = "https://cdn.jsdelivr.net/npm/medium-zoom@latest/dist" -}} +{{- end -}} + +{{- $isRemoteBase := or (strings.HasPrefix $zoomBase "http://") (strings.HasPrefix $zoomBase "https://") -}} +{{- $minSuffix := cond hugo.IsProduction ".min" "" -}} + +{{- /* JS retrieval: get raw JS from either local asset or remote, then process */ -}} +{{- if $isRemoteBase -}} + {{- $jsPath := cond (ne $zoomJsAsset "") $zoomJsAsset (printf "medium-zoom%s.js" $minSuffix) -}} + {{- $zoomJsUrl := urls.JoinPath $zoomBase $jsPath -}} + {{- with try (resources.GetRemote $zoomJsUrl) -}} + {{- with .Err -}} + {{- errorf "Could not retrieve Medium Zoom js file from %s. Reason: %s." $zoomJsUrl . -}} + {{- else with .Value -}} + {{- with resources.Copy (printf "js/medium-zoom%s.js" $minSuffix) . -}} + {{- $zoomJs := . | fingerprint -}} + + {{- end -}} + {{- end -}} + {{- end -}} +{{- else if $zoomJsAsset -}} + {{- with resources.Get $zoomJsAsset -}} + {{- $zoomJs := . | fingerprint -}} + + {{- else -}} + {{- errorf "Medium Zoom js asset not found at %q" $zoomJsAsset -}} + {{- end -}} +{{- end -}} + + diff --git a/themes/hextra/layouts/_partials/scripts/mermaid.html b/themes/hextra/layouts/_partials/scripts/mermaid.html new file mode 100644 index 0000000..60bc230 --- /dev/null +++ b/themes/hextra/layouts/_partials/scripts/mermaid.html @@ -0,0 +1,79 @@ +{{- /* Mermaid */ -}} + +{{- $mermaidBase := "" -}} +{{- $useDefaultCdn := true -}} +{{- with site.Params.mermaid.base -}} + {{- $mermaidBase = . -}} + {{- $useDefaultCdn = false -}} +{{- end -}} + +{{- $mermaidJsAsset := "" -}} +{{- with site.Params.mermaid.js -}} + {{- $mermaidJsAsset = . -}} +{{- end -}} + +{{- /* If only js is set without base, use local asset loading */ -}} +{{- if and $useDefaultCdn (ne $mermaidJsAsset "") -}} + {{- $useDefaultCdn = false -}} +{{- end -}} + +{{- /* Set default CDN base if needed */ -}} +{{- if $useDefaultCdn -}} + {{- $mermaidBase = "https://cdn.jsdelivr.net/npm/mermaid@latest/dist" -}} +{{- end -}} + +{{- $isRemoteBase := or (strings.HasPrefix $mermaidBase "http://") (strings.HasPrefix $mermaidBase "https://") -}} +{{- $minSuffix := cond hugo.IsProduction ".min" "" -}} + +{{- /* JS retrieval: get raw JS from either local asset or remote, then process */ -}} +{{- if $isRemoteBase -}} + {{- $jsPath := cond (ne $mermaidJsAsset "") $mermaidJsAsset (printf "mermaid%s.js" $minSuffix) -}} + {{- $mermaidJsUrl := urls.JoinPath $mermaidBase $jsPath -}} + {{- with try (resources.GetRemote $mermaidJsUrl) -}} + {{- with .Err -}} + {{- errorf "Could not retrieve Mermaid js file from %s. Reason: %s." $mermaidJsUrl . -}} + {{- else with .Value -}} + {{- with resources.Copy (printf "js/mermaid%s.js" $minSuffix) . -}} + {{- $mermaidJs := . | fingerprint -}} + + {{- end -}} + {{- end -}} + {{- end -}} +{{- else if $mermaidJsAsset -}} + {{- with resources.Get $mermaidJsAsset -}} + {{- $mermaidJs := . | fingerprint -}} + + {{- else -}} + {{- errorf "Mermaid js asset not found at %q" $mermaidJsAsset -}} + {{- end -}} +{{- end -}} + + diff --git a/themes/hextra/layouts/_partials/scripts/search.html b/themes/hextra/layouts/_partials/scripts/search.html new file mode 100644 index 0000000..363b3bc --- /dev/null +++ b/themes/hextra/layouts/_partials/scripts/search.html @@ -0,0 +1,63 @@ +{{/* Search */}} +{{- if (site.Params.search.enable | default true) -}} + {{- $searchType := site.Params.search.type | default "flexsearch" -}} + {{- if eq $searchType "flexsearch" -}} + {{- $jsSearchScript := printf "%s.search.js" .Language.Lang -}} + {{- $jsSearch := resources.Get "js/flexsearch.js" | resources.ExecuteAsTemplate $jsSearchScript . -}} + {{- if hugo.IsProduction -}} + {{- $jsSearch = $jsSearch | minify | fingerprint -}} + {{- end -}} + + {{- $flexSearchBase := "" -}} + {{- $useDefaultCdn := true -}} + {{- with site.Params.search.flexsearch.base -}} + {{- $flexSearchBase = . -}} + {{- $useDefaultCdn = false -}} + {{- end -}} + + {{- $flexSearchJsAsset := "" -}} + {{- with site.Params.search.flexsearch.js -}} + {{- $flexSearchJsAsset = . -}} + {{- end -}} + + {{- /* If only js is set without base, use local asset loading. */ -}} + {{- if and $useDefaultCdn (ne $flexSearchJsAsset "") -}} + {{- $useDefaultCdn = false -}} + {{- end -}} + + {{- $bundleSuffix := cond hugo.IsProduction ".min" ".debug" -}} + {{- if $useDefaultCdn -}} + {{- $flexSearchVersion := site.Params.search.flexsearch.version | default "0.8.143" -}} + {{- $flexSearchBase = printf "https://cdn.jsdelivr.net/npm/flexsearch@%s/dist" $flexSearchVersion -}} + {{- end -}} + + {{- $isRemoteBase := or (strings.HasPrefix $flexSearchBase "http://") (strings.HasPrefix $flexSearchBase "https://") -}} + {{- if $isRemoteBase -}} + {{- $jsPath := cond (ne $flexSearchJsAsset "") $flexSearchJsAsset (printf "flexsearch.bundle%s.js" $bundleSuffix) -}} + {{- $flexSearchJsUrl := urls.JoinPath $flexSearchBase $jsPath -}} + {{- with try (resources.GetRemote $flexSearchJsUrl) -}} + {{- with .Err -}} + {{- errorf "Could not retrieve FlexSearch js file from %s. Reason: %s." $flexSearchJsUrl . -}} + {{- else with .Value -}} + {{- with resources.Copy "js/flexsearch.js" . -}} + {{- $flexSearchJs := . | fingerprint -}} + + {{- end -}} + {{- end -}} + {{- end -}} + {{- else if $flexSearchJsAsset -}} + {{- with resources.Get $flexSearchJsAsset -}} + {{- $flexSearchJs := . | fingerprint -}} + + {{- else -}} + {{- errorf "FlexSearch js asset not found at %q" $flexSearchJsAsset -}} + {{- end -}} + {{- else if not $useDefaultCdn -}} + {{- errorf "FlexSearch local loading requires params.search.flexsearch.js when using non-remote base %q" $flexSearchBase -}} + {{- end -}} + + + {{- else -}} + {{- warnf `search type "%s" is not supported` $searchType -}} + {{- end -}} +{{- end -}} diff --git a/themes/hextra/layouts/_partials/search.html b/themes/hextra/layouts/_partials/search.html new file mode 100644 index 0000000..9e372e8 --- /dev/null +++ b/themes/hextra/layouts/_partials/search.html @@ -0,0 +1,30 @@ +{{- $placeholder := (T "searchPlaceholder") | default "Search..." -}} + + +
+
+ + + CTRL K + +
+ +
+
    +
    +
    +
    diff --git a/themes/hextra/layouts/_partials/shortcodes/badge.html b/themes/hextra/layouts/_partials/shortcodes/badge.html new file mode 100644 index 0000000..035d91a --- /dev/null +++ b/themes/hextra/layouts/_partials/shortcodes/badge.html @@ -0,0 +1,38 @@ +{{- $content := .content -}} +{{- $color := .color | default .type | default "" -}}{{- /* Compatibility with previous parameter. */ -}} +{{- $class := .class | default "" -}} +{{- $border := .border | default false -}} +{{- $icon := .icon | default "" -}} + +{{- /* Compatibility with previous names. */ -}} +{{- $mapping := (dict + "default" "gray" + "tip" "green" + "info" "blue" + "warning" "yellow" + "error" "red" + "important" "purple" + ) +-}} +{{- $color = index $mapping $color | default $color | default "gray" -}} + +{{- $styleClass := newScratch -}} +{{- $styleClass.Set "gray" "hx:text-gray-600 hx:bg-gray-100 hx:dark:bg-neutral-800 hx:dark:text-neutral-200 hx:border-gray-200 hx:dark:border-neutral-700" -}} +{{- $styleClass.Set "purple" "hx:border-purple-200 hx:bg-purple-100 hx:text-purple-900 hx:dark:border-purple-200/30 hx:dark:bg-purple-900/30 hx:dark:text-purple-200" -}} +{{- $styleClass.Set "indigo" "hx:border-indigo-200 hx:bg-indigo-100 hx:text-indigo-900 hx:dark:border-indigo-200/30 hx:dark:bg-indigo-900/30 hx:dark:text-indigo-200" -}} +{{- $styleClass.Set "blue" "hx:border-blue-200 hx:bg-blue-100 hx:text-blue-900 hx:dark:border-blue-200/30 hx:dark:bg-blue-900/30 hx:dark:text-blue-200" -}} +{{- $styleClass.Set "green" "hx:border-green-200 hx:bg-green-100 hx:text-green-900 hx:dark:border-green-200/30 hx:dark:bg-green-900/30 hx:dark:text-green-200" -}} +{{- $styleClass.Set "yellow" "hx:border-yellow-100 hx:bg-yellow-50 hx:text-yellow-900 hx:dark:border-yellow-200/30 hx:dark:bg-yellow-700/30 hx:dark:text-yellow-200" -}} +{{- $styleClass.Set "orange" "hx:border-orange-100 hx:bg-orange-50 hx:text-orange-800 hx:dark:border-orange-400/30 hx:dark:bg-orange-400/20 hx:dark:text-orange-300" -}} +{{- $styleClass.Set "amber" "hx:border-amber-200 hx:bg-amber-100 hx:text-amber-900 hx:dark:border-amber-200/30 hx:dark:bg-amber-900/30 hx:dark:text-amber-200" -}} +{{- $styleClass.Set "red" "hx:border-red-200 hx:bg-red-100 hx:text-red-900 hx:dark:border-red-200/30 hx:dark:bg-red-900/30 hx:dark:text-red-200" -}} + +{{- $borderClass := cond (eq $border true) "hx:border" "" -}} +{{- $badgeClass := or ($styleClass.Get $color) ($styleClass.Get "gray") -}} +
    +
    + {{- with $icon -}}{{- partial "utils/icon" (dict "name" . "attributes" "height=12") -}}{{- end -}} + {{- $content -}} +
    +
    +{{- /* Strip trailing newline. */ -}} diff --git a/themes/hextra/layouts/_partials/shortcodes/callout.html b/themes/hextra/layouts/_partials/shortcodes/callout.html new file mode 100644 index 0000000..176fdfe --- /dev/null +++ b/themes/hextra/layouts/_partials/shortcodes/callout.html @@ -0,0 +1,28 @@ +{{- $content := .content -}} +{{- $emoji := .emoji -}} +{{- $icon := .icon -}} + +{{- $defaultClass := "hx:border-orange-100 hx:bg-orange-50 hx:text-orange-800 hx:dark:border-orange-400/30 hx:dark:bg-orange-400/20 hx:dark:text-orange-300" -}} + +{{- $class := .class | default $defaultClass -}} + + +
    +
    + {{- with $emoji -}} +
    + {{- . -}} +
    + {{- else -}} + {{- with $icon -}} + {{ partial "utils/icon.html" (dict "name" . "attributes" `height=1.2em class="hx:inline-block hx:align-middle"`) -}} + {{- end -}} + {{- end -}} +
    + +
    +
    + {{- $content -}} +
    +
    +
    diff --git a/themes/hextra/layouts/_partials/shortcodes/card.html b/themes/hextra/layouts/_partials/shortcodes/card.html new file mode 100644 index 0000000..514d522 --- /dev/null +++ b/themes/hextra/layouts/_partials/shortcodes/card.html @@ -0,0 +1,70 @@ +{{- $link := .link -}} +{{- $title := .title -}} +{{- $icon := .icon -}} +{{- $subtitle := .subtitle -}} +{{- $image := .image -}} +{{- $alt := .alt | default $title -}} +{{- $width := .width -}} +{{- $height := .height -}} +{{- $imageStyle := .imageStyle -}} +{{- $tag := .tag -}} +{{- $tagColor := .tagColor | default .tagType | default "" -}}{{- /* Compatibility with previous parameter. */ -}} +{{- $tagBorder := not (eq .tagBorder false) | default true }} +{{- $tagIcon := .tagIcon -}} + +{{ $linkClass := "hx:hover:border-gray-300 hx:bg-transparent hx:shadow-xs hx:dark:border-neutral-800 hx:hover:bg-slate-50 hx:hover:shadow-md hx:dark:hover:border-neutral-700 hx:dark:hover:bg-neutral-900" }} +{{- with $image -}} + {{ $linkClass = "hx:hover:border-gray-300 hx:bg-gray-100 hx:shadow-sm hx:dark:border-neutral-700 hx:dark:bg-neutral-800 hx:dark:text-gray-50 hx:hover:shadow-lg hx:dark:hover:border-neutral-500 hx:dark:hover:bg-neutral-700" }} +{{- end -}} + +{{- $external := strings.HasPrefix $link "http" -}} +{{- $href := cond (strings.HasPrefix $link "/") ($link | relURL) $link -}} + + + + {{- with $image -}} + {{ $alt }} + {{- end -}} + + {{- $padding := "hx:p-4" -}} + {{- with $subtitle -}} + {{- $padding = "hx:pt-4 hx:px-4" -}} + {{- end -}} + +
    + + {{- with $icon }}{{ partial "utils/icon.html" (dict "name" $icon) -}}{{- end -}} + {{- $title -}} + + {{- with $subtitle -}} +
    {{- $subtitle | markdownify -}}
    + {{- end -}} +
    + + {{- if $tag }} + {{- partial "shortcodes/badge.html" (dict + "content" $tag + "color" $tagColor + "class" "hextra-card-tag" + "border" $tagBorder + "icon" $tagIcon + ) + -}} + {{- end -}} +
    +{{- /* Strip trailing newline. */ -}} diff --git a/themes/hextra/layouts/_partials/shortcodes/cards.html b/themes/hextra/layouts/_partials/shortcodes/cards.html new file mode 100644 index 0000000..dfd808b --- /dev/null +++ b/themes/hextra/layouts/_partials/shortcodes/cards.html @@ -0,0 +1,6 @@ +{{- $cols := .cols | default 3 -}} +{{- $content := .content -}} + +
    + {{- $content -}} +
    diff --git a/themes/hextra/layouts/_partials/shortcodes/tabs.html b/themes/hextra/layouts/_partials/shortcodes/tabs.html new file mode 100644 index 0000000..a4f31d8 --- /dev/null +++ b/themes/hextra/layouts/_partials/shortcodes/tabs.html @@ -0,0 +1,48 @@ +{{- $tabsID := .id }} + +{{- /* +The `tabs` parameter is a list of dict with the following keys: + - `id`: (int) the ID of the tab (the Ordinal of the tab shortcode). + - `name`: (string) the name of the tab (the title). + - `icon`: (string) the icon of the tab. + - `content`: (string) the content of the tab. + - `selected`: (bool) whether the tab is selected. +*/ -}} +{{- $tabs := .tabs }} + +{{- if eq (len $tabs) 0 -}} + {{ errorf "tabs must have at least one tab" }} +{{- end -}} + +{{- $enableSync := .enableSync }} + +{{- /* Create group data for syncing and select the first tab if none is selected. */ -}} +{{- $selectedIndex := 0 -}} +{{ $dataTabGroup := slice -}} + +{{- range $i, $item := $tabs -}} + {{- $dataTabGroup = $dataTabGroup | append ($item.name) -}} + + {{- if $item.selected -}} + {{- $selectedIndex = $i -}} + {{- end -}} +{{- end -}} + +{{- /* Generate a unique ID for each tab group. */ -}} +{{- $globalID := printf "tabs-%02v" $tabsID -}} + +{{- /* Keep HTML on single lines to avoid `>` being parsed as blockquote when nested in steps (#876) */ -}} +
    +
    + {{- range $i, $item := $tabs -}} + + {{- end -}} +
    +
    +
    + {{- range $i, $item := $tabs -}} +
    + {{- $item.content | markdownify -}} +
    + {{- end -}} +
    diff --git a/themes/hextra/layouts/_partials/sidebar.html b/themes/hextra/layouts/_partials/sidebar.html new file mode 100644 index 0000000..4dbba23 --- /dev/null +++ b/themes/hextra/layouts/_partials/sidebar.html @@ -0,0 +1,325 @@ +{{- $context := .context -}} + +{{- $disableSidebar := .disableSidebar | default false -}} +{{- $displayPlaceholder := .displayPlaceholder | default false -}} + +{{- $navRoot := cond (eq site.Home.Type "docs") site.Home $context.FirstSection -}} +{{- $pageURL := $context.RelPermalink -}} + +{{- if .context.Params.sidebar.hide -}} + {{- $disableSidebar = true -}} + {{- $displayPlaceholder = false -}} +{{- end -}} + +{{- $sidebarClass := "hx:md:sticky" -}} +{{- if $disableSidebar -}} + {{- if $displayPlaceholder -}} + {{- $sidebarClass = "hx:md:hidden hx:xl:block" -}} + {{- else -}} + {{- $sidebarClass = "hx:md:hidden" -}} + {{- end -}} +{{- end -}} + + + +{{- define "sidebar-main" -}} + {{ template "sidebar-tree" (dict "context" .context "level" 0 "page" .page "pageURL" .pageURL "toc" (.toc | default false)) }} +{{- end -}} + +{{- define "sidebar-tree" -}} + {{- if ge .level 4 -}} + {{- return -}} + {{- end -}} + + {{- $context := .context -}} + {{- $page := .page }} + {{- $pageURL := .page.RelPermalink -}} + {{- $level := .level -}} + {{- $toc := .toc | default false -}} + {{- $useMainMenu := and (eq $level 0) $toc -}} + {{- $mainMenuEntries := slice -}} + + {{- $items := where (union .context.RegularPages .context.Sections) "Params.sidebar.exclude" "!=" true -}} + {{- if $useMainMenu -}} + {{- range $menuItem := site.Menus.main -}} + {{- $menuType := $menuItem.Params.type | default "" -}} + {{- $isIconOnly := and $menuItem.Params.icon (ne $menuType "link") -}} + {{- /* Keep only navigation links in the mobile sidebar. */ -}} + {{- if or (eq $menuType "search") (eq $menuType "theme-toggle") (eq $menuType "language-switch") $isIconOnly -}} + {{- continue -}} + {{- end -}} + + {{- $menuTitle := or (T $menuItem.Identifier) $menuItem.Name -}} + {{- /* Dropdown parents mirror navbar behavior: render a labeled group of child links. */ -}} + {{- if $menuItem.HasChildren -}} + {{- $childEntries := slice -}} + {{- range $childItem := $menuItem.Children -}} + {{- $childType := $childItem.Params.type | default "" -}} + {{- $childIsIconOnly := and $childItem.Params.icon (ne $childType "link") -}} + {{- if or (eq $childType "search") (eq $childType "theme-toggle") (eq $childType "language-switch") $childIsIconOnly -}} + {{- continue -}} + {{- end -}} + + {{- $childTitle := or (T $childItem.Identifier) $childItem.Name -}} + {{- $childPage := $childItem.Page -}} + {{- with $childItem.PageRef -}} + {{- with $page.Site.GetPage . -}} + {{- $childPage = . -}} + {{- end -}} + {{- end -}} + {{- with $childPage -}} + {{- if ne .Params.sidebar.exclude true -}} + {{- $childEntries = $childEntries | append (dict "title" $childTitle "link" .RelPermalink) -}} + {{- end -}} + {{- continue -}} + {{- end -}} + + {{- $childLink := $childItem.URL -}} + {{- with $childItem.PageRef -}} + {{- if hasPrefix . "/" -}} + {{- $childLink = relLangURL (strings.TrimPrefix "/" .) -}} + {{- end -}} + {{- end -}} + {{- if $childLink -}} + {{- $childEntries = $childEntries | append (dict "title" $childTitle "link" $childLink) -}} + {{- end -}} + {{- end -}} + + {{- if gt (len $childEntries) 0 -}} + {{- $mainMenuEntries = $mainMenuEntries | append (dict "type" "group" "title" $menuTitle "children" $childEntries) -}} + {{- end -}} + {{- continue -}} + {{- end -}} + + {{- /* Normalize page-backed entries so we keep nested tree behavior. */ -}} + {{- $menuPage := $menuItem.Page -}} + {{- with $menuItem.PageRef -}} + {{- with $page.Site.GetPage . -}} + {{- $menuPage = . -}} + {{- end -}} + {{- end -}} + {{- with $menuPage -}} + {{- if ne .Params.sidebar.exclude true -}} + {{- $mainMenuEntries = $mainMenuEntries | append (dict "type" "page" "item" . "title" $menuTitle) -}} + {{- end -}} + {{- continue -}} + {{- end -}} + + {{- $link := $menuItem.URL -}} + {{- with $menuItem.PageRef -}} + {{- if hasPrefix . "/" -}} + {{- $link = relLangURL (strings.TrimPrefix "/" .) -}} + {{- end -}} + {{- end -}} + {{- if $link -}} + {{- $mainMenuEntries = $mainMenuEntries | append (dict "type" "url" "link" $link "title" $menuTitle) -}} + {{- end -}} + {{- end -}} + {{- end -}} + + {{- $useMainMenuEntries := and $useMainMenu (gt (len $mainMenuEntries) 0) -}} + {{- $hasItems := or (gt (len $items) 0) $useMainMenuEntries -}} + + {{- if $hasItems -}} + {{- if eq $level 0 -}} + {{- if $useMainMenuEntries -}} + {{- /* Mixed list: page entries render trees; url entries render leaf links. */ -}} + {{- range $entry := $mainMenuEntries -}} + {{- if eq (index $entry "type") "page" -}} + {{- $item := index $entry "item" -}} + {{- if $item.Params.sidebar.separator -}} +
  • + {{ index $entry "title" }} +
  • + {{- else -}} + {{- $active := eq (strings.TrimSuffix "/" $pageURL) (strings.TrimSuffix "/" $item.RelPermalink) -}} + {{- $shouldOpen := or ($item.Params.sidebar.open) ($item.IsAncestor $page) $active | default true }} +
  • + {{- template "sidebar-item-link" dict "context" $item "active" $active "open" $shouldOpen "title" (index $entry "title") "link" $item.RelPermalink -}} + {{- if and $toc $active (ne $item.Params.toc false) -}} + {{- template "sidebar-toc" dict "page" $item -}} + {{- end -}} + {{- template "sidebar-tree" dict "context" $item "page" $page "pageURL" $pageURL "level" (add $level 1) "toc" $toc -}} +
  • + {{- end -}} + {{- else if eq (index $entry "type") "group" -}} +
  • +
    + + {{- index $entry "title" -}} + +
    +
    +
      + {{- range $child := index $entry "children" -}} + {{- $link := index $child "link" -}} + {{- $active := eq (strings.TrimSuffix "/" $pageURL) (strings.TrimSuffix "/" $link) -}} +
    • + {{- template "sidebar-item-link" dict "active" $active "open" false "title" (index $child "title") "link" $link -}} +
    • + {{- end -}} +
    +
    +
  • + {{- else -}} + {{- $link := index $entry "link" -}} + {{- $active := eq (strings.TrimSuffix "/" $pageURL) (strings.TrimSuffix "/" $link) -}} +
  • {{ template "sidebar-item-link" dict "active" $active "open" false "title" (index $entry "title") "link" $link }}
  • + {{- end -}} + {{- end -}} + {{- else -}} + {{- range $items.ByWeight }} + {{- if .Params.sidebar.separator -}} +
  • + {{ partial "utils/title" . }} +
  • + {{- else -}} + {{- $active := eq $pageURL .RelPermalink -}} + {{- $shouldOpen := or (.Params.sidebar.open) (.IsAncestor $page) $active | default true }} +
  • + {{- $linkTitle := partial "utils/title" . -}} + {{- template "sidebar-item-link" dict "context" . "active" $active "open" $shouldOpen "title" $linkTitle "link" .RelPermalink -}} + {{- if and $toc $active (ne .Params.toc false) -}} + {{- template "sidebar-toc" dict "page" . -}} + {{- end -}} + {{- template "sidebar-tree" dict "context" . "page" $page "pageURL" $pageURL "level" (add $level 1) "toc" $toc -}} +
  • + {{- end -}} + {{- end -}} + {{- end -}} + {{- else -}} +
    +
      + {{- range $items.ByWeight }} + {{- $active := eq $pageURL .RelPermalink -}} + {{- $shouldOpen := or (.Params.sidebar.open) (.IsAncestor $page) $active | default true }} + {{- $linkTitle := partial "utils/title" . -}} +
    • + {{- template "sidebar-item-link" dict "context" . "active" $active "open" $shouldOpen "title" $linkTitle "link" .RelPermalink -}} + {{- if and $toc $active (ne .Params.toc false) -}} + {{ template "sidebar-toc" dict "page" . }} + {{- end }} + {{ template "sidebar-tree" dict "context" . "page" $page "pageURL" $pageURL "level" (add $level 1) "toc" $toc }} +
    • + {{- end -}} +
    +
    + {{- end -}} + {{- end -}} +{{- end -}} + +{{- define "sidebar-toc" -}} + {{ $page := .page }} + {{ with $page.Fragments.Headings }} + + {{ end }} +{{- end -}} + +{{- define "sidebar-footer" -}} + {{- range site.Menus.sidebar -}} + {{- $name := or (T .Identifier) .Name -}} + {{ if eq .Params.type "separator" }} +
  • + {{ $name }} +
  • + {{ else }} + {{- $link := .URL -}} + {{- with .PageRef -}} + {{- if hasPrefix . "/" -}} + {{- $link = relLangURL (strings.TrimPrefix "/" .) -}} + {{- end -}} + {{- end -}} +
  • {{ template "sidebar-item-link" dict "active" false "open" false "title" $name "link" $link }}
  • + {{ end }} + {{- end -}} +{{- end -}} + +{{- define "sidebar-item-link" -}} + {{- $external := strings.HasPrefix .link "http" -}} + {{- $open := .open | default true -}} + {{- $hasChildren := false -}} + {{- $linkClass := "hx:flex hx:items-center hx:justify-between hx:gap-2 hx:grow hx:cursor-pointer hx:rounded-sm hx:px-2 hx:py-1.5 hx:text-sm hx:transition-colors [-webkit-tap-highlight-color:transparent] [-webkit-touch-callout:none] hx:hextra-focus-visible-inset" -}} + {{- with .context }}{{ if or .RegularPages .Sections }}{{ $hasChildren = true }}{{ end }}{{ end -}} + {{- if $hasChildren -}} + {{- $linkClass = printf "%s hx:ltr:pr-8 hx:rtl:pl-8" $linkClass -}} + {{- end -}} + {{- if .active -}} + {{- $linkClass = printf "%s hextra-sidebar-active-item hx:bg-primary-100 hx:font-semibold hx:text-primary-800 hx:contrast-more:border hx:contrast-more:border-primary-500 hx:dark:bg-primary-400/10 hx:dark:text-primary-600 hx:contrast-more:dark:border-primary-500" $linkClass -}} + {{- else -}} + {{- $linkClass = printf "%s hx:text-gray-500 hx:hover:bg-gray-100 hx:hover:text-gray-900 hx:contrast-more:border hx:contrast-more:border-transparent hx:contrast-more:text-gray-900 hx:contrast-more:hover:border-gray-900 hx:dark:text-neutral-400 hx:dark:hover:bg-primary-100/5 hx:dark:hover:text-gray-50 hx:contrast-more:dark:text-gray-50 hx:contrast-more:dark:hover:border-gray-50" $linkClass -}} + {{- end -}} +
    + + {{- .title -}} + + {{- if $hasChildren }} + + {{- end }} +
    +{{- end -}} + +{{- define "sidebar-collapsible-button" -}} + +{{- end -}} diff --git a/themes/hextra/layouts/_partials/tags.html b/themes/hextra/layouts/_partials/tags.html new file mode 100644 index 0000000..446fb9d --- /dev/null +++ b/themes/hextra/layouts/_partials/tags.html @@ -0,0 +1,7 @@ +{{- $context := .context -}} + +{{- range $tag := $context.Params.tags -}} + {{- with $context.Site.GetPage (printf "/tags/%s" $tag) -}} + #{{ $tag }} + {{- end -}} +{{- end -}} diff --git a/themes/hextra/layouts/_partials/theme-toggle.html b/themes/hextra/layouts/_partials/theme-toggle.html new file mode 100644 index 0000000..e7b72ea --- /dev/null +++ b/themes/hextra/layouts/_partials/theme-toggle.html @@ -0,0 +1,83 @@ +{{- $hideLabel := .hideLabel -}} +{{- $iconHeight := .iconHeight | default 12 -}} +{{- $class := .class | default "hx:h-7 hx:px-2 hx:text-xs hx:hover:bg-gray-100 hx:hover:text-gray-900 hx:dark:hover:bg-primary-100/5 hx:dark:hover:text-gray-50 hx:font-medium hx:text-gray-600 hx:transition-colors hx:dark:text-gray-400" -}} +{{- $location := .location | default "bottom" -}} + +{{- $changeTheme := (T "changeTheme") | default "Change theme" -}} +{{- $light := (T "light") | default "Light" -}} +{{- $dark := (T "dark") | default "Dark" -}} +{{- $system := (T "system") | default "System" -}} + +
    + + +
    diff --git a/themes/hextra/layouts/_partials/toc.html b/themes/hextra/layouts/_partials/toc.html new file mode 100644 index 0000000..8084b45 --- /dev/null +++ b/themes/hextra/layouts/_partials/toc.html @@ -0,0 +1,91 @@ +{{/* Table of Contents */}} +{{/* TODO: toc bottom part should be able to hide */}} +{{- $toc := .Params.toc | default true -}} +{{- $onThisPage := (T "onThisPage") | default "On this page"}} +{{- $tags := (T "tags") | default "Tags"}} +{{- $editThisPage := (T "editThisPage") | default "Edit this page"}} +{{- $backToTop := (T "backToTop") | default "Scroll to top" -}} + + + +{{/* TOC subheadings component. This is a recursive component that renders a list of headings. */}} +{{- define "toc-subheading" -}} + {{- $headings := .headings -}} + {{- $level := .level -}} + {{- if ge $level 6 -}} + {{ return }} + {{- end -}} + + {{- $padding := (mul $level 4) -}} + {{- $class := cond (eq $level 0) "hx:font-medium" (printf "hx:ltr:pl-%d hx:rtl:pr-%d" $padding $padding) -}} + + {{- range $headings }} + {{- if .Title }} +
  • + + {{- .Title | safeHTML | plainify | htmlUnescape }} + +
  • + {{- end -}} + {{- with .Headings -}} + {{ template "toc-subheading" (dict "headings" . "level" (add $level 1)) }} + {{- end -}} + + {{- end -}} +{{- end -}} diff --git a/themes/hextra/layouts/_partials/utils/extract-headings.html b/themes/hextra/layouts/_partials/utils/extract-headings.html new file mode 100644 index 0000000..976d76e --- /dev/null +++ b/themes/hextra/layouts/_partials/utils/extract-headings.html @@ -0,0 +1,31 @@ +{{- /* +Extracts all headings from a page and adds them to the scratchpad. + +The keys can be obtained from the scratchpad by using the "keys" key. +The titles can be obtained from the scratchpad by using the "titles" key. + +The scratchpad must be initialized with empty slices before calling this function for the keys "keys" and "titles" + +@param {any} target The element to extract headings from. +@param {any} scratch The scratchpad to add the keys and titles to. + +@example {{ partial "utils/extract-headings.html" (dict "target" $h1 "scratch" $s) }} +*/ -}} + +{{- range $heading := index .target.Headings -}} + {{- if and (eq $heading.Level 0) (not $heading.Title) -}} + {{- $.scratch.Add "keys" (slice $heading.Title) -}} + {{- else -}} + {{- $key := (printf "%s#%s" $heading.ID $heading.Title) -}} + {{- $.scratch.Add "keys" (slice $key) -}} + {{- end -}} + + {{- $title := (printf "%s" $heading.Level $heading.Title) | htmlUnescape -}} + {{- $.scratch.Add "titles" (slice $title) -}} + + {{- partial "utils/extract-headings.html" (dict + "target" $heading + "scratch" $.scratch + ) + }} +{{- end -}} diff --git a/themes/hextra/layouts/_partials/utils/file-path.html b/themes/hextra/layouts/_partials/utils/file-path.html new file mode 100644 index 0000000..1794551 --- /dev/null +++ b/themes/hextra/layouts/_partials/utils/file-path.html @@ -0,0 +1,21 @@ +{{/* This utility is used to get the file path from absolute, relative path or URL. */}} + +{{- $path := .path -}} +{{- $page := .page -}} + +{{- $isLocal := not (urls.Parse $path).Scheme -}} +{{- $isPage := and (eq $page.Kind "page") (not $page.BundleType) -}} +{{- $startsWithSlash := hasPrefix $path "/" -}} +{{- $startsWithRelative := hasPrefix $path "../" -}} + +{{- if and $path $isLocal -}} + {{- if $startsWithSlash -}} + {{/* File under static directory */}} + {{- $path = (relURL (strings.TrimPrefix "/" $path)) -}} + {{- else if and $isPage (not $startsWithRelative) -}} + {{/* File is a sibling to the individual page file */}} + {{ $path = (printf "../%s" $path) }} + {{- end -}} +{{- end -}} + +{{- return $path -}} diff --git a/themes/hextra/layouts/_partials/utils/format-date.html b/themes/hextra/layouts/_partials/utils/format-date.html new file mode 100644 index 0000000..94cbdc4 --- /dev/null +++ b/themes/hextra/layouts/_partials/utils/format-date.html @@ -0,0 +1,3 @@ +{{- with . -}} + {{- . | time.Format (site.Params.dateFormat | default ":date_long") -}} +{{- end -}} diff --git a/themes/hextra/layouts/_partials/utils/fragments.html b/themes/hextra/layouts/_partials/utils/fragments.html new file mode 100644 index 0000000..511a4dd --- /dev/null +++ b/themes/hextra/layouts/_partials/utils/fragments.html @@ -0,0 +1,93 @@ +{{- /* + fragments.html - Split page content into searchable fragments + + This partial processes a Hugo page and splits its content into fragments based on headings, + creating a data structure suitable for search indexing. It supports different fragment types + and handles hierarchical heading structures (h1, h2). + + Parameters: + - .context (Page): The Hugo page to process + - .type (string): Fragment type - "content" (default), "heading", "title", or "summary" + + Returns: + - dict: Map of heading keys to content fragments + + Example: + Input page with content: + # Introduction + This is the intro text. + ## Setup + Setup instructions here. + # Configuration + Config details here. + + Output (type "content"): + { + "": "This is the intro text.", + "intro#Introduction": "This is the intro text. Setup instructions here.", + "setup#Setup": "Setup instructions here.", + "config#Configuration": "Config details here." + } + + Fragment types: + - "content": Splits page content by headings (default) + - "heading": Returns heading keys with empty content + - "title": Returns empty content (title handled elsewhere) + - "summary": Returns page summary only +*/ -}} + +{{- /* Extract page context and fragment type */ -}} +{{- $page := .context -}} +{{- $type := .type | default "content" -}} + +{{- /* Process all headings */ -}} +{{- $s := newScratch -}} +{{- $s.Set "keys" slice -}} +{{- $s.Set "titles" slice -}} + +{{- partial "utils/extract-headings.html" (dict "target" $page.Fragments "scratch" $s) -}} + +{{- $headingKeys := $s.Get "keys" -}} +{{- $headingTitles := $s.Get "titles" -}} + +{{- $content := $page.Content | htmlUnescape -}} +{{- $len := len $headingKeys -}} +{{- $data := dict -}} + +{{ if eq $type "content" }} + {{/* Include full content of the page */}} + {{ if eq $len 0 }} + {{ $data = $data | merge (dict "" ($page.Plain | htmlUnescape | strings.TrimSpace)) }} + {{ else }} + {{/* Split the raw content from bottom to top */}} + {{ range seq $len }} + {{ $i := sub $len . }} + {{ $headingKey := index $headingKeys $i }} + {{ $headingTitle := index $headingTitles $i }} + + {{ if eq $i 0 }} + {{ $data = $data | merge (dict $headingKey ($content | plainify | htmlUnescape | strings.TrimSpace)) }} + {{ else }} + {{ $parts := split $content (printf "%s" $headingTitle) }} + {{ $lastPart := index $parts (sub (len $parts) 1) }} + + {{ $data = $data | merge (dict $headingKey ($lastPart | plainify | htmlUnescape | strings.TrimSpace)) }} + {{ $content = strings.TrimSuffix $lastPart $content }} + {{ $content = strings.TrimSuffix (printf "%s" $headingTitle) $content }} + {{ end }} + {{ end }} + {{ end }} +{{ else if (eq $type "heading" ) }} + {{/* Put heading keys with empty content to the data object */}} + {{ $data = dict "" "" }} + {{ range $headingKeys }} + {{ $data = $data | merge (dict . "") }} + {{ end }} +{{ else if (eq $type "title") }} + {{/* Use empty data object since title is included in search-data.json */}} + {{ $data = $data | merge (dict "" "") }} +{{ else if (eq $type "summary" ) }} + {{ $data = $data | merge (dict "" ($page.Summary | plainify | htmlUnescape | strings.TrimSpace)) }} +{{ end }} + +{{ return $data }} diff --git a/themes/hextra/layouts/_partials/utils/hugo-compat/language-direction.html b/themes/hextra/layouts/_partials/utils/hugo-compat/language-direction.html new file mode 100644 index 0000000..24a65c2 --- /dev/null +++ b/themes/hextra/layouts/_partials/utils/hugo-compat/language-direction.html @@ -0,0 +1,15 @@ +{{/* +Returns the language direction using the supported Hugo API for the running version. + +Hugo v0.158.0 deprecated Language.LanguageDirection in favor of Language.Direction. +Keep the fallback so Hextra can continue supporting Hugo >= 0.146.0. +*/}} +{{- $language := . -}} +{{- $direction := "" -}} +{{- if ge (hugo.Version) "0.158.0" -}} + {{- $direction = $language.Direction -}} +{{- else -}} + {{- $direction = $language.LanguageDirection -}} +{{- end -}} + +{{- return $direction -}} diff --git a/themes/hextra/layouts/_partials/utils/hugo-compat/language-label.html b/themes/hextra/layouts/_partials/utils/hugo-compat/language-label.html new file mode 100644 index 0000000..b6a58bb --- /dev/null +++ b/themes/hextra/layouts/_partials/utils/hugo-compat/language-label.html @@ -0,0 +1,15 @@ +{{/* +Returns the language label using the supported Hugo API for the running version. + +Hugo v0.158.0 deprecated Language.LanguageName in favor of Language.Label. +Keep the fallback so Hextra can continue supporting Hugo >= 0.146.0. +*/}} +{{- $language := . -}} +{{- $label := "" -}} +{{- if ge (hugo.Version) "0.158.0" -}} + {{- $label = $language.Label -}} +{{- else -}} + {{- $label = $language.LanguageName -}} +{{- end -}} + +{{- return $label -}} diff --git a/themes/hextra/layouts/_partials/utils/hugo-compat/language-locale.html b/themes/hextra/layouts/_partials/utils/hugo-compat/language-locale.html new file mode 100644 index 0000000..1600218 --- /dev/null +++ b/themes/hextra/layouts/_partials/utils/hugo-compat/language-locale.html @@ -0,0 +1,15 @@ +{{/* +Returns the language locale using the supported Hugo API for the running version. + +Hugo v0.158.0 deprecated Language.LanguageCode in favor of Language.Locale. +Keep the fallback so Hextra can continue supporting Hugo >= 0.146.0. +*/}} +{{- $language := . -}} +{{- $locale := "" -}} +{{- if ge (hugo.Version) "0.158.0" -}} + {{- $locale = $language.Locale -}} +{{- else -}} + {{- $locale = $language.LanguageCode -}} +{{- end -}} + +{{- return $locale -}} diff --git a/themes/hextra/layouts/_partials/utils/hugo-compat/site-data.html b/themes/hextra/layouts/_partials/utils/hugo-compat/site-data.html new file mode 100644 index 0000000..7ba0511 --- /dev/null +++ b/themes/hextra/layouts/_partials/utils/hugo-compat/site-data.html @@ -0,0 +1,14 @@ +{{/* +Returns site data using the supported Hugo API for the running version. + +Hugo v0.156.0 deprecated site.Data / .Site.Data in favor of hugo.Data. +Keep the fallback so Hextra can continue supporting Hugo >= 0.146.0. +*/}} +{{- $siteData := dict -}} +{{- if ge (hugo.Version) "0.156.0" -}} + {{- $siteData = hugo.Data -}} +{{- else -}} + {{- $siteData = site.Data -}} +{{- end -}} + +{{- return $siteData -}} diff --git a/themes/hextra/layouts/_partials/utils/hugo-compat/sites.html b/themes/hextra/layouts/_partials/utils/hugo-compat/sites.html new file mode 100644 index 0000000..3f91c7b --- /dev/null +++ b/themes/hextra/layouts/_partials/utils/hugo-compat/sites.html @@ -0,0 +1,14 @@ +{{/* +Returns all sites using the supported Hugo API for the running version. + +Hugo v0.156.0 deprecated site.Sites / page.Sites in favor of hugo.Sites. +Keep the fallback so Hextra can continue supporting Hugo >= 0.146.0. +*/}} +{{- $sites := slice -}} +{{- if ge (hugo.Version) "0.156.0" -}} + {{- $sites = hugo.Sites -}} +{{- else -}} + {{- $sites = site.Sites -}} +{{- end -}} + +{{- return $sites -}} diff --git a/themes/hextra/layouts/_partials/utils/icon.html b/themes/hextra/layouts/_partials/utils/icon.html new file mode 100644 index 0000000..fc31d38 --- /dev/null +++ b/themes/hextra/layouts/_partials/utils/icon.html @@ -0,0 +1,79 @@ +{{/* Render raw svg icon from site data */}} +{{- $siteData := partial "utils/hugo-compat/site-data.html" . -}} +{{- $name := .name -}} +{{- $icon := index $siteData.icons $name -}} +{{- $isRemoteIcon := false -}} + +{{- if not $icon -}} + {{- $remoteProvider := "" -}} + {{- $remoteName := "" -}} + {{- if strings.Contains $name ":" -}} + {{- $parts := split $name ":" -}} + {{- if eq (len $parts) 2 -}} + {{- $remoteProvider = index $parts 0 -}} + {{- $remoteName = index $parts 1 -}} + {{- end -}} + {{- end -}} + + {{- if and $remoteProvider $remoteName -}} + {{- $remoteEnabled := true -}} + {{- $remoteProviders := dict + "lucide" (dict "url" "https://unpkg.com/lucide-static@1/icons/%s.svg") + "tabler" (dict "url" "https://unpkg.com/@tabler/icons@3/icons/outline/%s.svg") + "simple" (dict "url" "https://cdn.jsdelivr.net/npm/simple-icons@16/icons/%s.svg") + -}} + {{- with site.Params.icons.remote -}} + {{- if isset . "enable" -}} + {{- $remoteEnabled = .enable -}} + {{- end -}} + {{- with .providers -}} + {{- $remoteProviders = merge $remoteProviders . -}} + {{- end -}} + {{- end -}} + + {{- if $remoteEnabled -}} + {{- if not (findRE "^[A-Za-z0-9_-]+$" $remoteProvider) -}} + {{- errorf "invalid remote icon provider %q" $remoteProvider -}} + {{- end -}} + {{- if or (in $remoteName "..") (not (findRE "^[A-Za-z0-9._/-]+$" $remoteName)) -}} + {{- errorf "invalid remote icon name %q" $remoteName -}} + {{- end -}} + + {{- with index $remoteProviders $remoteProvider -}} + {{- $remoteUrl := printf .url $remoteName -}} + {{- with try (resources.GetRemote $remoteUrl) -}} + {{- with .Err -}} + {{- errorf "Could not retrieve remote icon %q from %s. Reason: %s." $name $remoteUrl . -}} + {{- else with .Value -}} + {{- $icon = .Content -}} + {{- $isRemoteIcon = true -}} + {{- if and (not (strings.Contains $icon "fill=")) (not (strings.Contains $icon "stroke=")) -}} + {{- $icon = replaceRE "]*?)\sclass=("[^"]*"|'[^']*'|[^\s>]+)` `$1` $icon -}} +{{- end -}} + +{{- if .attributes -}} + {{- $attributes := .attributes -}} + {{- if $isRemoteIcon -}} + {{- $icon = replaceRE `(]*?)\swidth=("[^"]*"|'[^']*'|[^\s>]+)` `$1` $icon -}} + {{- $icon = replaceRE `(]*?)\sheight=("[^"]*"|'[^']*'|[^\s>]+)` `$1` $icon -}} + {{- end -}} + {{- $icon = replaceRE " +:root { + --hextra-max-page-width: {{ $maxPageWidth }}; +} + +{{- end -}} diff --git a/themes/hextra/layouts/_partials/utils/sort-pages.html b/themes/hextra/layouts/_partials/utils/sort-pages.html new file mode 100644 index 0000000..4b8ee03 --- /dev/null +++ b/themes/hextra/layouts/_partials/utils/sort-pages.html @@ -0,0 +1,32 @@ +{{- $page := .page -}} +{{- $by := .by | default "weight" -}} +{{- $order := .order | default "asc" -}} + +{{- $pages := slice }} + +{{- if eq $by "weight" }} + {{- $pages = $page.Pages.ByWeight }} +{{- else if eq $by "date" }} + {{- $pages = $page.Pages.ByDate }} +{{- else if eq $by "title" }} + {{- $pages = $page.Pages.ByTitle }} +{{- else if eq $by "expiryDate" }} + {{- $pages = $page.Pages.ByExpiryDate }} +{{- else if eq $by "publishDate" }} + {{- $pages = $page.Pages.ByPublishDate }} +{{- else if eq $by "lastmod" }} + {{- $pages = $page.Pages.ByLastmod }} +{{- else if eq $by "linkTitle" }} + {{- $pages = $page.Pages.ByLinkTitle }} +{{- else if eq $by "length" }} + {{- $pages = $page.Pages.ByLength }} +{{- else }} + {{- warnf "sort-pages: unknown sort field %q" $by -}} + {{- $pages = $page.Pages }} +{{ end -}} + +{{- if eq $order "desc" }} + {{- $pages = $pages.Reverse }} +{{- end -}} + +{{- return $pages -}} diff --git a/themes/hextra/layouts/_partials/utils/template-url.html b/themes/hextra/layouts/_partials/utils/template-url.html new file mode 100644 index 0000000..9de73fd --- /dev/null +++ b/themes/hextra/layouts/_partials/utils/template-url.html @@ -0,0 +1,18 @@ +{{/* + This utility replaces placeholders in a URL template string. + + Usage: + {{ partial "utils/template-url.html" (dict "template" .url "values" (dict "url" $pageURL "title" $pageTitle "markdown_url" $markdownURL)) }} + + Placeholders use the format {key} and values are URL-encoded automatically. +*/}} +{{- $template := .template -}} +{{- $values := .values | default dict -}} + +{{- range $key, $value := $values -}} + {{- $placeholder := printf "{%s}" $key -}} + {{- $encoded := $value | urlquery -}} + {{- $template = replace $template $placeholder $encoded -}} +{{- end -}} + +{{- return $template -}} diff --git a/themes/hextra/layouts/_partials/utils/title.html b/themes/hextra/layouts/_partials/utils/title.html new file mode 100644 index 0000000..0ab0afa --- /dev/null +++ b/themes/hextra/layouts/_partials/utils/title.html @@ -0,0 +1,19 @@ +{{/* + This utility is used to retrieve the title of a page or section. + If no title is set, it falls back to using the directory or file name. + + Based on https://github.com/thegeeklab/hugo-geekdoc/blob/v0.44.0/layouts/partials/utils/title.html +*/}} +{{- $title := "" }} + +{{ if .LinkTitle }} + {{ $title = .LinkTitle }} +{{ else if .Title }} + {{ $title = .Title }} +{{ else if and .IsSection .File }} + {{ $title = path.Base .File.Dir | humanize | title }} +{{ else if and .IsPage .File }} + {{ $title = .File.BaseFileName | humanize | title }} +{{ end }} + +{{ return $title -}} diff --git a/themes/hextra/layouts/_shortcodes/asciinema.html b/themes/hextra/layouts/_shortcodes/asciinema.html new file mode 100644 index 0000000..167ef97 --- /dev/null +++ b/themes/hextra/layouts/_shortcodes/asciinema.html @@ -0,0 +1,88 @@ +{{- /* Get parameters */ -}} +{{- $castFile := .Get "file" | default (.Get 0) -}} +{{- $theme := .Get "theme" | default "asciinema" -}} +{{- $speed := .Get "speed" | default 1 -}} +{{- $autoplay := .Get "autoplay" | default false -}} +{{- $loop := .Get "loop" | default false -}} +{{- $poster := .Get "poster" | default "" -}} +{{- $markers := .Get "markers" | default "" -}} + +{{- /* Handle file path: support local files, absolute paths, and remote URLs */ -}} +{{- $isLocal := not (urls.Parse $castFile).Scheme -}} +{{- $isPage := and (eq .Page.Kind "page") (not .Page.BundleType) -}} + +{{- if $isLocal -}} + {{- /* Local file handling */ -}} + {{- $found := false -}} + + {{- /* Try page resources first */ -}} + {{- if not $isPage -}} + {{- with .Page.Resources.Get $castFile -}} + {{- $castFile = .RelPermalink -}} + {{- $found = true -}} + {{- end -}} + {{- end -}} + + {{- /* Try global resources if not found in page resources */ -}} + {{- if not $found -}} + {{- with resources.Get $castFile -}} + {{- $castFile = .RelPermalink -}} + {{- $found = true -}} + {{- end -}} + {{- end -}} + + {{- /* Try static files if not found in resources */ -}} + {{- if not $found -}} + {{- if hasPrefix $castFile "/" -}} + {{- $castFile = relURL (strings.TrimPrefix "/" $castFile) -}} + {{- $found = true -}} + {{- else -}} + {{- /* For relative paths, assume they're in static directory */ -}} + {{- $castFile = relURL $castFile -}} + {{- $found = true -}} + {{- end -}} + {{- end -}} + + {{- /* If still not found, raise an error */ -}} + {{- if not $found -}} + {{- errorf "Asciinema cast file not found: %s. Please ensure the file exists in your assets, static/, or provide a valid remote URL." $castFile -}} + {{- end -}} +{{- end -}} + +{{- /* Build marker configuration */ -}} +{{- $markerConfig := "" -}} +{{- if $markers -}} + {{- $markerParts := slice -}} + {{- range (split $markers ",") -}} + {{- $item := trim . " " -}} + {{- $colonIndex := findRE ":" $item -}} + {{- if $colonIndex -}} + {{- /* Marker with label */ -}} + {{- $pair := split $item ":" -}} + {{- if ge (len $pair) 2 -}} + {{- $time := printf "%.1f" (float (trim (index $pair 0) " ")) -}} + {{- $label := trim (index $pair 1) " " -}} + {{- $markerParts = $markerParts | append (printf "[%s,\"%s\"]" $time $label) -}} + {{- end -}} + {{- else -}} + {{- /* Simple marker */ -}} + {{- $markerParts = $markerParts | append (printf "%.1f" (float $item)) -}} + {{- end -}} + {{- end -}} + {{- $markerConfig = printf "[%s]" (delimit $markerParts ",") -}} +{{- end -}} + +{{- /* Mark page as using asciinema */ -}} +{{- .Page.Store.Set "hasAsciinema" true -}} + +
    +
    \ No newline at end of file diff --git a/themes/hextra/layouts/_shortcodes/badge.html b/themes/hextra/layouts/_shortcodes/badge.html new file mode 100644 index 0000000..a8005c2 --- /dev/null +++ b/themes/hextra/layouts/_shortcodes/badge.html @@ -0,0 +1,54 @@ +{{- /* +A shortcode to create a badge. + +@param {string} content The content of the badge. +@param {string} color The color of the badge. +@param {string} class The class of the badge. +@param {string} link The link of the badge. +@param {string} icon The icon of the badge. + +or + +@param {string} 0 The content of the badge. + +@example {{< badge content="Badge" color="blue" >}} +@example {{< badge "Badge" >}} +*/ -}} + +{{- if .IsNamedParams -}} + {{- $content := .Get "content" -}} + {{- $color := .Get "color" | default (.Get "type") | default "" -}}{{- /* Compatibility with previous parameter. */ -}} + {{- $class := .Get "class" | default "" -}} + {{- $link := .Get "link" | default "" -}} + {{- $icon := .Get "icon" | default "" -}} + {{- $border := not (eq (.Get "border") false) | default true }} + + {{- if $link -}} + + {{- partial "shortcodes/badge.html" (dict + "content" $content + "color" $color + "class" $class + "border" $border + "icon" $icon + ) + -}} + + {{- else -}} + {{- partial "shortcodes/badge.html" (dict + "content" $content + "color" $color + "class" $class + "border" $border + "icon" $icon + ) + -}} + {{- end -}} +{{- else -}} + {{- $content := .Get 0 -}} + {{- partial "shortcodes/badge.html" (dict + "content" $content + "border" true + ) + -}} +{{- end -}} diff --git a/themes/hextra/layouts/_shortcodes/callout.html b/themes/hextra/layouts/_shortcodes/callout.html new file mode 100644 index 0000000..ba939a7 --- /dev/null +++ b/themes/hextra/layouts/_shortcodes/callout.html @@ -0,0 +1,57 @@ +{{- /* +A shortcode to create a callout. + +@param {string} type The type of the callout (default, info, warning, error, important). +@param {string} content The content of the callout. +@param {string} emoji The emoji of the callout. +@param {string} icon The icon of the callout (related to type or can be a custom icon). + +@example {{< callout type="info" >}}Content{{< /callout >}} +*/ -}} + +{{- $type := .Get "type" | default "default" -}} +{{- $emoji := .Get "emoji" -}} +{{- $icon := .Get "icon" -}} + +{{- $styles := newScratch -}} +{{- $styles.Set "default" (dict + "icon" "light-bulb" + "style" "hx:border-green-200 hx:bg-green-100 hx:text-green-900 hx:dark:border-green-200/30 hx:dark:bg-green-900/30 hx:dark:text-green-200" + ) +-}} +{{- $styles.Set "info" (dict + "icon" "information-circle" + "style" "hx:border-blue-200 hx:bg-blue-100 hx:text-blue-900 hx:dark:border-blue-200/30 hx:dark:bg-blue-900/30 hx:dark:text-blue-200" + ) +-}} +{{- $styles.Set "warning" (dict + "icon" "exclamation" + "style" "hx:border-amber-200 hx:bg-amber-100 hx:text-amber-900 hx:dark:border-amber-200/30 hx:dark:bg-amber-900/30 hx:dark:text-amber-200" + ) +-}} +{{- $styles.Set "error" (dict + "icon" "ban" + "style" "hx:border-red-200 hx:bg-red-100 hx:text-red-900 hx:dark:border-red-200/30 hx:dark:bg-red-900/30 hx:dark:text-red-200" + ) +-}} +{{- $styles.Set "important" (dict + "icon" "exclamation-circle" + "style" "hx:border-purple-200 hx:bg-purple-100 hx:text-purple-900 hx:dark:border-purple-200/30 hx:dark:bg-purple-900/30 hx:dark:text-purple-200" + ) +-}} + +{{- $style := or ($styles.Get $type) ($styles.Get "default") -}} + +{{- if and (not $emoji) (not $icon) -}} + {{- $icon = $style.icon -}} +{{- end -}} + +{{- $content := .InnerDeindent | markdownify -}} + +{{- partial "shortcodes/callout.html" (dict + "content" $content + "emoji" $emoji + "icon" $icon + "class" $style.style + ) +-}} diff --git a/themes/hextra/layouts/_shortcodes/card.html b/themes/hextra/layouts/_shortcodes/card.html new file mode 100644 index 0000000..e3b47ce --- /dev/null +++ b/themes/hextra/layouts/_shortcodes/card.html @@ -0,0 +1,72 @@ +{{- /* + A shortcode to create a card. + + @param {string} link The link to the card. + @param {string} title The title of the card. + @param {string} icon The icon of the card. + @param {string} subtitle The subtitle of the card. + @param {string} tag The tag of the card. + @param {string} tagColor The color of the tag. + @param {string} image The image of the card. + @param {string} alt The alt text for the image (defaults to title if not provided). + @param {string} method The method to process the image. + @param {string} options The options to process the image. + @param {string} imageStyle The style of the image. + + @example {{< card link="/" title="Image Card" +}} +*/ -}} +{{- $link := .Get "link" -}} +{{- $title := .Get "title" -}} +{{- $icon := .Get "icon" -}} +{{- $subtitle := .Get "subtitle" -}} +{{- $image := .Get "image" -}} +{{- $alt := .Get "alt" | default $title -}} +{{- $width := 0 -}} +{{- $height := 0 -}} +{{- $imageStyle := .Get "imageStyle" -}} +{{- $tag := .Get "tag" -}} +{{- $tagColor := .Get "tagColor" | default (.Get "tagType") | default "" -}}{{- /* Compatibility with previous parameter. */ -}} +{{- $tagBorder := not (eq (.Get "tagBorder") false) | default true }} +{{- $tagIcon := .Get "tagIcon" | default "" -}} + +{{/* Image processing options */}} +{{- $method := .Get "method" | default "Resize" | humanize -}} +{{- $options := .Get "options" | default "800x webp q80" -}} +{{- $process := .Get "process" | default (printf "%s %s" $method $options) -}} + +{{- if and $image (not (urls.Parse $image).Scheme) -}} + {{- with or (.Page.Resources.Get $image) (resources.Get $image) -}} + {{/* .Process does not work on svgs */}} + {{- if (not (eq .MediaType.SubType "svg")) -}} + {{/* Retrieve the $image resource from local or global resources */}} + {{- $processed := .Process $process -}} + {{- $width = $processed.Width -}} + {{- $height = $processed.Height -}} + {{- $image = $processed.RelPermalink -}} + {{- end -}} + {{ else }} + {{/* Otherwise, use relative link of the image */}} + {{- if hasPrefix $image "/" -}} + {{- $image = relURL (strings.TrimPrefix "/" $image) -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- partial "shortcodes/card" (dict + "page" .Page + "link" $link + "title" $title + "icon" $icon + "subtitle" $subtitle + "image" $image + "alt" $alt + "width" $width + "height" $height + "imageStyle" $imageStyle + "tag" $tag + "tagType" $tagColor + "tagBorder" $tagBorder + "tagIcon" $tagIcon + ) +-}} diff --git a/themes/hextra/layouts/_shortcodes/cards.html b/themes/hextra/layouts/_shortcodes/cards.html new file mode 100644 index 0000000..0b6667f --- /dev/null +++ b/themes/hextra/layouts/_shortcodes/cards.html @@ -0,0 +1,11 @@ +{{- /* +A shortcode for creating cards. + +@param {string} cols The number of columns. + +@example {{< cards cols="3" >}}{{< /cards >}} +*/ -}} + +{{- $cols := .Get "cols" | default 3 -}} + +{{- partial "shortcodes/cards" (dict "cols" $cols "content" .Inner) -}} diff --git a/themes/hextra/layouts/_shortcodes/details.html b/themes/hextra/layouts/_shortcodes/details.html new file mode 100644 index 0000000..6835337 --- /dev/null +++ b/themes/hextra/layouts/_shortcodes/details.html @@ -0,0 +1,20 @@ +{{- /* +A built-in component to display a collapsible content. + +@param {string} title The title of the details. +@param {string} closed Whether the details are closed or not (default: false). + +@example {{% details title="Details" %}}Content{{% /details %}} +*/ -}} + +{{- $title := .Get "title" | default "" -}} +{{- $closed := eq (.Get "closed") "true" | default false -}} + +
    + + {{ $title | markdownify }} + +
    + {{ .InnerDeindent | $.Page.RenderString (dict "display" "block") }} +
    +
    diff --git a/themes/hextra/layouts/_shortcodes/filetree/container.html b/themes/hextra/layouts/_shortcodes/filetree/container.html new file mode 100644 index 0000000..184ce73 --- /dev/null +++ b/themes/hextra/layouts/_shortcodes/filetree/container.html @@ -0,0 +1,13 @@ +{{- /* +A file tree container. + +@example {{< filetree/container >}}{{< /filetree/container >}} +*/ -}} + +
    +
    +
      + {{- .InnerDeindent -}} +
    +
    +
    diff --git a/themes/hextra/layouts/_shortcodes/filetree/file.html b/themes/hextra/layouts/_shortcodes/filetree/file.html new file mode 100644 index 0000000..c48c8c9 --- /dev/null +++ b/themes/hextra/layouts/_shortcodes/filetree/file.html @@ -0,0 +1,16 @@ +{{- /* +A file in a file tree. + +@param {string} name The name of the file. + +@example {{< filetree/file name="_index.md" >}} +*/ -}} + +{{- $name := .Get "name" -}} + +
  • + + {{- partial "utils/icon" (dict "name" "document-text" "attributes" "width=1em") -}} + {{ $name | markdownify }} + +
  • diff --git a/themes/hextra/layouts/_shortcodes/filetree/folder.html b/themes/hextra/layouts/_shortcodes/filetree/folder.html new file mode 100644 index 0000000..58756ee --- /dev/null +++ b/themes/hextra/layouts/_shortcodes/filetree/folder.html @@ -0,0 +1,26 @@ +{{- /* +A folder in a file tree. + +@param {string} name The name of the folder. +@param {string} state The state of the folder. + +@example {{< filetree/folder name="docs" state="closed" >}} +*/ -}} + +{{- $name := .Get "name" -}} +{{- $state := .Get "state" | default "open" }} + +
  • + +
      + {{- .InnerDeindent -}} +
    +
  • diff --git a/themes/hextra/layouts/_shortcodes/hextra/feature-card.html b/themes/hextra/layouts/_shortcodes/hextra/feature-card.html new file mode 100644 index 0000000..fd8a648 --- /dev/null +++ b/themes/hextra/layouts/_shortcodes/hextra/feature-card.html @@ -0,0 +1,51 @@ +{{- /* +A shortcode for displaying a feature card. + +@param {string} title The title of the card. +@param {string} subtitle The subtitle of the card. +@param {string} class The class of the card. +@param {string} image The image of the card. +@param {string} imageClass The class of the image. +@param {string} style The style of the card. +@param {string} icon The icon of the card. +@param {string} link The link of the card. + +@example {{< hextra/feature-card title="Feature Card" subtitle="This is a feature card." >}} +*/ -}} + +{{- $title := .Get "title" -}} +{{- $subtitle := .Get "subtitle" -}} +{{- $class := .Get "class" -}} +{{- $image := .Get "image" -}} +{{- $imageClass := .Get "imageClass" -}} +{{- $style := .Get "style" -}} +{{- $icon := .Get "icon" -}} +{{- $link := .Get "link" -}} + +{{- $external := hasPrefix $link "http" -}} +{{- $href := cond (strings.HasPrefix $link "/") ($link | relURL) $link -}} + +{{- if hasPrefix $image "/" -}} + {{- $image = relURL (strings.TrimPrefix "/" $image) -}} +{{- end -}} + + +
    +

    + {{ with $icon -}} + + {{- partial "utils/icon.html" (dict "name" . "attributes" "height=1.5rem") -}} + + {{ end -}} + {{ $title }} +

    +

    {{ $subtitle | markdownify }}

    +
    + {{- with $image -}} + {{ $title }} + {{- end -}} +
    diff --git a/themes/hextra/layouts/_shortcodes/hextra/feature-grid.html b/themes/hextra/layouts/_shortcodes/hextra/feature-grid.html new file mode 100644 index 0000000..aace898 --- /dev/null +++ b/themes/hextra/layouts/_shortcodes/hextra/feature-grid.html @@ -0,0 +1,21 @@ +{{- /* +A shortcode for displaying a feature grid. + +@param {string} cols The number of columns. +@param {string} style The style of the grid. + +@example {{< hextra/feature-grid cols="3" >}}{{< /hextra/feature-grid >}} +*/ -}} + +{{- $cols := .Get "cols" | default 3 -}} +{{- $style := .Get "style" | default "" -}} + +{{- $css := printf "--hextra-feature-grid-cols: %v; %s" $cols $style -}} + + +
    + {{ .Inner }} +
    diff --git a/themes/hextra/layouts/_shortcodes/hextra/hero-badge.html b/themes/hextra/layouts/_shortcodes/hextra/hero-badge.html new file mode 100644 index 0000000..9fdcbbd --- /dev/null +++ b/themes/hextra/layouts/_shortcodes/hextra/hero-badge.html @@ -0,0 +1,24 @@ +{{- /* +A shortcode for rendering a badge with a link. + +@param {string} link The link of the badge. +@param {string} class The class of the badge. +@param {string} style The style of the badge. + +@example {{< hextra/hero-badge >}}{{< /hextra/hero-badge >}} +*/ -}} + +{{- $link := .Get "link" -}} +{{- $external := hasPrefix $link "http" -}} +{{- $href := cond (hasPrefix $link "/") ($link | relURL) $link -}} +{{- $class := .Get "class" }} +{{- $style := .Get "style" -}} + + + {{ .Inner | markdownify }} + diff --git a/themes/hextra/layouts/_shortcodes/hextra/hero-button.html b/themes/hextra/layouts/_shortcodes/hextra/hero-button.html new file mode 100644 index 0000000..0295c2a --- /dev/null +++ b/themes/hextra/layouts/_shortcodes/hextra/hero-button.html @@ -0,0 +1,25 @@ +{{- /* +A shortcode for rendering a button with a link. + +@param {string} link The link of the button. +@param {string} text The text of the button. +@param {string} style The style of the button. + +@example {{< hextra/hero-button text="Get Started" link="docs" >}} +*/ -}} + +{{- $link := .Get "link" -}} +{{- $text := .Get "text" -}} +{{- $style := .Get "style" -}} + +{{- $external := hasPrefix $link "http" -}} +{{- $href := cond (hasPrefix $link "/") ($link | relURL) $link -}} + + + {{- $text -}} + diff --git a/themes/hextra/layouts/_shortcodes/hextra/hero-container.html b/themes/hextra/layouts/_shortcodes/hextra/hero-container.html new file mode 100644 index 0000000..a7153d4 --- /dev/null +++ b/themes/hextra/layouts/_shortcodes/hextra/hero-container.html @@ -0,0 +1,56 @@ +{{- /* +A simple hero container with an image on the left side. + +@param {string} class The class of the container. +@param {string} cols The number of columns (default: 2). +@param {string} image The image of the container. +@param {bool} imageCard Whether to display the image as a card (default: false). +@param {string} imageClass The class of the image. +@param {string} imageLink The link of the image. +@param {string} imageStyle The style of the image. +@param {string} imageTitle The title of the image. +@param {int} imageWidth The width of the image (default: 350). +@param {int} imageHeight The height of the image (default: 350). +@param {string} style The style of the container. + +@example {{< hextra/hero-container image="image.png" imageLink="https://example.com" imageTitle="Example Image" >}} +*/ -}} + +{{- $class := .Get "class" -}} +{{- $cols := .Get "cols" | default 2 -}} +{{- $image := .Get "image" -}} +{{- $imageCard := .Get "imageCard" | default false -}} +{{- $imageClass := .Get "imageClass" -}} +{{- $imageLink := .Get "imageLink" -}} +{{- $imageLinkExternal := hasPrefix $imageLink "http" -}} +{{- $imageStyle := .Get "imageStyle" -}} +{{- $imageTitle := .Get "imageTitle" -}} +{{- $imageWidth := .Get "imageWidth" | default 350 -}} +{{- $imageHeight := .Get "imageHeight" | default 350 -}} +{{- $style := .Get "style" -}} + +{{- $css := printf "--hextra-feature-grid-cols: %v; %s" $cols $style -}} +{{- $href := cond (hasPrefix $imageLink "/") ($imageLink | relURL) $imageLink -}} +{{- if hasPrefix $image "/" -}} + {{- $image = relURL (strings.TrimPrefix "/" $image) -}} +{{- end -}} + +
    +
    + {{ .Inner }} +
    + {{- with $image }} +
    + + + +
    + {{ end -}} +
    diff --git a/themes/hextra/layouts/_shortcodes/hextra/hero-headline.html b/themes/hextra/layouts/_shortcodes/hextra/hero-headline.html new file mode 100644 index 0000000..6dd313a --- /dev/null +++ b/themes/hextra/layouts/_shortcodes/hextra/hero-headline.html @@ -0,0 +1,16 @@ +{{- /* +A shortcode for displaying a hero headline. + +@param {string} style The style of the headline. + +@example {{< hextra/hero-headline >}}{{< /hextra/hero-headline >}} +*/ -}} + +{{- $style := .Get "style" -}} + +

    + {{ .Inner | markdownify }} +

    diff --git a/themes/hextra/layouts/_shortcodes/hextra/hero-section.html b/themes/hextra/layouts/_shortcodes/hextra/hero-section.html new file mode 100644 index 0000000..1e7131e --- /dev/null +++ b/themes/hextra/layouts/_shortcodes/hextra/hero-section.html @@ -0,0 +1,20 @@ +{{- /* +A simple hero section with a heading and optional style. + +@param {string} heading The heading level (default: h2). +@param {string} style The style of the heading. +@param {string} content The content of the heading. + +@example {{< hextra/hero-section heading="h3" >}}{{< /hextra/hero-section >}}> +*/ -}} + +{{- $style := .Get "style" -}} +{{- $heading := int (strings.TrimPrefix "h" (.Get "heading" | default "h2")) -}} +{{- $size := cond (ge $heading 4) "xl" (cond (eq $heading 3) "2xl" "4xl") -}} + + + {{ .Inner | markdownify }} + diff --git a/themes/hextra/layouts/_shortcodes/hextra/hero-subtitle.html b/themes/hextra/layouts/_shortcodes/hextra/hero-subtitle.html new file mode 100644 index 0000000..614c427 --- /dev/null +++ b/themes/hextra/layouts/_shortcodes/hextra/hero-subtitle.html @@ -0,0 +1,16 @@ +{{- /* +A shortcode for displaying a hero subtitle. + +@param {string} style The style of the subtitle. + +@example {{< hextra/hero-subtitle >}}{{< /hextra/hero-subtitle >}} +*/ -}} + +{{- $style := .Get "style" -}} + +

    + {{ .Inner | markdownify }} +

    diff --git a/themes/hextra/layouts/_shortcodes/icon.html b/themes/hextra/layouts/_shortcodes/icon.html new file mode 100644 index 0000000..77f98c9 --- /dev/null +++ b/themes/hextra/layouts/_shortcodes/icon.html @@ -0,0 +1,20 @@ +{{- /* +Create an icon. + +@param {string} name The name of the icon. +@param {string} attributes The attributes of the icon. + +or + +@param {string} 0 The name of the icon. + +@example {{< icon name="github" >}} +@example {{< icon "github" >}} +*/ -}} +{{- $name := .Get "name" | default (.Get 0) -}} +{{- $attributes := .Get "attributes" | default "height=1em" }} +{{- $icon := partial "utils/icon.html" (dict "name" $name "attributes" $attributes) -}} + + + {{- $icon | safeHTML -}} + diff --git a/themes/hextra/layouts/_shortcodes/include.html b/themes/hextra/layouts/_shortcodes/include.html new file mode 100644 index 0000000..8bdf440 --- /dev/null +++ b/themes/hextra/layouts/_shortcodes/include.html @@ -0,0 +1,22 @@ +{{- /* +https://github.com/gohugoio/gohugoioTheme/blob/master/layouts/shortcodes/include.html + +Renders the page using the RenderShortcode method on the Page object. + +You must call this shortcode using the {{% %}} notation. + +@param {string} (positional parameter 0) The path to the page, relative to the content directory. +@returns template.HTML + +@example {{% include "functions/_common/glob-patterns" %}} +*/}} + +{{- with .Get 0 }} + {{- with site.GetPage . }} + {{- .RenderShortcodes }} + {{- else }} + {{- errorf "The %q shortcode was unable to find %q. See %s" $.Name . $.Position }} + {{- end }} +{{- else }} + {{- errorf "The %q shortcode requires a positional parameter indicating the path of the file to include. See %s" .Name .Position }} +{{- end }} diff --git a/themes/hextra/layouts/_shortcodes/jupyter.html b/themes/hextra/layouts/_shortcodes/jupyter.html new file mode 100644 index 0000000..08ca593 --- /dev/null +++ b/themes/hextra/layouts/_shortcodes/jupyter.html @@ -0,0 +1,88 @@ +{{- /* +Render Jupyter Notebook + +@param {string} 0 The path of the Jupyter Notebook. + +@example {{% jupyter "notebook.ipynb" %}} +*/ -}} + +{{- $path := .Get 0 -}} +{{- $data := "" -}} +{{- $page := .Page -}} + +{{- $isLocal := not (urls.Parse $path).Scheme -}} +{{- $isPage := and (eq .Page.Kind "page") (not .Page.BundleType) -}} + +{{/* https://gohugo.io/functions/transform/unmarshal/ */}} +{{- if (not $isLocal) -}} + {{- with resources.GetRemote $path -}} + {{- with unmarshal .Content -}}{{- $data = . -}}{{- end -}} + {{- else -}} + {{- errorf "Remote resource not found: %s" $path -}} + {{- end -}} +{{- else if (not $isPage) -}} + {{- with .Page.Resources.Get $path -}} + {{- with unmarshal .Content -}}{{- $data = . -}}{{- end -}} + {{- else -}} + {{- errorf "Local resource not found: %s" $path -}} + {{- end -}} +{{- else -}} + {{- with resources.Get $path -}} + {{- with unmarshal .Content -}}{{- $data = . -}}{{- end -}} + {{- else -}} + {{- errorf "Local resource not found: %s" $path -}} + {{- end -}} +{{- end -}} + +{{- $language := index $data "metadata" "language_info" "name" | default "python" -}} + +{{- with index $data "cells" -}} + {{- range $cell := . -}} + {{- if eq (index $cell "cell_type") "code" -}} + {{- $source := index $cell "source" -}} + {{- $sourceContent := (cond (reflect.IsSlice $source) (delimit $source "") $source) -}} +{{- with ($sourceContent | strings.Chomp) -}} +{{ (printf "\n\n```%s\n%s\n```\n" $language .) | safeHTML -}} +{{- end -}} + +
    + {{- $outputs := index $cell "outputs" -}} + {{- with $outputs -}} +
    +
    + {{- range $output := . -}} + {{- if eq (index $output "output_type") "display_data" -}} + {{- $data := index $output "data" -}} + {{- $image := index $data "image/png" -}} + {{- if $image -}} + image + {{- end -}} + {{- else if eq (index $output "output_type") "stream" -}} + {{- $text := index $output "text" -}} + {{- $textContent := (cond (reflect.IsSlice $text) (delimit $text "") $text) -}} +
    {{- $textContent -}}
    + {{- else if eq (index $output "output_type") "execute_result" -}} + {{- $data := index $output "data" -}} + {{- $text := index $data "text/plain" -}} + {{- $textContent := (cond (reflect.IsSlice $text) (delimit $text "") $text) -}} +
    {{- $textContent -}}
    + {{- $html := index $data "text/html" -}} + {{- if $html -}} + {{- $htmlText := delimit $html "" -}} +
    + {{- $htmlText | safeHTML -}} +
    + {{- end -}} + {{- end -}} + {{- end -}} +
    +
    + {{- end -}} +
    + {{- else if eq (index $cell "cell_type") "markdown" -}} + {{- $source := index $cell "source" }} + {{- $sourceContent := (cond (reflect.IsSlice $source) (delimit $source "") $source) }} +{{ (printf "\n%s\n" $sourceContent) | safeHTML }} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/themes/hextra/layouts/_shortcodes/pdf.html b/themes/hextra/layouts/_shortcodes/pdf.html new file mode 100644 index 0000000..991d6eb --- /dev/null +++ b/themes/hextra/layouts/_shortcodes/pdf.html @@ -0,0 +1,15 @@ +{{- /* +Shortcode to include a PDF file in a page. + +@param {string} 0 The path to the PDF file. + +@example {{< pdf "path/to/file.pdf" >}} +*/ -}} + +{{- $path := .Get 0 -}} +{{- $url := partial "utils/file-path" (dict "page" .Page "path" $path) -}} + + +
    + +
    diff --git a/themes/hextra/layouts/_shortcodes/steps.html b/themes/hextra/layouts/_shortcodes/steps.html new file mode 100644 index 0000000..1bccad1 --- /dev/null +++ b/themes/hextra/layouts/_shortcodes/steps.html @@ -0,0 +1,9 @@ +{{- /* +A shortcode for creating a step list. + +@example {{% steps %}}{{% /steps %}} +*/ -}} + +
    + {{- .Inner -}} +
    diff --git a/themes/hextra/layouts/_shortcodes/tab.html b/themes/hextra/layouts/_shortcodes/tab.html new file mode 100644 index 0000000..e57c460 --- /dev/null +++ b/themes/hextra/layouts/_shortcodes/tab.html @@ -0,0 +1,28 @@ +{{- /* +Create a tab. + +@param {string} name The name of the tab. +@param {string} icon The icon of the tab. +@param {string} selected Whether the tab is selected. + +@example {{< tab name="Foo" icon="document-text" selected=true >}}content{{< /tab >}} +*/ -}} + +{{- $name := .Get "name" | default (printf "Tab %d" .Ordinal) -}} + +{{- $icon := .Get "icon" -}} + +{{- $selected := .Get "selected" -}} +{{- if .Parent.Get "defaultIndex" -}} + {{- $selected = eq .Ordinal (int (.Parent.Get "defaultIndex")) -}} +{{- end -}} + +{{- $tabs := .Parent.Store.Get "tabs" | default slice -}} +{{ .Parent.Store.Set "tabs" ($tabs | append (dict + "id" .Ordinal + "name" $name + "icon" $icon + "content" .InnerDeindent + "selected" $selected + )) +-}} diff --git a/themes/hextra/layouts/_shortcodes/tabs.html b/themes/hextra/layouts/_shortcodes/tabs.html new file mode 100644 index 0000000..efdf5a6 --- /dev/null +++ b/themes/hextra/layouts/_shortcodes/tabs.html @@ -0,0 +1,39 @@ +{{- /* +Create a tabbed interface with the given items. + +@example {{< tabs >}}...{{< /tabs >}} +*/ -}} + +{{- /* Unused, but required for the shortcode to work. */ -}} +{{- .Inner -}} + +{{- /* Enable syncing of tabs across the page. */ -}} +{{- $enableSync := false -}} +{{- if or (eq .Page.Params.tabs.sync false) (eq .Page.Params.tabs.sync true) -}} + {{- $enableSync = .Page.Params.tabs.sync -}} +{{- else -}} + {{- $enableSync = site.Params.page.tabs.sync | default false -}} +{{- end -}} + +{{- $tabs := ($.Store.Get "tabs") | default slice -}} + +{{- /* Compatibility with previous parameter "items". */ -}} +{{- if .Get "defaultIndex" -}} + {{- warnf "The 'defaultIndex' parameter of the 'tabs' shortcode is deprecated. Please use 'selected' on 'tab' instead." -}} +{{- end -}} + +{{- if .Get "items" -}} + {{- warnf "The 'items' parameter of the 'tabs' shortcode is deprecated. Please use 'name' on 'tab' instead." -}} + + {{- $items := split (.Get "items") "," -}} + + {{- $temp := slice -}} + {{- range $i, $item := $items -}} + {{- $tab := index $tabs $i -}} + {{- $temp = $temp | append (merge $tab (dict "name" $item)) -}} + {{- end -}} + + {{- $tabs = $temp -}} +{{- end -}} + +{{- partial "shortcodes/tabs" (dict "tabs" $tabs "enableSync" $enableSync "id" .Ordinal) -}} diff --git a/themes/hextra/layouts/_shortcodes/term.html b/themes/hextra/layouts/_shortcodes/term.html new file mode 100644 index 0000000..8d437bb --- /dev/null +++ b/themes/hextra/layouts/_shortcodes/term.html @@ -0,0 +1,33 @@ +{{- /* +Highlight a glossary term + +@param {string} entry Either the glossary abbreviation or the term. + +or + +@param {string} 0 Either the glossary abbreviation or the term. + +@example {{< term entry="MAC" >}} +@example {{< term "MAC" >}} +*/ -}} + +{{- $entry := .Get "entry" | default (.Get 0) -}} +{{- $entryLower := lower $entry -}} +{{- $match := dict -}} +{{- $siteData := partial "utils/hugo-compat/site-data.html" . -}} + + +{{- range (index $siteData .Site.Language.Lang "termbase") -}} + {{- if or (eq (lower .abbr) $entryLower) (eq (lower .term) $entryLower) -}} + {{- $match = . -}} + {{- break -}} + {{- end -}} +{{- end -}} + +{{- with $match }} + + {{- $entry -}} + +{{- else }} + {{- $entry -}} +{{- end }} diff --git a/themes/hextra/layouts/archives.html b/themes/hextra/layouts/archives.html new file mode 100644 index 0000000..14aac55 --- /dev/null +++ b/themes/hextra/layouts/archives.html @@ -0,0 +1,38 @@ +{{ define "main" }} + {{- $section := site.Params.archives.section | default "blog" -}} + {{- $dateFormat := site.Params.archives.dateFormat | default "Jan 02" -}} + {{- $pages := where site.RegularPages "Section" $section -}} + {{- $groups := $pages.GroupByDate "2006" "desc" -}} +
    + {{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" true) }} + {{ partial "toc.html" (dict "Params" (dict "toc" false)) }} +
    +
    +
    + {{ if .Title }}

    {{ .Title }}

    {{ end }} +
    + {{- if $groups }} + {{- range $groups }} +

    {{ .Key }}

    +
    + {{- range .Pages }} + {{- $page := . -}} +
    + +
    + {{ .Title }} + {{- with .Params.tags }} + {{ partial "tags.html" (dict "context" $page) }} + {{- end }} +
    +
    + {{- end }} +
    + {{- end }} + {{- else }} +

    {{ (T "noResultsFound") | default "No posts found." }}

    + {{- end }} +
    +
    +
    +{{- end -}} diff --git a/themes/hextra/layouts/baseof.html b/themes/hextra/layouts/baseof.html new file mode 100644 index 0000000..6238ee6 --- /dev/null +++ b/themes/hextra/layouts/baseof.html @@ -0,0 +1,16 @@ + + + {{- partial "head.html" . -}} + + + {{- (T "skipToContent") | default "Skip to content" -}} + + {{- partial "banner.html" . -}} + {{- partial "navbar.html" . -}} + {{- block "main" . }}{{ end -}} + {{- if or (eq .Site.Params.footer.enable nil) (.Site.Params.footer.enable) }} + {{ partial "footer.html" . }} + {{ end }} + {{ partial "scripts.html" . }} + + diff --git a/themes/hextra/layouts/blog/list.html b/themes/hextra/layouts/blog/list.html new file mode 100644 index 0000000..848f808 --- /dev/null +++ b/themes/hextra/layouts/blog/list.html @@ -0,0 +1,39 @@ +{{ define "main" }} + {{- $readMore := (T "readMore") | default "Read more →" -}} +
    + {{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" true) }} +
    +
    + {{ partial "breadcrumb.html" (dict "page" . "enable" false) }} +
    + {{ if .Title }}

    {{ .Title }}

    {{ end }} +
    {{ .Content }}
    + {{- $pages := partial "utils/sort-pages" (dict "page" . "by" site.Params.blog.list.sortBy "order" site.Params.blog.list.sortOrder) -}} + {{- $pagerSize := site.Params.blog.list.pagerSize | default 10 -}} + {{- $paginator := .Paginate $pages $pagerSize -}} + {{- range $paginator.Pages }} +
    +

    {{ .Title }}

    + {{ if site.Params.blog.list.displayTags }} +
    + {{ partial "tags.html" (dict "context" .) }} +
    + {{ end }} +

    {{- partial "utils/page-description" . -}}

    +

    + + {{- $readMore -}} + +

    +

    {{ partial "utils/format-date" .Date }}

    +
    + {{ end -}} + + {{- if gt $paginator.TotalPages 1 -}} + {{ partial "components/blog-pager.html" $paginator }} + {{- end -}} +
    +
    +
    +
    +{{- end -}} diff --git a/themes/hextra/layouts/blog/single.html b/themes/hextra/layouts/blog/single.html new file mode 100644 index 0000000..561031c --- /dev/null +++ b/themes/hextra/layouts/blog/single.html @@ -0,0 +1,55 @@ +{{ define "main" }} +
    + {{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" true) }} + {{ partial "toc.html" . }} +
    +
    + {{ partial "breadcrumb.html" (dict "page" . "enable" true) }} + {{ if .Title }} +
    +

    {{ .Title }}

    + {{ partial "components/page-context-menu" . }} +
    + {{ end }} +
    + {{- with $date := .Date }}{{ partial "utils/format-date" $date }}{{ end -}} + {{- $lazyLoading := site.Params.enableImageLazyLoading | default true -}} + {{ if and .Date .Params.authors }}·{{ end -}} + {{- with $.Params.authors -}} + {{- range $i, $author := . -}} + {{- if reflect.IsMap $author -}} + {{- if and $i (not $author.image) }},{{ end -}} + + {{- with $image := $author.image }} + {{- $isLocal := not (urls.Parse $image).Scheme -}} + {{- $startsWithSlash := hasPrefix $image "/" -}} + {{- if and $isLocal $startsWithSlash }} + {{- $image = (relURL (strings.TrimPrefix "/" $image)) -}} + {{ end -}} + {{ $author.name }} + {{ end -}} +
    {{ $author.name }}
    +
    + {{- else -}} + {{- if $i }},{{ end -}}{{ $author }} + {{- end -}} + {{- end -}} + {{- end -}} +
    +
    + {{ .Content }} +
    + {{- partial "components/last-updated.html" . -}} + {{- if (site.Params.blog.article.displayPagination | default true) -}} + {{- .Store.Set "reversePagination" (.Params.reversePagination | default true) -}} + {{- partial "components/pager.html" . -}} + {{ end }} + {{- partial "components/comments.html" . -}} +
    +
    +
    +{{ end }} diff --git a/themes/hextra/layouts/docs/list.html b/themes/hextra/layouts/docs/list.html new file mode 100644 index 0000000..c921e6c --- /dev/null +++ b/themes/hextra/layouts/docs/list.html @@ -0,0 +1,25 @@ +{{ define "main" }} +
    + {{ partial "sidebar.html" (dict "context" .) }} + {{ partial "toc.html" . }} +
    +
    + {{ partial "breadcrumb.html" (dict "page" . "enable" true) }} +
    + {{ if .Title }} +
    +

    {{ .Title }}

    + {{ partial "components/page-context-menu" . }} +
    + {{ end }} + {{ .Content }} +
    + {{ partial "components/last-updated.html" . }} + {{- if (site.Params.page.displayPagination | default true) -}} + {{- partial "components/pager.html" . -}} + {{- end -}} + {{ partial "components/comments.html" . }} +
    +
    +
    +{{ end }} diff --git a/themes/hextra/layouts/docs/single.html b/themes/hextra/layouts/docs/single.html new file mode 100644 index 0000000..40f2eab --- /dev/null +++ b/themes/hextra/layouts/docs/single.html @@ -0,0 +1,25 @@ +{{ define "main" }} +
    + {{ partial "sidebar.html" (dict "context" .) }} + {{ partial "toc.html" . }} +
    +
    + {{ partial "breadcrumb.html" (dict "page" . "enable" true) }} +
    + {{ if .Title }} +
    +

    {{ .Title }}

    + {{ partial "components/page-context-menu" . }} +
    + {{ end }} + {{ .Content }} +
    + {{ partial "components/last-updated.html" . }} + {{- if (site.Params.page.displayPagination | default true) -}} + {{- partial "components/pager.html" . -}} + {{- end -}} + {{ partial "components/comments.html" . }} +
    +
    +
    +{{ end }} \ No newline at end of file diff --git a/themes/hextra/layouts/glossary.html b/themes/hextra/layouts/glossary.html new file mode 100644 index 0000000..d235741 --- /dev/null +++ b/themes/hextra/layouts/glossary.html @@ -0,0 +1,26 @@ +{{ define "main" }} +
    + {{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" true) }} + {{ partial "toc.html" (dict "Params" (dict "toc" false)) }} +
    +
    + {{ if .Title }}

    {{ .Title }}

    {{ end }} +
    + {{- $siteData := partial "utils/hugo-compat/site-data.html" . -}} + {{- with (index $siteData .Site.Language.Lang "termbase") -}} +
    + {{- range sort . "term" -}} +
    + {{- .term }}{{ with .abbr }} ({{ . -}}){{ end -}} +
    +
    + {{- .definition -}} +
    + {{- end -}} +
    + {{- end -}} +
    +
    +
    +
    +{{ end }} diff --git a/themes/hextra/layouts/glossary.markdown.md b/themes/hextra/layouts/glossary.markdown.md new file mode 100644 index 0000000..9e3197f --- /dev/null +++ b/themes/hextra/layouts/glossary.markdown.md @@ -0,0 +1,3 @@ +{{- .Title | replaceRE "\n" " " | printf "# %s" -}} + +{{ .RawContent }} diff --git a/themes/hextra/layouts/hextra-home.html b/themes/hextra/layouts/hextra-home.html new file mode 100644 index 0000000..265f84e --- /dev/null +++ b/themes/hextra/layouts/hextra-home.html @@ -0,0 +1,10 @@ +{{ define "main" }} +
    + {{ partial "sidebar.html" (dict "context" . "disableSidebar" true) }} +
    +
    + {{ .Content }} +
    +
    +
    +{{ end }} diff --git a/themes/hextra/layouts/home.html b/themes/hextra/layouts/home.html new file mode 100644 index 0000000..47b9608 --- /dev/null +++ b/themes/hextra/layouts/home.html @@ -0,0 +1,14 @@ +{{ define "main" }} +
    + {{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" true) }} + {{ partial "toc.html" . }} +
    +
    + {{ if .Title }}

    {{ .Title }}

    {{ end }} +
    + {{ .Content }} +
    +
    +
    +
    +{{ end }} diff --git a/themes/hextra/layouts/list.html b/themes/hextra/layouts/list.html new file mode 100644 index 0000000..504eea2 --- /dev/null +++ b/themes/hextra/layouts/list.html @@ -0,0 +1,18 @@ +{{ define "main" }} +
    + {{ partial "sidebar.html" (dict "context" .) }} + {{ partial "toc.html" . }} +
    +
    + {{ partial "breadcrumb.html" (dict "page" . "enable" false) }} +
    + {{ if .Title }}

    {{ .Title }}

    {{ end }} + {{ .Content }} +
    +
    + {{ partial "components/last-updated.html" . }} + {{ partial "components/comments.html" . }} +
    +
    +
    +{{ end }} \ No newline at end of file diff --git a/themes/hextra/layouts/list.rss.xml b/themes/hextra/layouts/list.rss.xml new file mode 100644 index 0000000..3586d3b --- /dev/null +++ b/themes/hextra/layouts/list.rss.xml @@ -0,0 +1,43 @@ + + + {{ .Site.Title }} – {{ .Title }} + {{ .Permalink }} + Recent content {{ if ne .Title .Site.Title }}{{ with .Title }}in {{.}} {{ end }}{{ end }}on {{ .Site.Title }} + Hugo -- gohugo.io{{ with partial "utils/hugo-compat/language-locale.html" .Site.Language }} + {{.}}{{end}}{{ with .Site.Params.Author.email }} + {{.}}{{ with $.Site.Params.Author.name }} ({{.}}){{end}}{{end}}{{ with .Site.Params.Author.email }} + {{.}}{{ with $.Site.Params.Author.name }} ({{.}}){{end}}{{end}}{{ with .Site.Copyright }} + {{.}}{{end}}{{ if not .Date.IsZero }} + {{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}{{ end }} + {{ with .OutputFormats.Get "RSS" }} + {{ printf "" .Permalink .MediaType | safeHTML }} + {{ end }} + {{ if not $.Section }} + {{ $sections := .Site.Params.rss.sections | default (slice "blog") }} + {{ .Store.Set "rssPages" (first 50 (where $.Site.RegularPages "Type" "in" $sections )) }} + {{ else }} + {{ if $.Parent.IsHome }} + {{ .Store.Set "rssPages" (first 50 (where $.Site.RegularPages "Type" $.Section )) }} + {{ else }} + {{ .Store.Set "rssPages" (first 50 $.Pages) }} + {{ end }} + {{ end }} + {{ range (.Store.Get "rssPages") }} + + {{ .Title }} + {{ .Permalink }} + {{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }} + {{ with .Site.Params.Author.email }}{{.}}{{ with $.Site.Params.Author.name }} ({{.}}){{end}}{{end}} + {{ .Permalink }} + + {{ $img := (.Resources.ByType "image").GetMatch "*featured*" }} + {{ with $img }} + {{ $img := .Resize "640x" }} + {{ printf "]]>" $img.Permalink $img.Width $img.Height | safeHTML }} + {{ end }} + {{ .Content | html }} + + + {{ end }} + + diff --git a/themes/hextra/layouts/llms.txt b/themes/hextra/layouts/llms.txt new file mode 100644 index 0000000..970e206 --- /dev/null +++ b/themes/hextra/layouts/llms.txt @@ -0,0 +1,35 @@ +# {{ .Site.Title }} + +> {{ .Site.Params.description | default "" }} + +{{ range where site.Sections "Params.llms" "ne" false }} +{{- template "llms-section-tree" dict "context" . "level" 2 }} +{{ end }} + +{{- $rootPages := where (where site.RegularPages "Section" "") "Params.llms" "ne" false }} +{{- if $rootPages }} +## Root Pages +{{- range $rootPages }} +- [{{ .Title }}]({{ .Permalink }}): {{ .Summary | plainify | truncate 100 | strings.TrimSpace }}{{ if .Date }} - Published {{ .Date.Format "2006-01-02" }}{{ end }} +{{- end }} +{{- end }} + +--- +Generated on {{ now.Format "2006-01-02 15:04:05 UTC" }} +Site: {{ .Site.BaseURL }} + +{{- define "llms-section-tree" -}} +{{- $context := .context -}} +{{- $level := .level | default 2 -}} +{{- $headerHashes := strings.Repeat $level "#" -}} +{{- "\n" -}} +{{ $headerHashes }} {{ $context.Title }} + +{{- range where $context.RegularPages "Params.llms" "ne" false }} +- [{{ .Title }}]({{ .Permalink }}): {{ .Summary | plainify | truncate 100 | strings.TrimSpace }}{{ if .Date }} - Published {{ .Date.Format "2006-01-02" }}{{ end }} +{{- end }} + +{{- range where $context.Sections "Params.llms" "ne" false }} +{{ template "llms-section-tree" dict "context" . "level" (add $level 1) }} +{{- end }} +{{- end -}} diff --git a/themes/hextra/layouts/page.markdown.md b/themes/hextra/layouts/page.markdown.md new file mode 100644 index 0000000..46afc77 --- /dev/null +++ b/themes/hextra/layouts/page.markdown.md @@ -0,0 +1,2 @@ +{{- .Title | replaceRE "\n" " " | printf "# %s" }} +{{ .RawContent }} diff --git a/themes/hextra/layouts/section.markdown.md b/themes/hextra/layouts/section.markdown.md new file mode 100644 index 0000000..46afc77 --- /dev/null +++ b/themes/hextra/layouts/section.markdown.md @@ -0,0 +1,2 @@ +{{- .Title | replaceRE "\n" " " | printf "# %s" }} +{{ .RawContent }} diff --git a/themes/hextra/layouts/single.html b/themes/hextra/layouts/single.html new file mode 100644 index 0000000..8244123 --- /dev/null +++ b/themes/hextra/layouts/single.html @@ -0,0 +1,19 @@ +{{ define "main" }} +
    + {{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" true) }} + {{ partial "toc.html" . }} +
    +
    + {{ partial "breadcrumb.html" (dict "page" . "enable" false) }} +
    + {{ if .Title }}

    {{ .Title }}

    {{ end }} +
    +
    + {{ .Content }} +
    +
    + {{ partial "components/comments.html" . }} +
    +
    +
    +{{ end }} diff --git a/themes/hextra/layouts/taxonomy.html b/themes/hextra/layouts/taxonomy.html new file mode 100644 index 0000000..490f4ea --- /dev/null +++ b/themes/hextra/layouts/taxonomy.html @@ -0,0 +1,30 @@ +{{ define "main" }} +
    + {{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" true) }} + {{ partial "toc.html" (dict "Params" (dict "toc" false)) }} + +
    +{{ end }} diff --git a/themes/hextra/layouts/term.html b/themes/hextra/layouts/term.html new file mode 100644 index 0000000..eee338d --- /dev/null +++ b/themes/hextra/layouts/term.html @@ -0,0 +1,33 @@ +{{ define "main" }} +
    + {{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" true) }} + {{ partial "toc.html" (dict "Params" (dict "toc" false)) }} +
    +
    +
    + {{ if .Title }}

    {{ .Title }}

    {{ end }} +
    +
    + {{ .Content }} +
    +
    + {{- range .Pages -}} +
    +

    + + {{ .Title }} + +

    +

    {{ partial "utils/format-date" .Date }}

    +
    + {{- end -}} +
    +
    +
    +
    +{{ end }} diff --git a/themes/hextra/layouts/wide.html b/themes/hextra/layouts/wide.html new file mode 100644 index 0000000..fce2810 --- /dev/null +++ b/themes/hextra/layouts/wide.html @@ -0,0 +1,12 @@ +{{ define "main" }} +
    + {{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" false) }} +
    +
    + {{ if .Title }}

    {{ .Title }}

    {{ end }} +
    + {{ .Content }} +
    +
    +
    +{{ end }} \ No newline at end of file diff --git a/themes/hextra/layouts/wide.markdown.md b/themes/hextra/layouts/wide.markdown.md new file mode 100644 index 0000000..9e3197f --- /dev/null +++ b/themes/hextra/layouts/wide.markdown.md @@ -0,0 +1,3 @@ +{{- .Title | replaceRE "\n" " " | printf "# %s" -}} + +{{ .RawContent }} diff --git a/themes/hextra/netlify.toml b/themes/hextra/netlify.toml new file mode 100644 index 0000000..1788f05 --- /dev/null +++ b/themes/hextra/netlify.toml @@ -0,0 +1,12 @@ +[build] +publish = "docs/public" +command = "cd docs && hugo --gc --minify --themesDir ../.. -b ${DEPLOY_PRIME_URL}" + +# Always trigger a rebuild, even if the files haven't changed. +# See https://docs.netlify.com/configure-builds/file-based-configuration/#ignore-builds +ignore = "false" + +[build.environment] +HUGO_VERSION = "0.156.0" +GO_VERSION = "1.26.0" +NODE_VERSION = "24.13.1" diff --git a/themes/hextra/package-lock.json b/themes/hextra/package-lock.json new file mode 100644 index 0000000..b6f28ca --- /dev/null +++ b/themes/hextra/package-lock.json @@ -0,0 +1,1564 @@ +{ + "name": "hextra", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "@axe-core/playwright": "^4.11.3", + "@playwright/test": "^1.60.0", + "@tailwindcss/postcss": "^4.3.0", + "postcss-cli": "^11.0.1", + "prettier": "^3.8.3", + "prettier-plugin-go-template": "^0.0.15", + "tailwindcss": "^4.3.0" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@axe-core/playwright": { + "version": "4.11.3", + "resolved": "https://registry.npmjs.org/@axe-core/playwright/-/playwright-4.11.3.tgz", + "integrity": "sha512-h/kfksv4F0cVIDlKpT4700OehdRgpvuVskuQ2nb7/JmtWUXpe9ftHAPtwyXGvVSsa6SJ64A9ER7Zrzc/sIvC4w==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "axe-core": "~4.11.4" + }, + "peerDependencies": { + "playwright-core": ">= 1.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@playwright/test": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.60.0.tgz", + "integrity": "sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.60.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.0.tgz", + "integrity": "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.21.0", + "jiti": "^2.6.1", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.0" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.0.tgz", + "integrity": "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.0", + "@tailwindcss/oxide-darwin-arm64": "4.3.0", + "@tailwindcss/oxide-darwin-x64": "4.3.0", + "@tailwindcss/oxide-freebsd-x64": "4.3.0", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.0", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.0", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.0", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.0", + "@tailwindcss/oxide-linux-x64-musl": "4.3.0", + "@tailwindcss/oxide-wasm32-wasi": "4.3.0", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.0", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.0" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.0.tgz", + "integrity": "sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.0.tgz", + "integrity": "sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.0.tgz", + "integrity": "sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.0.tgz", + "integrity": "sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.0.tgz", + "integrity": "sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.0.tgz", + "integrity": "sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.0.tgz", + "integrity": "sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.0.tgz", + "integrity": "sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.0.tgz", + "integrity": "sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.0.tgz", + "integrity": "sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.10.0", + "@emnapi/runtime": "^1.10.0", + "@emnapi/wasi-threads": "^1.2.1", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.0.tgz", + "integrity": "sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.0.tgz", + "integrity": "sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.3.0.tgz", + "integrity": "sha512-Jm05Tjx+9yCLGv5qw1c+84Psds8MnyrEQYCB+FFk2lgGiUjlRqdxke4mVTuYrj2xnVZqKim2Apr5ySuQRYAw/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.3.0", + "@tailwindcss/oxide": "4.3.0", + "postcss": "^8.5.10", + "tailwindcss": "4.3.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/axe-core": { + "version": "4.11.4", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.4.tgz", + "integrity": "sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/dependency-graph": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-1.0.0.tgz", + "integrity": "sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.21.4", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.4.tgz", + "integrity": "sha512-wE4fDO8OjJhrPFH69HUQStq5oKvGRTNXEyW+k5C/pUQLASSsTu7obd2V3GvCDgPcY9AWjhJ4jz9Kh7iRvrxhJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-extra": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", + "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/playwright": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz", + "integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.60.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz", + "integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/postcss": { + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-cli": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/postcss-cli/-/postcss-cli-11.0.1.tgz", + "integrity": "sha512-0UnkNPSayHKRe/tc2YGW6XnSqqOA9eqpiRMgRlV1S6HdGi16vwJBx7lviARzbV1HpQHqLLRH3o8vTcB0cLc+5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.3.0", + "dependency-graph": "^1.0.0", + "fs-extra": "^11.0.0", + "picocolors": "^1.0.0", + "postcss-load-config": "^5.0.0", + "postcss-reporter": "^7.0.0", + "pretty-hrtime": "^1.0.3", + "read-cache": "^1.0.0", + "slash": "^5.0.0", + "tinyglobby": "^0.2.12", + "yargs": "^17.0.0" + }, + "bin": { + "postcss": "index.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-load-config": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-5.1.0.tgz", + "integrity": "sha512-G5AJ+IX0aD0dygOE0yFZQ/huFFMSNneyfp0e3/bT05a8OfPC5FUoZRPfGijUdGOJNMewJiwzcHJXFafFzeKFVA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1", + "yaml": "^2.4.2" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + } + } + }, + "node_modules/postcss-reporter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-7.1.0.tgz", + "integrity": "sha512-/eoEylGWyy6/DOiMP5lmFRdmDKThqgn7D6hP2dXKJI/0rJSO1ADFNngZfDzxL0YAxFvws+Rtpuji1YIHj4mySA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "thenby": "^1.3.4" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/prettier": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-go-template": { + "version": "0.0.15", + "resolved": "https://registry.npmjs.org/prettier-plugin-go-template/-/prettier-plugin-go-template-0.0.15.tgz", + "integrity": "sha512-WqU92E1NokWYNZ9mLE6ijoRg6LtIGdLMePt2C7UBDjXeDH9okcRI3zRqtnWR4s5AloiqyvZ66jNBAa9tmRY5EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ulid": "^2.3.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "prettier": "^3.0.0" + } + }, + "node_modules/pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tailwindcss": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz", + "integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/thenby": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/thenby/-/thenby-1.3.4.tgz", + "integrity": "sha512-89Gi5raiWA3QZ4b2ePcEwswC3me9JIg+ToSgtE0JWeCynLnLxNr/f9G+xfo9K+Oj4AFdom8YNJjibIARTJmapQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ulid": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ulid/-/ulid-2.4.0.tgz", + "integrity": "sha512-fIRiVTJNcSRmXKPZtGzFQv9WRrZ3M9eoptl/teFJvjOzmpU+/K/JH6HZ8deBfb5vMEpicJcLn7JmvdknlMq7Zg==", + "dev": true, + "license": "MIT", + "bin": { + "ulid": "bin/cli.js" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yaml": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + } + } +} diff --git a/themes/hextra/package.json b/themes/hextra/package.json new file mode 100644 index 0000000..3762876 --- /dev/null +++ b/themes/hextra/package.json @@ -0,0 +1,20 @@ +{ + "scripts": { + "dev:theme": "hugo server --logLevel=debug --config=hugo.yaml,../dev.toml --environment=theme --source=docs --themesDir=../.. -D -F --port 1313", + "dev": "hugo server --source=docs --themesDir=../.. --disableFastRender -D --port 1313", + "build:css": "npx postcss --config postcss.config.mjs --env production assets/css/styles.css -o assets/css/compiled/main.css", + "build": "hugo --gc --minify --themesDir=../.. --source=docs", + "test": "npx playwright test", + "test:a11y": "npx playwright test tests/accessibility.spec.ts", + "test:mobile-menu": "npx playwright test tests/mobile-menu.spec.ts" + }, + "devDependencies": { + "@axe-core/playwright": "^4.11.3", + "@playwright/test": "^1.60.0", + "@tailwindcss/postcss": "^4.3.0", + "postcss-cli": "^11.0.1", + "prettier": "^3.8.3", + "prettier-plugin-go-template": "^0.0.15", + "tailwindcss": "^4.3.0" + } +} diff --git a/themes/hextra/playwright.config.ts b/themes/hextra/playwright.config.ts new file mode 100644 index 0000000..9f8a25f --- /dev/null +++ b/themes/hextra/playwright.config.ts @@ -0,0 +1,21 @@ +import { defineConfig } from "@playwright/test"; + +const baseURL = process.env.BASE_URL || "http://localhost:1313"; + +export default defineConfig({ + testDir: "./tests", + timeout: 60_000, + retries: 0, + reporter: [["list"], ["html"]], + use: { + baseURL, + }, + webServer: process.env.BASE_URL + ? undefined + : { + command: "npx serve docs/public -l tcp://localhost:1313 --no-clipboard", + url: baseURL, + reuseExistingServer: !process.env.CI, + timeout: 30_000, + }, +}); diff --git a/themes/hextra/postcss.config.mjs b/themes/hextra/postcss.config.mjs new file mode 100644 index 0000000..c2ddf74 --- /dev/null +++ b/themes/hextra/postcss.config.mjs @@ -0,0 +1,5 @@ +export default { + plugins: { + "@tailwindcss/postcss": {}, + }, +}; diff --git a/themes/hextra/static/android-chrome-192x192.png b/themes/hextra/static/android-chrome-192x192.png new file mode 100644 index 0000000..7f0493c Binary files /dev/null and b/themes/hextra/static/android-chrome-192x192.png differ diff --git a/themes/hextra/static/android-chrome-512x512.png b/themes/hextra/static/android-chrome-512x512.png new file mode 100644 index 0000000..faea4c2 Binary files /dev/null and b/themes/hextra/static/android-chrome-512x512.png differ diff --git a/themes/hextra/static/apple-touch-icon.png b/themes/hextra/static/apple-touch-icon.png new file mode 100644 index 0000000..eb281cb Binary files /dev/null and b/themes/hextra/static/apple-touch-icon.png differ diff --git a/themes/hextra/static/favicon-16x16.png b/themes/hextra/static/favicon-16x16.png new file mode 100644 index 0000000..0f2dd2b Binary files /dev/null and b/themes/hextra/static/favicon-16x16.png differ diff --git a/themes/hextra/static/favicon-32x32.png b/themes/hextra/static/favicon-32x32.png new file mode 100644 index 0000000..5c1aea5 Binary files /dev/null and b/themes/hextra/static/favicon-32x32.png differ diff --git a/themes/hextra/static/favicon.ico b/themes/hextra/static/favicon.ico new file mode 100644 index 0000000..553fa15 Binary files /dev/null and b/themes/hextra/static/favicon.ico differ diff --git a/themes/hextra/static/favicon.svg b/themes/hextra/static/favicon.svg new file mode 100644 index 0000000..6a08d10 --- /dev/null +++ b/themes/hextra/static/favicon.svg @@ -0,0 +1,13 @@ + + + diff --git a/themes/hextra/static/images/logo-dark.svg b/themes/hextra/static/images/logo-dark.svg new file mode 100644 index 0000000..2857264 --- /dev/null +++ b/themes/hextra/static/images/logo-dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/themes/hextra/static/images/logo.svg b/themes/hextra/static/images/logo.svg new file mode 100644 index 0000000..1ed7daf --- /dev/null +++ b/themes/hextra/static/images/logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/themes/hextra/static/site.webmanifest b/themes/hextra/static/site.webmanifest new file mode 100644 index 0000000..c36f3b3 --- /dev/null +++ b/themes/hextra/static/site.webmanifest @@ -0,0 +1,20 @@ +{ + "name": "Hextra", + "short_name": "Hextra", + "start_url": "index.html", + "icons": [ + { + "src": "android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#000000", + "background_color": "#000000", + "display": "standalone" +} diff --git a/themes/hextra/tests/accessibility.spec.ts b/themes/hextra/tests/accessibility.spec.ts new file mode 100644 index 0000000..ce0d510 --- /dev/null +++ b/themes/hextra/tests/accessibility.spec.ts @@ -0,0 +1,103 @@ +import { test, expect } from "@playwright/test"; +import AxeBuilder from "@axe-core/playwright"; + +const WCAG_TAGS = ["wcag2a", "wcag2aa", "wcag22aa"]; +// TODO: Re-enable once known baseline issues are resolved and tracked. +const DISABLED_RULES = ["color-contrast", "target-size"]; +const EXCLUDED_SELECTORS = [ + // Third-party player internals are outside the theme's control and can change + // independently, while the iframe element itself remains covered by page HTML. + "iframe[src*=\"youtube.com/embed\"]", + "iframe[src*=\"youtube-nocookie.com/embed\"]", +]; + +type Violation = Awaited< + ReturnType["analyze"]> +>["violations"][number]; + +function decodeXmlEntities(value: string): string { + return value + .replace(/&/g, "&") + .replace(/</g, "<") + .replace(/>/g, ">") + .replace(/"/g, '"') + .replace(/'/g, "'"); +} + +function parseLocUrlsFromSitemap(xml: string): string[] { + const locRegex = /\s*([^<]+?)\s*<\/loc>/gi; + const urls: string[] = []; + let match: RegExpExecArray | null; + + while ((match = locRegex.exec(xml)) !== null) { + urls.push(decodeXmlEntities(match[1])); + } + + return urls; +} + +async function getEnglishPages(baseURL: string): Promise { + const sitemapUrl = `${baseURL}/en/sitemap.xml`; + const response = await fetch(sitemapUrl); + if (!response.ok) { + throw new Error( + `Failed to fetch sitemap (${response.status} ${response.statusText}) at ${sitemapUrl}`, + ); + } + + const xml = await response.text(); + const pages = parseLocUrlsFromSitemap(xml) + .map((url) => { + try { + return new URL(url).pathname; + } catch { + return url; + } + }); + + if (pages.length === 0) { + throw new Error(`Sitemap at ${sitemapUrl} returned no URLs.`); + } + + return pages; +} + +function formatViolation(v: Violation): string { + return `• ${v.id} (${v.impact}) — ${v.nodes.length} element(s)\n ${v.help}\n ${v.helpUrl}`; +} + +test("all English pages pass axe-core WCAG AA", async ({ page, baseURL }) => { + const pages = await getEnglishPages(baseURL!); + const failures: string[] = []; + + for (const path of pages) { + await test.step(path, async () => { + await page.goto(path, { waitUntil: "load" }); + + const axe = new AxeBuilder({ page }) + .withTags(WCAG_TAGS) + .disableRules(DISABLED_RULES); + + for (const selector of EXCLUDED_SELECTORS) { + axe.exclude(selector); + } + + const results = await axe.analyze(); + + if (results.violations.length === 0) { + return; + } + + failures.push( + `--- ${path} ---\n${results.violations + .map(formatViolation) + .join("\n\n")}`, + ); + }); + } + + expect( + failures, + `Accessibility violations found:\n\n${failures.join("\n\n")}`, + ).toHaveLength(0); +}); diff --git a/themes/hextra/tests/mobile-menu.spec.ts b/themes/hextra/tests/mobile-menu.spec.ts new file mode 100644 index 0000000..bc29e4d --- /dev/null +++ b/themes/hextra/tests/mobile-menu.spec.ts @@ -0,0 +1,157 @@ +import { test, expect } from "@playwright/test"; +import { execFileSync } from "node:child_process"; +import { + mkdirSync, + mkdtempSync, + readFileSync, + rmSync, + symlinkSync, + writeFileSync, +} from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +test("clicking mobile hamburger does not focus the sidebar search input", async ({ page }) => { + await page.setViewportSize({ width: 375, height: 812 }); + await page.goto("/", { waitUntil: "load" }); + + const menuButton = page.locator(".hextra-hamburger-menu"); + await expect(menuButton).toBeVisible(); + + const sidebarSearchInput = page.locator(".hextra-sidebar-container .hextra-search-input").first(); + await expect(sidebarSearchInput).toBeVisible(); + + await menuButton.click(); + + await expect(menuButton).toHaveAttribute("aria-expanded", "true"); + await expect(sidebarSearchInput).not.toBeFocused(); +}); + +test("mobile sidebar exposes main menu dropdown children", async ({ page }) => { + await page.setViewportSize({ width: 390, height: 844 }); + await page.goto("/", { waitUntil: "load" }); + + await page.getByRole("button", { name: "Menu" }).click(); + + const sidebar = page.locator("aside.hextra-sidebar-container"); + await expect(sidebar.getByRole("link", { name: "Development ↗" })).toBeVisible(); + await expect(sidebar.getByRole("link", { name: "v0.10 ↗" })).toBeVisible(); + await expect(sidebar.getByRole("link", { name: "v0.11 ↗" })).toBeVisible(); +}); + +test("mobile sidebar uses localized page titles for zh-cn docs navigation", async ({ page }) => { + await page.setViewportSize({ width: 390, height: 844 }); + await page.goto("/zh-cn/", { waitUntil: "load" }); + + await page.locator(".hextra-hamburger-menu").click(); + + const sidebar = page.locator("aside.hextra-sidebar-container"); + const gettingStarted = sidebar.locator('a[href="/zh-cn/docs/getting-started/"]'); + const guide = sidebar.locator('a[href="/zh-cn/docs/guide/"]'); + const organizeFiles = sidebar.locator('a[href="/zh-cn/docs/guide/organize-files/"]'); + + await expect(gettingStarted).toBeVisible(); + await expect(gettingStarted).toHaveText("快速开始"); + await expect(guide).toBeVisible(); + await expect(guide).toHaveText("指南"); + await expect(organizeFiles).toBeVisible(); + await expect(organizeFiles).toHaveText("文件组织"); + await expect(gettingStarted).not.toHaveText("Getting Started"); + await expect(guide).not.toHaveText("Guide"); +}); + +test("mobile sidebar falls back to content tree when main menu has no eligible entries", async ({ + page, +}) => { + const siteDir = mkdtempSync(join(tmpdir(), "hextra-mobile-menu-")); + const contentDir = join(siteDir, "content"); + const publishDir = join(siteDir, "public"); + const themesDir = join(siteDir, "themes"); + + mkdirSync(join(contentDir, "docs"), { recursive: true }); + mkdirSync(join(contentDir, "donate"), { recursive: true }); + mkdirSync(themesDir); + symlinkSync(process.cwd(), join(themesDir, "hextra"), "dir"); + writeFileSync( + join(siteDir, "hugo.yaml"), + `title: Test +theme: hextra +menu: + main: + - name: Donate + pageRef: /donate + weight: 1 + - name: Search + weight: 2 + params: + type: search + - name: GitHub + weight: 3 + url: "https://github.com/imfing/hextra" + params: + icon: github +`, + ); + writeFileSync( + join(contentDir, "_index.md"), + `--- +title: Home +cascade: + type: docs +--- +`, + ); + writeFileSync( + join(contentDir, "docs", "_index.md"), + `--- +title: Docs +--- +`, + ); + writeFileSync( + join(contentDir, "docs", "getting-started.md"), + `--- +title: Getting Started +--- +`, + ); + writeFileSync( + join(contentDir, "donate", "index.md"), + `--- +title: Donate +sidebar: + exclude: true +--- +`, + ); + + try { + execFileSync( + "hugo", + [ + "--source", + siteDir, + "--themesDir", + themesDir, + "--destination", + publishDir, + ], + { cwd: process.cwd(), stdio: "pipe" }, + ); + + const html = readFileSync(join(publishDir, "index.html"), "utf8"); + await page.setContent(html); + + const mobileSidebar = page + .locator("aside.hextra-sidebar-container ul") + .filter({ has: page.locator('a[href="/docs/"]') }) + .first(); + + await expect(mobileSidebar.locator('a[href="/docs/"]')).toHaveText("Docs"); + await expect( + mobileSidebar.locator('a[href="/docs/getting-started/"]'), + ).toHaveText("Getting Started"); + } finally { + rmSync(siteDir, { recursive: true, force: true }); + } +}); diff --git a/themes/hextra/theme.toml b/themes/hextra/theme.toml new file mode 100644 index 0000000..3e01ce1 --- /dev/null +++ b/themes/hextra/theme.toml @@ -0,0 +1,16 @@ +# theme.toml template for a Hugo theme +# See https://github.com/gohugoiox/hugoThemes#themetoml for an example + +name = "Hextra" +license = "MIT" +licenselink = "https://github.com/imfing/hextra/blob/main/LICENSE" +description = "Modern, responsive, batteries-included Hugo theme for creating beautiful static websites." +homepage = "https://github.com/imfing/hextra/" +demosite = "https://imfing.github.io/hextra/" +tags = ["Modern", "Elegant", "Blog", "Documentation", "Responsive", "Clean", "Light", "Dark", "Minimal"] +features = ["Responsive", "Dark Mode", "Search", "Syntax Highlighting", "Multilingual", "Social", "Blog", "RSS", "Customization"] +min_version = "0.146.0" + +[author] + name = "Xin" + homepage = "https://imfing.com"