restyle test

This commit is contained in:
javl
2026-08-14 16:13:15 +02:00
parent 1781aac67f
commit b5bc6c3774
4 changed files with 1145 additions and 121 deletions
+664 -101
View File
@@ -1,63 +1,367 @@
:root {
/* Colors (from restyle design tokens) */
--primary: #006b58;
--primary-container: #1abc9c;
--on-primary: #ffffff;
--on-primary-container: #004538;
--secondary: #4e6073;
--on-secondary: #ffffff;
--on-secondary-container: #526478;
--background: #f7f9fb;
--surface: #f7f9fb;
--surface-container-lowest: #ffffff;
--surface-container-low: #f2f4f6;
--surface-container: #eceef0;
--surface-container-high: #e6e8ea;
--surface-container-highest: #e0e3e5;
--on-surface: #191c1e;
--on-surface-variant: #3c4a45;
--on-background: #191c1e;
--text-muted: #64748b;
--border-subtle: #e2e8f0;
--outline: #6c7a75;
--outline-variant: #bbcac3;
--editor-bg: #1e1e1e;
--error: #ba1a1a;
/* Radii */
--radius: 0.125rem;
--radius-lg: 0.25rem;
--radius-xl: 0.5rem;
--radius-full: 0.75rem;
/* Fonts */
--font-body: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
--font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body{
font-family: arial;
body {
font-family: var(--font-body);
font-size: 16px;
line-height: 24px;
color: var(--on-background);
background-color: var(--background);
min-height: 100vh;
}
/* ---- Top nav bar ---- */
.topnav {
position: sticky;
top: 0;
z-index: 50;
background: var(--surface-container-lowest);
border-bottom: 1px solid var(--border-subtle);
}
.topnav-inner {
max-width: 1200px;
margin: auto;
height: 64px;
padding: 0 24px;
display: flex;
align-items: center;
justify-content: space-between;
}
.topnav-left {
display: flex;
align-items: center;
gap: 32px;
}
.brand {
font-size: 20px;
font-weight: 700;
color: var(--primary);
}
.topnav-links {
display: flex;
gap: 24px;
}
.topnav-links a {
color: var(--secondary);
font-weight: 500;
font-size: 14px;
}
.topnav-links a:hover {
color: var(--primary);
text-decoration: none;
}
.topnav-right {
display: flex;
align-items: center;
gap: 12px;
}
.icon-btn {
background: transparent;
color: var(--secondary);
border: none;
font-size: 18px;
line-height: 1;
padding: 6px;
margin: 0;
border-radius: var(--radius-lg);
cursor: pointer;
}
.icon-btn:hover {
color: var(--primary);
background: var(--surface-container);
opacity: 1;
}
.theme-icon-light {
display: none;
}
.btn-reset {
margin: 0;
}
/* ---- Layout: sidebar + main ---- */
.layout {
display: flex;
gap: 32px;
max-width: 1200px;
margin: auto;
padding: 32px 24px 64px;
align-items: flex-start;
}
.sidebar {
position: sticky;
top: 96px;
flex: 0 0 220px;
width: 220px;
background: var(--surface-container-low);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-xl);
padding: 24px 0;
}
.sidebar-nav {
display: flex;
flex-direction: column;
}
.sidebar-link {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 24px;
font-family: var(--font-mono);
font-size: 13px;
font-weight: 500;
color: var(--secondary);
border-right: 3px solid transparent;
}
.sidebar-link:hover {
background: var(--surface-container-high);
text-decoration: none;
}
.sidebar-link.active {
color: var(--primary);
font-weight: 700;
background: rgba(26, 188, 156, 0.1);
border-right-color: var(--primary);
}
.sidebar-num {
display: inline-flex;
align-items: center;
justify-content: center;
width: 22px;
height: 22px;
border-radius: 4px;
background: var(--surface-container-high);
color: var(--secondary);
font-size: 12px;
font-weight: 700;
flex-shrink: 0;
}
.sidebar-link.active .sidebar-num {
background: var(--primary);
color: var(--on-primary);
}
.wrapper {
display: flex;
flex-direction: column;
margin: auto;
width: 900px;
gap: 24px;
flex: 1;
min-width: 0;
}
/* ---- Step number rail ---- */
.section.step {
position: relative;
padding-left: 72px;
scroll-margin-top: 88px;
}
.step-num {
position: absolute;
left: 16px;
top: 32px;
width: 40px;
height: 40px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
background: var(--surface-container-high);
color: var(--secondary);
border: 1px solid var(--border-subtle);
z-index: 2;
}
.active .step-num {
background: var(--primary);
color: var(--on-primary);
border-color: var(--primary);
}
.step::before {
content: "";
position: absolute;
left: 35px;
top: 40px;
bottom: -24px;
width: 2px;
background: var(--border-subtle);
z-index: 1;
}
#step-4.step::before {
display: none;
}
/* ---- Typography ---- */
h1 {
font-size: 30px;
line-height: 38px;
letter-spacing: -0.02em;
font-weight: 600;
color: var(--primary);
margin-bottom: 8px;
}
h2 {
font-size: 20px;
line-height: 28px;
font-weight: 600;
color: var(--on-surface);
}
.sub-section-title {
font-family: var(--font-mono);
font-size: 13px;
line-height: 16px;
font-weight: 500;
color: var(--primary);
margin: 0 0 12px;
}
p {
margin: 16px 0;
color: var(--on-surface-variant);
}
a {
color: var(--primary);
text-decoration: none;
font-weight: 500;
}
a:hover {
text-decoration: underline;
}
code {
font-family: var(--font-mono);
font-size: 0.9em;
background: var(--surface-container);
padding: 1px 5px;
border-radius: var(--radius-lg);
}
/* ---- Section cards ---- */
.section {
margin: 10px 0;
background: var(--surface-container-lowest);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-xl);
padding: 32px;
}
/* First (intro) section: keep it flush/light */
.section:first-child {
background: transparent;
border: none;
padding: 8px 0 0;
}
.section h2 {
margin-bottom: 16px;
}
/* Legacy divider no longer needed; cards separate content */
.bottom-divider {
border-bottom: 2px solid #000000;
padding-bottom: 20px;
border-bottom: none;
padding-bottom: 32px;
}
.sub-section {
clear: both;
margin-bottom: 1px;
}
.section,
.sub-section {
width: 100%;
}
.section {
width: 100%;
}
/* ---- Multi-column (select image row) ---- */
.column {
float: left;
}
.column-center {
min-width: 160px;
min-width: 120px;
text-align: center;
color: var(--text-muted);
}
.column-right {
float: right;
}
.sub-section-title {
margin: 0 0 10px;
}
p {
margin: 20px 0;
}
/* ---- Table layout for settings ---- */
.table {
display: table;
margin: 10px 0 0;
margin: 8px 0 0;
width: 100%;
border-collapse: collapse;
}
.table-row {
@@ -67,11 +371,13 @@ p {
.table-cell {
display: table-cell;
padding: 5px 0;
padding: 10px 0;
vertical-align: top;
}
.table-cell:first-child {
width: 30%;
padding-right: 16px;
}
.table-cell:last-child {
@@ -79,11 +385,16 @@ p {
}
.table-cell:first-child label {
font-weight: bold;
font-family: var(--font-mono);
font-size: 13px;
font-weight: 500;
color: var(--primary);
}
.table-cell:last-child label {
margin-right: 10px;
color: var(--on-surface-variant);
font-size: 14px;
}
.nested-table {
@@ -91,83 +402,99 @@ p {
}
.nested-table .table-cell {
color: #666;
font-size: .9em;
color: var(--text-muted);
font-size: 0.9em;
width: 200px;
}
#format-caption-container div {
color: #505050;
color: var(--text-muted);
display: none;
font-size: .9em;
line-height: 1.4em;
font-size: 14px;
line-height: 1.5em;
padding: 10px 0 15px;
width: 100%;
}
/* ---- Notes / messages ---- */
.note {
color: var(--text-muted);
font-size: 14px;
line-height: 1.5em;
margin: 4px 0;
}
.upload-note {
max-width: 300px;
max-width: 320px;
}
.msg {
font-size: 16px;
color: var(--text-muted);
}
.error-msg {
color: var(--error);
display: none;
}
/* ---- Form controls ---- */
input[type="text"],
input[type="number"],
textarea,
select {
font-family: var(--font-body);
font-size: 14px;
color: var(--on-surface-variant);
background: var(--surface-container);
border: 1px solid transparent;
border-radius: var(--radius-lg);
padding: 8px 10px;
transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
input[type="text"]:focus,
input[type="number"]:focus,
textarea:focus,
select:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 2px rgba(0, 107, 88, 0.25);
}
select {
cursor: pointer;
}
input[type="checkbox"],
input[type="radio"] {
accent-color: var(--primary);
width: 16px;
height: 16px;
vertical-align: middle;
cursor: pointer;
}
.byte-input {
min-height: 160px;
min-width: 360px;
}
.code-output {
height: 200px;
width: 100%;
font-family: var(--font-mono);
font-size: 14px;
line-height: 22px;
resize: vertical;
}
.note {
color: #666666;
font-size: .9em;
line-height: 1.4em;
margin: 3px 0;
.text-input-size {
margin: 8px 0;
color: var(--on-surface-variant);
font-size: 14px;
}
.file-input-entry {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
}
button,
input[type="file"] {
background: #00CB99;
border-radius: 3px;
border: none;
color: #fff;
font-size: .9em;
font-weight: bold;
margin: 10px 0 0 0;
padding: 4px 8px;
}
input[type="file"] {
font-size: 1.0em;
padding: 6px 20px;
}
.generate-button {
margin: 40px 0 20px;
}
.remove-button {
margin: 0 0 0 10px;
padding: 1px 4px;
}
.file-info {
color: #505050;
font-size: .7em;
margin-left: 20px;
max-width: 300px;
white-space: pre;
}
.size-input{
width: 45px;
.size-input {
width: 72px;
}
.glyph-input {
@@ -175,18 +502,122 @@ input[type="file"] {
margin-left: 10px;
}
.text-input {
font-family: var(--font-mono);
}
/* ---- Buttons ---- */
button {
font-family: var(--font-mono);
font-size: 13px;
font-weight: 500;
background: var(--primary-container);
color: var(--on-primary-container);
border: none;
border-radius: var(--radius-lg);
padding: 8px 16px;
margin: 10px 8px 0 0;
cursor: pointer;
transition: opacity 0.15s ease, background-color 0.15s ease;
}
button:hover {
opacity: 0.9;
}
button:active {
opacity: 0.8;
}
input[type="file"] {
background: var(--surface-container);
color: var(--on-surface-variant);
border: 2px dashed var(--outline-variant);
border-radius: var(--radius-lg);
font-size: 14px;
padding: 16px 20px;
margin: 10px 0 0;
cursor: pointer;
width: 100%;
max-width: 360px;
}
input[type="file"]:hover {
background: var(--surface-container-high);
}
input[type="file"]::file-selector-button {
font-family: var(--font-mono);
font-weight: 500;
background: var(--primary-container);
color: var(--on-primary-container);
border: none;
border-radius: var(--radius-lg);
padding: 6px 12px;
margin-right: 12px;
cursor: pointer;
}
.generate-button {
background: var(--primary);
color: var(--on-primary);
margin: 32px 8px 20px 0;
padding: 10px 20px;
}
#copy-button,
#download-button {
background: var(--secondary);
color: var(--on-secondary);
margin-top: 32px;
}
.remove-button {
margin: 0 0 0 10px;
padding: 2px 8px;
background: var(--surface-container-high);
color: var(--on-surface-variant);
}
/* ---- File info / size list ---- */
.file-input-entry {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
}
.file-info {
color: var(--text-muted);
font-size: 12px;
margin-left: 20px;
max-width: 320px;
white-space: pre;
}
.file-name {
display: block;
margin-left: 0;
margin-bottom: 8px;
font-weight: 500;
color: var(--on-surface-variant);
}
/* Tighten gap between a row and the explanatory note row that follows */
.note-row .table-cell {
padding-top: 0;
}
#image-size-settings {
list-style-type: none;
}
#image-size-settings li {
margin: 4px 0;
margin: 6px 0;
}
#images-canvas-container canvas {
border: 3px solid #88DAC5;
margin: 10px 15px;
}
/* ---- Preview canvases ---- */
#images-canvas-container {
align-items: flex-start;
@@ -194,33 +625,165 @@ input[type="file"] {
flex-wrap: wrap;
}
#images-canvas-container canvas {
border: 1px solid var(--outline-variant);
border-radius: var(--radius-lg);
background: var(--editor-bg);
margin: 10px 15px 10px 0;
padding: 4px;
}
.inlineImg {
border-radius: var(--radius-lg);
margin-top: 8px;
}
/* ---- Code output (dark editor look) ---- */
.code-output {
height: 400px;
width: 100%;
margin-top: 16px;
background: var(--editor-bg);
color: #dcdcaa;
border: 1px solid var(--on-surface-variant);
border-radius: var(--radius-xl);
font-family: var(--font-mono);
font-size: 14px;
line-height: 22px;
padding: 20px;
resize: vertical;
}
.code-output:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 2px rgba(0, 107, 88, 0.25);
}
/* ---- Hidden by default (toggled via JS) ---- */
#arduino-identifier,
#adafruit-gfx-settings,
#all-same-size {
display: none;
}
.msg {
font-size: 1.2em;
/* ---- Sponsor block ---- */
.sp-block {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 12px;
}
.error-msg {
color: #ff0000;
.sp-block img {
vertical-align: middle;
}
/* ---- Responsive ---- */
@media (max-width: 900px) {
.layout {
flex-direction: column;
}
.sidebar {
position: static;
width: 100%;
flex: none;
}
.sidebar-nav {
flex-direction: row;
flex-wrap: wrap;
}
.sidebar-link {
border-right: none;
border-radius: var(--radius-lg);
}
.sidebar-link.active {
border-right: none;
}
}
@media (max-width: 640px) {
.topnav-links {
display: none;
}
.section.step {
padding-left: 32px;
}
.step-num {
position: static;
margin-bottom: 16px;
}
.step::before {
display: none;
}
.column,
.column-right {
float: none;
width: 100%;
}
.byte-input {
min-width: 0;
}
.table-cell:first-child,
.table-cell:last-child {
display: block;
width: 100%;
}
.table-cell:first-child {
padding-bottom: 0;
}
}
/* ---- Dark theme ---- */
:root[data-theme="dark"] {
--primary: #4eddbb;
--primary-container: #1abc9c;
--on-primary: #003a2e;
--on-primary-container: #e5fff7;
--secondary: #b5c8df;
--on-secondary: #17293b;
--on-secondary-container: #cfe2f9;
--background: #0f1416;
--surface: #0f1416;
--surface-container-lowest: #171d1f;
--surface-container-low: #1a2124;
--surface-container: #22282b;
--surface-container-high: #2a3033;
--surface-container-highest: #343a3d;
--on-surface: #e0e3e5;
--on-surface-variant: #c3d0c9;
--on-background: #e0e3e5;
--text-muted: #8a9a94;
--border-subtle: #2d3133;
--outline: #8a938f;
--outline-variant: #414a47;
--editor-bg: #0b0e0f;
}
:root[data-theme="dark"] .theme-icon-dark {
display: none;
}
h1{
padding: 10px;
color: white;
background-color: #00cb99;
:root[data-theme="dark"] .theme-icon-light {
display: inline;
}
/*.sp-block {
border: 1px solid rgb(65, 65, 65);
padding: .5rem;
border-radius: 1rem;
:root[data-theme="dark"] .sidebar-link.active {
background: rgba(26, 188, 156, 0.18);
}
.sp-btn {
display: block;
margin-top: .5rem;
}*/
+76 -18
View File
@@ -3,12 +3,43 @@
<head>
<meta charset="UTF-8">
<title>image2cpp</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<script src="./js/dithering.js" defer></script>
<script src="./js/script.js" defer></script>
<link rel="stylesheet" href="./css/style.css">
</head>
<body>
<div class="wrapper">
<header class="topnav">
<div class="topnav-inner">
<div class="topnav-left">
<span class="brand">image2cpp</span>
<nav class="topnav-links">
<a href="https://github.com/javl/image2cpp" target="_blank">GitHub Repository</a>
</nav>
</div>
<div class="topnav-right">
<button class="icon-btn" id="theme-toggle" title="Toggle dark mode" aria-label="Toggle dark mode">
<span class="theme-icon-dark">&#9789;</span>
<span class="theme-icon-light">&#9788;</span>
</button>
<button class="btn-reset" onclick="location.reload()">Reset page</button>
</div>
</div>
</header>
<div class="layout">
<aside class="sidebar">
<nav class="sidebar-nav">
<a href="#step-1" class="sidebar-link" data-step="step-1"><span class="sidebar-num">1</span>Select Image</a>
<a href="#step-2" class="sidebar-link" data-step="step-2"><span class="sidebar-num">2</span>Settings</a>
<a href="#step-3" class="sidebar-link" data-step="step-3"><span class="sidebar-num">3</span>Preview</a>
<a href="#step-4" class="sidebar-link" data-step="step-4"><span class="sidebar-num">4</span>Output</a>
</nav>
</aside>
<main class="wrapper">
<section class="section">
<h1>image2cpp</h1>
<p><strong>image2cpp</strong> is a simple tool to change images into byte arrays (or arrays back into an images) for use with (monochrome) displays such as OLEDs on your Arduino or Raspberry Pi.<br>
@@ -17,20 +48,16 @@
<p>More info (and credits) can be found in the <a href="https://github.com/javl/image2cpp" target="_blank">Github repository</a>. This is also where you can report any <a href="https://github.com/javl/image2cpp/issues" target="_blank">issues</a> you might come across.</p>
<p class="sp-block">
Did you find this tool useful? Feel free to support my open source software on Github, especially if used commercially.<br />
<a href="https://github.com/sponsors/javl" target="_blank">
<img src="https://img.shields.io/github/sponsors/javl?label=Sponsor&logo=GitHub" alt="GitHub Sponsor">
</a>
<a href="https://www.buymeacoffee.com/javl" target="_blank" rel="noopener">
<img decoding="async" class="" style="height: 34px; width: 123px;" src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee">
</a>
<a href="https://github.com/sponsors/javl"><img src="https://camo.githubusercontent.com/7fcc34127227d7a0293435c20a74bce4bf3eb26d86f6c5000aa620da147223dd/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5f2d73706f6e736f725f6f6e5f4769746875622d626c75653f6c6f676f3d676974687562" alt="GitHub Sponsor" data-canonical-src="https://img.shields.io/badge/_-sponsor_on_Github-blue?logo=github" style="max-width: 100%;"></a>
<a href="https://www.buymeacoffee.com/javl" rel="nofollow"><img src="https://camo.githubusercontent.com/0eeabd01753f37f754419d1b3f1b9fd755b1653c23283c8c8b17b1fbb18191e4/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4275795f4d655f615f436f666665652d6f72616e67653f6c6f676f3d6275796d6561636f66666565" alt="BMC" data-canonical-src="https://img.shields.io/badge/Buy_Me_a_Coffee-orange?logo=buymeacoffee" style="max-width: 100%;"></a>
</p>
</section>
<section class="section bottom-divider">
<section class="section step bottom-divider" id="step-1">
<div class="step-num">1</div>
<section class="sub-section">
<div class="column" id="file-input-column">
<h2 class="sub-section-title">1. Select image</h2>
<h2 class="sub-section-title">Select image</h2>
<div class="note upload-note">All processing is done locally in your browser; your images are not uploaded or stored anywhere online.</div>
<input type="file" id="file-input" name="file-input" multiple/><br />
</div>
@@ -38,7 +65,7 @@
<h2 class="sub-section-title">or</h2>
</div>
<div class="column column-right">
<h2 class="sub-section-title">1. Paste byte array</h2>
<h2 class="sub-section-title">Paste byte array</h2>
<textarea id="byte-input" class="byte-input"></textarea><br />
<div class="text-input-size">
<input type="number" min="0" id="text-input-width" class="size-input" value="128" /> x
@@ -53,8 +80,9 @@
</section>
</section>
<section class="section bottom-divider">
<h2>2. Image Settings</h2>
<section class="section step bottom-divider" id="step-2">
<div class="step-num">2</div>
<h2>Image Settings</h2>
<section class="sub-section">
<div class="table">
@@ -131,7 +159,7 @@
</div>
</div>
<div class="table-row">
<div class="table-row note-row">
<div class="table-cell"></div>
<div class="table-cell">
<i class="note">Centering the image only works when using a canvas larger than the original image.</i>
@@ -165,16 +193,18 @@
</section>
</section>
<section class="section bottom-divider">
<h2>3. Preview</h2>
<section class="section step bottom-divider" id="step-3">
<div class="step-num">3</div>
<h2>Preview</h2>
<section class="sub-section">
<div class="no-file-selected" class="msg">No files selected</div>
<div id="images-canvas-container"></div>
</section>
</section>
<section class="section">
<h2>4. Output</h2>
<section class="section step" id="step-4">
<div class="step-num">4</div>
<h2>Output</h2>
<section class="sub-section">
<div class="table">
<div class="table-row">
@@ -286,7 +316,35 @@
<textarea id="code-output" class="code-output"></textarea>
</section>
</section>
</main>
</div>
<script>
// Dark mode toggle (persisted)
(function () {
var root = document.documentElement;
var stored = localStorage.getItem('theme');
if (stored) root.setAttribute('data-theme', stored);
var btn = document.getElementById('theme-toggle');
if (btn) btn.addEventListener('click', function () {
var next = root.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
root.setAttribute('data-theme', next);
localStorage.setItem('theme', next);
});
// Sidebar scrollspy
var links = Array.prototype.slice.call(document.querySelectorAll('.sidebar-link'));
var steps = links.map(function (l) { return document.getElementById(l.dataset.step); });
function onScroll() {
var pos = window.scrollY + 120;
var active = 0;
steps.forEach(function (s, i) { if (s && s.offsetTop <= pos) active = i; });
links.forEach(function (l, i) { l.classList.toggle('active', i === active); });
steps.forEach(function (s, i) { if (s) s.classList.toggle('active', i === active); });
}
window.addEventListener('scroll', onScroll, { passive: true });
onScroll();
})();
</script>
</body>
</html>
+1 -2
View File
@@ -808,9 +808,8 @@ function handleImageSelection(evt) {
};
const fn = document.createElement('span');
fn.className = 'file-info';
fn.className = 'file-info file-name';
fn.innerHTML = `${file.name} (file resolution: ${img.width} x ${img.height})`;
fn.innerHTML += '<br />';
const rb = document.createElement('button');
rb.className = 'remove-button';
+404
View File
@@ -0,0 +1,404 @@
<!DOCTYPE html><html class="light" lang="en" style=""><head>
<meta charset="utf-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<title>image2cpp | Image to Byte Array Converter</title>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&amp;family=JetBrains+Mono:wght@400;500&amp;display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet">
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
"colors": {
"on-primary-container": "#004538",
"on-tertiary": "#ffffff",
"surface-variant": "#e0e3e5",
"on-primary-fixed": "#002019",
"on-secondary-fixed": "#091d2e",
"border-subtle": "#E2E8F0",
"outline": "#6c7a75",
"inverse-surface": "#2d3133",
"tertiary-container": "#fa866f",
"tertiary-fixed-dim": "#ffb4a5",
"success-teal": "#1ABC9C",
"secondary-fixed-dim": "#b5c8df",
"inverse-on-surface": "#eff1f3",
"on-secondary-container": "#526478",
"surface-container-low": "#f2f4f6",
"on-tertiary-fixed-variant": "#7f2a1a",
"on-primary": "#ffffff",
"editor-bg": "#1E1E1E",
"secondary": "#4e6073",
"surface-tint": "#006b58",
"primary-container": "#1abc9c",
"on-secondary": "#ffffff",
"tertiary": "#9f402e",
"on-surface": "#191c1e",
"on-primary-fixed-variant": "#005141",
"text-muted": "#64748B",
"tertiary-fixed": "#ffdad3",
"surface-bright": "#f7f9fb",
"on-tertiary-fixed": "#3f0400",
"error": "#ba1a1a",
"on-surface-variant": "#3c4a45",
"inverse-primary": "#4eddbb",
"primary": "#006b58",
"surface-container": "#eceef0",
"surface-container-high": "#e6e8ea",
"error-container": "#ffdad6",
"on-error": "#ffffff",
"primary-fixed-dim": "#4eddbb",
"surface-container-lowest": "#ffffff",
"secondary-container": "#cfe2f9",
"surface-dim": "#d8dadc",
"on-secondary-fixed-variant": "#36485b",
"background": "#f7f9fb",
"surface": "#f7f9fb",
"on-tertiary-container": "#711f10",
"outline-variant": "#bbcac3",
"on-background": "#191c1e",
"surface-container-highest": "#e0e3e5",
"secondary-fixed": "#d1e4fb",
"on-error-container": "#93000a",
"primary-fixed": "#6ff9d6"
},
"borderRadius": {
"DEFAULT": "0.125rem",
"lg": "0.25rem",
"xl": "0.5rem",
"full": "0.75rem"
},
"spacing": {
"step-gap": "48px",
"container-max": "1200px",
"margin-desktop": "40px",
"margin-mobile": "16px",
"gutter": "24px"
},
"fontFamily": {
"body-md": ["Inter"],
"headline-lg-mobile": ["Inter"],
"code-block": ["JetBrains Mono"],
"body-sm": ["Inter"],
"headline-lg": ["Inter"],
"label-mono": ["JetBrains Mono"],
"headline-md": ["Inter"]
},
"fontSize": {
"body-md": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
"headline-lg-mobile": ["24px", {"lineHeight": "32px", "fontWeight": "600"}],
"code-block": ["14px", {"lineHeight": "22px", "fontWeight": "400"}],
"body-sm": ["14px", {"lineHeight": "20px", "fontWeight": "400"}],
"headline-lg": ["30px", {"lineHeight": "38px", "letterSpacing": "-0.02em", "fontWeight": "600"}],
"label-mono": ["13px", {"lineHeight": "16px", "fontWeight": "500"}],
"headline-md": ["20px", {"lineHeight": "28px", "fontWeight": "600"}]
}
},
},
}
</script>
<style>
.material-symbols-outlined {
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
}
.step-active { color: #006b58; }
.step-line { position: absolute; left: 19px; top: 40px; bottom: -20px; width: 2px; background-color: #E2E8F0; }
.last-step-line { display: none; }
.custom-scrollbar::-webkit-scrollbar { width: 8px; }
.custom-scrollbar::-webkit-scrollbar-track { background: #f1f1f1; }
.custom-scrollbar::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 4px; }
</style>
</head>
<body class="bg-background text-on-background font-body-md min-h-screen">
<!-- TopNavBar -->
<header class="bg-surface dark:bg-background border-b border-border-subtle dark:border-outline-variant full-width top-0 sticky z-50">
<div class="flex justify-between items-center h-16 px-margin-desktop max-w-container-max mx-auto">
<div class="flex items-center gap-8">
<span class="font-headline-md text-headline-md font-bold text-primary dark:text-primary-fixed-dim cursor-pointer">image2cpp</span>
<nav class="hidden md:flex gap-6 font-body-md text-body-md"><a class="text-secondary dark:text-secondary-fixed-dim hover:text-primary transition-colors cursor-pointer" href="#">GitHub</a></nav>
</div>
<div class="flex items-center gap-4">
<button class="material-symbols-outlined text-secondary hover:text-primary transition-colors cursor-pointer" data-icon="dark_mode">dark_mode</button>
<button class="bg-primary-container text-on-primary-container px-4 py-2 font-label-mono text-label-mono rounded-lg hover:opacity-90 active:opacity-80 transition-all"><div class="">Reset page</div></button>
</div>
</div>
</header>
<div class="flex max-w-container-max mx-auto">
<!-- SideNavBar -->
<aside class="hidden lg:flex flex-col py-6 h-screen w-64 fixed left-0 top-16 bg-surface-container-low dark:bg-surface-container border-r border-border-subtle dark:border-outline-variant">
<div class="px-6 mb-8">
<h2 class="font-headline-md text-headline-md text-primary">Workflow</h2>
<p class="font-body-sm text-body-sm text-text-muted">Conversion Steps</p>
</div>
<nav class="flex-1 space-y-1 font-label-mono text-label-mono">
<div class="flex items-center gap-3 text-primary dark:text-primary-fixed-dim font-bold bg-primary-container/10 dark:bg-primary-container/20 border-r-4 border-primary dark:border-primary-fixed-dim py-3 px-6 cursor-pointer hover:bg-surface-container-high transition-all">
<span class="material-symbols-outlined" data-icon="upload_file">upload_file</span>
<span class="">1. Select Image</span>
</div>
<div class="flex items-center gap-3 text-secondary dark:text-secondary-fixed-dim py-3 px-6 cursor-pointer hover:bg-surface-container-high transition-all">
<span class="material-symbols-outlined" data-icon="tune">tune</span>
<span class="">2. Settings</span>
</div>
<div class="flex items-center gap-3 text-secondary dark:text-secondary-fixed-dim py-3 px-6 cursor-pointer hover:bg-surface-container-high transition-all">
<span class="material-symbols-outlined" data-icon="visibility">visibility</span>
<span class="">3. Preview</span>
</div>
<div class="flex items-center gap-3 text-secondary dark:text-secondary-fixed-dim py-3 px-6 cursor-pointer hover:bg-surface-container-high transition-all">
<span class="material-symbols-outlined" data-icon="code">code</span>
<span class="">4. Output</span>
</div>
</nav>
<div class="p-6 mt-auto"><button class="w-full bg-secondary text-on-secondary py-2 rounded font-label-mono text-label-mono hover:bg-on-secondary-container transition-colors">Export All</button></div></aside>
<!-- Main Content -->
<main class="flex-1 lg:ml-64 px-margin-desktop py-12 max-w-4xl mx-auto">
<div class="mb-12">
<h1 class="font-headline-lg text-headline-lg mb-2">Image to C++ Converter</h1>
<p class="text-text-muted">Generate monochrome byte arrays for OLED displays on Arduino or Raspberry Pi. All processing happens in your browser.</p>
<div class="flex items-center gap-4 mt-4"><span class="text-text-muted font-body-sm">Support on</span><a class="w-10 h-10 rounded-full bg-surface-container-high flex items-center justify-center text-primary hover:bg-primary hover:text-on-primary transition-all" href="#"><span class="material-symbols-outlined" style="font-variation-settings: &quot;FILL&quot; 1;">favorite</span></a><a class="w-10 h-10 rounded-full bg-surface-container-high flex items-center justify-center text-primary hover:bg-primary hover:text-on-primary transition-all" href="#"><span class="material-symbols-outlined" style="font-variation-settings: &quot;FILL&quot; 1;">coffee</span></a></div></div>
<!-- Workflow Steps -->
<div class="space-y-step-gap">
<!-- Step 1: Select Image -->
<section class="relative pl-12" id="step-1">
<div class="step-line"></div>
<div class="absolute left-0 top-0 w-10 h-10 bg-primary rounded-full flex items-center justify-center text-on-primary font-bold shadow-sm z-10">1</div>
<div class="bg-surface-container-lowest border border-border-subtle rounded-xl p-8 space-y-6">
<div class="flex flex-col md:flex-row gap-6">
<div class="flex-1">
<label class="block font-label-mono text-label-mono text-primary mb-2">Upload Image File</label>
<div class="border-2 border-dashed border-outline-variant rounded-lg p-10 flex flex-col items-center justify-center bg-surface-container hover:bg-surface-container-high transition-colors cursor-pointer group">
<span class="material-symbols-outlined text-4xl text-outline mb-3 group-hover:scale-110 transition-transform" data-icon="cloud_upload">cloud_upload</span>
<p class="font-body-md text-body-md text-on-surface-variant text-center">Drag and drop images here or <span class="text-primary font-bold">Browse</span></p>
<p class="font-body-sm text-body-sm text-text-muted mt-2">Supports JPG, PNG, BMP, GIF</p>
<input class="hidden" multiple="" type="file">
</div>
</div>
<div class="flex-1">
<label class="block font-label-mono text-label-mono text-primary mb-2">Or Paste Byte Array</label>
<textarea class="w-full h-[180px] bg-surface-container text-on-surface-variant font-code-block text-code-block p-4 rounded-lg border-none focus:ring-2 focus:ring-primary focus:outline-none resize-none" placeholder="0xFF, 0x00, 0xAA..."></textarea>
</div>
</div>
</div>
</section>
<!-- Step 2: Settings -->
<section class="relative pl-12" id="step-2">
<div class="step-line"></div>
<div class="absolute left-0 top-0 w-10 h-10 bg-surface-container-high border border-border-subtle rounded-full flex items-center justify-center text-secondary font-bold z-10">2</div>
<div class="bg-surface-container-lowest border border-border-subtle rounded-xl p-8 space-y-8">
<div class="flex justify-between items-center mb-6"><h3 class="font-headline-md text-headline-md text-on-surface">Image Configuration</h3><div class="flex gap-2"><button class="p-1 text-secondary hover:text-primary transition-colors"></button></div></div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-x-gutter gap-y-8">
<!-- Canvas Size -->
<div>
<label class="block font-label-mono text-label-mono text-primary mb-3">Canvas Size (px)</label>
<div class="flex items-center gap-2">
<input class="w-full bg-surface-container border-none rounded p-2 font-label-mono" type="number" value="128">
<span class="text-outline">×</span>
<input class="w-full bg-surface-container border-none rounded p-2 font-label-mono" type="number" value="64">
</div>
</div>
<!-- Background Color -->
<div>
<label class="block font-label-mono text-label-mono text-primary mb-3">Background Color</label>
<div class="flex gap-1 bg-surface-container p-1 rounded-lg">
<button class="flex-1 py-1.5 rounded text-label-mono bg-on-secondary-container text-white shadow-sm">White</button>
<button class="flex-1 py-1.5 rounded text-label-mono text-secondary hover:bg-surface-container-high">Black</button>
<button class="flex-1 py-1.5 rounded text-label-mono text-secondary hover:bg-surface-container-high">Transp.</button>
</div>
</div>
<!-- Scaling -->
<div>
<label class="block font-label-mono text-label-mono text-primary mb-3">Scaling</label>
<select class="w-full bg-surface-container border-none rounded p-2 font-body-sm focus:ring-primary">
<option>Original Size</option>
<option>Scale to Fit</option>
<option>Stretch to Fill</option>
<option>Stretch Horizontally</option>
<option>Stretch Vertically</option>
</select>
</div>
<!-- Dithering -->
<div>
<label class="block font-label-mono text-label-mono text-primary mb-3">Dithering Algorithm</label>
<select class="w-full bg-surface-container border-none rounded p-2 font-body-sm focus:ring-primary">
<option>Binary (Threshold)</option>
<option>Bayer (Ordered)</option>
<option>Floyd-Steinberg</option>
<option>Atkinson</option>
</select>
</div>
<!-- Brightness -->
<div>
<label class="block font-label-mono text-label-mono text-primary mb-3">Threshold (0-255)</label>
<div class="flex items-center gap-4">
<input class="flex-1 accent-primary" max="255" min="0" type="range" value="128">
<span class="font-label-mono text-primary">128</span>
</div>
</div>
<!-- Toggles -->
<div class="">
<label class="block font-label-mono text-label-mono text-primary mb-3">Image Adjustments</label>
<div class="space-y-4">
<label class="flex items-center gap-3 cursor-pointer">
<div class="relative inline-flex items-center">
<input class="sr-only peer" type="checkbox">
<div class="w-11 h-6 bg-outline-variant peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary"></div>
</div>
<span class="font-body-sm">Invert Colors</span>
</label>
<div class="flex gap-4">
<button class="flex items-center gap-2 text-label-mono text-secondary hover:text-primary transition-colors">
<span class="material-symbols-outlined text-sm">rotate_right</span> Rotate Image</button>
<button class="flex items-center gap-2 text-label-mono text-secondary hover:text-primary transition-colors">
<span class="material-symbols-outlined text-sm">flip</span> Flip Image</button>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Step 3: Preview -->
<section class="relative pl-12" id="step-3">
<div class="step-line"></div>
<div class="absolute left-0 top-0 w-10 h-10 bg-surface-container-high border border-border-subtle rounded-full flex items-center justify-center text-secondary font-bold z-10">3</div>
<div class="bg-surface-container-lowest border border-border-subtle rounded-xl p-8">
<div class="flex justify-between items-center mb-6">
<h3 class="font-headline-md text-headline-md text-on-surface">Preview</h3>
<div class="flex gap-2">
<button class="p-1 text-secondary hover:text-primary transition-colors"></button>
<button class="p-1 text-secondary hover:text-primary transition-colors"></button>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<div class="space-y-3">
<span class="font-label-mono text-label-mono text-text-muted">Original</span>
<div class="aspect-video bg-surface-container-high rounded-lg flex items-center justify-center border border-dashed border-outline-variant relative overflow-hidden">
<img class="max-w-full max-h-full object-contain" data-alt="A clean, high-resolution original digital photo of a detailed robot head against a neutral gray background. The lighting is crisp and highlights the mechanical textures and metallic finishes. The composition is centered, creating a professional studio-quality look for image processing software. The aesthetic is tech-focused and minimal." src="https://lh3.googleusercontent.com/aida-public/AB6AXuAq1r2m_Da66R4eXvBlWq7d0AICWfEENZNfsexBvtCHmM5luhvCaPECRItz1w85o1XVfQtgy1XdkamKUDYAaRIQBJHQTB1Mt8pfbUMK7GNzDWYCQx6MUC1DEqnG0QSJBPc_zMJj6hZPoBKjAz_NUOg-mD4nC2cszzgH8dPaU_Z1B5fGEeHecdKtRIlPwCAjuArVWmSh1ayhApkbopqBSNOWmVjLk0RX5VkT_IwoHSYQFYFTtWJqAShF1g">
</div>
</div>
<div class="space-y-3">
<span class="font-label-mono text-label-mono text-text-muted">Converted (1-bit)</span>
<div class="aspect-video bg-editor-bg rounded-lg flex items-center justify-center border border-on-surface-variant relative overflow-hidden">
<!-- This would be the canvas rendering -->
<img class="max-w-full max-h-full object-contain grayscale brightness-200 contrast-200" data-alt="A stylized 1-bit black and white monochrome rendering of the previous robot head image. The image is pixelated and high-contrast, mimicking an OLED display output with crisp white pixels on a deep black background. The conversion uses a dithered effect to represent shadows, creating a high-tech retro-digital aesthetic." src="https://lh3.googleusercontent.com/aida-public/AB6AXuBPXPnQHpXR6vyt7gdrlrgvIepXIaQ6-86RRIK88PdiKphM3fnxyX1eP2V4py6FTykrNAL4OpC8kmEFdzZhRfjPtMOmqisw66exgyOzOF6keumlvHUzPvVrBeWj7cr2gB88geHSizBpGdRxu8N30B0Kgl9VEdLwrE2juDzZM7169j4gPDbn6UcyKHHSjvMmkSGbynfboN3URFYfDA1CsyegObewcHcJ--8-gMSF1OzJ57FqHbvzpMjEtQ">
</div>
</div>
</div>
</div>
</section>
<!-- Step 4: Output -->
<section class="relative pl-12" id="step-4">
<div class="step-line last-step-line"></div>
<div class="absolute left-0 top-0 w-10 h-10 bg-surface-container-high border border-border-subtle rounded-full flex items-center justify-center text-secondary font-bold z-10">4</div>
<div class="bg-surface-container-lowest border border-border-subtle rounded-xl p-8 space-y-8">
<div class="flex flex-col lg:flex-row justify-between items-start gap-8">
<div class="w-full lg:w-1/3 space-y-6">
<h3 class="font-headline-md text-headline-md text-on-surface">Output Settings</h3>
<div class="space-y-4">
<div>
<label class="block font-label-mono text-label-mono text-primary mb-2">Output Format</label>
<select class="w-full bg-surface-container border-none rounded p-2 font-body-sm focus:ring-primary">
<option>Plain bytes</option>
<option selected="">Arduino code</option>
<option>Arduino code (Single Bitmap)</option>
<option>Adafruit GFX Bitmap Font</option>
</select>
</div>
<div>
<label class="block font-label-mono text-label-mono text-primary mb-2">Identifier / Prefix</label>
<input class="w-full bg-surface-container border-none rounded p-2 font-label-mono focus:ring-primary" type="text" value="myBitmap">
</div>
<div>
<label class="block font-label-mono text-label-mono text-primary mb-2">Draw Mode</label>
<select class="w-full bg-surface-container border-none rounded p-2 font-body-sm focus:ring-primary">
<option>Horizontal - 1 bit per pixel</option>
<option>Vertical - 1 bit per pixel</option>
<option>Horizontal - 2 bytes per pixel (565)</option>
</select>
</div>
<div class="pt-2 space-y-3">
<label class="flex items-center gap-3 cursor-pointer">
<input class="w-5 h-5 rounded border-outline-variant text-primary focus:ring-primary" type="checkbox">
<span class="font-body-sm">Swap bits in byte</span>
</label>
<label class="flex items-center gap-3 cursor-pointer">
<input class="w-5 h-5 rounded border-outline-variant text-primary focus:ring-primary" type="checkbox">
<span class="font-body-sm">Remove '0x' and commas</span>
</label>
</div>
</div>
</div>
<div class="w-full lg:w-2/3 space-y-4">
<div class="flex justify-between items-end">
<div class="flex gap-2">
<button class="bg-primary text-on-primary px-4 py-2 rounded-lg font-label-mono text-label-mono hover:bg-on-primary-fixed-variant transition-all flex items-center gap-2">
<span class="material-symbols-outlined text-sm" data-icon="play_arrow">play_arrow</span> Generate
</button>
<button class="bg-surface-container-high text-on-surface-variant px-4 py-2 rounded-lg font-label-mono text-label-mono hover:bg-surface-container-highest transition-all flex items-center gap-2">
<span class="material-symbols-outlined text-sm" data-icon="content_copy">content_copy</span> Copy
</button>
<button class="bg-secondary text-on-secondary px-4 py-2 rounded-lg font-label-mono text-label-mono hover:opacity-90 transition-all flex items-center gap-2">
<span class="material-symbols-outlined text-sm" data-icon="download">download</span> .bin
</button>
</div>
</div>
<div class="bg-editor-bg rounded-xl p-6 font-code-block text-code-block text-white overflow-hidden border border-on-surface-variant h-[400px] flex flex-col">
<div class="flex-1 overflow-auto custom-scrollbar">
<pre class="text-green-400 opacity-60 italic mb-2">// Generated by image2cpp
// 128x64px, myBitmap
</pre>
<code class="block text-[#dcdcaa]">const unsigned char myBitmap [] PROGMEM = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff,
0xff, 0xff, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0x00, 0xff, 0xff,
0xff, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0xff,
0xff, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0xff,
0xff, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0xff,
0xff, 0xff, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0x00, 0xff, 0xff,
0xff, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff,
0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};</code>
</div>
<div class="bg-surface-container-low p-4 rounded-lg flex items-start gap-3">
<span class="material-symbols-outlined text-primary text-xl" data-icon="info">info</span>
<p class="font-body-sm text-body-sm text-on-secondary-container leading-relaxed">If the preview looks scrambled on your hardware, try switching the <strong>Draw Mode</strong> between Horizontal and Vertical.
</p>
</div>
</div>
</div>
</div>
</div></section>
</div>
</main>
</div>
<!-- Footer -->
<script>
// Simple interactivity for demonstration
document.querySelectorAll('nav div, nav a').forEach(item => {
item.addEventListener('click', function() {
// Remove active classes from all
document.querySelectorAll('nav div').forEach(d => {
d.classList.remove('text-primary', 'font-bold', 'bg-primary-container/10', 'border-r-4', 'border-primary');
d.classList.add('text-secondary');
});
// Add to clicked
if (this.tagName === 'DIV') {
this.classList.add('text-primary', 'font-bold', 'bg-primary-container/10', 'border-r-4', 'border-primary');
this.classList.remove('text-secondary');
}
});
});
// Sticky side nav highlight on scroll logic could be added here
</script>
</body></html>