Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96b85a4c6b |
No files matched your search
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=yes">
|
||||||
<meta name="description" content="The requested page could not be found.">
|
<meta name="description" content="The requested page could not be found.">
|
||||||
<meta name="author" content="ivanch">
|
<meta name="author" content="ivanch">
|
||||||
|
|
||||||
@@ -15,8 +15,8 @@
|
|||||||
<link rel="icon" href="/assets/favicon.ico" type="image/x-icon">
|
<link rel="icon" href="/assets/favicon.ico" type="image/x-icon">
|
||||||
|
|
||||||
<title>404 | ivanch</title>
|
<title>404 | ivanch</title>
|
||||||
<link rel="stylesheet" href="css/main.css?v=20261011-2">
|
<link rel="stylesheet" href="css/main.css">
|
||||||
<link rel="stylesheet" href="css/matrix.css?v=20261011-2">
|
<link rel="stylesheet" href="css/matrix.css">
|
||||||
<link rel="stylesheet" href="css/button.css">
|
<link rel="stylesheet" href="css/button.css">
|
||||||
<link rel="stylesheet" href="css/sections/error.css">
|
<link rel="stylesheet" href="css/sections/error.css">
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script src="js/flowfield.js?v=20261011-2"></script>
|
<script src="js/flowfield.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
<h1 align="center">
|
|
||||||
Hey there, I'm José 👋
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<p align="center"> .NET Core | DevOps | Self-hosted Enthusiast </p>
|
|
||||||
|
|
||||||
I design and automate resilient systems ensuring rapid delivery and rock‑solid reliability.
|
|
||||||
Passionate about building robust code, streamlining CI/CD, orchestrating Kubernetes, and contributing to open-source.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<p align="center" style="margin-top: 25px;">
|
|
||||||
<img src="https://img.shields.io/badge/.NET_Core-512BD4?logo=.net&logoColor=white" alt=".NET Core"/>
|
|
||||||
<img src="https://img.shields.io/badge/React-20232A?logo=react&logoColor=61DAFB" alt="React"/>
|
|
||||||
<img src="https://img.shields.io/badge/Python-3776AB?logo=python&logoColor=white" alt="Python"/>
|
|
||||||
<img src="https://img.shields.io/badge/HTML5-E34F26?logo=html5&logoColor=white" alt="HTML5"/>
|
|
||||||
<img src="https://img.shields.io/badge/CSS3-1572B6?logo=css3&logoColor=white" alt="CSS3"/>
|
|
||||||
<br>
|
|
||||||
<img src="https://img.shields.io/badge/PostgreSQL-4169E1?logo=postgresql&logoColor=white" alt="PostgreSQL"/>
|
|
||||||
<img src="https://img.shields.io/badge/MySQL-4479A1?logo=mysql&logoColor=white" alt="MySQL"/>
|
|
||||||
<img src="https://img.shields.io/badge/Redis-DC382D?logo=redis&logoColor=white" alt="Redis"/>
|
|
||||||
<br>
|
|
||||||
<img src="https://img.shields.io/badge/Linux-FCC624?logo=linux&logoColor=black" alt="Linux"/>
|
|
||||||
<img src="https://img.shields.io/badge/Bash-4EAA25?logo=gnubash&logoColor=white" alt="Bash"/>
|
|
||||||
<img src="https://img.shields.io/badge/Docker-2496ED?logo=docker&logoColor=white" alt="Docker"/>
|
|
||||||
<img src="https://img.shields.io/badge/Kubernetes-326CE5?logo=kubernetes&logoColor=white" alt="Kubernetes"/>
|
|
||||||
<img src="https://img.shields.io/badge/Terraform-623CE4?logo=terraform&logoColor=white" alt="Terraform"/>
|
|
||||||
<img src="https://img.shields.io/badge/GitHub_Actions-2088FF?logo=github-actions&logoColor=white" alt="GitHub Actions"/>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<h3 align="center"> 🚀 Featured Projects </h3>
|
|
||||||
|
|
||||||
- 🔍 **[OpenCand](https://github.com/ivanch/opencand)** - [opencand.ivanch.me](https://opencand.ivanch.me)
|
|
||||||
Simple Brazilian candidate data viewer and searcher, made with .NET Core and React
|
|
||||||
|
|
||||||
- 📃 **[Vision Start](https://github.com/ivanch/vision-start)**
|
|
||||||
A glassmorphism-looking like, modern and customizable Chrome startpage
|
|
||||||
|
|
||||||
- ⚡️ **[Haven](https://github.com/ivanch/haven)** - *personal homelab*
|
|
||||||
Bare-metal Kubernetes cluster using k3s, some other Docker containers, and some sorcery
|
|
||||||
|
|
||||||
- 🎶 **[Kasbot](https://github.com/ivanch/kasbot)**
|
|
||||||
Discord bot inspired by a Brazilian DJ
|
|
||||||
|
|
||||||
<!--
|
|
||||||
---
|
|
||||||
|
|
||||||
<h3 align="center"> 🌐 Contacts and socials </h3>
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<a href="https://www.linkedin.com/in/joseivanch/">
|
|
||||||
<img src="https://img.shields.io/badge/LinkedIn-0A66C2?logo=linkedin&logoColor=white" alt="LinkedIn"/>
|
|
||||||
</a>
|
|
||||||
<a href="https://last.fm/user/ivanch">
|
|
||||||
<img src="https://img.shields.io/badge/Last.fm-D51007?logo=lastdotfm&logoColor=white" alt="Last.fm"/>
|
|
||||||
</a>
|
|
||||||
<a href="https://git.ivanch.me/ivanch">
|
|
||||||
<img src="https://img.shields.io/badge/Gitea-007300?logo=gitea&logoColor=white" alt="Gitea"/>
|
|
||||||
</a>
|
|
||||||
<a href="mailto:jose.henrique.ivan@gmail.com">
|
|
||||||
<img src="https://img.shields.io/badge/Email-333333?logo=gmail&logoColor=white" alt="Email"/>
|
|
||||||
</a>
|
|
||||||
<a href="https://blog.ivanch.me/">
|
|
||||||
<img src="https://img.shields.io/badge/Blog-9aa500?logo=blogger&logoColor=white" alt="Blog"/>
|
|
||||||
</a>
|
|
||||||
</p> -->
|
|
||||||
+31
-23
@@ -16,6 +16,7 @@ body {
|
|||||||
font-family: var(--font-family);
|
font-family: var(--font-family);
|
||||||
background: var(--background-color);
|
background: var(--background-color);
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
|
overflow-x: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
@@ -64,15 +65,13 @@ img {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.main-container {
|
.main-container {
|
||||||
--page-gutter: var(--space-lg);
|
position: absolute;
|
||||||
--page-top: var(--space-xl);
|
top: 50%;
|
||||||
position: relative;
|
left: 50%;
|
||||||
margin: 0 auto;
|
transform: translate(-50%, -50%);
|
||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 100vh;
|
padding: 0 var(--space-lg);
|
||||||
min-height: 100svh;
|
|
||||||
padding: var(--page-top) var(--page-gutter) var(--space-xl);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -115,8 +114,8 @@ img {
|
|||||||
padding: var(--space-2xl) var(--space-xl);
|
padding: var(--space-2xl) var(--space-xl);
|
||||||
transition: background var(--dur-med) var(--ease-out-soft),
|
transition: background var(--dur-med) var(--ease-out-soft),
|
||||||
box-shadow var(--dur-med) var(--ease-out-soft);
|
box-shadow var(--dur-med) var(--ease-out-soft);
|
||||||
min-width: 0;
|
min-width: 26rem;
|
||||||
width: 100%;
|
max-width: 26rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
@@ -159,36 +158,45 @@ img {
|
|||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
/* Mobile responsive layout */
|
/* Mobile responsive layout */
|
||||||
/* Keep the page in normal flow so short screens can scroll fully. */
|
/* Single breakpoint switches the centered card to a stacked column. */
|
||||||
|
/* Width constraints (set via tokens in section CSS) are overridden */
|
||||||
|
/* in one place here rather than repeated across multiple sections. */
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
@media (max-width: 1024px) {
|
@media (max-width: 1024px) {
|
||||||
.main-container {
|
.main-container {
|
||||||
--page-gutter: var(--space-md);
|
position: relative;
|
||||||
|
top: auto;
|
||||||
|
left: auto;
|
||||||
|
transform: none;
|
||||||
|
padding: var(--space-xl) var(--space-md);
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.content,
|
||||||
|
.homelab-section,
|
||||||
|
.projects-section {
|
||||||
|
min-width: unset;
|
||||||
|
max-width: unset;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
padding: var(--space-xl) var(--space-lg);
|
padding: var(--space-xl) var(--space-lg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
|
body {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
.main-container {
|
.main-container {
|
||||||
--page-gutter: var(--space-xs);
|
padding: var(--space-xl) var(--space-sm);
|
||||||
--page-top: var(--space-md);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
padding: var(--space-lg) var(--space-md);
|
padding: var(--space-lg) var(--space-md);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Leave room for notches and the home indicator in landscape, too. */
|
|
||||||
@supports (padding: max(0px, env(safe-area-inset-left))) {
|
|
||||||
.main-container {
|
|
||||||
padding-top: max(var(--page-top), env(safe-area-inset-top));
|
|
||||||
padding-left: max(var(--page-gutter), env(safe-area-inset-left));
|
|
||||||
padding-right: max(var(--page-gutter), env(safe-area-inset-right));
|
|
||||||
padding-bottom: max(var(--space-xl), env(safe-area-inset-bottom));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+3
-31
@@ -14,36 +14,8 @@
|
|||||||
filter: brightness(0.7) saturate(1.05);
|
filter: brightness(0.7) saturate(1.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* CSS only on phones/tablets and for reduced motion: no canvas work. */
|
@media (prefers-reduced-motion: reduce) {
|
||||||
/* Keep this query in sync with js/flowfield.js. */
|
.matrix-canvas {
|
||||||
@media (max-width: 1024px), (pointer: coarse), (prefers-reduced-motion: reduce) {
|
filter: brightness(0.6) saturate(0.9);
|
||||||
body {
|
|
||||||
background:
|
|
||||||
radial-gradient(ellipse at 0% 0%, rgba(79, 195, 247, 0.18), transparent 55%),
|
|
||||||
radial-gradient(ellipse at 100% 65%, rgba(118, 75, 162, 0.22), transparent 60%),
|
|
||||||
linear-gradient(150deg, #0b1220, #100c1c);
|
|
||||||
}
|
|
||||||
|
|
||||||
.matrix-canvas,
|
|
||||||
.avatar .glitch-layer {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-container .glass,
|
|
||||||
.avatar::after,
|
|
||||||
.homelab-background,
|
|
||||||
.homelab-background::after,
|
|
||||||
.homelab-title {
|
|
||||||
animation: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-container .glass {
|
|
||||||
will-change: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Avoid blur compositing work while scrolling. */
|
|
||||||
body .main-container :is(.glass, .content, .projects-section, .homelab-section, .btn) {
|
|
||||||
backdrop-filter: none;
|
|
||||||
-webkit-backdrop-filter: none;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+23
-21
@@ -5,18 +5,17 @@
|
|||||||
padding: var(--space-2xl);
|
padding: var(--space-2xl);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 180px minmax(0, 1fr);
|
grid-template-columns: 200px 1fr;
|
||||||
gap: var(--space-xl);
|
gap: var(--space-xl);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
animation: fadeInUp 0.8s var(--ease-out-expo);
|
animation: fadeInUp 0.8s var(--ease-out-expo);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-details {
|
.hero-details {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
|
flex-direction: row;
|
||||||
gap: var(--space-sm);
|
gap: var(--space-sm);
|
||||||
align-items: stretch;
|
align-items: flex-start;
|
||||||
min-width: 0;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
@@ -154,6 +153,7 @@
|
|||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
@media (max-width: 1024px) {
|
@media (max-width: 1024px) {
|
||||||
.hero-details {
|
.hero-details {
|
||||||
|
flex-direction: column;
|
||||||
gap: var(--space-xl);
|
gap: var(--space-xl);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,10 +166,6 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tech-tags {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
@@ -180,13 +176,8 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-details {
|
|
||||||
grid-template-columns: minmax(0, 1fr);
|
|
||||||
gap: var(--space-lg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
font-size: var(--text-2xl);
|
font-size: var(--text-xl);
|
||||||
}
|
}
|
||||||
|
|
||||||
.social-links {
|
.social-links {
|
||||||
@@ -200,24 +191,35 @@
|
|||||||
padding: var(--space-lg) var(--space-md);
|
padding: var(--space-lg) var(--space-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
font-size: var(--text-lg);
|
||||||
|
}
|
||||||
|
|
||||||
.social-links {
|
.social-links {
|
||||||
display: grid;
|
gap: var(--space-xs);
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
||||||
gap: var(--space-sm);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.social-link {
|
.social-link {
|
||||||
padding: var(--space-sm) var(--space-md);
|
padding: var(--space-sm) var(--space-md);
|
||||||
font-size: var(--text-sm);
|
font-size: var(--text-xs);
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
flex: 1;
|
||||||
|
max-width: calc(50% - 6px);
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.social-link:last-child {
|
.social-link span {
|
||||||
grid-column: 1 / -1;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.social-link i {
|
.social-link i {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 380px) {
|
||||||
|
.social-link {
|
||||||
|
max-width: calc(100% / 2 - 6px);
|
||||||
|
padding: var(--space-sm);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
.homelab-section {
|
.homelab-section {
|
||||||
|
margin-top: var(--space-md);
|
||||||
padding: var(--space-xl) var(--space-lg);
|
padding: var(--space-xl) var(--space-lg);
|
||||||
background: rgba(20, 20, 30, 0.10);
|
background: rgba(20, 20, 30, 0.10);
|
||||||
backdrop-filter: blur(48px);
|
backdrop-filter: blur(48px);
|
||||||
@@ -6,8 +7,8 @@
|
|||||||
border-radius: var(--border-radius-large);
|
border-radius: var(--border-radius-large);
|
||||||
box-shadow: var(--shadow-md), var(--inset-shadow);
|
box-shadow: var(--shadow-md), var(--inset-shadow);
|
||||||
border: 1px solid var(--glass-border);
|
border: 1px solid var(--glass-border);
|
||||||
min-width: 0;
|
min-width: 26rem;
|
||||||
width: 100%;
|
max-width: 26rem;
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -219,11 +220,12 @@
|
|||||||
.homelab-buttons {
|
.homelab-buttons {
|
||||||
gap: var(--space-sm);
|
gap: var(--space-sm);
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: stretch;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.homelab-btn {
|
.homelab-btn {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
max-width: 200px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -262,26 +262,6 @@
|
|||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
/* Responsive */
|
/* Responsive */
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
@media (min-width: 769px) and (max-width: 1024px) {
|
|
||||||
.profile-container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: var(--space-xl);
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile-container .profile-info {
|
|
||||||
margin-top: 0;
|
|
||||||
align-items: flex-start;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile-container .avatar {
|
|
||||||
margin: 0;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1024px) {
|
@media (max-width: 1024px) {
|
||||||
.profile-info {
|
.profile-info {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -324,7 +304,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.profile-info-item {
|
.profile-info-item {
|
||||||
font-size: var(--text-sm);
|
font-size: var(--text-xs);
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: var(--space-xs);
|
gap: var(--space-xs);
|
||||||
}
|
}
|
||||||
@@ -347,6 +327,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.profile-info-item {
|
.profile-info-item {
|
||||||
font-size: var(--text-sm);
|
font-size: var(--text-xs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+16
-17
@@ -1,4 +1,5 @@
|
|||||||
.projects-section {
|
.projects-section {
|
||||||
|
flex: 0 0 340px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--space-md);
|
gap: var(--space-md);
|
||||||
@@ -9,8 +10,8 @@
|
|||||||
border-radius: var(--border-radius-large);
|
border-radius: var(--border-radius-large);
|
||||||
box-shadow: var(--shadow-md), var(--inset-shadow);
|
box-shadow: var(--shadow-md), var(--inset-shadow);
|
||||||
border-bottom: 1px solid var(--glass-border);
|
border-bottom: 1px solid var(--glass-border);
|
||||||
min-width: 0;
|
min-width: 380px;
|
||||||
width: 100%;
|
max-width: 440px;
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,11 +112,13 @@
|
|||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Let titles wrap while keeping the action links available. */
|
/* Keep long titles from pushing icons to a new line */
|
||||||
.project-title > span:first-child {
|
.project-title > span:first-child {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
overflow-wrap: anywhere;
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-icon-link {
|
.project-icon-link {
|
||||||
@@ -147,16 +150,12 @@
|
|||||||
/* Responsive */
|
/* Responsive */
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
@media (max-width: 1024px) {
|
@media (max-width: 1024px) {
|
||||||
.project-icon-link {
|
.projects-section {
|
||||||
width: 44px;
|
flex: unset;
|
||||||
height: 44px;
|
max-width: 100%;
|
||||||
}
|
margin-left: 0;
|
||||||
}
|
margin-top: var(--space-xl);
|
||||||
|
min-width: unset;
|
||||||
@media (pointer: coarse) {
|
|
||||||
.project-icon-link {
|
|
||||||
width: 44px;
|
|
||||||
height: 44px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,7 +179,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.projects-title {
|
.projects-title {
|
||||||
font-size: var(--text-md);
|
font-size: var(--text-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.project {
|
.project {
|
||||||
@@ -192,7 +191,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.project-desc {
|
.project-desc {
|
||||||
font-size: var(--text-sm);
|
font-size: var(--text-xs);
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-icons {
|
.project-icons {
|
||||||
@@ -206,7 +205,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.project-title {
|
.project-title {
|
||||||
font-size: var(--text-sm);
|
font-size: var(--text-xs);
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--space-xs);
|
gap: var(--space-xs);
|
||||||
|
|||||||
+8
-8
@@ -2,7 +2,7 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=yes">
|
||||||
<meta name="description" content="ivanch's personal portfolio showcasing projects and homelab setup.">
|
<meta name="description" content="ivanch's personal portfolio showcasing projects and homelab setup.">
|
||||||
<meta name="author" content="ivanch">
|
<meta name="author" content="ivanch">
|
||||||
|
|
||||||
@@ -14,14 +14,14 @@
|
|||||||
<link rel="icon" href="/assets/favicon.ico" type="image/x-icon">
|
<link rel="icon" href="/assets/favicon.ico" type="image/x-icon">
|
||||||
|
|
||||||
<title>ivanch</title>
|
<title>ivanch</title>
|
||||||
<link rel="stylesheet" href="css/main.css?v=20261011-2">
|
<link rel="stylesheet" href="css/main.css">
|
||||||
|
|
||||||
<link rel="stylesheet" href="css/sections/profile.css?v=20261011-2">
|
<link rel="stylesheet" href="css/sections/profile.css">
|
||||||
<link rel="stylesheet" href="css/sections/homelab.css?v=20261011-2">
|
<link rel="stylesheet" href="css/sections/homelab.css">
|
||||||
<link rel="stylesheet" href="css/sections/projects.css?v=20261011-2">
|
<link rel="stylesheet" href="css/sections/projects.css">
|
||||||
<link rel="stylesheet" href="css/sections/hero.css?v=20261011-2">
|
<link rel="stylesheet" href="css/sections/hero.css">
|
||||||
|
|
||||||
<link rel="stylesheet" href="css/matrix.css?v=20261011-2">
|
<link rel="stylesheet" href="css/matrix.css">
|
||||||
<link rel="stylesheet" href="css/button.css">
|
<link rel="stylesheet" href="css/button.css">
|
||||||
|
|
||||||
<!-- Remix Icon CDN for social icons -->
|
<!-- Remix Icon CDN for social icons -->
|
||||||
@@ -149,7 +149,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script src="js/flowfield.js?v=20261011-2"></script>
|
<script src="js/flowfield.js"></script>
|
||||||
<script src="js/social.js"></script>
|
<script src="js/social.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
+18
-44
@@ -116,8 +116,7 @@ class FlowFieldBackground {
|
|||||||
this.sparks = [];
|
this.sparks = [];
|
||||||
this.mouse = { x: 0, y: 0, active: false };
|
this.mouse = { x: 0, y: 0, active: false };
|
||||||
this.rafId = null;
|
this.rafId = null;
|
||||||
this.resizeRaf = null;
|
this.prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||||
this.events = new AbortController();
|
|
||||||
|
|
||||||
this.width = 0;
|
this.width = 0;
|
||||||
this.height = 0;
|
this.height = 0;
|
||||||
@@ -158,44 +157,47 @@ class FlowFieldBackground {
|
|||||||
this.bindEvents();
|
this.bindEvents();
|
||||||
this.handleResize();
|
this.handleResize();
|
||||||
|
|
||||||
|
if (this.prefersReducedMotion) {
|
||||||
|
// Single long-exposure render; no animation loop at all.
|
||||||
|
this.warmup(180);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Pre-run a short exposure so the first paint is already textured.
|
// Pre-run a short exposure so the first paint is already textured.
|
||||||
this.warmup(60);
|
this.warmup(60);
|
||||||
this.animate();
|
this.animate();
|
||||||
}
|
}
|
||||||
|
|
||||||
bindEvents() {
|
bindEvents() {
|
||||||
const signal = this.events.signal;
|
|
||||||
// The canvas has pointer-events: none, so listen on window.
|
// The canvas has pointer-events: none, so listen on window.
|
||||||
window.addEventListener('pointermove', (e) => {
|
window.addEventListener('pointermove', (e) => {
|
||||||
this.mouse.x = e.clientX;
|
this.mouse.x = e.clientX;
|
||||||
this.mouse.y = e.clientY;
|
this.mouse.y = e.clientY;
|
||||||
this.mouse.active = true;
|
this.mouse.active = true;
|
||||||
}, { passive: true, signal });
|
}, { passive: true });
|
||||||
|
|
||||||
window.addEventListener('pointerdown', (e) => {
|
window.addEventListener('pointerdown', (e) => {
|
||||||
this.spawnClickBurst(e.clientX, e.clientY);
|
this.spawnClickBurst(e.clientX, e.clientY);
|
||||||
}, { passive: true, signal });
|
}, { passive: true });
|
||||||
|
|
||||||
window.addEventListener('pointerout', () => {
|
window.addEventListener('pointerout', () => {
|
||||||
this.mouse.active = false;
|
this.mouse.active = false;
|
||||||
}, { signal });
|
});
|
||||||
|
|
||||||
// Pause the rAF loop when the tab is hidden to save CPU / battery.
|
// Pause the rAF loop when the tab is hidden to save CPU / battery.
|
||||||
document.addEventListener('visibilitychange', () => {
|
document.addEventListener('visibilitychange', () => {
|
||||||
if (document.hidden) {
|
if (document.hidden) {
|
||||||
this.stop();
|
this.stop();
|
||||||
} else {
|
} else if (!this.prefersReducedMotion) {
|
||||||
this.start();
|
this.start();
|
||||||
}
|
}
|
||||||
}, { signal });
|
|
||||||
|
|
||||||
window.addEventListener('resize', () => {
|
|
||||||
if (this.resizeRaf) cancelAnimationFrame(this.resizeRaf);
|
|
||||||
this.resizeRaf = requestAnimationFrame(() => {
|
|
||||||
this.resizeRaf = null;
|
|
||||||
this.handleResize();
|
|
||||||
});
|
});
|
||||||
}, { signal });
|
|
||||||
|
let resizeRaf = null;
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
if (resizeRaf) cancelAnimationFrame(resizeRaf);
|
||||||
|
resizeRaf = requestAnimationFrame(() => this.handleResize());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
@@ -210,17 +212,6 @@ class FlowFieldBackground {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
|
||||||
this.stop();
|
|
||||||
this.events.abort();
|
|
||||||
if (this.resizeRaf) cancelAnimationFrame(this.resizeRaf);
|
|
||||||
this.particles = [];
|
|
||||||
this.sparks = [];
|
|
||||||
// Release the high-DPI drawing buffer when switching to CSS only.
|
|
||||||
this.canvas.width = 1;
|
|
||||||
this.canvas.height = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleResize() {
|
handleResize() {
|
||||||
const w = window.innerWidth;
|
const w = window.innerWidth;
|
||||||
const h = window.innerHeight;
|
const h = window.innerHeight;
|
||||||
@@ -513,22 +504,5 @@ class FlowFieldBackground {
|
|||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
// Keep this query in sync with the static backdrop in css/matrix.css.
|
new FlowFieldBackground();
|
||||||
const staticBackground = window.matchMedia(
|
|
||||||
'(max-width: 1024px), (pointer: coarse), (prefers-reduced-motion: reduce)'
|
|
||||||
);
|
|
||||||
let background = null;
|
|
||||||
|
|
||||||
function updateBackground() {
|
|
||||||
if (staticBackground.matches) {
|
|
||||||
if (background) background.destroy();
|
|
||||||
background = null;
|
|
||||||
} else if (!background) {
|
|
||||||
background = new FlowFieldBackground();
|
|
||||||
if (document.hidden) background.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
staticBackground.addEventListener('change', updateBackground);
|
|
||||||
updateBackground();
|
|
||||||
});
|
});
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
|
||||||
|
}
|
||||||
Reference in new issue
Block a user