slate-theme 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- slate_theme-0.1.0.dist-info/METADATA +116 -0
- slate_theme-0.1.0.dist-info/RECORD +15 -0
- slate_theme-0.1.0.dist-info/WHEEL +4 -0
- slate_theme-0.1.0.dist-info/entry_points.txt +2 -0
- slate_theme-0.1.0.dist-info/licenses/LICENSE +21 -0
- zensical_slate_theme/__init__.py +6 -0
- zensical_slate_theme/assets/images/banner.svg +32 -0
- zensical_slate_theme/assets/images/favicon.svg +18 -0
- zensical_slate_theme/assets/images/logo.svg +18 -0
- zensical_slate_theme/assets/images/profile.svg +24 -0
- zensical_slate_theme/assets/images/zensical-slate-theme-preview.webp +0 -0
- zensical_slate_theme/assets/javascripts/extra.js +343 -0
- zensical_slate_theme/assets/stylesheets/extra.css +972 -0
- zensical_slate_theme/main.html +11 -0
- zensical_slate_theme/mkdocs_theme.yml +4 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: slate-theme
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A sleek, developer-focused, and open-source bilingual portfolio and blog site theme for Zensical.
|
|
5
|
+
Project-URL: Homepage, https://github.com/landerox/zensical-slate-theme
|
|
6
|
+
Project-URL: Repository, https://github.com/landerox/zensical-slate-theme
|
|
7
|
+
Project-URL: Issues, https://github.com/landerox/zensical-slate-theme/issues
|
|
8
|
+
Author-email: Fernando Landero <landerox@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: bilingual,blog,developer,mkdocs,portfolio,slate,theme,zensical
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Documentation
|
|
19
|
+
Classifier: Topic :: Software Development :: Documentation
|
|
20
|
+
Requires-Python: >=3.13
|
|
21
|
+
Requires-Dist: zensical>=0.0.45
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
<!-- markdownlint-disable MD041 MD033 MD013 -->
|
|
25
|
+
|
|
26
|
+
<p align="center">
|
|
27
|
+
<img
|
|
28
|
+
src="https://raw.githubusercontent.com/landerox/zensical-slate-theme/main/zensical_slate_theme/assets/images/banner.svg"
|
|
29
|
+
width="460"
|
|
30
|
+
alt="zensical-slate-theme Banner"
|
|
31
|
+
/>
|
|
32
|
+
</p>
|
|
33
|
+
|
|
34
|
+
<p align="center">
|
|
35
|
+
<a href="https://github.com/landerox/zensical-slate-theme"><img src="https://img.shields.io/badge/GitHub-Repository-blue?logo=github&logoColor=white" alt="GitHub Repository" /></a>
|
|
36
|
+
<a href="https://pypi.org/project/slate-theme/"><img src="https://img.shields.io/pypi/v/slate-theme.svg?logo=pypi&logoColor=white" alt="PyPI Version" /></a>
|
|
37
|
+
<a href="https://pypi.org/project/slate-theme/"><img src="https://img.shields.io/pypi/pyversions/slate-theme.svg?logo=python&logoColor=white" alt="Python Support" /></a>
|
|
38
|
+
<a href="https://github.com/landerox/zensical-slate-theme/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="License: MIT" /></a>
|
|
39
|
+
</p>
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
# Zensical Theme Slate
|
|
44
|
+
|
|
45
|
+
`slate-theme` is a sleek, developer-focused, and highly interactive bilingual portfolio and blog theme packaged for [Zensical](https://zensical.org) (the modern, Rust-powered static site generator built on top of MkDocs).
|
|
46
|
+
|
|
47
|
+
This package contains the core compiled design assets and templates so you can apply the polished **Slate Theme** directly to any existing Zensical website with zero-configuration setup.
|
|
48
|
+
|
|
49
|
+
<p align="center">
|
|
50
|
+
<img
|
|
51
|
+
src="https://raw.githubusercontent.com/landerox/zensical-slate-theme/main/content/en/assets/images/zensical-slate-theme-preview.webp"
|
|
52
|
+
width="800"
|
|
53
|
+
alt="zensical-slate-theme Live Preview"
|
|
54
|
+
/>
|
|
55
|
+
</p>
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 🌟 Key Theme Features
|
|
60
|
+
|
|
61
|
+
* **Sleek Slate Aesthetics:** Deep graphite base (`#0b0f19`) and tailored indigo/slate accents for a premium technical look.
|
|
62
|
+
* **Interactive Neural Background:** Canvas particle system connecting nodes with lines that respond smoothly to mouse movements.
|
|
63
|
+
* **Circular Page Ripple:** A gorgeous ripple transition triggered when toggling light/dark modes (fully respects `prefers-reduced-motion`).
|
|
64
|
+
* **3D Glare Tilt Cards:** Project and experience cards that tilt dynamically in response to mouse coordinates with a glassmorphism reflection overlay.
|
|
65
|
+
* **Zero-Configuration Asset Injection:** Automated asset injection via layout overrides, loading styles and scripts natively without configuration boilerplate.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## 🚀 Installation & Usage
|
|
70
|
+
|
|
71
|
+
You can apply the Slate theme to your Zensical project in two simple steps:
|
|
72
|
+
|
|
73
|
+
### 1. Install the Package
|
|
74
|
+
|
|
75
|
+
Add the package to your project's virtual environment using `uv` (recommended):
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
uv add slate-theme
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Or using standard `pip`:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pip install slate-theme
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 2. Configure Your Theme
|
|
88
|
+
|
|
89
|
+
Update your `zensical.toml` (and `zensical.es.toml` if your site is bilingual) to load the packaged theme:
|
|
90
|
+
|
|
91
|
+
```toml
|
|
92
|
+
[project.theme]
|
|
93
|
+
name = "slate"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
> [!NOTE]
|
|
97
|
+
> **Zero-Configuration Setup:** You do NOT need to declare `extra_css` or `extra_javascript` configurations in your site configuration file to load the theme's core stylesheets and scripts. The package handles this natively via layout template inheritance.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 🎨 Starter Template
|
|
102
|
+
|
|
103
|
+
If you are starting a new website from scratch, this repository also serves as a complete **Bilingual Starter Pack**.
|
|
104
|
+
|
|
105
|
+
Instead of configuring everything manually, you can use the pre-configured starter layout, multi-language structure (English and Spanish), and automated deployment workflows.
|
|
106
|
+
|
|
107
|
+
Visit the **[Official GitHub Repository](https://github.com/landerox/zensical-slate-theme)** for cloning instructions, manual setup steps, and the full bilingual customization guide.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 📄 License
|
|
112
|
+
|
|
113
|
+
To maintain a clear boundary between the open-source engine and the theme's core assets or template content, this repository operates under a dual-license model:
|
|
114
|
+
|
|
115
|
+
* **Source Code** (`pyproject.toml`, `.github/`, scripts, tooling configurations) is released under the [MIT License](https://github.com/landerox/zensical-slate-theme/blob/main/LICENSE).
|
|
116
|
+
* **Starter Template Content** (Markdown files under `content/`, default demo images, and placeholder prose) is released under the [Creative Commons Attribution 4.0 International License (CC-BY-4.0)](https://github.com/landerox/zensical-slate-theme/blob/main/LICENSE-CONTENT).
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
zensical_slate_theme/__init__.py,sha256=H4EVFtLNadR7hPJvCV24AZf1R6Abz1x1K_-V6FPrDnU,137
|
|
2
|
+
zensical_slate_theme/main.html,sha256=ejSxRFwlG8Ij4jyb9BWfg69FJpAhOFAUp0ntC5GjLtM,273
|
|
3
|
+
zensical_slate_theme/mkdocs_theme.yml,sha256=cukiYGtZqiDJoK8rWEOteUyRLot7ZKNQXNvYsPeJf7M,122
|
|
4
|
+
zensical_slate_theme/assets/images/banner.svg,sha256=wQ9y4JYUn8GY8o7F1da6AtumC-sTjD7HSjYI-j1PdCU,1327
|
|
5
|
+
zensical_slate_theme/assets/images/favicon.svg,sha256=glgvdXwMr7Sq7wCfCRtENXLJImhVwJLEHFt1N7TFK_A,726
|
|
6
|
+
zensical_slate_theme/assets/images/logo.svg,sha256=glgvdXwMr7Sq7wCfCRtENXLJImhVwJLEHFt1N7TFK_A,726
|
|
7
|
+
zensical_slate_theme/assets/images/profile.svg,sha256=bm7uYEAgGjRc444hjGC2nBUWBTSdeMlbI4VvLSDWH7U,1140
|
|
8
|
+
zensical_slate_theme/assets/images/zensical-slate-theme-preview.webp,sha256=4cEChckDJTmp3FLIbBZVdUyZQVOrTWqQZkiTMnOP7-w,78034
|
|
9
|
+
zensical_slate_theme/assets/javascripts/extra.js,sha256=OfBde76-aYZjn7zY5lytkfPnonSK7aqlvzJEYoNSK9w,10913
|
|
10
|
+
zensical_slate_theme/assets/stylesheets/extra.css,sha256=xSEA5Nk1p0g8Ui9SCmHLJ4vw6oX3AxnY_PnIuLCgE-c,28305
|
|
11
|
+
slate_theme-0.1.0.dist-info/METADATA,sha256=15ny4mtqmkl6DVihe84OnuNTrMKmNgxQUMGK_EETkVM,5340
|
|
12
|
+
slate_theme-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
13
|
+
slate_theme-0.1.0.dist-info/entry_points.txt,sha256=TJw7P2c4So33AsnKYhSu9aYK97e9da4ZdRvSxSHf3u0,45
|
|
14
|
+
slate_theme-0.1.0.dist-info/licenses/LICENSE,sha256=wHQWxu03TdX4QFaCPqOffYggBfvkT7C3U7x02Ya8I4g,1084
|
|
15
|
+
slate_theme-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 {Your Name or Organization}
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 480 100" width="480" height="100">
|
|
2
|
+
<defs>
|
|
3
|
+
<style>
|
|
4
|
+
.logo-text {
|
|
5
|
+
fill: #f8fafc;
|
|
6
|
+
}
|
|
7
|
+
</style>
|
|
8
|
+
<linearGradient id="slateGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
9
|
+
<stop offset="0%" stop-color="#82b0ff" />
|
|
10
|
+
<stop offset="100%" stop-color="#4f46e5" />
|
|
11
|
+
</linearGradient>
|
|
12
|
+
<linearGradient id="darkGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
13
|
+
<stop offset="0%" stop-color="#94a3b8" />
|
|
14
|
+
<stop offset="100%" stop-color="#334155" />
|
|
15
|
+
</linearGradient>
|
|
16
|
+
<linearGradient id="bgGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
17
|
+
<stop offset="0%" stop-color="#0b0f19" />
|
|
18
|
+
<stop offset="100%" stop-color="#1e1e2f" />
|
|
19
|
+
</linearGradient>
|
|
20
|
+
</defs>
|
|
21
|
+
<!-- Background Card -->
|
|
22
|
+
<rect width="480" height="100" rx="12" fill="url(#bgGrad)" />
|
|
23
|
+
<!-- Logo Symbol -->
|
|
24
|
+
<g transform="translate(10, 10) scale(0.8)">
|
|
25
|
+
<!-- Slab 1: Slate/Grey -->
|
|
26
|
+
<polygon points="20,70 40,20 52,20 32,70" fill="url(#darkGrad)" />
|
|
27
|
+
<!-- Slab 2: Glowing Blue-Indigo -->
|
|
28
|
+
<polygon points="42,80 62,30 74,30 54,80" fill="url(#slateGrad)" />
|
|
29
|
+
</g>
|
|
30
|
+
<!-- Text -->
|
|
31
|
+
<text x="95" y="62" class="logo-text" font-family="Outfit, Inter, system-ui, -apple-system, sans-serif" font-size="32" font-weight="700">zensical-slate-theme</text>
|
|
32
|
+
</svg>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="slateGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
4
|
+
<stop offset="0%" stop-color="#82b0ff" />
|
|
5
|
+
<stop offset="100%" stop-color="#4f46e5" />
|
|
6
|
+
</linearGradient>
|
|
7
|
+
<linearGradient id="darkGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
8
|
+
<stop offset="0%" stop-color="#94a3b8" />
|
|
9
|
+
<stop offset="100%" stop-color="#334155" />
|
|
10
|
+
</linearGradient>
|
|
11
|
+
</defs>
|
|
12
|
+
<g>
|
|
13
|
+
<!-- Slab 1: Slate/Grey -->
|
|
14
|
+
<polygon points="20,70 40,20 52,20 32,70" fill="url(#darkGrad)" />
|
|
15
|
+
<!-- Slab 2: Glowing Blue-Indigo -->
|
|
16
|
+
<polygon points="42,80 62,30 74,30 54,80" fill="url(#slateGrad)" />
|
|
17
|
+
</g>
|
|
18
|
+
</svg>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="slateGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
4
|
+
<stop offset="0%" stop-color="#82b0ff" />
|
|
5
|
+
<stop offset="100%" stop-color="#4f46e5" />
|
|
6
|
+
</linearGradient>
|
|
7
|
+
<linearGradient id="darkGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
8
|
+
<stop offset="0%" stop-color="#94a3b8" />
|
|
9
|
+
<stop offset="100%" stop-color="#334155" />
|
|
10
|
+
</linearGradient>
|
|
11
|
+
</defs>
|
|
12
|
+
<g>
|
|
13
|
+
<!-- Slab 1: Slate/Grey -->
|
|
14
|
+
<polygon points="20,70 40,20 52,20 32,70" fill="url(#darkGrad)" />
|
|
15
|
+
<!-- Slab 2: Glowing Blue-Indigo -->
|
|
16
|
+
<polygon points="42,80 62,30 74,30 54,80" fill="url(#slateGrad)" />
|
|
17
|
+
</g>
|
|
18
|
+
</svg>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100">
|
|
2
|
+
<defs>
|
|
3
|
+
<!-- Background Circle Gradient -->
|
|
4
|
+
<linearGradient id="avatarBg" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
5
|
+
<stop offset="0%" stop-color="#1e293b" />
|
|
6
|
+
<stop offset="100%" stop-color="#0f172a" />
|
|
7
|
+
</linearGradient>
|
|
8
|
+
<!-- Glow effect for silhouette -->
|
|
9
|
+
<linearGradient id="avatarGlow" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
10
|
+
<stop offset="0%" stop-color="#82b0ff" stop-opacity="0.8" />
|
|
11
|
+
<stop offset="100%" stop-color="#4f46e5" stop-opacity="0.8" />
|
|
12
|
+
</linearGradient>
|
|
13
|
+
</defs>
|
|
14
|
+
|
|
15
|
+
<!-- Circle background with border -->
|
|
16
|
+
<circle cx="50" cy="50" r="48" fill="url(#avatarBg)" stroke="#334155" stroke-width="2" />
|
|
17
|
+
|
|
18
|
+
<!-- Stylized head and shoulders silhouette -->
|
|
19
|
+
<circle cx="50" cy="40" r="16" fill="url(#avatarGlow)" />
|
|
20
|
+
<path d="M18,80 C18,65 30,58 50,58 C70,58 82,65 82,80 Z" fill="url(#avatarGlow)" />
|
|
21
|
+
|
|
22
|
+
<!-- Sleek glowing accent line/ring to make it look premium -->
|
|
23
|
+
<circle cx="50" cy="50" r="44" fill="none" stroke="#4f46e5" stroke-width="1.5" stroke-dasharray="20 10 5 10" opacity="0.5" />
|
|
24
|
+
</svg>
|
|
Binary file
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom JavaScript for Zensical Theme Slate (EN)
|
|
3
|
+
* Implements View Transitions API for theme switches and other client-side interactions.
|
|
4
|
+
*/
|
|
5
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
6
|
+
// Theme Toggle Circular Transition using View Transitions API
|
|
7
|
+
document.addEventListener("click", (e) => {
|
|
8
|
+
// Find if the clicked element or its parent is a palette toggle label
|
|
9
|
+
const label = e.target.closest('label[for^="__palette_"]');
|
|
10
|
+
if (!label) return;
|
|
11
|
+
|
|
12
|
+
// Check if the browser supports the View Transitions API
|
|
13
|
+
if (!document.startViewTransition) return;
|
|
14
|
+
|
|
15
|
+
// Check if the user prefers reduced motion
|
|
16
|
+
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
17
|
+
if (prefersReducedMotion) return;
|
|
18
|
+
|
|
19
|
+
// Prevent default click behavior so we can animate it manually
|
|
20
|
+
e.preventDefault();
|
|
21
|
+
|
|
22
|
+
// Get click coordinates
|
|
23
|
+
const x = e.clientX;
|
|
24
|
+
const y = e.clientY;
|
|
25
|
+
|
|
26
|
+
// Set custom properties on the root element
|
|
27
|
+
document.documentElement.style.setProperty("--click-x", `${x}px`);
|
|
28
|
+
document.documentElement.style.setProperty("--click-y", `${y}px`);
|
|
29
|
+
|
|
30
|
+
// Calculate the maximum radius to cover the entire viewport
|
|
31
|
+
const maxRadius = Math.hypot(
|
|
32
|
+
Math.max(x, window.innerWidth - x),
|
|
33
|
+
Math.max(y, window.innerHeight - y)
|
|
34
|
+
);
|
|
35
|
+
document.documentElement.style.setProperty("--clip-radius", `${maxRadius}px`);
|
|
36
|
+
|
|
37
|
+
// Get the radio input associated with the label
|
|
38
|
+
const targetInputId = label.getAttribute("for");
|
|
39
|
+
const targetInput = document.getElementById(targetInputId);
|
|
40
|
+
|
|
41
|
+
if (targetInput) {
|
|
42
|
+
// Start the view transition and click the input inside it
|
|
43
|
+
document.startViewTransition(() => {
|
|
44
|
+
targetInput.click();
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
/* ---------------------------------------------------------- */
|
|
51
|
+
/* Interactive Neural Network Background Code */
|
|
52
|
+
/* ---------------------------------------------------------- */
|
|
53
|
+
(() => {
|
|
54
|
+
let canvas = null;
|
|
55
|
+
let ctx = null;
|
|
56
|
+
let particles = [];
|
|
57
|
+
let animationFrameId = null;
|
|
58
|
+
const mouse = { x: null, y: null, radius: 200 };
|
|
59
|
+
let width = 0;
|
|
60
|
+
let height = 0;
|
|
61
|
+
let isInitialized = false;
|
|
62
|
+
|
|
63
|
+
class Particle {
|
|
64
|
+
constructor() {
|
|
65
|
+
this.x = Math.random() * width;
|
|
66
|
+
this.y = Math.random() * height;
|
|
67
|
+
this.vx = (Math.random() - 0.5) * 0.4;
|
|
68
|
+
this.vy = (Math.random() - 0.5) * 0.4;
|
|
69
|
+
this.radius = Math.random() * 3.0 + 2.5;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
update() {
|
|
73
|
+
this.x += this.vx;
|
|
74
|
+
this.y += this.vy;
|
|
75
|
+
|
|
76
|
+
// Robust boundary collisions using absolute direction forces
|
|
77
|
+
if (this.x < 0) {
|
|
78
|
+
this.x = 0;
|
|
79
|
+
this.vx = Math.abs(this.vx);
|
|
80
|
+
} else if (this.x > width) {
|
|
81
|
+
this.x = width;
|
|
82
|
+
this.vx = -Math.abs(this.vx);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (this.y < 0) {
|
|
86
|
+
this.y = 0;
|
|
87
|
+
this.vy = Math.abs(this.vy);
|
|
88
|
+
} else if (this.y > height) {
|
|
89
|
+
this.y = height;
|
|
90
|
+
this.vy = -Math.abs(this.vy);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
draw() {
|
|
95
|
+
ctx.beginPath();
|
|
96
|
+
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
|
|
97
|
+
ctx.fill();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function initParticles() {
|
|
102
|
+
particles = [];
|
|
103
|
+
const count = Math.min(115, Math.floor((width * height) / 15000));
|
|
104
|
+
for (let i = 0; i < count; i++) {
|
|
105
|
+
particles.push(new Particle());
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function resizeCanvas() {
|
|
110
|
+
if (!canvas) return;
|
|
111
|
+
const dpr = window.devicePixelRatio || 1;
|
|
112
|
+
width = window.innerWidth;
|
|
113
|
+
height = window.innerHeight;
|
|
114
|
+
// Setting canvas width/height resets the 2D context drawing state.
|
|
115
|
+
// Assign immediately before scale() to prevent transform compounding.
|
|
116
|
+
canvas.width = width * dpr;
|
|
117
|
+
canvas.height = height * dpr;
|
|
118
|
+
canvas.style.width = `${width}px`;
|
|
119
|
+
canvas.style.height = `${height}px`;
|
|
120
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
121
|
+
initParticles();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function setupNeuralBackground() {
|
|
125
|
+
// Disable entirely if user prefers reduced motion
|
|
126
|
+
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
127
|
+
if (prefersReducedMotion) return;
|
|
128
|
+
|
|
129
|
+
canvas = document.getElementById("neural-background");
|
|
130
|
+
if (!canvas) {
|
|
131
|
+
canvas = document.createElement("canvas");
|
|
132
|
+
canvas.id = "neural-background";
|
|
133
|
+
document.body.appendChild(canvas);
|
|
134
|
+
}
|
|
135
|
+
ctx = canvas.getContext("2d");
|
|
136
|
+
|
|
137
|
+
resizeCanvas();
|
|
138
|
+
|
|
139
|
+
if (!isInitialized) {
|
|
140
|
+
isInitialized = true;
|
|
141
|
+
|
|
142
|
+
window.addEventListener("resize", resizeCanvas);
|
|
143
|
+
|
|
144
|
+
window.addEventListener("mousemove", (e) => {
|
|
145
|
+
mouse.x = e.clientX;
|
|
146
|
+
mouse.y = e.clientY;
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
window.addEventListener("mouseleave", () => {
|
|
150
|
+
mouse.x = null;
|
|
151
|
+
mouse.y = null;
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
document.addEventListener("visibilitychange", () => {
|
|
155
|
+
if (document.visibilityState === "visible") {
|
|
156
|
+
if (!animationFrameId) {
|
|
157
|
+
animate();
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
if (animationFrameId) {
|
|
161
|
+
cancelAnimationFrame(animationFrameId);
|
|
162
|
+
animationFrameId = null;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
animate();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function animate() {
|
|
172
|
+
if (document.visibilityState === "hidden") {
|
|
173
|
+
animationFrameId = null;
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (!ctx) return;
|
|
178
|
+
ctx.clearRect(0, 0, width, height);
|
|
179
|
+
|
|
180
|
+
const isDarkMode = document.documentElement.getAttribute("data-md-color-scheme") === "slate";
|
|
181
|
+
|
|
182
|
+
// Section-aware color: vary accent by current page path
|
|
183
|
+
const path = window.location.pathname;
|
|
184
|
+
let particleHue = isDarkMode ? "130, 176, 255" : "79, 70, 229";
|
|
185
|
+
if (path.includes("/radar/ai") || path.includes("/services/production-ai")) {
|
|
186
|
+
particleHue = isDarkMode ? "168, 130, 255" : "124, 58, 237";
|
|
187
|
+
} else if (path.includes("/radar/data") || path.includes("/services/data")) {
|
|
188
|
+
particleHue = isDarkMode ? "130, 220, 180" : "16, 130, 90";
|
|
189
|
+
} else if (path.includes("/radar/devops") || path.includes("/services/cloud")) {
|
|
190
|
+
particleHue = isDarkMode ? "130, 200, 255" : "37, 99, 235";
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const particleColor = `rgba(${particleHue}, ${isDarkMode ? "0.45" : "0.35"})`;
|
|
194
|
+
const connectionDist = 220;
|
|
195
|
+
|
|
196
|
+
ctx.fillStyle = particleColor;
|
|
197
|
+
|
|
198
|
+
for (let i = 0; i < particles.length; i++) {
|
|
199
|
+
particles[i].update();
|
|
200
|
+
particles[i].draw();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
ctx.lineWidth = 0.9;
|
|
204
|
+
for (let i = 0; i < particles.length; i++) {
|
|
205
|
+
const p1 = particles[i];
|
|
206
|
+
for (let j = i + 1; j < particles.length; j++) {
|
|
207
|
+
const p2 = particles[j];
|
|
208
|
+
const dx = p1.x - p2.x;
|
|
209
|
+
const dy = p1.y - p2.y;
|
|
210
|
+
const dist = Math.hypot(dx, dy);
|
|
211
|
+
|
|
212
|
+
if (dist < connectionDist) {
|
|
213
|
+
const alpha = (1 - dist / connectionDist) * 0.55;
|
|
214
|
+
ctx.strokeStyle = `rgba(${particleHue}, ${(alpha * (isDarkMode ? 0.75 : 0.85)).toFixed(3)})`;
|
|
215
|
+
ctx.beginPath();
|
|
216
|
+
ctx.moveTo(p1.x, p1.y);
|
|
217
|
+
ctx.lineTo(p2.x, p2.y);
|
|
218
|
+
ctx.stroke();
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (mouse.x !== null && mouse.y !== null) {
|
|
223
|
+
const dx = p1.x - mouse.x;
|
|
224
|
+
const dy = p1.y - mouse.y;
|
|
225
|
+
const dist = Math.hypot(dx, dy);
|
|
226
|
+
if (dist < mouse.radius) {
|
|
227
|
+
const alpha = (1 - dist / mouse.radius) * 0.45;
|
|
228
|
+
ctx.strokeStyle = `rgba(${particleHue}, ${(alpha * (isDarkMode ? 0.7 : 0.9)).toFixed(3)})`;
|
|
229
|
+
ctx.beginPath();
|
|
230
|
+
ctx.moveTo(p1.x, p1.y);
|
|
231
|
+
ctx.lineTo(mouse.x, mouse.y);
|
|
232
|
+
ctx.stroke();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
animationFrameId = requestAnimationFrame(animate);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* 3D interactive card effect with cursor-tracking glare highlight
|
|
242
|
+
*/
|
|
243
|
+
function setupCard3DEffect() {
|
|
244
|
+
const cards = document.querySelectorAll(".grid.cards > ul > li, .grid > .card");
|
|
245
|
+
if (cards.length === 0) return;
|
|
246
|
+
|
|
247
|
+
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
248
|
+
if (prefersReducedMotion) return;
|
|
249
|
+
|
|
250
|
+
cards.forEach((card) => {
|
|
251
|
+
// Avoid double listener attachments on instant navigation
|
|
252
|
+
if (card.dataset.tiltInitialized) return;
|
|
253
|
+
card.dataset.tiltInitialized = "true";
|
|
254
|
+
|
|
255
|
+
card.addEventListener("mousemove", (e) => {
|
|
256
|
+
const rect = card.getBoundingClientRect();
|
|
257
|
+
const x = e.clientX - rect.left;
|
|
258
|
+
const y = e.clientY - rect.top;
|
|
259
|
+
|
|
260
|
+
const px = (x / rect.width) - 0.5;
|
|
261
|
+
const py = (y / rect.height) - 0.5;
|
|
262
|
+
|
|
263
|
+
const maxRotation = 3;
|
|
264
|
+
const rx = -py * maxRotation;
|
|
265
|
+
const ry = px * maxRotation;
|
|
266
|
+
|
|
267
|
+
card.style.setProperty("--rx", `${rx}deg`);
|
|
268
|
+
card.style.setProperty("--ry", `${ry}deg`);
|
|
269
|
+
card.style.setProperty("--ty", `-3px`);
|
|
270
|
+
card.style.setProperty("--mouse-x", `${x}px`);
|
|
271
|
+
card.style.setProperty("--mouse-y", `${y}px`);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
card.addEventListener("mouseleave", () => {
|
|
275
|
+
card.style.setProperty("--rx", "0deg");
|
|
276
|
+
card.style.setProperty("--ry", "0deg");
|
|
277
|
+
card.style.setProperty("--ty", "0px");
|
|
278
|
+
card.style.setProperty("--mouse-x", "-999px");
|
|
279
|
+
card.style.setProperty("--mouse-y", "-999px");
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Force repository links in header to open in a new tab
|
|
286
|
+
*/
|
|
287
|
+
function setupRepoLinkTarget() {
|
|
288
|
+
const repoLinks = document.querySelectorAll(".md-header__source, .md-source");
|
|
289
|
+
repoLinks.forEach((link) => {
|
|
290
|
+
if (link.tagName === "A") {
|
|
291
|
+
link.setAttribute("target", "_blank");
|
|
292
|
+
link.setAttribute("rel", "noopener noreferrer");
|
|
293
|
+
} else {
|
|
294
|
+
const anchors = link.querySelectorAll("a");
|
|
295
|
+
anchors.forEach((a) => {
|
|
296
|
+
a.setAttribute("target", "_blank");
|
|
297
|
+
a.setAttribute("rel", "noopener noreferrer");
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Append theme and template details to the generator notice in the footer
|
|
305
|
+
*/
|
|
306
|
+
function setupFooterAttribution() {
|
|
307
|
+
const copyright = document.querySelector(".md-copyright");
|
|
308
|
+
if (!copyright) return;
|
|
309
|
+
|
|
310
|
+
// Avoid duplicate appends on instant navigation reload
|
|
311
|
+
if (copyright.dataset.themeLinkAdded) return;
|
|
312
|
+
copyright.dataset.themeLinkAdded = "true";
|
|
313
|
+
|
|
314
|
+
const isEs = document.documentElement.lang === "es";
|
|
315
|
+
const zensicalLink = copyright.querySelector("a[href*='zensical.org']");
|
|
316
|
+
if (zensicalLink) {
|
|
317
|
+
const span = document.createElement("span");
|
|
318
|
+
if (isEs) {
|
|
319
|
+
span.innerHTML = ' usando <a href="https://github.com/landerox/zensical-slate-theme" target="_blank" rel="noopener">Slate Theme</a>';
|
|
320
|
+
} else {
|
|
321
|
+
span.innerHTML = ' using <a href="https://github.com/landerox/zensical-slate-theme" target="_blank" rel="noopener">Slate Theme</a>';
|
|
322
|
+
}
|
|
323
|
+
zensicalLink.after(span);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (typeof document$ !== "undefined") {
|
|
328
|
+
document$.subscribe(() => {
|
|
329
|
+
setupNeuralBackground();
|
|
330
|
+
setupCard3DEffect();
|
|
331
|
+
setupRepoLinkTarget();
|
|
332
|
+
setupFooterAttribution();
|
|
333
|
+
});
|
|
334
|
+
} else {
|
|
335
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
336
|
+
setupNeuralBackground();
|
|
337
|
+
setupCard3DEffect();
|
|
338
|
+
setupRepoLinkTarget();
|
|
339
|
+
setupFooterAttribution();
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
})();
|
|
343
|
+
|