diff --git a/index.html b/index.html index d0f0969..79c67a4 100644 --- a/index.html +++ b/index.html @@ -253,7 +253,7 @@
L0│tab

Stack · Folder

-

Manila folders — name tabs on top, always visible when stacked.

+

Tabs on top, staggered left — all readable. Click tab or L0–L6 rail to jump.

folder · tabs · office
diff --git a/shared/stack-scroll.js b/shared/stack-scroll.js new file mode 100644 index 0000000..fc585cd --- /dev/null +++ b/shared/stack-scroll.js @@ -0,0 +1,43 @@ +/** Shared scroll-depth tracking + jump-to-layer for stack variants */ +export function initStackScroll(options = {}) { + const { + sectionSelector = '.scroll-section', + depthEl = document.getElementById('depth'), + depthPrefix = 'L', + tabSelector = '[data-goto], .jump', + } = options; + + const sections = document.querySelectorAll(sectionSelector); + if (!sections.length) return; + + const mid = () => window.innerHeight * 0.4; + + function updateDepth() { + let active = 0; + sections.forEach((sec) => { + const r = sec.getBoundingClientRect(); + if (r.top <= mid() && r.bottom > mid()) active = Number(sec.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; + if (n === undefined) return; + el.classList.toggle('active', Number(n) === active); + }); + } + + document.querySelectorAll(tabSelector).forEach((tab) => { + tab.addEventListener('click', (e) => { + e.preventDefault(); + const layer = tab.dataset.goto; + const target = document.querySelector(`${sectionSelector}[data-layer="${layer}"]`); + if (!target) return; + const navH = 56; + const y = target.getBoundingClientRect().top + window.scrollY - navH; + window.scrollTo({ top: y, behavior: 'smooth' }); + }); + }); + + window.addEventListener('scroll', updateDepth, { passive: true }); + updateDepth(); +} diff --git a/shared/stack-vars.css b/shared/stack-vars.css new file mode 100644 index 0000000..6d1a97d --- /dev/null +++ b/shared/stack-vars.css @@ -0,0 +1,8 @@ +/* Import in each stack variant: @import '../shared/stack-vars.css'; */ +:root { + --stack-nav: 2.5rem; + --stack-stick: 0.5rem; + --stack-step: 1.75rem; + --stack-tick: 13vh; + --stack-overlap: 11.5vh; +} diff --git a/stack-folder/folder.css b/stack-folder/folder.css index 89fb8e0..04985cd 100644 --- a/stack-folder/folder.css +++ b/stack-folder/folder.css @@ -1,10 +1,7 @@ +@import '../shared/stack-vars.css'; + :root { - --nav: 2.5rem; - --stick: 0.5rem; - --step: 1.85rem; - --tab-h: 1.65rem; - --scroll-tick: 26vh; - --overlap: 22vh; + --tab-offset: 2.4rem; --mono: 'IBM Plex Mono', monospace; --sans: 'Instrument Sans', system-ui, sans-serif; } @@ -14,94 +11,152 @@ body { font-family: var(--sans); background: #2a2824; color: #1a1814; } .nav { - position: fixed; top: 0; left: 0; right: 0; z-index: 300; - display: flex; gap: 1rem; padding: 0.5rem 1rem; + position: fixed; top: 0; left: 0; right: 0; z-index: 400; + display: flex; flex-wrap: wrap; gap: 0.5rem 1rem; padding: 0.5rem 1rem; font-family: var(--mono); font-size: 0.62rem; - background: rgba(42,40,36,0.95); color: #c4b8a8; + background: rgba(42,40,36,0.97); color: #c4b8a8; } .nav a { color: #8a8278; text-decoration: none; } +.nav a:hover { color: #d4a574; } .depth { margin-left: auto; color: #d4a574; font-weight: 600; } -.mount { - max-width: 520px; - margin: 0 auto; - padding: calc(var(--nav) + 0.5rem) 1rem 0; +/* Quick-jump rail */ +.tab-rail { + position: fixed; + top: 2.5rem; + right: max(0.5rem, calc(50% - 360px)); + z-index: 350; + display: flex; + flex-direction: column; + gap: 0.2rem; + padding: 0.35rem; + background: rgba(42,40,36,0.9); + border: 1px solid #4a4844; + border-radius: 6px; } -.scroll-section { height: var(--scroll-tick); position: relative; } -.scroll-section--final { height: 16vh; min-height: 180px; } -.stop { height: 1px; margin-bottom: 3rem; } +.rail-tab { + font-family: var(--mono); + font-size: 0.55rem; + padding: 0.2rem 0.45rem; + border: none; + border-radius: 3px; + background: transparent; + color: #8a8278; + cursor: pointer; + text-align: left; +} + +.rail-tab:hover, +.rail-tab.active { + background: #c9a86c; + color: #2a2824; +} + +.mount { + width: min(640px, 100%); + margin: 0 auto; + padding: calc(var(--stack-nav) + 0.25rem) 1rem 0; +} + +.scroll-section { + height: var(--stack-tick); + position: relative; +} + +.scroll-section--final { + height: 12vh; + min-height: 220px; +} + +.stack-stop { height: 1px; margin-bottom: 4rem; } -/* Folder stacks; tab sits above body and stays visible */ .folder { position: sticky; - margin-bottom: calc(-1 * var(--overlap)); - padding-top: 0; + width: 100%; + margin-bottom: calc(-1 * var(--stack-overlap)); } +/* Tab: sticky with layer, offset left so all labels readable when stacked */ .tab { - position: relative; - z-index: 50; - display: inline-block; + position: sticky; + display: block; + width: fit-content; + max-width: calc(100% - 1rem); font-family: var(--mono); - font-size: 0.58rem; + font-size: 0.62rem; font-weight: 600; - letter-spacing: 0.05em; - padding: 0.35rem 0.85rem 0.3rem; - border-radius: 6px 6px 0 0; - border: 1px solid rgba(0,0,0,0.12); + letter-spacing: 0.04em; + padding: 0.4rem 1rem 0.35rem; + border: 1px solid rgba(0,0,0,0.15); border-bottom: none; - margin-left: 0.5rem; - box-shadow: 0 -2px 8px rgba(0,0,0,0.12); + border-radius: 8px 8px 0 0; + cursor: pointer; + text-align: left; + z-index: 50; + box-shadow: 0 -3px 10px rgba(0,0,0,0.15); + transition: filter 0.15s, transform 0.15s; } +.tab:hover { filter: brightness(1.08); transform: translateY(-1px); } + .body { - position: relative; - z-index: 1; background: #e8e2d4; border: 1px solid #c4b8a8; - border-radius: 0 8px 8px 8px; - padding: 1rem 1.2rem 1.1rem; - box-shadow: 3px 6px 20px rgba(0,0,0,0.2); - min-height: 100px; + border-radius: 0 10px 10px 10px; + padding: 1.25rem 1.4rem 1.35rem; + min-height: 200px; + box-shadow: 0 8px 28px rgba(0,0,0,0.22); + position: relative; + z-index: 1; } -.f0 { top: calc(var(--nav) + var(--stick)); z-index: 1; } -.f0 .tab { background: #c9a86c; color: #2a2824; z-index: 107; } -.f1 { top: calc(var(--nav) + var(--stick) + var(--step)); z-index: 2; } -.f1 .tab { background: #a8c4d4; color: #1a2830; z-index: 108; } -.f2 { top: calc(var(--nav) + var(--stick) + var(--step) * 2); z-index: 3; } -.f2 .tab { background: #b8d4a8; color: #1a2818; z-index: 109; } -.f3 { top: calc(var(--nav) + var(--stick) + var(--step) * 3); z-index: 4; } -.f3 .tab { background: #d4b8c4; color: #2a1820; z-index: 110; } -.f4 { top: calc(var(--nav) + var(--stick) + var(--step) * 4); z-index: 5; } -.f4 .tab { background: #d4c8a8; color: #2a2418; z-index: 111; } -.f5 { top: calc(var(--nav) + var(--stick) + var(--step) * 5); z-index: 6; } -.f5 .tab { background: #c4c4c4; color: #2a2a2a; z-index: 112; } +.f0 { top: calc(var(--stack-nav) + var(--stack-stick)); z-index: 1; } +.f0 .tab { top: calc(var(--stack-nav) + var(--stack-stick)); margin-left: calc(var(--tab-offset) * 0); z-index: 170; background: #c9a86c; color: #2a2824; } + +.f1 { top: calc(var(--stack-nav) + var(--stack-stick) + var(--stack-step)); z-index: 2; } +.f1 .tab { top: calc(var(--stack-nav) + var(--stack-stick) + var(--stack-step)); margin-left: calc(var(--tab-offset) * 1); z-index: 171; background: #a8c4d4; color: #1a2830; } + +.f2 { top: calc(var(--stack-nav) + var(--stack-stick) + var(--stack-step) * 2); z-index: 3; } +.f2 .tab { top: calc(var(--stack-nav) + var(--stack-stick) + var(--stack-step) * 2); margin-left: calc(var(--tab-offset) * 2); z-index: 172; background: #b8d4a8; color: #1a2818; } + +.f3 { top: calc(var(--stack-nav) + var(--stack-stick) + var(--stack-step) * 3); z-index: 4; } +.f3 .tab { top: calc(var(--stack-nav) + var(--stack-stick) + var(--stack-step) * 3); margin-left: calc(var(--tab-offset) * 3); z-index: 173; background: #d4b8c4; color: #2a1820; } + +.f4 { top: calc(var(--stack-nav) + var(--stack-stick) + var(--stack-step) * 4); z-index: 5; } +.f4 .tab { top: calc(var(--stack-nav) + var(--stack-stick) + var(--stack-step) * 4); margin-left: calc(var(--tab-offset) * 4); z-index: 174; background: #d4c8a8; color: #2a2418; } + +.f5 { top: calc(var(--stack-nav) + var(--stack-stick) + var(--stack-step) * 5); z-index: 6; } +.f5 .tab { top: calc(var(--stack-nav) + var(--stack-stick) + var(--stack-step) * 5); margin-left: calc(var(--tab-offset) * 5); z-index: 175; background: #c4c4c4; color: #2a2a2a; } + .f6 { - top: calc(var(--nav) + var(--stick) + var(--step) * 6); + top: calc(var(--stack-nav) + var(--stack-stick) + var(--stack-step) * 6); z-index: 7; margin-bottom: 0; } -.f6 .tab { background: #2a4a6b; color: #e8e2d4; z-index: 113; } +.f6 .tab { top: calc(var(--stack-nav) + var(--stack-stick) + var(--stack-step) * 6); margin-left: calc(var(--tab-offset) * 6); z-index: 176; background: #2a4a6b; color: #e8e2d4; } -.body h1, .body h2 { font-size: 1.15rem; margin-bottom: 0.3rem; } -.body p { font-size: 0.86rem; color: #4a4844; } +.body h1 { font-size: 1.65rem; margin-bottom: 0.35rem; } +.body h2 { font-size: 1.25rem; margin-bottom: 0.4rem; } +.body p { font-size: 0.92rem; color: #4a4844; line-height: 1.5; } .body a { color: #2a4a6b; } -.avail { font-family: var(--mono); font-size: 0.62rem; color: #3d6b3d; margin-top: 0.3rem; } +.avail { font-family: var(--mono); font-size: 0.68rem; color: #3d6b3d; margin-top: 0.5rem; } .btn { - font-family: var(--mono); font-size: 0.65rem; padding: 0.35rem 0.65rem; - background: #2a4a6b; color: #fff; text-decoration: none; border-radius: 3px; margin-right: 0.3rem; + font-family: var(--mono); font-size: 0.7rem; padding: 0.45rem 0.75rem; + background: #2a4a6b; color: #fff; text-decoration: none; border-radius: 4px; margin-right: 0.4rem; display: inline-block; margin-top: 0.35rem; } .btn.ghost { background: transparent; color: #2a4a6b; border: 1px solid #2a4a6b; } .foot { - display: flex; justify-content: space-between; max-width: 520px; margin: 0 auto; - padding: 0 1rem 2rem; font-family: var(--mono); font-size: 0.6rem; color: #6a6458; + display: flex; justify-content: space-between; + width: min(640px, 100%); margin: 0 auto; + padding: 0 1rem 2rem; font-family: var(--mono); font-size: 0.62rem; color: #6a6458; } .foot a { color: #6a6458; text-decoration: none; } -@media (max-width: 700px) { - :root { --step: 1.5rem; --overlap: 20vh; --scroll-tick: 24vh; } +@media (max-width: 720px) { + .tab-rail { display: none; } + :root { --tab-offset: 1.5rem; --stack-tick: 12vh; --stack-overlap: 10.5vh; } + .f6 .tab { margin-left: calc(var(--tab-offset) * 3); } } diff --git a/stack-folder/folder.js b/stack-folder/folder.js deleted file mode 100644 index 171d96c..0000000 --- a/stack-folder/folder.js +++ /dev/null @@ -1,10 +0,0 @@ -const sections = document.querySelectorAll('.scroll-section'); -const depthEl = document.getElementById('depth'); -const mid = () => window.innerHeight * 0.42; -function tick() { - let a = 0; - sections.forEach((s) => { const r = s.getBoundingClientRect(); if (r.top <= mid() && r.bottom > mid()) a = +s.dataset.layer; }); - depthEl.textContent = `L${a}`; -} -window.addEventListener('scroll', tick, { passive: true }); -tick(); diff --git a/stack-folder/index.html b/stack-folder/index.html index 30da629..9cbc8bd 100644 --- a/stack-folder/index.html +++ b/stack-folder/index.html @@ -6,21 +6,101 @@ Levkin — Stack Folder + - + + +
+ + + + + + + +
+
-
L0 · foundation

Levkin

Software development · Canada · remote · 15+ yrs

Taking new engagements

-
L1 · application

Custom software

Web apps, APIs, tools — TS · Python · .NET

-
-
-
-
-
-
+
+
+ +
+

Levkin

+

Software development · Canada · remote

+

Taking new engagements · 15+ yrs · 8h→2m

+
+
+
+
+
+ +
+

Custom software

+

Web apps, APIs, tools — TypeScript · Python · .NET · PostgreSQL

+
+
+
+
+
+ +
+

Automation

+

n8n · Zapier · CI/CD · LLMs · auto.levkin.ca

+
+
+
+
+
+ +
+

CaseWare & CaseView

+

15+ years · CaseWare Intl, MNP, JazzIt · caseware.levkin.ca

+
+
+
+
+
+ +
+

Quality engineering

+

Senior SDET · iliadobkin.com

+
+
+
+
+
+ +
+

Job Ops

+

Internal hiring orchestrator · jobs.levkin.ca

+
+
+
+
+ +
+
- - + + + diff --git a/stack-rack/index.html b/stack-rack/index.html index 90f4ed4..1859206 100644 --- a/stack-rack/index.html +++ b/stack-rack/index.html @@ -6,26 +6,28 @@ Levkin — Stack Rack + - - +
LEV-RACK-01
-
U0foundation
Levkin

Software dev · CA · 15+ yrs · new work open

-
U1application
custom_software

TS · Python · .NET

-
U2automationauto↗
pipeline_cluster

n8n · CI/CD · LLM hooks

-
U3enterprisecaseware↗
caseware_module

MNP · JazzIt · 8h→2m

-
-
U5opsjobs↗
job_ops

auth · internal

-
+
Levkin

Software dev · CA · 15+ yrs

+
custom_software

TS · Python · .NET

+
auto↗
pipeline

n8n · CI/CD

+
case↗
caseware

MNP · JazzIt

+
sdet↗
sdet_suite

iliadobkin.com

+
+
- - + diff --git a/stack-rack/rack.css b/stack-rack/rack.css index 1c4ead2..1a21d54 100644 --- a/stack-rack/rack.css +++ b/stack-rack/rack.css @@ -1,17 +1,9 @@ -:root { - --nav: 2.5rem; - --stick: 0.5rem; - --step: 1.7rem; - --scroll-tick: 26vh; - --overlap: 22vh; - --rack: #12141a; - --mono: 'IBM Plex Mono', monospace; -} +@import '../shared/stack-vars.css'; * { box-sizing: border-box; margin: 0; padding: 0; } body { - font-family: var(--mono); + font-family: 'IBM Plex Mono', ui-monospace, monospace; background: #08090c; color: #9ca3af; font-size: 0.75rem; @@ -28,101 +20,79 @@ body { #depth { margin-left: auto; color: #4ade80; font-weight: 600; } .rack-frame { - max-width: 540px; + width: min(600px, 100%); margin: 0 auto; - padding: calc(var(--nav) + 0.75rem) 0.75rem 0; + padding: calc(var(--stack-nav) + 0.5rem) 0.5rem 0; border-left: 4px solid #2a3040; border-right: 4px solid #2a3040; - background: linear-gradient(90deg, #0c0e14 0%, var(--rack) 8%, var(--rack) 92%, #0c0e14 100%); + background: linear-gradient(90deg, #0c0e14 0%, #12141a 8%, #12141a 92%, #0c0e14 100%); min-height: 100vh; - box-shadow: inset 0 0 40px rgba(0,0,0,0.5); } .rack-label { - font-size: 0.55rem; - letter-spacing: 0.2em; - color: #3a4458; - padding: 0 0.5rem 0.75rem; - border-bottom: 1px dashed #2a3040; - margin-bottom: 0.5rem; + font-size: 0.55rem; letter-spacing: 0.2em; color: #3a4458; + padding: 0 0.5rem 0.65rem; border-bottom: 1px dashed #2a3040; } -.mount { padding: 0 0.35rem; } +.mount { padding: 0 0.25rem; } -.scroll-section { height: var(--scroll-tick); position: relative; } -.scroll-section--final { height: 16vh; min-height: 160px; } -.stop { height: 1px; margin-bottom: 2rem; } +.scroll-section { height: var(--stack-tick); position: relative; } +.scroll-section--final { height: 12vh; min-height: 200px; } +.stop { height: 1px; margin-bottom: 4rem; } .unit { position: sticky; - margin-bottom: calc(-1 * var(--overlap)); + margin-bottom: calc(-1 * var(--stack-overlap)); border: 1px solid #2a3448; background: #161a22; border-radius: 2px; overflow: hidden; - box-shadow: 0 4px 0 #0a0c10, inset 0 1px 0 rgba(255,255,255,0.04); + box-shadow: 0 6px 0 #0a0c10, 0 12px 24px rgba(0,0,0,0.4); + min-height: 160px; } .unit-head { - display: flex; - align-items: center; - gap: 0.5rem; - padding: 0.35rem 0.65rem; + display: flex; align-items: center; gap: 0.5rem; + padding: 0.4rem 0.65rem; background: #1a2030; border-bottom: 1px solid #2a3448; - font-size: 0.62rem; - position: relative; - z-index: 20; + position: sticky; + z-index: 30; } -.led { - width: 6px; - height: 6px; - border-radius: 50%; - background: #3a4458; - flex-shrink: 0; +.unit-head button.jump { + background: none; border: none; font: inherit; color: inherit; + cursor: pointer; display: flex; align-items: center; gap: 0.5rem; flex: 1; + text-align: left; padding: 0; } +.unit-head button.jump:hover .uid { color: #4ade80; } + +.led { width: 6px; height: 6px; border-radius: 50%; background: #3a4458; flex-shrink: 0; } .led.on { background: #4ade80; box-shadow: 0 0 6px #4ade80; } .led.blink { background: #fbbf24; animation: blink 1.2s ease infinite; } +@keyframes blink { 50% { opacity: 0.35; } } -@keyframes blink { - 50% { opacity: 0.35; } -} - -.uid { color: #6b7280; font-weight: 600; min-width: 1.5rem; } +.uid { color: #6b7280; font-weight: 600; min-width: 1.4rem; } .svc { color: #9ca3af; text-transform: uppercase; letter-spacing: 0.08em; flex: 1; } -.unit-head a { - color: #60a5fa; - text-decoration: none; - font-size: 0.58rem; -} +.unit-head a.ext { color: #60a5fa; text-decoration: none; font-size: 0.58rem; } -.unit-body { - padding: 0.55rem 0.65rem 0.65rem; - position: relative; - z-index: 1; -} - -.unit-body strong { color: #e5e7eb; font-weight: 500; display: block; margin-bottom: 0.15rem; } -.unit-body p { color: #6b7280; font-size: 0.68rem; } +.unit-body { padding: 0.6rem 0.7rem 0.75rem; min-height: 120px; } +.unit-body strong { color: #e5e7eb; display: block; margin-bottom: 0.2rem; } +.unit-body p { color: #6b7280; font-size: 0.7rem; } .unit-body a { color: #60a5fa; text-decoration: none; } -.unit-body a:hover { text-decoration: underline; } -.u0 { top: calc(var(--nav) + var(--stick)); z-index: 1; } -.u1 { top: calc(var(--nav) + var(--stick) + var(--step)); z-index: 2; } -.u2 { top: calc(var(--nav) + var(--stick) + var(--step) * 2); z-index: 3; } -.u3 { top: calc(var(--nav) + var(--stick) + var(--step) * 3); z-index: 4; } -.u4 { top: calc(var(--nav) + var(--stick) + var(--step) * 4); z-index: 5; } -.u5 { top: calc(var(--nav) + var(--stick) + var(--step) * 5); z-index: 6; } -.u6 { - top: calc(var(--nav) + var(--stick) + var(--step) * 6); - z-index: 7; - margin-bottom: 0; -} +.u0 { top: calc(var(--stack-nav) + var(--stack-stick)); z-index: 1; } +.u1 { top: calc(var(--stack-nav) + var(--stack-stick) + var(--stack-step)); z-index: 2; } +.u2 { top: calc(var(--stack-nav) + var(--stack-stick) + var(--stack-step) * 2); z-index: 3; } +.u3 { top: calc(var(--stack-nav) + var(--stack-stick) + var(--stack-step) * 3); z-index: 4; } +.u4 { top: calc(var(--stack-nav) + var(--stack-stick) + var(--stack-step) * 4); z-index: 5; } +.u5 { top: calc(var(--stack-nav) + var(--stack-stick) + var(--stack-step) * 5); z-index: 6; } +.u6 { top: calc(var(--stack-nav) + var(--stack-stick) + var(--stack-step) * 6); z-index: 7; margin-bottom: 0; } .foot { - display: flex; justify-content: space-between; max-width: 540px; margin: 0 auto; - padding: 1rem 1.25rem 2rem; font-size: 0.58rem; color: #3a4458; + display: flex; justify-content: space-between; + width: min(600px, 100%); margin: 0 auto; + padding: 1rem 1.25rem 2.5rem; font-size: 0.58rem; color: #3a4458; } .foot a { color: #3a4458; text-decoration: none; } diff --git a/stack-rack/rack.js b/stack-rack/rack.js deleted file mode 100644 index 5063847..0000000 --- a/stack-rack/rack.js +++ /dev/null @@ -1,15 +0,0 @@ -const sections = document.querySelectorAll('.scroll-section'); -const depthEl = document.getElementById('depth'); -const mid = () => window.innerHeight * 0.42; - -function tick() { - let a = 0; - sections.forEach((s) => { - const r = s.getBoundingClientRect(); - if (r.top <= mid() && r.bottom > mid()) a = +s.dataset.layer; - }); - depthEl.textContent = `U${a}`; -} - -window.addEventListener('scroll', tick, { passive: true }); -tick(); diff --git a/stack-trace/index.html b/stack-trace/index.html index 6ca2165..f128a51 100644 --- a/stack-trace/index.html +++ b/stack-trace/index.html @@ -6,21 +6,25 @@ Levkin — Stack Trace +
-
at Levkin.foundation (levkin.ca:0)
Levkin

Canadian software practice · 15+ yrs · remote

-
at Levkin.application (levkin.ca:1)
custom_software()

TS · Python · .NET · APIs

-
at Levkin.automation (auto.levkin.ca:2)
automation_pipeline()

n8n · CI/CD · LLMs

-
at Levkin.caseware (caseware.levkin.ca:3)
enterprise_module()

CaseWare · MNP · JazzIt

-
at Levkin.quality (iliadobkin.com:4)
sdet_suite()

iliadobkin.com — traces · Playwright

-
at Levkin.operations (jobs.levkin.ca:5)
job_ops()

internal · auth required

-
+
Levkin

Canadian software practice · 15+ yrs · remote

+
custom_software()

TS · Python · .NET · APIs

+
automation_pipeline()

n8n · CI/CD · LLMs

+
enterprise_module()

CaseWare · MNP · JazzIt

+
sdet_suite()

iliadobkin.com — traces · Playwright

+
job_ops()

internal · auth required

+
- + diff --git a/stack-trace/trace.css b/stack-trace/trace.css index 9918b52..1e1bf0f 100644 --- a/stack-trace/trace.css +++ b/stack-trace/trace.css @@ -1,18 +1,16 @@ -:root { - --nav: 2.5rem; - --stick: 0.5rem; - --step: 1.75rem; - --scroll-tick: 26vh; - --overlap: 22vh; - --mono: 'IBM Plex Mono', monospace; -} +@import '../shared/stack-vars.css'; * { box-sizing: border-box; margin: 0; padding: 0; } -body { font-family: var(--mono); background: #0d0d0f; color: #b8b4af; font-size: 0.8rem; } +body { + font-family: 'IBM Plex Mono', ui-monospace, monospace; + background: #0d0d0f; + color: #b8b4af; + font-size: 0.8rem; +} .nav { - position: fixed; top: 0; left: 0; right: 0; z-index: 100; + position: fixed; top: 0; left: 0; right: 0; z-index: 200; display: flex; gap: 1rem; padding: 0.5rem 1rem; font-size: 0.62rem; background: rgba(13,13,15,0.95); border-bottom: 1px solid #2a2a30; } @@ -21,48 +19,57 @@ body { font-family: var(--mono); background: #0d0d0f; color: #b8b4af; font-size: .nav a:hover { color: #7eb87a; } #depth { margin-left: auto; color: #7eb87a; font-weight: 600; } -.mount { max-width: 560px; margin: 0 auto; padding-top: var(--nav); } +.mount { + width: min(600px, 100%); + margin: 0 auto; + padding-top: var(--stack-nav); +} -.scroll-section { height: var(--scroll-tick); position: relative; } -.scroll-section--final { height: 16vh; min-height: 180px; } -.stop { height: 1px; margin-bottom: 3rem; } +.scroll-section { height: var(--stack-tick); position: relative; } +.scroll-section--final { height: 12vh; min-height: 200px; } +.stop { height: 1px; margin-bottom: 4rem; } .frame { position: sticky; - margin: 0 0.75rem calc(-1 * var(--overlap)) 0.75rem; + margin: 0 0.5rem calc(-1 * var(--stack-overlap)) 0.5rem; border-left: 3px solid #3a3a44; background: #141418; - padding: 0.6rem 0 0.6rem 0.9rem; - box-shadow: 0 6px 0 #0a0a0c, 0 12px 24px rgba(0,0,0,0.4); + padding: 0.65rem 0 0.65rem 1rem; + min-height: 180px; + box-shadow: 0 8px 0 #0a0a0c, 0 14px 28px rgba(0,0,0,0.45); } -.frame code { +.frame-line { display: block; font-size: 0.66rem; color: #6b9b6b; - margin-bottom: 0.35rem; + margin-bottom: 0.4rem; + background: none; + border: none; + font-family: inherit; + cursor: pointer; + text-align: left; + padding: 0; + width: 100%; } -.frame-body strong { color: #e8e6e3; font-weight: 500; } -.frame-body p { color: #6b6966; font-size: 0.72rem; margin-top: 0.15rem; } +.frame-line:hover { color: #9fdf9f; text-decoration: underline; } + +.frame-body strong { color: #e8e6e3; font-weight: 500; display: block; margin-bottom: 0.2rem; } +.frame-body p { color: #6b6966; font-size: 0.74rem; } .frame-body a { color: #8b9cb3; text-decoration: none; } -.frame-body a:hover { color: #7eb87a; } -.f0 { top: calc(var(--nav) + var(--stick)); z-index: 1; border-color: #c4a574; } -.f1 { top: calc(var(--nav) + var(--stick) + var(--step)); z-index: 2; } -.f2 { top: calc(var(--nav) + var(--stick) + var(--step) * 2); z-index: 3; } -.f3 { top: calc(var(--nav) + var(--stick) + var(--step) * 3); z-index: 4; } -.f4 { top: calc(var(--nav) + var(--stick) + var(--step) * 4); z-index: 5; border-color: #6b8b9b; } -.f5 { top: calc(var(--nav) + var(--stick) + var(--step) * 5); z-index: 6; } -.f6 { - top: calc(var(--nav) + var(--stick) + var(--step) * 6); - z-index: 7; - border-color: #7eb87a; - margin-bottom: 0; -} +.f0 { top: calc(var(--stack-nav) + var(--stack-stick)); z-index: 1; border-color: #c4a574; } +.f1 { top: calc(var(--stack-nav) + var(--stack-stick) + var(--stack-step)); z-index: 2; } +.f2 { top: calc(var(--stack-nav) + var(--stack-stick) + var(--stack-step) * 2); z-index: 3; } +.f3 { top: calc(var(--stack-nav) + var(--stack-stick) + var(--stack-step) * 3); z-index: 4; } +.f4 { top: calc(var(--stack-nav) + var(--stack-stick) + var(--stack-step) * 4); z-index: 5; border-color: #6b8b9b; } +.f5 { top: calc(var(--stack-nav) + var(--stack-stick) + var(--stack-step) * 5); z-index: 6; } +.f6 { top: calc(var(--stack-nav) + var(--stack-stick) + var(--stack-step) * 6); z-index: 7; border-color: #7eb87a; margin-bottom: 0; } .foot { - display: flex; justify-content: space-between; max-width: 560px; margin: 0 auto; + display: flex; justify-content: space-between; + width: min(600px, 100%); margin: 0 auto; padding: 0 1.5rem 2.5rem; font-size: 0.6rem; color: #3a3a40; } .foot a { color: #3a3a40; text-decoration: none; } diff --git a/stack-trace/trace.js b/stack-trace/trace.js deleted file mode 100644 index de10304..0000000 --- a/stack-trace/trace.js +++ /dev/null @@ -1,10 +0,0 @@ -const sections = document.querySelectorAll('.scroll-section'); -const depthEl = document.getElementById('depth'); -const mid = () => window.innerHeight * 0.42; -function tick() { - let a = 0; - sections.forEach((s) => { const r = s.getBoundingClientRect(); if (r.top <= mid() && r.bottom > mid()) a = +s.dataset.layer; }); - depthEl.textContent = `#${a}`; -} -window.addEventListener('scroll', tick, { passive: true }); -tick(); diff --git a/stack/index.html b/stack/index.html index 90b77f9..3cae9ae 100644 --- a/stack/index.html +++ b/stack/index.html @@ -9,6 +9,7 @@ + @@ -20,7 +21,7 @@
-
L0foundation
+
foundation

Levkin

Software · Canada · remote

Boutique engineering — production systems, automation, enterprise.

@@ -29,37 +30,37 @@
-
L1application
+
application

Custom software

Web apps, APIs, internal tools — TS · Python · .NET

-
L2automationauto.levkin.ca ↗
+
automationauto.levkin.ca ↗

Automation

n8n · Zapier · CI/CD · webhooks · LLMs

-
L3enterprisecaseware.levkin.ca ↗
+
enterprisecaseware.levkin.ca ↗

CaseWare & CaseView

15+ years · CaseWare Intl, MNP, JazzIt

-
L4qualityiliadobkin.com ↗
+
qualityiliadobkin.com ↗

Quality engineering

Senior SDET · test automation · CI/CD · trace-driven QA

-
L5operationsjobs.levkin.ca ↗
+
operationsjobs.levkin.ca ↗

Job Ops

Internal hiring orchestrator (auth required)

-
L6interface
+
interface

Engage

Discover → Proposal → Ship → Maintain

@@ -73,7 +74,18 @@
- - +
+ + + + + + + +
+ diff --git a/stack/stack.css b/stack/stack.css index a7697a8..807f404 100644 --- a/stack/stack.css +++ b/stack/stack.css @@ -1,115 +1,94 @@ +@import '../shared/stack-vars.css'; + :root { - --bg: #0e0e10; --mono: 'IBM Plex Mono', ui-monospace, monospace; --sans: 'Instrument Sans', system-ui, sans-serif; - --nav-h: 2.5rem; - --step: 2rem; - --scroll-tick: 28vh; - --overlap: 24vh; } * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: var(--sans); - background: var(--bg); + background: #0e0e10; color: #e8e6e3; line-height: 1.5; } .nav { - position: fixed; - top: 0; left: 0; right: 0; - z-index: 200; - display: flex; - align-items: center; - gap: 1rem; - padding: 0.55rem 1rem; - font-family: var(--mono); - font-size: 0.62rem; - background: rgba(14, 14, 16, 0.92); - backdrop-filter: blur(8px); + position: fixed; top: 0; left: 0; right: 0; z-index: 200; + display: flex; align-items: center; gap: 1rem; + padding: 0.55rem 1rem; font-family: var(--mono); font-size: 0.62rem; + background: rgba(14, 14, 16, 0.92); backdrop-filter: blur(8px); border-bottom: 1px solid rgba(255,255,255,0.05); } .nav a { color: #6b6966; text-decoration: none; } .nav a:hover { color: #c4a574; } .variants { margin-left: auto; color: #4a4844; } -.variants a { color: #5a5854; } .depth { color: #c4a574; font-weight: 600; } .stack-mount { - max-width: 500px; + width: min(560px, 100%); margin: 0 auto; - padding: var(--nav-h) 1rem 0; + padding: var(--stack-nav) 1rem 0; } -.scroll-section { - height: var(--scroll-tick); - position: relative; -} - -.scroll-section--final { - height: 18vh; - min-height: 200px; -} - -.stack-stop { height: 1px; margin-bottom: 3rem; } +.scroll-section { height: var(--stack-tick); position: relative; } +.scroll-section--final { height: 12vh; min-height: 220px; } +.stack-stop { height: 1px; margin-bottom: 4rem; } .layer { position: sticky; - margin: 0 0.35rem calc(-1 * var(--overlap)) 0.35rem; + margin: 0 0 calc(-1 * var(--stack-overlap)) 0; border-radius: 8px 8px 6px 6px; border: 1px solid rgba(255,255,255,0.08); - box-shadow: 0 10px 32px rgba(0,0,0,0.45); + box-shadow: 0 12px 36px rgba(0,0,0,0.5); } .layer-tab { position: absolute; - top: -6px; - left: 10px; - right: 10px; - height: 6px; + top: -7px; left: 8px; right: 8px; height: 7px; border-radius: 5px 5px 0 0; background: inherit; - filter: brightness(1.12); + filter: brightness(1.15); border: 1px solid rgba(255,255,255,0.07); border-bottom: none; + pointer-events: none; } -.layer-0 { z-index: 1; background: #1c1c20; top: calc(var(--nav-h) + 0.5rem); } -.layer-1 { z-index: 2; background: #24242c; top: calc(var(--nav-h) + 0.5rem + var(--step)); } -.layer-2 { z-index: 3; background: #2c2c36; top: calc(var(--nav-h) + 0.5rem + var(--step) * 2); } -.layer-3 { z-index: 4; background: #343440; top: calc(var(--nav-h) + 0.5rem + var(--step) * 3); } -.layer-4 { z-index: 5; background: #3c3c4a; top: calc(var(--nav-h) + 0.5rem + var(--step) * 4); } -.layer-5 { z-index: 6; background: #444454; top: calc(var(--nav-h) + 0.5rem + var(--step) * 5); } -.layer-6 { - z-index: 7; - background: #4c4c5e; - top: calc(var(--nav-h) + 0.5rem + var(--step) * 6); - margin-bottom: 0; -} +.layer-0 { z-index: 1; background: #1c1c20; top: calc(var(--stack-nav) + var(--stack-stick)); } +.layer-1 { z-index: 2; background: #24242c; top: calc(var(--stack-nav) + var(--stack-stick) + var(--stack-step)); } +.layer-2 { z-index: 3; background: #2c2c36; top: calc(var(--stack-nav) + var(--stack-stick) + var(--stack-step) * 2); } +.layer-3 { z-index: 4; background: #343440; top: calc(var(--stack-nav) + var(--stack-stick) + var(--stack-step) * 3); } +.layer-4 { z-index: 5; background: #3c3c4a; top: calc(var(--stack-nav) + var(--stack-stick) + var(--stack-step) * 4); } +.layer-5 { z-index: 6; background: #444454; top: calc(var(--stack-nav) + var(--stack-stick) + var(--stack-step) * 5); } +.layer-6 { z-index: 7; background: #4c4c5e; top: calc(var(--stack-nav) + var(--stack-stick) + var(--stack-step) * 6); margin-bottom: 0; } + +.layer-inner { padding: 1.2rem 1.35rem 1.3rem; min-height: 200px; } -.layer-inner { padding: 1.1rem 1.25rem 1.2rem; } .layer-head { display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem 0.65rem; - margin-bottom: 0.6rem; padding-bottom: 0.45rem; + margin-bottom: 0.65rem; padding-bottom: 0.5rem; border-bottom: 1px solid rgba(255,255,255,0.06); font-family: var(--mono); font-size: 0.6rem; } -.layer-id { color: #c4a574; font-weight: 600; } + +.layer-id { + color: #c4a574; font-weight: 600; background: none; border: none; + font-family: var(--mono); font-size: 0.6rem; cursor: pointer; padding: 0; +} +.layer-id:hover { text-decoration: underline; } + .layer-name { color: #6b6966; text-transform: uppercase; letter-spacing: 0.1em; } .layer-link { margin-left: auto; color: #8b9cb3; text-decoration: none; font-size: 0.58rem; } -.layer-link:hover { color: #c4a574; } -.layer h1 { font-size: 2rem; font-weight: 600; letter-spacing: -0.03em; line-height: 1.1; } -.layer h2 { font-size: 1.15rem; font-weight: 600; margin-bottom: 0.3rem; } +.layer h1 { font-size: 2rem; font-weight: 600; letter-spacing: -0.03em; } +.layer h2 { font-size: 1.2rem; font-weight: 600; margin-bottom: 0.35rem; } .tagline { font-family: var(--mono); font-size: 0.65rem; color: #6b6966; margin-bottom: 0.4rem; } -.body { font-size: 0.88rem; color: #a8a6a1; margin-bottom: 0.5rem; } -.chips { display: flex; flex-wrap: wrap; gap: 0.3rem; margin-bottom: 0.4rem; } +.body { font-size: 0.9rem; color: #a8a6a1; } +.chips { display: flex; flex-wrap: wrap; gap: 0.3rem; margin: 0.5rem 0; } .chips span { font-family: var(--mono); font-size: 0.55rem; padding: 0.15rem 0.4rem; - background: rgba(196,165,116,0.1); color: #c4a574; - border: 1px solid rgba(196,165,116,0.2); border-radius: 3px; + background: rgba(196,165,116,0.1); color: #c4a574; border: 1px solid rgba(196,165,116,0.2); border-radius: 3px; } .avail { font-family: var(--mono); font-size: 0.62rem; color: #7eb87a; } .contact-row { display: flex; flex-wrap: wrap; gap: 0.45rem; margin: 0.65rem 0 0.4rem; } @@ -120,18 +99,23 @@ body { .btn-ghost { background: transparent; color: #c4a574; border: 1px solid rgba(196,165,116,0.35); } .guarantees { font-family: var(--mono); font-size: 0.58rem; color: #5a5854; } .site-foot { - display: flex; justify-content: space-between; max-width: 500px; margin: 0 auto; + display: flex; justify-content: space-between; + width: min(560px, 100%); margin: 0 auto; padding: 0 1.5rem 2.5rem; font-family: var(--mono); font-size: 0.6rem; color: #4a4844; } .site-foot a { color: #4a4844; text-decoration: none; } .stack-ruler { - position: fixed; right: 0.75rem; top: 50%; transform: translateY(-50%); - z-index: 50; display: flex; flex-direction: column; gap: 0.15rem; - font-family: var(--mono); font-size: 0.48rem; color: #2a2a30; pointer-events: none; + position: fixed; right: max(0.5rem, calc(50% - 300px)); top: 50%; transform: translateY(-50%); + z-index: 50; display: flex; flex-direction: column; gap: 0.12rem; + font-family: var(--mono); font-size: 0.48rem; color: #2a2a30; } -.stack-ruler span.active { color: #c4a574; } +.stack-ruler button { + background: none; border: none; color: inherit; font: inherit; cursor: pointer; padding: 0.1rem 0; + text-align: right; +} +.stack-ruler button:hover, +.stack-ruler button.active { color: #c4a574; } @media (max-width: 700px) { - .stack-ruler, .variants { display: none; } - :root { --scroll-tick: 26vh; --overlap: 22vh; --step: 1.65rem; } + .variants { display: none; } } diff --git a/stack/stack.js b/stack/stack.js deleted file mode 100644 index c77ab29..0000000 --- a/stack/stack.js +++ /dev/null @@ -1,17 +0,0 @@ -const sections = document.querySelectorAll('.scroll-section'); -const depthEl = document.getElementById('depth'); -const rulerSpans = document.querySelectorAll('.stack-ruler span'); - -function updateDepth() { - const mid = window.innerHeight * 0.42; - let active = 0; - sections.forEach((sec) => { - const r = sec.getBoundingClientRect(); - if (r.top <= mid && r.bottom > mid) active = Number(sec.dataset.layer); - }); - depthEl.textContent = `L${active}`; - rulerSpans.forEach((s, i) => s.classList.toggle('active', i === active)); -} - -window.addEventListener('scroll', updateDepth, { passive: true }); -updateDepth();