Restore L6 stack with scroll stop; add folder, paper, trace variants.

Fix card stacking with scroll-section drivers; expand Spec iliadobkin.com docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
ilia 2026-05-20 22:47:14 -04:00
parent b03a9972cf
commit 531acf6969
16 changed files with 519 additions and 390 deletions

View File

@ -1,16 +1,26 @@
# levkin.ca
Five design concepts for the Levkin software development company homepage.
Design concepts for the Levkin software development company homepage.
### Brand directions
| Option | Path | Vibe |
|--------|------|------|
| **Spec** | `/spec/` | RFC documentation, endpoints, required properties |
| **Slab** | `/slab/` | Brutalist poster, massive typography |
| **Relay** | `/relay/` | Telegraph, signal chain, decoded messages |
| **Vault** | `/vault/` | Institutional trust, enterprise gravitas |
| **Stack** | `/stack/` | Sticky scroll layers L0L6, services as a card stack |
| **Spec** | `/spec/` | RFC documentation, endpoints, iliadobkin.com |
| **Slab** | `/slab/` | Brutalist poster |
| **Relay** | `/relay/` | Telegraph, decoded messages |
| **Vault** | `/vault/` | Institutional trust |
Open `/` to compare all five.
### Stack variants (L0L6, scroll stops at interface)
| Variant | Path | Metaphor |
|---------|------|----------|
| **Cards** | `/stack/` | Dark sticky cards with stack lips |
| **Folder** | `/stack-folder/` | Manila folders, side tabs |
| **Paper** | `/stack-paper/` | Cream document pile |
| **Trace** | `/stack-trace/` | Call-stack / devtools frames |
Open `/` to compare all.
## Develop

View File

@ -157,16 +157,43 @@
}
footer a { color: var(--accent); text-decoration: none; }
footer code { font-family: 'DM Mono', monospace; font-size: 0.75rem; }
.section-label {
font-family: 'DM Mono', monospace;
font-size: 0.68rem;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--muted);
margin: 2rem 0 1rem;
}
.grid-stacks { margin-bottom: 0; }
.preview--folder {
background: #e8e2d4;
color: #2a2824;
font-family: 'DM Mono', monospace;
font-size: 0.65rem;
border-left: 12px solid #c9a86c;
}
.preview--paper {
background: #3d3832;
font-size: 2rem;
}
.preview--trace {
background: #0d0d0f;
color: #6b9b6b;
font-family: 'DM Mono', monospace;
font-size: 0.6rem;
}
</style>
</head>
<body>
<div class="wrap">
<header>
<p class="eyebrow">levkin.ca · round 3</p>
<h1>Five directions.</h1>
<p class="lead">Enriched from auto, caseware, iliadobkin, and jobs — plus Stack: scroll the layers. Company-first throughout.</p>
<h1>Eight directions.</h1>
<p class="lead">Five brand concepts + four stack variants (L0L6, stops on time). Spec updated with iliadobkin.com.</p>
</header>
<p class="section-label">Brand</p>
<div class="grid">
<a class="card" href="/spec/">
<div class="preview preview--spec">GET /company → 200</div>
@ -204,18 +231,42 @@
</div>
</a>
</div>
<p class="section-label">Stack variants (scroll test)</p>
<div class="grid grid-stacks">
<a class="card" href="/stack/">
<div class="preview preview--stack">
<span class="card-layer"></span>
<span class="card-layer"></span>
<span class="card-layer"></span>
<span class="card-layer"></span>
<span class="card-layer"></span>
<span class="card-layer"></span><span class="card-layer"></span><span class="card-layer"></span><span class="card-layer"></span><span class="card-layer"></span>
</div>
<div class="card-body">
<h2>Stack</h2>
<p>Sticky card stack L0L4. Scroll peels layers back — tight, architectural, no bloat.</p>
<span class="tag">layers · scroll · architectural</span>
<h2>Stack · Cards</h2>
<p>L0L6 sticky cards with stack lips. Scroll stops at L6.</p>
<span class="tag">default · dark · technical</span>
</div>
</a>
<a class="card" href="/stack-folder/">
<div class="preview preview--folder">L0│tab</div>
<div class="card-body">
<h2>Stack · Folder</h2>
<p>Manila folders — colored side tabs, vertical labels.</p>
<span class="tag">folder · tabs · office</span>
</div>
</a>
<a class="card" href="/stack-paper/">
<div class="preview preview--paper">📄</div>
<div class="card-body">
<h2>Stack · Paper</h2>
<p>Offset document pile — cream sheets, slight rotation.</p>
<span class="tag">paper · desk · serif</span>
</div>
</a>
<a class="card" href="/stack-trace/">
<div class="preview preview--trace">at Levkin.*</div>
<div class="card-body">
<h2>Stack · Trace</h2>
<p>Call-stack frames — iliadobkin.com as quality frame.</p>
<span class="tag">trace · devtools · mono</span>
</div>
</a>
</div>

View File

