fix layout issue on 2nd section

This commit is contained in:
javl
2026-08-17 20:30:58 +02:00
parent 720faac3f9
commit 591b0644c1
2 changed files with 153 additions and 65 deletions
+137 -63
View File
@@ -25,6 +25,17 @@
--radius-lg: 0.25rem; --radius-lg: 0.25rem;
--radius-xl: 0.5rem; --radius-xl: 0.5rem;
/* Spacing scale (use these for margin/padding/gap instead of magic numbers) */
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-5: 20px;
--space-6: 24px;
--space-7: 32px;
--space-8: 48px;
--space-9: 64px;
/* Fonts */ /* Fonts */
--font-body: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif; --font-body: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
--font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
@@ -59,7 +70,7 @@ body {
max-width: 1400px; max-width: 1400px;
margin: auto; margin: auto;
height: 64px; height: 64px;
padding: 0 24px; padding: 0 var(--space-6);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
@@ -68,7 +79,7 @@ body {
.topnav-left { .topnav-left {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 32px; gap: var(--space-7);
} }
.brand { .brand {
@@ -79,7 +90,7 @@ body {
.topnav-links { .topnav-links {
display: flex; display: flex;
gap: 24px; gap: var(--space-6);
} }
.topnav-links a { .topnav-links a {
@@ -97,7 +108,7 @@ body {
.topnav-right { .topnav-right {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 12px; gap: var(--space-3);
} }
.icon-btn { .icon-btn {
@@ -130,10 +141,10 @@ body {
.layout { .layout {
display: flex; display: flex;
gap: 32px; gap: var(--space-7);
max-width: 1400px; max-width: 1400px;
margin: auto; margin: auto;
padding: 32px 24px 64px; padding: var(--space-7) var(--space-6) var(--space-9);
align-items: flex-start; align-items: flex-start;
} }
@@ -156,8 +167,8 @@ body {
.sidebar-link { .sidebar-link {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 12px; gap: var(--space-3);
padding: 12px 24px; padding: var(--space-3) var(--space-6);
font-family: var(--font-mono); font-family: var(--font-mono);
font-size: 13px; font-size: 13px;
font-weight: 500; font-weight: 500;
@@ -199,7 +210,7 @@ body {
.wrapper { .wrapper {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 24px; gap: var(--space-6);
flex: 1; flex: 1;
min-width: 0; min-width: 0;
} }
@@ -212,9 +223,6 @@ body {
scroll-margin-top: 88px; scroll-margin-top: 88px;
} }
.section.first-step {
}
.step-num { .step-num {
position: absolute; position: absolute;
@@ -254,6 +262,12 @@ body {
display: none; display: none;
} }
/* step-3 (Preview) lives as a pinned side panel next to Settings, not
inline in the vertical step flow, so it doesn't carry a connecting rail */
#step-3.step::before {
display: none;
}
/* ---- Typography ---- */ /* ---- Typography ---- */
h1 { h1 {
@@ -262,7 +276,7 @@ h1 {
letter-spacing: -0.02em; letter-spacing: -0.02em;
font-weight: 600; font-weight: 600;
color: var(--primary); color: var(--primary);
margin-bottom: 8px; margin-bottom: var(--space-2);
} }
h2 { h2 {
@@ -278,11 +292,11 @@ h2 {
line-height: 16px; line-height: 16px;
font-weight: 500; font-weight: 500;
color: var(--primary); color: var(--primary);
margin: 0 0 12px; margin: 0 0 var(--space-3);
} }
p { p {
margin: 16px 0; margin: var(--space-4) 0;
color: var(--on-surface-variant); color: var(--on-surface-variant);
} }
@@ -310,24 +324,24 @@ code {
background: var(--surface-container-lowest); background: var(--surface-container-lowest);
border: 1px solid var(--border-subtle); border: 1px solid var(--border-subtle);
border-radius: var(--radius-xl); border-radius: var(--radius-xl);
padding: 32px; padding: var(--space-7);
} }
/* First (intro) section: keep it flush/light */ /* First (intro) section: keep it flush/light */
.section:first-child { .section.first-step {
background: transparent; background: transparent;
border: none; border: none;
padding: 8px 0 0; padding: var(--space-2) 0 0;
} }
.section h2 { .section h2 {
margin-bottom: 16px; margin-bottom: var(--space-4);
} }
/* Legacy divider no longer needed; cards separate content */ /* Legacy divider no longer needed; cards separate content */
.bottom-divider { .bottom-divider {
border-bottom: none; border-bottom: none;
padding-bottom: 32px; padding-bottom: var(--space-7);
} }
.sub-section { .sub-section {
@@ -339,12 +353,35 @@ code {
width: 100%; width: 100%;
} }
/* ---- Settings + live preview layout (step 2 + step 3) ----
Preview sits pinned next to Settings so the effect of dithering /
threshold / invert etc is visible without scrolling away from the
controls that change it. */
.settings-preview-row {
display: flex;
align-items: flex-start;
gap: var(--space-6);
}
.settings-preview-row #step-2 {
flex: 1 1 auto;
min-width: 0;
}
.settings-preview-row #step-3 {
flex: 0 0 300px;
width: 300px;
position: sticky;
top: 96px;
}
/* ---- Multi-column (select image row) ---- */ /* ---- Multi-column (select image row) ---- */
.input-methods-row { .input-methods-row {
display: flex; display: flex;
align-items: space-between; align-items: space-between;
gap: 20px; gap: var(--space-5);
} }
.input-methods-row .column-left { .input-methods-row .column-left {
@@ -355,14 +392,11 @@ code {
} }
.column-left { .column-left {
float: left;
border-right: 1px solid var(--border-subtle); border-right: 1px solid var(--border-subtle);
padding-right: 12px; padding-right: var(--space-3);
} }
.column-right { .column-right {
float: right;
margin-left: auto;
border-right: none; border-right: none;
} }
@@ -370,7 +404,7 @@ code {
.table { .table {
display: table; display: table;
margin: 8px 0 0; margin: var(--space-2) 0 0;
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
} }
@@ -382,13 +416,13 @@ code {
.table-cell { .table-cell {
display: table-cell; display: table-cell;
padding: 10px 0; padding: var(--space-2) 0;
vertical-align: top; vertical-align: top;
} }
.table-cell:first-child { .table-cell:first-child {
width: 30%; width: 30%;
padding-right: 16px; padding-right: var(--space-4);
} }
.table-cell:last-child { .table-cell:last-child {
@@ -403,7 +437,7 @@ code {
} }
.table-cell:last-child label { .table-cell:last-child label {
margin-right: 10px; margin-right: var(--space-2);
color: var(--on-surface-variant); color: var(--on-surface-variant);
font-size: 14px; font-size: 14px;
} }
@@ -412,22 +446,25 @@ code {
margin: 0; margin: 0;
} }
/* ---- Settings grid (step 2) ---- */ /* ---- Settings grid (step 2) ----
2-column min-width forces a predictable 2-up (or 1-up) grid instead of
auto-fitting 3 columns, which paired short/tall fields at random and
left ragged whitespace between rows. */
.settings-grid { .settings-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 28px 32px; gap: var(--space-6) var(--space-7);
} }
.settings-grid.table { .settings-grid.table {
margin: 8px 0 0; margin: var(--space-2) 0 0;
} }
.settings-grid .table-row { .settings-grid .table-row {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 8px; gap: var(--space-2);
} }
.settings-grid .table-row.full-row { .settings-grid .table-row.full-row {
@@ -454,12 +491,26 @@ code {
width: 200px; width: 200px;
} }
#format-caption-container {
/* Reserve space for the longest caption so switching output format
doesn't shift the fields/buttons below it. */
min-height: 100px;
}
#extra-settings-container {
/* Reserve space for the tallest combination (Adafruit GFX shows both its
own fields and the Arduino identifier fields) so the Generate/Copy/
Download buttons don't jump around when switching output format. */
min-height: 278px;
transition: min-height 0.15s ease;
}
#format-caption-container div { #format-caption-container div {
color: var(--text-muted); color: var(--text-muted);
display: none; display: none;
font-size: 14px; font-size: 14px;
line-height: 1.5em; line-height: 1.5em;
padding: 10px 0 15px; padding: var(--space-2) 0 var(--space-4);
width: 100%; width: 100%;
} }
@@ -469,7 +520,7 @@ code {
color: var(--text-muted); color: var(--text-muted);
font-size: 14px; font-size: 14px;
line-height: 1.5em; line-height: 1.5em;
margin: 4px 0; margin: var(--space-1) 0;
} }
#glyph-name-note { #glyph-name-note {
@@ -482,7 +533,7 @@ code {
#continue-note { #continue-note {
display: none; display: none;
margin-top: 30px; margin-top: var(--space-7);
} }
.msg { .msg {
@@ -545,7 +596,7 @@ input[type="radio"] {
#text-input-error { #text-input-error {
color: var(--error); color: var(--error);
font-size: 14px; font-size: 14px;
margin-top: 8px; margin-top: var(--space-2);
display: none; display: none;
} }
@@ -554,7 +605,7 @@ input[type="radio"] {
border: 1px solid var(--outline-variant); border: 1px solid var(--outline-variant);
border-radius: var(--radius-lg); border-radius: var(--radius-lg);
/* background: var(--primary-container); */ /* background: var(--primary-container); */
margin: 10px 0; margin: var(--space-2) 0;
/* padding: 4px; */ /* padding: 4px; */
max-width: 100%; max-width: 100%;
} }
@@ -564,7 +615,7 @@ input[type="radio"] {
} }
.text-input-size { .text-input-size {
margin: 8px 0; margin: var(--space-2) 0;
color: var(--on-surface-variant); color: var(--on-surface-variant);
font-size: 14px; font-size: 14px;
} }
@@ -576,8 +627,8 @@ input[type="radio"] {
.sub-field { .sub-field {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: var(--space-2);
margin-top: 8px; margin-top: var(--space-2);
} }
.sub-field:first-child { .sub-field:first-child {
@@ -590,7 +641,7 @@ input[type="radio"] {
.glyph-input { .glyph-input {
width: 300px; width: 300px;
margin-left: 10px; margin-left: var(--space-2);
} }
.text-input { .text-input {
@@ -608,7 +659,7 @@ button {
border: none; border: none;
border-radius: var(--radius-lg); border-radius: var(--radius-lg);
padding: 8px 16px; padding: 8px 16px;
margin: 10px 8px 0 0; margin: var(--space-2) var(--space-2) 0 0;
cursor: pointer; cursor: pointer;
transition: opacity 0.15s ease, background-color 0.15s ease; transition: opacity 0.15s ease, background-color 0.15s ease;
} }
@@ -628,7 +679,7 @@ input[type="file"] {
border-radius: var(--radius-lg); border-radius: var(--radius-lg);
font-size: 14px; font-size: 14px;
padding: 16px 20px; padding: 16px 20px;
margin: 10px 0 0; margin: var(--space-2) 0 0;
cursor: pointer; cursor: pointer;
width: 100%; width: 100%;
max-width: 360px; max-width: 360px;
@@ -646,14 +697,13 @@ input[type="file"]::file-selector-button {
border: none; border: none;
border-radius: var(--radius-lg); border-radius: var(--radius-lg);
padding: 6px 12px; padding: 6px 12px;
margin-right: 12px; margin-right: var(--space-3);
cursor: pointer; cursor: pointer;
} }
.generate-button { .generate-button {
background: var(--primary); background: var(--primary);
color: var(--on-primary); color: var(--on-primary);
margin: 32px 8px 20px 0;
padding: 10px 20px; padding: 10px 20px;
} }
@@ -661,26 +711,34 @@ input[type="file"]::file-selector-button {
#download-button { #download-button {
background: var(--secondary); background: var(--secondary);
color: var(--on-secondary); color: var(--on-secondary);
margin-top: 32px; }
/* Same margin rhythm across all three action buttons so the gap above
them and before the code output box is consistent regardless of which
buttons happen to be in view. */
.generate-button,
#copy-button,
#download-button {
margin: var(--space-7) var(--space-2) var(--space-6) 0;
} }
.remove-button { .remove-button {
margin: 0 0 0 10px; margin: 0 0 0 var(--space-2);
padding: 2px 8px; padding: 2px 8px;
/* background: var(--surface-container-high); */ /* background: var(--surface-container-high); */
/* color: var(--on-surface-variant); */ /* color: var(--on-surface-variant); */
} }
#no-images-error { #no-images-error {
margin-bottom: 10px; margin-bottom: var(--space-2);
} }
/* ---- File info / size list ---- */ /* ---- File info / size list ---- */
.file-input-entry { .file-input-entry {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: var(--space-2);
margin: 10px 0; margin: var(--space-2) 0;
} }
.file-input-entry span { .file-input-entry span {
@@ -691,7 +749,7 @@ input[type="file"]::file-selector-button {
.file-info { .file-info {
color: var(--text-muted); color: var(--text-muted);
font-size: 12px; font-size: 12px;
margin-left: 20px; margin-left: var(--space-5);
max-width: 320px; max-width: 320px;
white-space: pre; white-space: pre;
} }
@@ -699,7 +757,7 @@ input[type="file"]::file-selector-button {
.file-name { .file-name {
display: block; display: block;
margin-left: 0; margin-left: 0;
margin-bottom: 8px; margin-bottom: var(--space-2);
font-weight: 500; font-weight: 500;
color: var(--on-surface-variant); color: var(--on-surface-variant);
} }
@@ -716,11 +774,11 @@ input[type="file"]::file-selector-button {
#image-size-settings li { #image-size-settings li {
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
gap: 12px; gap: var(--space-3);
margin: 6px 0; margin: var(--space-2) 0;
border: 1px solid var(--border-subtle); border: 1px solid var(--border-subtle);
border-radius: var(--radius-lg); border-radius: var(--radius-lg);
padding: 8px 12px; padding: var(--space-2) var(--space-3);
} }
.image-size-thumb { .image-size-thumb {
@@ -752,13 +810,20 @@ input[type="file"]::file-selector-button {
border: 1px solid var(--outline-variant); border: 1px solid var(--outline-variant);
border-radius: var(--radius-lg); border-radius: var(--radius-lg);
/* background: var(--primary-container); */ /* background: var(--primary-container); */
margin: 10px 15px 10px 0; margin: var(--space-2) var(--space-4) var(--space-2) 0;
/* padding: 4px; */ max-width: 100%;
/* Small (e.g. 16x16/32x32) glyphs were rendering at native size, i.e. as
an illegible postage stamp. Enforce a minimum on-screen size and keep
pixel edges crisp when the browser scales the canvas up. */
min-width: 96px;
min-height: 96px;
image-rendering: crisp-edges;
image-rendering: pixelated;
} }
.inlineImg { .inlineImg {
border-radius: var(--radius-lg); border-radius: var(--radius-lg);
margin-top: 8px; margin-top: var(--space-2);
} }
/* ---- Code output (dark editor look) ---- */ /* ---- Code output (dark editor look) ---- */
@@ -798,7 +863,7 @@ input[type="file"]::file-selector-button {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
align-items: center; align-items: center;
gap: 12px; gap: var(--space-3);
} }
.sp-block img { .sp-block img {
@@ -831,6 +896,16 @@ input[type="file"]::file-selector-button {
.sidebar-link.active { .sidebar-link.active {
border-right: none; border-right: none;
} }
.settings-preview-row {
flex-direction: column;
}
.settings-preview-row #step-3 {
position: static;
width: 100%;
flex: none;
}
} }
@media (max-width: 640px) { @media (max-width: 640px) {
@@ -844,7 +919,7 @@ input[type="file"]::file-selector-button {
.step-num { .step-num {
position: static; position: static;
margin-bottom: 16px; margin-bottom: var(--space-4);
} }
.step::before { .step::before {
@@ -857,7 +932,6 @@ input[type="file"]::file-selector-button {
.column-left, .column-left,
.column-right { .column-right {
float: none;
width: 100%; width: 100%;
margin-left: 0; margin-left: 0;
} }
+16 -2
View File
@@ -41,7 +41,7 @@
</aside> </aside>
<main class="wrapper"> <main class="wrapper">
<section class="section step" id="step-0"> <section class="section step first-step" id="step-0">
<h1>image2cpp</h1> <h1>image2cpp</h1>
<p><strong>image2cpp</strong> is a simple tool to convert images into byte arrays (and vice versa) for use with (monochrome) displays such as OLEDs on your Arduino or Raspberry Pi.<br> <p><strong>image2cpp</strong> is a simple tool to convert images into byte arrays (and vice versa) for use with (monochrome) displays such as OLEDs on your Arduino or Raspberry Pi.<br>
It was originally made to work with the Adafruit OLED library (for which your can find an example Arduino sketch <a href="https://github.com/javl/image2cpp/blob/master/oled_example/oled_example.ino" target="_blank">here</a>) but It was originally made to work with the Adafruit OLED library (for which your can find an example Arduino sketch <a href="https://github.com/javl/image2cpp/blob/master/oled_example/oled_example.ino" target="_blank">here</a>) but
@@ -84,6 +84,7 @@
</section> </section>
</section> </section>
<div class="settings-preview-row">
<section class="section step bottom-divider" id="step-2"> <section class="section step bottom-divider" id="step-2">
<div class="step-num">2</div> <div class="step-num">2</div>
<h2>Image Settings</h2> <h2>Image Settings</h2>
@@ -208,6 +209,7 @@
<div id="images-canvas-container"></div> <div id="images-canvas-container"></div>
</section> </section>
</section> </section>
</div>
<section class="section step" id="step-4"> <section class="section step" id="step-4">
<div class="step-num">4</div> <div class="step-num">4</div>
@@ -364,10 +366,22 @@
// Sidebar scrollspy // Sidebar scrollspy
var links = Array.prototype.slice.call(document.querySelectorAll('.sidebar-link')); var links = Array.prototype.slice.call(document.querySelectorAll('.sidebar-link'));
var steps = links.map(function (l) { return document.getElementById(l.dataset.step); }); var steps = links.map(function (l) { return document.getElementById(l.dataset.step); });
// Preview (step 3) is pinned beside Settings as a sticky side panel, not
// an independent scroll region - and Chrome updates a sticky element's
// offsetTop to its stuck position once scrolled past, which would
// otherwise make it permanently outrank Settings here. Skip it.
var previewIndex = links.findIndex(function (l) { return l.dataset.step === 'step-3'; });
function onScroll() { function onScroll() {
var pos = window.scrollY + 120; var pos = window.scrollY + 120;
var active = 0; var active = 0;
steps.forEach(function (s, i) { if (s && s.offsetTop <= pos) active = i; }); var bestTop = -Infinity;
steps.forEach(function (s, i) {
if (i === previewIndex) return;
if (s && s.offsetTop <= pos && s.offsetTop > bestTop) {
bestTop = s.offsetTop;
active = i;
}
});
links.forEach(function (l, i) { l.classList.toggle('active', i === active); }); links.forEach(function (l, i) { l.classList.toggle('active', i === active); });
steps.forEach(function (s, i) { if (s) s.classList.toggle('active', i === active); }); steps.forEach(function (s, i) { if (s) s.classList.toggle('active', i === active); });
} }