feat: adjust max and step values for sharpen, edgeThreshold, and scanlines controls.

This commit is contained in:
syntaxbullet
2026-02-10 15:21:34 +01:00
parent 73a6681ceb
commit a9d2c43bfd
16 changed files with 294 additions and 104 deletions

View File

@@ -3,24 +3,13 @@ import TuiSlider from "./TuiSlider.astro";
import TuiSegment from "./TuiSegment.astro";
import TuiToggle from "./TuiToggle.astro";
import TuiButton from "./TuiButton.astro";
import { ChevronDown } from "@lucide/astro";
---
<footer id="tui-controls" class="control-panel">
<div class="mobile-controls-header">
<span class="mobile-controls-title">CONTROLS</span>
<svg
class="mobile-toggle-icon"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
<ChevronDown class="mobile-toggle-icon" size={24} />
</div>
<div class="control-panel-inner">
@@ -569,6 +558,10 @@ import TuiButton from "./TuiButton.astro";
.sliders-grid {
grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
}
.shortcuts-hint {
display: none;
}
}
.tui-color-btn {

View File

@@ -1,23 +1,11 @@
---
import { ChevronDown, Zap, FileText, Mail } from "@lucide/astro";
---
<aside class="sidebar">
<div class="mobile-header">
<span class="mobile-brand">SYNTAXBULLET</span>
<svg
class="mobile-toggle-icon"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
<ChevronDown class="mobile-toggle-icon" size={24} />
</div>
<div class="sidebar-content">
<div class="brand-group">
@@ -38,25 +26,24 @@
<div class="sidebar-actions">
<a href="/" class="sidebar-link">
<span class="icon"></span> GENERATE
<span class="icon"><Zap size={20} /></span> GENERATE
</a>
<a href="/blog" class="sidebar-link">
<span class="icon">📝</span> BLOG
<span class="icon"><FileText size={20} /></span> BLOG
</a>
<a href="mailto:me@syntaxbullet.com" class="sidebar-link">
<span class="icon">✉️</span> CONTACT
<span class="icon"><Mail size={20} /></span> CONTACT
</a>
</div>
<div class="sidebar-social">
<a
href="https://github.com/syntaxbullet"
href="https://git.ayau.me/syntaxbullet"
target="_blank"
rel="noopener noreferrer"
aria-label="GitHub"
aria-label="Git"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
@@ -65,19 +52,20 @@
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
><path
d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"
></path><path d="M9 18c-4.51 2-5-2.64-5-2.64"></path></svg
>
<path
d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"
></path>
<path d="M9 18c-4.51 2-5-2-7-2"></path>
</svg>
</a>
<a
href="https://linkedin.com/in/syntaxbullet"
href="https://www.linkedin.com/in/ivan-jovanovic-51b319187/"
target="_blank"
rel="noopener noreferrer"
aria-label="LinkedIn"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
@@ -86,32 +74,13 @@
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
><path
>
<path
d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"
></path><rect width="4" height="12" x="2" y="9"
></rect><circle cx="4" cy="4" r="2"></circle></svg
>
</a>
<a
href="https://twitter.com/syntaxbullet"
target="_blank"
rel="noopener noreferrer"
aria-label="Twitter"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
><path
d="M22 4s-.7 2.1-2 3.4c1.6 10-9.4 17.3-18 11.6 2.2.1 4.4-.6 6-2C3 15.5.5 9.6 3 5c2.2 2.6 5.6 4.1 9 4-.9-4.2 4-6.6 7-3.8 1.1 0 3-1.2 3-1.2z"
></path></svg
>
></path>
<rect width="4" height="12" x="2" y="9"></rect>
<circle cx="4" cy="4" r="2"></circle>
</svg>
</a>
</div>
</div>

View File

@@ -0,0 +1,20 @@
---
title: "The Future of Syntaxbullet"
description: "A glimpse into what's coming next for this digital garden."
pubDate: "2026-02-10"
heroImage: "/blog-placeholder-1.jpg"
---
# Welcome to my Website
This digital garden is currently sprouting.
I'm working on a series of articles that explore the intersection of **engineering, design, and artificial intelligence**.
Upcoming topics will include:
- Deep dives into the ASCII art generation techniques used on the homepage.
- Modern web performance optimization strategies.
- Thoughts on the evolving role of AI in software development.
- Case studies of successful AI-powered software projects and papers.
Stay tuned for updates. In the meantime, feel free to play with the [generator](/).

View File

@@ -1,24 +0,0 @@
---
title: 'System Initialization'
description: 'Bootstrapping the Neko ASCII Generator.'
pubDate: '2026-02-08'
heroImage: '/blog/boot.png'
---
## Initializing Core Systems...
The Neko ASCII Auto-Generator has been successfully migrated to the Astro framework.
### Features
- Real-time image processing
- CLI-inspired controls
- Dynamic font scaling
- Automatic parameter tuning based on image histogram
### Changelog v2.0
- Migrated from vanilla HTML/JS to Astro
- Added Blog module
- Improved mobile responsiveness
Running diagnostics... **OK**
Systems online.

View File

@@ -12,6 +12,10 @@ const { title, showScroll = false } = Astro.props;
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="description"
content="Syntaxbullet - Full Stack Engineer & Creative Technologist. Building high-performance digital experiences with a focus on engineering, design, and AI."
/>
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />

View File

@@ -14,8 +14,33 @@ import ControlPanel from "../components/ControlPanel.astro";
<!-- Canvas Layer -->
<div class="canvas-layer">
<div id="loading">Loading...</div>
<pre id="ascii-result">Initializing...</pre>
<pre id="ascii-result"></pre>
<canvas id="ascii-canvas"></canvas>
<!-- Landing Screen -->
<div id="landing-screen" class="landing-overlay">
<div class="landing-content">
<h1>ASCII Art Generator</h1>
<p>
Generate stunning ASCII art from images. Pull a
random image from an anime API or upload your own to
get started.
</p>
<div class="landing-buttons">
<button id="btn-start-api" class="landing-btn"
>Anime API</button
>
<button id="btn-start-upload" class="landing-btn"
>Upload Image</button
>
</div>
<p class="disclaimer">
<b>Disclaimer:</b> Images loaded via the API are not my
own and are not filtered or curated. In rare cases, they
might contain sensitive material.
</p>
</div>
</div>
</div>
<ControlPanel />
@@ -114,10 +139,35 @@ import ControlPanel from "../components/ControlPanel.astro";
}
}
// ============= Initialize UI and Load First Image =============
// ============= Initialize UI =============
ui.init();
loadNewImage().then(() => {
queue.ensureFilled();
// ============= Landing Screen Logic =============
const landingScreen = document.getElementById("landing-screen");
const btnStartApi = document.getElementById("btn-start-api");
const btnStartUpload = document.getElementById("btn-start-upload");
const fileInput = document.getElementById(
"file-upload",
) as HTMLInputElement;
const hideLanding = () => {
landingScreen?.classList.add("hidden");
};
btnStartApi?.addEventListener("click", () => {
hideLanding();
loadNewImage().then(() => {
queue.ensureFilled();
});
});
btnStartUpload?.addEventListener("click", () => {
fileInput?.click();
});
// Hide landing screen when an image is imported (manual upload)
document.addEventListener("ascii-image-imported", () => {
hideLanding();
});
</script>
<Tooltip />
@@ -189,6 +239,114 @@ import ControlPanel from "../components/ControlPanel.astro";
border-radius: 4px;
}
/* Landing Overlay */
.landing-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.85);
backdrop-filter: blur(12px);
display: flex;
justify-content: center;
align-items: center;
z-index: 100;
transition:
opacity 0.6s cubic-bezier(0.4, 0, 0.2, 1),
visibility 0.6s;
}
.landing-overlay.hidden {
opacity: 0;
visibility: hidden;
pointer-events: none;
}
.landing-content {
max-width: 440px;
padding: 2.5rem;
text-align: center;
background: rgba(15, 15, 15, 0.9);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 16px;
box-shadow: 0 30px 60px rgba(0, 0, 0, 0.6);
animation: landing-in 0.8s cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes landing-in {
from {
opacity: 0;
transform: translateY(20px) scale(0.95);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.landing-content h1 {
font-size: 1.75rem;
margin-bottom: 1rem;
font-weight: 700;
letter-spacing: -0.02em;
color: #fff;
}
.landing-content p {
font-size: 0.95rem;
line-height: 1.6;
color: rgba(255, 255, 255, 0.6);
margin-bottom: 2rem;
}
.landing-buttons {
display: flex;
gap: 0.75rem;
justify-content: center;
margin-bottom: 2rem;
}
.landing-btn {
padding: 0.75rem 1.25rem;
border-radius: 8px;
font-weight: 600;
font-size: 0.85rem;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
border: 1px solid rgba(255, 255, 255, 0.15);
background: rgba(255, 255, 255, 0.05);
color: #fff;
cursor: pointer;
flex: 1;
}
.landing-btn:hover {
background: #fff;
color: #000;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(255, 255, 255, 0.2);
}
#btn-start-api {
background: #fff;
color: #000;
border: none;
}
#btn-start-api:hover {
background: #ccc;
}
.disclaimer {
font-size: 0.7rem !important;
color: rgba(255, 255, 255, 0.3) !important;
line-height: 1.4 !important;
margin-bottom: 0 !important;
text-align: left;
padding-top: 1.5rem;
border-top: 1px solid rgba(255, 255, 255, 0.05);
}
/* Responsive */
@media (max-width: 1024px) {
.split-layout {

View File

@@ -92,7 +92,7 @@ export class AsciiExporter {
const b = pixels[i + 2];
// 1. Calculate Luma
let luma = (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255;
// 1. Calculate Luma (skipped, using adjusted color luma below)
// 2. Apply Adjustments
// Note: For color mode, we might want to keep original color

View File

@@ -472,6 +472,8 @@ export class UIBindings {
this.updateUI();
await this.controller.generate();
this.controller.hideLoading();
// Notify that an image was successfully imported
document.dispatchEvent(new CustomEvent('ascii-image-imported'));
} catch (err) {
console.error("Import failed:", err);
this.controller.hideLoading();