@ -41,6 +41,7 @@
<section id="abstract">
<h2>Abstract</h2>
<p>This document describes <strong>Levkin</strong>, a Canadian software development practice specializing in production systems, business automation, and enterprise tooling. Remote across North American and European time zones. Levkin ships software that must work when nobody is watching — with error handling, documentation, and tests as non-optional requirements.</p>
<p>Quality engineering and SDET work are documented at <a href="https://iliadobkin.com">iliadobkin.com</a> — an interactive portfolio (Playwright-style test runner UI, career timeline, trace-driven debugging showcase).</p>
<p><span class="badge">AVAILABLE</span> Currently taking on new engagements.</p>
</section>
@ -104,7 +105,12 @@
<span class="path">/quality-engineering</span>
<span class="ext"><a href="https://iliadobkin.com">iliadobkin.com</a></span>
</div>
<p>Senior SDET services — test automation, CI/CD pipelines, trace-driven debugging. Interactive portfolio at iliadobkin.com.</p>
<p>Senior SDET services — test automation, CI/CD pipelines, trace-driven debugging, contract QA leadership.</p>
<ul>
<li>Portfolio: <a href="https://iliadobkin.com">iliadobkin.com</a> — runnable career specs, trace viewer</li>
<li>Remote (ET) · Canadian · git.levkin.ca for source</li>
<li>Playwright / JS automation · enterprise CI (Jenkins, Azure DevOps, GitHub Actions)</li>
</ul>
</div>
</section>

68
stack-folder/folder.css Normal file
View File

