Levkin
-Software development · Canada · remote
-Taking new engagements · 15+ yrs · 8h→2m
-diff --git a/shared/stack-layout.css b/shared/stack-layout.css
index 13c6743..e67d41f 100644
--- a/shared/stack-layout.css
+++ b/shared/stack-layout.css
@@ -1,19 +1,6 @@
-.scroll-section {
- height: var(--stack-slot);
- position: relative;
-}
-
-.scroll-section + .scroll-section {
- margin-top: calc(-1 * var(--stack-pull));
-}
-
-.scroll-section--final {
- height: var(--stack-slot-last);
- min-height: 280px;
-}
-
+/* Spacer after L6 so scroll can stop */
.stack-stop,
.stop {
- height: 2rem;
- margin-bottom: 3rem;
+ height: 1px;
+ margin-bottom: 4rem;
}
diff --git a/shared/stack-scroll.js b/shared/stack-scroll.js
index 33ede73..85dba7d 100644
--- a/shared/stack-scroll.js
+++ b/shared/stack-scroll.js
@@ -1,7 +1,7 @@
-/** Scroll depth + jump-to-layer for stack variants */
+/** Scroll depth + jump — panels are [data-layer] sticky cards */
export function initStackScroll(options = {}) {
const {
- sectionSelector = '.scroll-section',
+ sectionSelector = '.layer[data-layer], .folder[data-layer], .frame[data-layer], .unit[data-layer]',
depthEl = document.getElementById('depth'),
depthPrefix = 'L',
tabSelector = '[data-goto], .jump',
@@ -10,30 +10,30 @@ export function initStackScroll(options = {}) {
const sections = document.querySelectorAll(sectionSelector);
if (!sections.length) return;
- const mid = () => window.innerHeight * 0.42;
+ const mid = () => window.innerHeight * 0.45;
function updateDepth() {
let active = 0;
- sections.forEach((sec) => {
- const r = sec.getBoundingClientRect();
- if (r.top <= mid() && r.bottom > mid()) active = Number(sec.dataset.layer);
+ sections.forEach((el) => {
+ const r = el.getBoundingClientRect();
+ if (r.top <= mid() && r.bottom > mid()) active = Number(el.dataset.layer);
});
if (depthEl) depthEl.textContent = `${depthPrefix}${active}`;
- document.querySelectorAll('.stack-ruler button, .stack-ruler [data-goto], .tab-rail button, .tab[data-goto]').forEach((el) => {
- const n = el.dataset.layer ?? el.dataset.goto;
+ document.querySelectorAll('.stack-ruler button, .stack-ruler [data-goto], .tab-rail button, .tab[data-goto]').forEach((tab) => {
+ const n = tab.dataset.layer ?? tab.dataset.goto;
if (n === undefined) return;
- el.classList.toggle('active', Number(n) === active);
+ tab.classList.toggle('active', Number(n) === active);
});
}
document.querySelectorAll(tabSelector).forEach((tab) => {
tab.addEventListener('click', (e) => {
e.preventDefault();
- const layer = tab.dataset.goto;
+ const layer = tab.dataset.goto ?? tab.dataset.layer;
const target = document.querySelector(`${sectionSelector}[data-layer="${layer}"]`);
if (!target) return;
- const y = target.getBoundingClientRect().top + window.scrollY - 48;
+ const y = target.getBoundingClientRect().top + window.scrollY - 56;
window.scrollTo({ top: Math.max(0, y), behavior: 'smooth' });
});
});
diff --git a/shared/stack-vars.css b/shared/stack-vars.css
index 0e5439c..ea6bb6e 100644
--- a/shared/stack-vars.css
+++ b/shared/stack-vars.css
@@ -1,12 +1,7 @@
-/* Sticky card stack — enough scroll per layer for cover effect */
+/* Original working sticky stack (cfca7aa) */
:root {
--stack-nav: 2.5rem;
- --stack-stick: 3rem;
+ --stack-stick: 3.5rem;
--stack-step: 2.75rem;
- --stack-slot: 100vh;
- --stack-slot-last: 55vh;
- /* ~70vh scroll per layer before the next card slides over */
- --stack-scroll-step: 70vh;
- --stack-pull: calc(var(--stack-slot) - var(--stack-scroll-step));
- --stack-body-h: calc(100dvh - var(--stack-stick) - var(--stack-step) * 6 - 3rem);
+ --stack-card-min: 52vh;
}
diff --git a/stack-folder/folder.css b/stack-folder/folder.css
index 0552425..39d3ff6 100644
--- a/stack-folder/folder.css
+++ b/stack-folder/folder.css
@@ -49,10 +49,7 @@ body { font-family: var(--sans); background: #2a2824; color: #1a1814; }
}
.rail-tab:hover,
-.rail-tab.active {
- background: #c9a86c;
- color: #2a2824;
-}
+.rail-tab.active { background: #c9a86c; color: #2a2824; }
.mount {
width: min(640px, 100%);
@@ -60,9 +57,11 @@ body { font-family: var(--sans); background: #2a2824; color: #1a1814; }
padding: var(--stack-nav) 1rem 0;
}
-/* Card = tab + body; sticky with stepped top; higher z covers previous */
+/* Same pattern as /stack/ — one sticky card, next covers previous */
.folder {
position: sticky;
+ min-height: var(--stack-card-min);
+ margin-bottom: 1.25rem;
width: 100%;
}
@@ -81,7 +80,7 @@ body { font-family: var(--sans); background: #2a2824; color: #1a1814; }
border-radius: 8px 8px 0 0;
cursor: pointer;
text-align: left;
- z-index: 60;
+ z-index: 80;
box-shadow: 0 -2px 8px rgba(0,0,0,0.12);
}
@@ -92,30 +91,29 @@ body { font-family: var(--sans); background: #2a2824; color: #1a1814; }
border: 1px solid #c4b8a8;
border-top: none;
border-radius: 0 10px 10px 10px;
- padding: 1.25rem 1.4rem 2rem;
- min-height: var(--stack-body-h);
+ padding: 1.25rem 1.4rem 1.5rem;
box-shadow: 0 10px 32px rgba(0,0,0,0.25);
}
-.f0 { top: calc(var(--stack-stick) + var(--stack-step) * 0); z-index: 10; }
-.f0 .tab { top: calc(var(--stack-stick) + var(--stack-step) * 0); margin-left: calc(var(--tab-offset) * 0); background: #c9a86c; color: #2a2824; }
+.f0 { top: var(--stack-stick); z-index: 1; }
+.f0 .tab { top: var(--stack-stick); margin-left: calc(var(--tab-offset) * 0); background: #c9a86c; color: #2a2824; }
-.f1 { top: calc(var(--stack-stick) + var(--stack-step) * 1); z-index: 11; }
-.f1 .tab { top: calc(var(--stack-stick) + var(--stack-step) * 1); margin-left: calc(var(--tab-offset) * 1); background: #a8c4d4; color: #1a2830; }
+.f1 { top: calc(var(--stack-stick) + var(--stack-step)); z-index: 2; }
+.f1 .tab { top: calc(var(--stack-stick) + var(--stack-step)); margin-left: calc(var(--tab-offset) * 1); background: #a8c4d4; color: #1a2830; }
-.f2 { top: calc(var(--stack-stick) + var(--stack-step) * 2); z-index: 12; }
+.f2 { top: calc(var(--stack-stick) + var(--stack-step) * 2); z-index: 3; }
.f2 .tab { top: calc(var(--stack-stick) + var(--stack-step) * 2); margin-left: calc(var(--tab-offset) * 2); background: #b8d4a8; color: #1a2818; }
-.f3 { top: calc(var(--stack-stick) + var(--stack-step) * 3); z-index: 13; }
+.f3 { top: calc(var(--stack-stick) + var(--stack-step) * 3); z-index: 4; }
.f3 .tab { top: calc(var(--stack-stick) + var(--stack-step) * 3); margin-left: calc(var(--tab-offset) * 3); background: #d4b8c4; color: #2a1820; }
-.f4 { top: calc(var(--stack-stick) + var(--stack-step) * 4); z-index: 14; }
+.f4 { top: calc(var(--stack-stick) + var(--stack-step) * 4); z-index: 5; }
.f4 .tab { top: calc(var(--stack-stick) + var(--stack-step) * 4); margin-left: calc(var(--tab-offset) * 4); background: #d4c8a8; color: #2a2418; }
-.f5 { top: calc(var(--stack-stick) + var(--stack-step) * 5); z-index: 15; }
+.f5 { top: calc(var(--stack-stick) + var(--stack-step) * 5); z-index: 6; }
.f5 .tab { top: calc(var(--stack-stick) + var(--stack-step) * 5); margin-left: calc(var(--tab-offset) * 5); background: #c4c4c4; color: #2a2a2a; }
-.f6 { top: calc(var(--stack-stick) + var(--stack-step) * 6); z-index: 16; }
+.f6 { top: calc(var(--stack-stick) + var(--stack-step) * 6); z-index: 7; margin-bottom: 4rem; }
.f6 .tab { top: calc(var(--stack-stick) + var(--stack-step) * 6); margin-left: calc(var(--tab-offset) * 6); background: #2a4a6b; color: #e8e2d4; }
.body h1 { font-size: 1.65rem; margin-bottom: 0.35rem; }
@@ -139,7 +137,7 @@ body { font-family: var(--sans); background: #2a2824; color: #1a1814; }
@media (max-width: 720px) {
.tab-rail { display: none; }
- :root { --tab-offset: 1.4rem; --stack-step: 2.25rem; }
+ :root { --stack-step: 1.75rem; --tab-offset: 1.4rem; --stack-card-min: 48vh; }
.f5 .tab { margin-left: calc(var(--tab-offset) * 4); }
.f6 .tab { margin-left: calc(var(--tab-offset) * 3); }
}
diff --git a/stack-folder/index.html b/stack-folder/index.html
index b51554c..3fab136 100644
--- a/stack-folder/index.html
+++ b/stack-folder/index.html
@@ -28,71 +28,64 @@
Software development · Canada · remote Taking new engagements · 15+ yrs · 8h→2m Web apps, APIs, tools — TypeScript · Python · .NET · PostgreSQL n8n · Zapier · CI/CD · LLMs · auto.levkin.ca 15+ years · CaseWare Intl, MNP, JazzIt · caseware.levkin.ca Senior SDET · iliadobkin.com Internal hiring orchestrator · jobs.levkin.ca Software development · Canada · remote Taking new engagements · 15+ yrs · 8h→2m Web apps, APIs, tools — TypeScript · Python · .NET · PostgreSQL n8n · Zapier · CI/CD · LLMs · auto.levkin.ca 15+ years · CaseWare Intl, MNP, JazzIt · caseware.levkin.ca Senior SDET · iliadobkin.com Internal hiring orchestrator · jobs.levkin.caLevkin
- Custom software
- Automation
- CaseWare & CaseView
- Quality engineering
- Job Ops
- Levkin
+ Custom software
+ Automation
+ CaseWare & CaseView
+ Quality engineering
+ Job Ops
+
Software dev · CA · 15+ yrs
TS · Python · .NET
n8n · CI/CD
MNP · JazzIt
iliadobkin.com
auth
Software dev · CA · 15+ yrs
TS · Python · .NET
n8n · CI/CD
MNP · JazzIt
iliadobkin.com
auth
Canadian software practice · 15+ yrs · remote
TS · Python · .NET · APIs
n8n · CI/CD · LLMs
CaseWare · MNP · JazzIt
iliadobkin.com — traces · Playwright
internal · auth required
Canadian software practice · 15+ yrs · remote
TS · Python · .NET · APIs
n8n · CI/CD · LLMs
CaseWare · MNP · JazzIt
iliadobkin.com — traces · Playwright
internal · auth required
Software · Canada · remote
-Boutique engineering — production systems, automation, enterprise.
-Taking new engagements
-Web apps, APIs, internal tools — TS · Python · .NET
-Senior SDET · test automation · CI/CD · trace-driven QA
-Discover → Proposal → Ship → Maintain
-Software · Canada · remote
+Boutique engineering — production systems, automation, enterprise.
+Taking new engagements
Retries · docs · tests first
-Web apps, APIs, internal tools — TS · Python · .NET
+Senior SDET · test automation · CI/CD · trace-driven QA
+Discover → Proposal → Ship → Maintain
+Retries · docs · tests first
+