@ -0,0 +1,68 @@
:root {
--nav: 2.5rem; --stick: 1rem; --step: 1.1rem; --unit: 56vh;
--mono: 'IBM Plex Mono', monospace; --sans: 'Instrument Sans', system-ui, sans-serif;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: var(--sans); background: #2a2824; color: #1a1814; }
.nav {
position: fixed; top: 0; left: 0; right: 0; z-index: 100;
display: flex; gap: 1rem; padding: 0.5rem 1rem; font-family: var(--mono); font-size: 0.62rem;
background: rgba(42,40,36,0.95); color: #c4b8a8;
}
.nav a { color: #8a8278; text-decoration: none; }
.depth { margin-left: auto; color: #d4a574; font-weight: 600; }
.mount { max-width: 520px; margin: 0 auto; padding-top: var(--nav); }
.scroll-section { height: var(--unit); }
.scroll-section--final { height: calc(var(--unit) * 0.5); min-height: 240px; }
.stop { height: 1px; margin-bottom: 3rem; }
.folder {
position: sticky;
margin-left: 2.5rem;
background: #e8e2d4;
border: 1px solid #c4b8a8;
border-radius: 0 6px 6px 6px;
box-shadow: 4px 4px 0 rgba(0,0,0,0.15), 0 12px 28px rgba(0,0,0,0.25);
min-height: 140px;
}
.tab {
position: absolute;
left: -2.5rem;
top: 0;
width: 2.35rem;
height: 100%;
min-height: 120px;
writing-mode: vertical-rl;
transform: rotate(180deg);
font-family: var(--mono);
font-size: 0.55rem;
letter-spacing: 0.06em;
padding: 0.5rem 0.25rem;
border-radius: 6px 0 0 6px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 500;
}
.f0 { top: calc(var(--nav) + var(--stick)); z-index: 1; }
.f0 .tab { background: #c9a86c; color: #2a2824; }
.f1 { top: calc(var(--nav) + var(--stick) + var(--step)); z-index: 2; }
.f1 .tab { background: #a8c4d4; }
.f2 { top: calc(var(--nav) + var(--stick) + var(--step)*2); z-index: 3; }
.f2 .tab { background: #b8d4a8; }
.f3 { top: calc(var(--nav) + var(--stick) + var(--step)*3); z-index: 4; }
.f3 .tab { background: #d4b8c4; }
.f4 { top: calc(var(--nav) + var(--stick) + var(--step)*4); z-index: 5; }
.f4 .tab { background: #d4c8a8; }
.f5 { top: calc(var(--nav) + var(--stick) + var(--step)*5); z-index: 6; }
.f5 .tab { background: #c4c4c4; }
.f6 { top: calc(var(--nav) + var(--stick) + var(--step)*6); z-index: 7; }
.f6 .tab { background: #2a4a6b; color: #e8e2d4; }
.body { padding: 1.1rem 1.25rem; }
.body h1, .body h2 { font-size: 1.2rem; margin-bottom: 0.35rem; }
.body p { font-size: 0.88rem; color: #4a4844; }
.body a { color: #2a4a6b; }
.avail { font-family: var(--mono); font-size: 0.65rem; color: #3d6b3d; margin-top: 0.35rem; }
.btn { font-family: var(--mono); font-size: 0.68rem; padding: 0.4rem 0.7rem; background: #2a4a6b; color: #fff; text-decoration: none; border-radius: 3px; margin-right: 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; }
.foot a { color: #6a6458; text-decoration: none; }

10
stack-folder/folder.js Normal file
View File

@ -0,0 +1,10 @@
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();

26
stack-folder/index.html Normal file
View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Levkin — Stack Folder</title>
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500&family=Instrument+Sans:wght@400;500;600&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="./folder.css" />
</head>
<body>
<nav class="nav"><a href="/">← options</a><a href="/stack/">cards</a><span class="depth" id="depth">L0</span></nav>
<main class="mount">
<div class="scroll-section" data-layer="0"><article class="folder f0"><div class="tab">L0 · foundation</div><div class="body"><h1>Levkin</h1><p>Software development · Canada · remote · 15+ yrs</p><p class="avail">Taking new engagements</p></div></article></div>
<div class="scroll-section" data-layer="1"><article class="folder f1"><div class="tab">L1 · application</div><div class="body"><h2>Custom software</h2><p>Web apps, APIs, tools — TS · Python · .NET</p></div></article></div>
<div class="scroll-section" data-layer="2"><article class="folder f2"><div class="tab">L2 · automation</div><div class="body"><h2>Automation</h2><p>n8n · CI/CD · LLMs · <a href="https://auto.levkin.ca">auto.levkin.ca</a></p></div></article></div>
<div class="scroll-section" data-layer="3"><article class="folder f3"><div class="tab">L3 · enterprise</div><div class="body"><h2>CaseWare</h2><p>15+ years · MNP · JazzIt · <a href="https://caseware.levkin.ca">caseware.levkin.ca</a></p></div></article></div>
<div class="scroll-section" data-layer="4"><article class="folder f4"><div class="tab">L4 · quality</div><div class="body"><h2>Quality engineering</h2><p>Senior SDET · <a href="https://iliadobkin.com">iliadobkin.com</a></p></div></article></div>
<div class="scroll-section" data-layer="5"><article class="folder f5"><div class="tab">L5 · operations</div><div class="body"><h2>Job Ops</h2><p><a href="https://jobs.levkin.ca">jobs.levkin.ca</a> (auth)</p></div></article></div>
<div class="scroll-section scroll-section--final" data-layer="6"><article class="folder f6"><div class="tab">L6 · interface</div><div class="body"><h2>Engage</h2><p><a class="btn" href="https://cal.levkin.ca/ilia/consult">Book 15 min</a> <a class="btn ghost" href="mailto:hello@levkine.ca">email</a></p></div></article></div>
<div class="stop"></div>
</main>
<footer class="foot"><a href="https://git.levkin.ca">git</a><span>levkin.ca</span></footer>
<script src="./folder.js" type="module"></script>
</body>
</html>

26
stack-paper/index.html Normal file
View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Levkin — Stack Paper</title>
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<link href="https://fonts.googleapis.com/css2?family=Literata:opsz,wght@7..72,400;7..72,600&family=IBM+Plex+Mono:wght@400&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="./paper.css" />
</head>
<body>
<nav class="nav"><a href="/">← options</a><a href="/stack/">cards</a><span id="depth">L0</span></nav>
<main class="mount">
<div class="scroll-section" data-layer="0"><article class="sheet s0"><span class="edge"></span><div class="inner"><h1>Levkin</h1><p class="sub">Software development · Canada</p><p>15+ years · 8h→2min releases · taking new work</p></div></article></div>
<div class="scroll-section" data-layer="1"><article class="sheet s1"><span class="edge"></span><div class="inner"><h2>Custom software</h2><p>Apps, APIs, internal tools</p></div></article></div>
<div class="scroll-section" data-layer="2"><article class="sheet s2"><span class="edge"></span><div class="inner"><h2>Automation</h2><p><a href="https://auto.levkin.ca">auto.levkin.ca</a> — n8n, CI/CD, LLMs</p></div></article></div>
<div class="scroll-section" data-layer="3"><article class="sheet s3"><span class="edge"></span><div class="inner"><h2>CaseWare</h2><p><a href="https://caseware.levkin.ca">caseware.levkin.ca</a> — MNP, JazzIt</p></div></article></div>
<div class="scroll-section" data-layer="4"><article class="sheet s4"><span class="edge"></span><div class="inner"><h2>Quality engineering</h2><p><a href="https://iliadobkin.com">iliadobkin.com</a> — Senior SDET portfolio</p></div></article></div>
<div class="scroll-section" data-layer="5"><article class="sheet s5"><span class="edge"></span><div class="inner"><h2>Operations</h2><p><a href="https://jobs.levkin.ca">jobs.levkin.ca</a></p></div></article></div>
<div class="scroll-section scroll-section--final" data-layer="6"><article class="sheet s6"><span class="edge"></span><div class="inner"><h2>Engage</h2><p><a href="https://cal.levkin.ca/ilia/consult">Book 15 min</a> · <a href="mailto:hello@levkine.ca">hello@levkine.ca</a></p></div></article></div>
<div class="stop"></div>
</main>
<footer class="foot">levkin.ca · <a href="https://git.levkin.ca">git</a></footer>
<script src="./paper.js" type="module"></script>
</body>
</html>

46
stack-paper/paper.css Normal file
View File

@ -0,0 +1,46 @@
:root {
--nav: 2.5rem; --stick: 0.85rem; --step: 0.95rem; --unit: 54vh;
--desk: #3d3832; --paper: #f6f3eb; --ink: #1a1814;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: 'Literata', Georgia, serif; background: var(--desk); color: var(--ink); }
.nav {
position: fixed; top: 0; left: 0; right: 0; z-index: 100;
display: flex; gap: 1rem; padding: 0.5rem 1rem;
font-family: 'IBM Plex Mono', monospace; font-size: 0.62rem;
background: rgba(61,56,50,0.92); color: #c4b8a8;
}
.nav a { color: #9a9288; text-decoration: none; }
#depth { margin-left: auto; color: #8b7355; font-weight: 600; }
.mount { max-width: 480px; margin: 0 auto; padding: var(--nav) 1.25rem 0; }
.scroll-section { height: var(--unit); }
.scroll-section--final { height: calc(var(--unit) * 0.48); min-height: 220px; }
.stop { height: 1px; margin-bottom: 3rem; }
.sheet {
position: sticky;
background: var(--paper);
border: 1px solid #d4cfc4;
padding: 0;
box-shadow: 2px 3px 0 #e0dcd2, 4px 6px 0 #d8d4c8, 0 16px 32px rgba(0,0,0,0.2);
}
.edge {
display: block;
height: 6px;
background: linear-gradient(180deg, #ebe6dc, #f6f3eb);
border-bottom: 1px solid #e0dcd2;
}
.inner { padding: 1.15rem 1.35rem 1.25rem; }
.inner h1 { font-size: 1.85rem; font-weight: 600; }
.inner h2 { font-size: 1.15rem; font-weight: 600; margin-bottom: 0.3rem; }
.sub { font-family: 'IBM Plex Mono', monospace; font-size: 0.65rem; color: #6a6458; margin-bottom: 0.5rem; }
.inner p { font-size: 0.9rem; color: #4a4844; line-height: 1.55; }
.inner a { color: #2a4a6b; }
.s0 { top: calc(var(--nav) + var(--stick)); z-index: 1; transform: rotate(-0.4deg); margin-left: 0; }
.s1 { top: calc(var(--nav) + var(--stick) + var(--step)); z-index: 2; transform: rotate(0.3deg); margin-left: 6px; }
.s2 { top: calc(var(--nav) + var(--stick) + var(--step)*2); z-index: 3; transform: rotate(-0.2deg); margin-left: 12px; }
.s3 { top: calc(var(--nav) + var(--stick) + var(--step)*3); z-index: 4; transform: rotate(0.5deg); margin-left: 8px; }
.s4 { top: calc(var(--nav) + var(--stick) + var(--step)*4); z-index: 5; transform: rotate(-0.3deg); margin-left: 14px; }
.s5 { top: calc(var(--nav) + var(--stick) + var(--step)*5); z-index: 6; transform: rotate(0.2deg); margin-left: 10px; }
.s6 { top: calc(var(--nav) + var(--stick) + var(--step)*6); z-index: 7; transform: rotate(0deg); margin-left: 4px; }
.foot { text-align: center; padding: 0 1rem 2.5rem; font-family: 'IBM Plex Mono', monospace; font-size: 0.6rem; color: #7a7468; }
.foot a { color: #7a7468; }

10
stack-paper/paper.js Normal file
View File

@ -0,0 +1,10 @@
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();

26
stack-trace/index.html Normal file
View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Levkin — Stack Trace</title>
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="./trace.css" />
</head>
<body>
<nav class="nav"><a href="/">← options</a><a href="/stack/">cards</a><span id="depth">#0</span></nav>
<main class="mount">
<div class="scroll-section" data-layer="0"><article class="frame f0"><code>at Levkin.foundation (levkin.ca:0)</code><div class="frame-body"><strong>Levkin</strong><p>Canadian software practice · 15+ yrs · remote</p></div></article></div>
<div class="scroll-section" data-layer="1"><article class="frame f1"><code>at Levkin.application (levkin.ca:1)</code><div class="frame-body"><strong>custom_software()</strong><p>TS · Python · .NET · APIs</p></div></article></div>
<div class="scroll-section" data-layer="2"><article class="frame f2"><code>at Levkin.automation (auto.levkin.ca:2)</code><div class="frame-body"><strong>automation_pipeline()</strong><p>n8n · CI/CD · LLMs</p></div></article></div>
<div class="scroll-section" data-layer="3"><article class="frame f3"><code>at Levkin.caseware (caseware.levkin.ca:3)</code><div class="frame-body"><strong>enterprise_module()</strong><p>CaseWare · MNP · JazzIt</p></div></article></div>
<div class="scroll-section" data-layer="4"><article class="frame f4"><code>at Levkin.quality (iliadobkin.com:4)</code><div class="frame-body"><strong>sdet_suite()</strong><p><a href="https://iliadobkin.com">iliadobkin.com</a> — traces · Playwright</p></div></article></div>
<div class="scroll-section" data-layer="5"><article class="frame f5"><code>at Levkin.operations (jobs.levkin.ca:5)</code><div class="frame-body"><strong>job_ops()</strong><p>internal · auth required</p></div></article></div>
<div class="scroll-section scroll-section--final" data-layer="6"><article class="frame f6"><code>at Levkin.engage (interface:6)</code><div class="frame-body"><strong>main()</strong><p><a href="https://cal.levkin.ca/ilia/consult">book_consult()</a> · <a href="mailto:hello@levkine.ca">send_mail()</a></p></div></article></div>
<div class="stop"></div>
</main>
<footer class="foot"><span>// end of stack</span><a href="https://git.levkin.ca">git.levkin.ca</a></footer>
<script src="./trace.js" type="module"></script>
</body>
</html>

48
stack-trace/trace.css Normal file
View File

@ -0,0 +1,48 @@
:root {
--nav: 2.5rem; --stick: 0.9rem; --step: 1rem; --unit: 54vh;
--bg: #0d0d0f; --mono: 'IBM Plex Mono', monospace;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: var(--mono); background: var(--bg); color: #b8b4af; font-size: 0.8rem; }
.nav {
position: fixed; top: 0; left: 0; right: 0; z-index: 100;
display: flex; gap: 1rem; padding: 0.5rem 1rem; font-size: 0.62rem;
background: rgba(13,13,15,0.95); border-bottom: 1px solid #2a2a30;
}
.nav a { color: #5a5854; text-decoration: none; }
.nav a:hover { color: #7eb87a; }
#depth { margin-left: auto; color: #7eb87a; }
.mount { max-width: 560px; margin: 0 auto; padding-top: var(--nav); }
.scroll-section { height: var(--unit); }
.scroll-section--final { height: calc(var(--unit) * 0.5); min-height: 200px; }
.stop { height: 1px; margin-bottom: 3rem; }
.frame {
position: sticky;
margin: 0 1rem;
border-left: 3px solid #3a3a44;
background: #141418;
padding: 0.65rem 0 0.65rem 1rem;
box-shadow: 0 4px 0 #0a0a0c, 0 8px 20px rgba(0,0,0,0.35);
}
.frame code {
display: block;
font-size: 0.68rem;
color: #6b9b6b;
margin-bottom: 0.45rem;
}
.frame-body strong { color: #e8e6e3; font-weight: 500; }
.frame-body p { color: #6b6966; font-size: 0.75rem; margin-top: 0.2rem; }
.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; }
.foot {
display: flex; justify-content: space-between; max-width: 560px; margin: 0 auto;
padding: 0 1.5rem 2.5rem; font-size: 0.6rem; color: #3a3a40;
}
.foot a { color: #3a3a40; text-decoration: none; }

10
stack-trace/trace.js Normal file
View File

@ -0,0 +1,10 @@
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();

View File

@ -14,97 +14,66 @@
<body>
<nav class="nav">
<a href="/">← options</a>
<span class="variants"><a href="/stack-folder/">folder</a> · <a href="/stack-paper/">paper</a> · <a href="/stack-trace/">trace</a></span>
<span class="depth" id="depth">L0</span>
</nav>
<div class="stack-viewport">
<section class="layer layer-0" data-layer="0">
<div class="layer-inner">
<header class="layer-head">
<span class="layer-id">L0</span>
<span class="layer-name">foundation</span>
</header>
<h1>Levkin</h1>
<p class="tagline">Software · Canada · remote</p>
<p class="body">Boutique engineering — production systems, automation, enterprise. Fixed scope, documented handoff.</p>
<div class="chips">
<span>15+ yrs</span>
<span>8h→2m</span>
<span>24/7</span>
</div>
<p class="avail">Taking new engagements</p>
<main class="stack-mount">
<div class="scroll-section" data-layer="0"><section class="layer layer-0"><div class="layer-tab" aria-hidden="true"></div><div class="layer-inner">
<header class="layer-head"><span class="layer-id">L0</span><span class="layer-name">foundation</span></header>
<h1>Levkin</h1>
<p class="tagline">Software · Canada · remote</p>
<p class="body">Boutique engineering — production systems, automation, enterprise.</p>
<div class="chips"><span>15+ yrs</span><span>8h→2m</span><span>24/7</span></div>
<p class="avail">Taking new engagements</p>
</div></section></div>
<div class="scroll-section" data-layer="1"><section class="layer layer-1"><div class="layer-tab" aria-hidden="true"></div><div class="layer-inner">
<header class="layer-head"><span class="layer-id">L1</span><span class="layer-name">application</span></header>
<h2>Custom software</h2>
<p class="body">Web apps, APIs, internal tools — TS · Python · .NET</p>
</div></section></div>
<div class="scroll-section" data-layer="2"><section class="layer layer-2"><div class="layer-tab" aria-hidden="true"></div><div class="layer-inner">
<header class="layer-head"><span class="layer-id">L2</span><span class="layer-name">automation</span><a href="https://auto.levkin.ca" class="layer-link">auto.levkin.ca ↗</a></header>
<h2>Automation</h2>
<p class="body">n8n · Zapier · CI/CD · webhooks · LLMs</p>
</div></section></div>
<div class="scroll-section" data-layer="3"><section class="layer layer-3"><div class="layer-tab" aria-hidden="true"></div><div class="layer-inner">
<header class="layer-head"><span class="layer-id">L3</span><span class="layer-name">enterprise</span><a href="https://caseware.levkin.ca" class="layer-link">caseware.levkin.ca ↗</a></header>
<h2>CaseWare &amp; CaseView</h2>
<p class="body">15+ years · CaseWare Intl, MNP, JazzIt</p>
</div></section></div>
<div class="scroll-section" data-layer="4"><section class="layer layer-4"><div class="layer-tab" aria-hidden="true"></div><div class="layer-inner">
<header class="layer-head"><span class="layer-id">L4</span><span class="layer-name">quality</span><a href="https://iliadobkin.com" class="layer-link">iliadobkin.com ↗</a></header>
<h2>Quality engineering</h2>
<p class="body">Senior SDET · test automation · CI/CD · trace-driven QA</p>
</div></section></div>
<div class="scroll-section" data-layer="5"><section class="layer layer-5"><div class="layer-tab" aria-hidden="true"></div><div class="layer-inner">
<header class="layer-head"><span class="layer-id">L5</span><span class="layer-name">operations</span><a href="https://jobs.levkin.ca" class="layer-link">jobs.levkin.ca ↗</a></header>
<h2>Job Ops</h2>
<p class="body">Internal hiring orchestrator (auth required)</p>
</div></section></div>
<div class="scroll-section scroll-section--final" data-layer="6"><section class="layer layer-6"><div class="layer-tab" aria-hidden="true"></div><div class="layer-inner">
<header class="layer-head"><span class="layer-id">L6</span><span class="layer-name">interface</span></header>
<h2>Engage</h2>
<p class="body">Discover → Proposal → Ship → Maintain</p>
<div class="contact-row">
<a class="btn" href="https://cal.levkin.ca/ilia/consult">Book 15 min</a>
<a class="btn btn-ghost" href="mailto:hello@levkine.ca">hello@levkine.ca</a>
</div>
</section>
<p class="guarantees">Retries · docs · tests first</p>
</div></section></div>
<section class="layer layer-1" data-layer="1">
<div class="layer-inner">
<header class="layer-head">
<span class="layer-id">L1</span>
<span class="layer-name">application</span>
</header>
<h2>Custom software</h2>
<p class="body">Web apps, APIs, internal tools.</p>
<ul class="stack-list">
<li>TS · Python · .NET · PostgreSQL · React</li>
</ul>
</div>
</section>
<section class="layer layer-2" data-layer="2">
<div class="layer-inner">
<header class="layer-head">
<span class="layer-id">L2</span>
<span class="layer-name">automation</span>
<a href="https://auto.levkin.ca" class="layer-link">auto.levkin.ca ↗</a>
</header>
<h2>Automation</h2>
<p class="body">n8n · Zapier · CI/CD · webhooks · LLMs</p>
</div>
</section>
<section class="layer layer-3" data-layer="3">
<div class="layer-inner">
<header class="layer-head">
<span class="layer-id">L3</span>
<span class="layer-name">enterprise</span>
<a href="https://caseware.levkin.ca" class="layer-link">caseware.levkin.ca ↗</a>
</header>
<h2>CaseWare &amp; CaseView</h2>
<p class="body">15+ years · CaseWare Intl, MNP, JazzIt · C# · .NET · release pipelines</p>
</div>
</section>
<section class="layer layer-4" data-layer="4">
<div class="layer-inner">
<header class="layer-head">
<span class="layer-id">L4</span>
<span class="layer-name">surface</span>
</header>
<h2>Quality &amp; ops</h2>
<p class="body">SDET · test automation · CI/CD traces.</p>
<p class="sat-links">
<a href="https://iliadobkin.com">iliadobkin.com</a>
<span class="sep">·</span>
<a href="https://jobs.levkin.ca">jobs.levkin.ca</a>
</p>
<div class="contact-row">
<a class="btn" href="https://cal.levkin.ca/ilia/consult">Book 15 min</a>
<a class="btn btn-ghost" href="mailto:hello@levkine.ca">hello@levkine.ca</a>
</div>
<p class="guarantees">Retries · docs · tests first · smallest fit</p>
</div>
</section>
</div>
<footer class="site-foot">
<a href="https://git.levkin.ca">git.levkin.ca</a>
<span>levkin.ca</span>
</footer>
<div class="stack-ruler" aria-hidden="true">
<span>L0</span><span>L1</span><span>L2</span><span>L3</span><span>L4</span>
</div>
<div class="stack-stop" aria-hidden="true"></div>
</main>
<footer class="site-foot"><a href="https://git.levkin.ca">git.levkin.ca</a><span>levkin.ca</span></footer>
<div class="stack-ruler" aria-hidden="true"><span>L0</span><span>L1</span><span>L2</span><span>L3</span><span>L4</span><span>L5</span><span>L6</span></div>
<script src="./stack.js" type="module"></script>
</body>
</html>

View File

@ -2,310 +2,137 @@
--bg: #0e0e10;
--mono: 'IBM Plex Mono', ui-monospace, monospace;
--sans: 'Instrument Sans', system-ui, sans-serif;
--sticky-top: 3rem;
--layer-offset: 1.35rem;
--layer-scroll: 38vh;
--nav-h: 2.5rem;
--stick: 1.15rem;
--step: 1.25rem;
--scroll-unit: 58vh;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
body {
font-family: var(--sans);
background: var(--bg);
color: #e8e6e3;
line-height: 1.5;
overflow-x: hidden;
}
.nav {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
top: 0; left: 0; right: 0;
z-index: 200;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.65rem 1.25rem;
gap: 1rem;
padding: 0.55rem 1rem;
font-family: var(--mono);
font-size: 0.68rem;
background: rgba(14, 14, 16, 0.9);
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;
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; }
.depth {
color: #c4a574;
font-weight: 600;
font-variant-numeric: tabular-nums;
.stack-mount {
max-width: 500px;
margin: 0 auto;
padding: var(--nav-h) 1rem 0;
}
.stack-viewport {
padding: 0.5rem 1.25rem 2rem;
max-width: 520px;
margin: 0 auto;
/* Each section = one scroll “tick”; sticky card stacks inside */
.scroll-section {
height: var(--scroll-unit);
position: relative;
}
.scroll-section--final {
height: calc(var(--scroll-unit) * 0.55);
min-height: 280px;
}
.stack-stop {
height: 1px;
margin-bottom: 4rem;
}
.layer {
position: sticky;
min-height: auto;
padding-bottom: var(--layer-scroll);
margin-bottom: -calc(var(--layer-scroll) - var(--layer-offset) * 2);
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.07);
box-shadow:
0 12px 28px rgba(0, 0, 0, 0.35),
0 2px 6px rgba(0, 0, 0, 0.2);
top: calc(var(--nav-h) + var(--stick));
margin: 0 0.5rem;
border-radius: 6px 6px 4px 4px;
border: 1px solid rgba(255,255,255,0.08);
box-shadow: 0 8px 24px rgba(0,0,0,0.4);
}
.layer-0 {
top: var(--sticky-top);
z-index: 1;
background: #1a1a1e;
margin-bottom: -calc(var(--layer-scroll) - var(--layer-offset));
}
.layer-1 {
top: calc(var(--sticky-top) + var(--layer-offset));
z-index: 2;
background: #222228;
}
.layer-2 {
top: calc(var(--sticky-top) + var(--layer-offset) * 2);
z-index: 3;
background: #2a2a32;
}
.layer-3 {
top: calc(var(--sticky-top) + var(--layer-offset) * 3);
z-index: 4;
background: #32323c;
}
.layer-4 {
top: calc(var(--sticky-top) + var(--layer-offset) * 4);
z-index: 5;
background: #3a3a46;
margin-bottom: 0;
padding-bottom: 1rem;
}
.layer-inner {
padding: 1.25rem 1.35rem 1.35rem;
}
.layer-head {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.35rem 0.75rem;
margin-bottom: 0.75rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
font-family: var(--mono);
font-size: 0.62rem;
}
.layer-id {
color: #c4a574;
font-weight: 600;
letter-spacing: 0.06em;
}
.layer-name {
color: #6b6966;
text-transform: uppercase;
letter-spacing: 0.1em;
}
.layer-link {
margin-left: auto;
color: #8b9cb3;
text-decoration: none;
font-size: 0.6rem;
}
.layer-link:hover { color: #c4a574; }
.layer h1 {
font-size: clamp(2rem, 8vw, 2.65rem);
font-weight: 600;
letter-spacing: -0.04em;
margin-bottom: 0.2rem;
line-height: 1.1;
}
.layer h2 {
font-size: 1.25rem;
font-weight: 600;
letter-spacing: -0.02em;
margin-bottom: 0.35rem;
}
.tagline {
font-family: var(--mono);
font-size: 0.68rem;
color: #6b6966;
margin-bottom: 0.5rem;
}
.body {
color: #a8a6a1;
font-size: 0.9rem;
margin-bottom: 0.65rem;
}
.chips {
display: flex;
flex-wrap: wrap;
gap: 0.35rem;
margin-bottom: 0.5rem;
}
.chips span {
font-family: var(--mono);
font-size: 0.58rem;
padding: 0.2rem 0.45rem;
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.65rem;
color: #7eb87a;
}
.stack-list {
list-style: none;
font-family: var(--mono);
font-size: 0.68rem;
color: #6b6966;
}
.sat-links {
font-family: var(--mono);
font-size: 0.68rem;
margin-bottom: 0.85rem;
}
.sat-links a {
color: #8b9cb3;
text-decoration: none;
}
.sat-links a:hover { color: #c4a574; }
.sat-links .sep {
color: #3a3a40;
margin: 0 0.25rem;
}
.contact-row {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-bottom: 0.65rem;
}
.btn {
font-family: var(--mono);
font-size: 0.72rem;
padding: 0.5rem 0.85rem;
background: #c4a574;
color: #0e0e10;
text-decoration: none;
font-weight: 600;
border-radius: 4px;
}
.btn:hover { background: #e8d4b0; }
.btn-ghost {
background: transparent;
color: #c4a574;
border: 1px solid rgba(196, 165, 116, 0.35);
}
.btn-ghost:hover {
background: rgba(196, 165, 116, 0.08);
}
.guarantees {
font-family: var(--mono);
font-size: 0.6rem;
color: #5a5854;
}
.site-foot {
display: flex;
justify-content: space-between;
max-width: 520px;
margin: 0 auto;
padding: 1.5rem 1.25rem 3rem;
font-family: var(--mono);
font-size: 0.62rem;
color: #4a4844;
}
.site-foot a {
color: #4a4844;
text-decoration: none;
}
.site-foot a:hover { color: #c4a574; }
.stack-ruler {
position: fixed;
right: 1rem;
top: 50%;
transform: translateY(-50%);
z-index: 50;
display: flex;
flex-direction: column;
gap: 0.2rem;
font-family: var(--mono);
font-size: 0.5rem;
color: #2e2e34;
pointer-events: none;
}
.stack-ruler span {
transition: color 0.15s;
}
.stack-ruler span.active {
color: #c4a574;
}
.layer::before {
content: '';
/* Visible stack lip — previous layers peek through */
.layer-tab {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
border-radius: 8px 8px 0 0;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.08), transparent);
pointer-events: none;
top: -5px;
left: 12px;
right: 12px;
height: 5px;
border-radius: 4px 4px 0 0;
background: inherit;
filter: brightness(1.15);
border: 1px solid rgba(255,255,255,0.06);
border-bottom: none;
box-shadow: 0 -2px 6px rgba(0,0,0,0.2);
}
@media (max-width: 700px) {
.stack-ruler { display: none; }
:root {
--layer-offset: 1rem;
--layer-scroll: 32vh;
}
.layer-0 { z-index: 1; background: #1c1c20; top: calc(var(--nav-h) + var(--stick)); }
.layer-1 { z-index: 2; background: #24242c; top: calc(var(--nav-h) + var(--stick) + var(--step)); }
.layer-2 { z-index: 3; background: #2c2c36; top: calc(var(--nav-h) + var(--stick) + var(--step) * 2); }
.layer-3 { z-index: 4; background: #343440; top: calc(var(--nav-h) + var(--stick) + var(--step) * 3); }
.layer-4 { z-index: 5; background: #3c3c4a; top: calc(var(--nav-h) + var(--stick) + var(--step) * 4); }
.layer-5 { z-index: 6; background: #444454; top: calc(var(--nav-h) + var(--stick) + var(--step) * 5); }
.layer-6 { z-index: 7; background: #4c4c5e; top: calc(var(--nav-h) + var(--stick) + var(--step) * 6); }
.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;
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-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; }
.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; }
.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;
}
.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; }
.btn {
font-family: var(--mono); font-size: 0.68rem; padding: 0.45rem 0.75rem;
background: #c4a574; color: #0e0e10; text-decoration: none; font-weight: 600; border-radius: 4px;
}
.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;
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;
}
.stack-ruler span.active { color: #c4a574; }
@media (max-width: 700px) {
.stack-ruler, .variants { display: none; }
:root { --scroll-unit: 52vh; --step: 1rem; }
}

View File

@ -1,23 +1,16 @@
const layers = document.querySelectorAll('.layer');
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.45;
const mid = window.innerHeight * 0.42;
let active = 0;
layers.forEach((layer) => {
const rect = layer.getBoundingClientRect();
if (rect.top <= mid && rect.bottom > mid) {
active = Number(layer.dataset.layer);
}
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((span, i) => {
span.classList.toggle('active', i === active);
});
rulerSpans.forEach((s, i) => s.classList.toggle('active', i === active));
}
window.addEventListener('scroll', updateDepth, { passive: true });

View File

@ -11,6 +11,9 @@ export default defineConfig({
relay: resolve(__dirname, 'relay/index.html'),
vault: resolve(__dirname, 'vault/index.html'),
stack: resolve(__dirname, 'stack/index.html'),
stackFolder: resolve(__dirname, 'stack-folder/index.html'),
stackPaper: resolve(__dirname, 'stack-paper/index.html'),
stackTrace: resolve(__dirname, 'stack-trace/index.html'),
},
},
},