diff --git a/README.md b/README.md index 58b72d4..ddfdae7 100644 --- a/README.md +++ b/README.md @@ -1,79 +1,64 @@ # levkin.ca -Design concepts for the Levkin software development company homepage. +Homepage design concepts for Levkin Inc. -| Option | Path | Vibe | -|--------|------|------| -| **Spec** | `/spec/` | RFC documentation, endpoints, iliadobkin.com | -| **Cards** | `/stack/` | Dark overlapping sticky cards — click a layer to bring it forward | -| **Folder** | `/stack-folder/` | Manila folders, staggered tabs (L0–L7), site previews | +| Page | Path | Description | +|------|------|-------------| +| **Spec** | `/spec/` | Company story as RFC-style documentation | +| **Folder** | `/stack-folder/` | Manila folders L0–L7, sticky tab stack, site previews | -Open `/` to compare all three. +Open `/` to pick a direction. ## Develop ```bash -cd ~/Documents/code/levkin.ca npm install npm run dev ``` -Vite serves the multi-page app (default `http://localhost:5173`). If that port is busy, pass another: `npx vite --port 5175`. +Vite default: `http://localhost:5173` | Page | URL | |------|-----| -| Compare | `/` | +| Home | `/` | | Spec | `/spec/` | -| Cards | `/stack/` | | Folders | `/stack-folder/` | ## Build ```bash npm run build -# output in dist/ +# dist/ ``` -## Folder site (`/stack-folder/`) +## Folder stack (`/stack-folder/`) -Eight manila folders (L0–L7) with labeled tabs. Scroll stacks earlier tabs on a shared rail; L7 rises into the row last. Scroll depth is capped when all tabs align (`is-folded`). +Eight layers with labeled tabs. Scroll pins earlier tabs on a shared rail; L7 joins last. Scroll stops when all tabs align. -- **L0** — Company + Cal embed -- **L1** — Scope + spec preview -- **L2** — Services -- **L3–L5** — Automation, CaseWare, QA previews -- **L6** — Git repos preview -- **L7** — Terms + Cal embed - -Modules: `shared/stack-scroll.js` (scroll/fold), `folder-rail.js` (side rail), `folder-cal.js` (Cal.com embeds). +- `shared/stack-scroll.js` — scroll depth, fold state, layer jump +- `stack-folder/folder-rail.js` — side rail +- `stack-folder/folder-cal.js` — Cal.com embeds ### Preview screenshots -Each linked folder shows a screenshot; refresh captures with dev server running: - ```bash -npm run dev # separate terminal +npm run dev # separate terminal npm run capture-previews ``` -Writes PNGs to `stack-folder/previews/`. +Writes PNGs used in `stack-folder/previews/` (spec, auto, caseware, iliadobkin, git-repos, cal dark/light). -### Tests (Playwright) - -Requires dev server on the URL you pass: +### Test ```bash npm run dev STACK_URL=http://localhost:5173/stack-folder/ npm run test:folder -STACK_URL=http://localhost:5173/stack-folder/ npm run test:stack-scroll ``` -`test:folder` checks tab alignment at max scroll and L7 jump. `test:stack-scroll` covers scroll/blur behavior on the card stack. - ## Related sites -- [auto.levkin.ca](https://auto.levkin.ca) — automation -- [caseware.levkin.ca](https://caseware.levkin.ca) — CaseWare consulting -- [jobs.levkin.ca](https://jobs.levkin.ca) — job orchestration -- [git.levkin.ca](https://git.levkin.ca) — source control -- [iliadobkin.com](https://iliadobkin.com) — SDET portfolio · quality engineering +- [auto.levkin.ca](https://auto.levkin.ca) +- [caseware.levkin.ca](https://caseware.levkin.ca) +- [git.levkin.ca](https://git.levkin.ca) +- [cal.levkin.ca](https://cal.levkin.ca/ilia/consult) +- [iliadobkin.com](https://iliadobkin.com) diff --git a/index.html b/index.html index 0ccfaec..f820674 100644 --- a/index.html +++ b/index.html @@ -24,7 +24,7 @@ min-height: 100vh; line-height: 1.5; } - .wrap { max-width: 900px; margin: 0 auto; padding: 4rem 1.5rem 6rem; } + .wrap { max-width: 720px; margin: 0 auto; padding: 4rem 1.5rem 6rem; } header { margin-bottom: 3rem; } .eyebrow { font-family: 'DM Mono', monospace; @@ -44,7 +44,7 @@ .grid { display: grid; gap: 1.25rem; - grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); + grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); } a.card { display: block; @@ -72,22 +72,6 @@ font-family: 'DM Mono', monospace; letter-spacing: 0.06em; } - .preview--stack { - background: #0e0e10; - flex-direction: column; - gap: 4px; - padding: 1.5rem; - } - .preview--stack .card-layer { - width: 70%; - height: 12px; - border-radius: 3px; - border: 1px solid rgba(255,255,255,0.1); - background: linear-gradient(90deg, #2a2a32, #3a3a46); - } - .preview--stack .card-layer:nth-child(1) { width: 55%; opacity: 0.5; } - .preview--stack .card-layer:nth-child(2) { width: 62%; opacity: 0.7; } - .preview--stack .card-layer:nth-child(3) { width: 75%; background: #4a4a58; } .preview--folder { background: #e8e2d4; color: #2a2824; @@ -121,8 +105,8 @@

levkin.ca

-

Three directions.

-

Spec for the company story. Cards and Folder for the L0–L6 scroll stack — click a layer to bring it to the front.

+

Two directions.

+

Spec for the company story. Folder for the L0–L7 scroll stack with site previews.

@@ -135,29 +119,18 @@
- -
- -
-
-

Cards

-

Dark sticky stack. Scroll or click L0–L6 to focus a layer.

- stack · scroll -
-
- -
L0│ L1│ L2│
+
L0 · L1 · L2 · … L7

Folder

-

Spec as a filing cabinet — manila tabs, clause sections, pull a file forward.

- folder · spec +

Manila tabs, sticky stack, previews of related sites.

+ folder · scroll
diff --git a/package.json b/package.json index 7947630..e385ed4 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,7 @@ "build": "vite build", "preview": "vite preview", "capture-previews": "node scripts/capture-previews.mjs", - "test:folder": "node scripts/test-stack-folder.mjs", - "test:stack-scroll": "node scripts/test-stack-scroll.mjs" + "test:folder": "node scripts/test-stack-folder.mjs" }, "devDependencies": { "playwright": "^1.60.0", diff --git a/relay/index.html b/relay/index.html deleted file mode 100644 index 81b5584..0000000 --- a/relay/index.html +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - Levkin — Relay - - - - - - - - - - - - -
-
-

Levkin Relay

-

Software development · signal in · production out

-
- -
-
- INCOMING - -
-

-      
-    
- -
-

Active lines

- -
- -
-
15+ yrs enterprise
-
8h→2m releases
-
open engagements
-
- -
-

Every transmission handled with retries, documentation, and tests before it reaches production. Canadian · remote NA & EU · fixed-scope quotes after discovery.

-

CaseWare International · MNP · JazzIt

-
- -
-

Schedule transmission:

- 15 min consultation → -

Or reply to:

- hello@levkine.ca -
-
- - - - - - diff --git a/relay/relay.css b/relay/relay.css deleted file mode 100644 index 6fb6f4c..0000000 --- a/relay/relay.css +++ /dev/null @@ -1,240 +0,0 @@ -:root { - --bg: #1a1814; - --paper: #e8e0d4; - --amber: #d4a574; - --dim: #6b6358; - --mono: 'Courier Prime', 'Courier New', monospace; -} - -* { box-sizing: border-box; margin: 0; padding: 0; } - -body { - font-family: var(--mono); - background: var(--bg); - color: var(--paper); - min-height: 100vh; - line-height: 1.55; -} - -.grain { - position: fixed; - inset: 0; - pointer-events: none; - opacity: 0.04; - background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); - z-index: 0; -} - -.nav { - position: relative; - z-index: 2; - display: flex; - justify-content: space-between; - padding: 1.25rem 2rem; - font-size: 0.75rem; - letter-spacing: 0.1em; -} - -.nav a { - color: var(--dim); - text-decoration: none; -} - -.nav a:hover { color: var(--amber); } - -.station { color: var(--amber); } - -main { - position: relative; - z-index: 1; - max-width: 560px; - margin: 0 auto; - padding: 2rem 2rem 4rem; -} - -.header { - margin-bottom: 2.5rem; - padding-bottom: 1.5rem; - border-bottom: 1px solid rgba(212, 165, 116, 0.25); -} - -.header h1 { - font-size: 1.5rem; - font-weight: 700; - letter-spacing: 0.15em; - text-transform: uppercase; - color: var(--amber); - margin-bottom: 0.5rem; -} - -.sub { - font-size: 0.8rem; - color: var(--dim); - letter-spacing: 0.05em; -} - -.tape { - background: rgba(0, 0, 0, 0.35); - border: 1px solid rgba(212, 165, 116, 0.2); - padding: 1.25rem; - margin-bottom: 2.5rem; -} - -.tape-head { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 1rem; - font-size: 0.65rem; - letter-spacing: 0.2em; - color: var(--amber); -} - -.decode-btn { - font-family: var(--mono); - font-size: 0.7rem; - background: transparent; - border: 1px solid var(--amber); - color: var(--amber); - padding: 0.35rem 0.65rem; - cursor: pointer; - letter-spacing: 0.08em; -} - -.decode-btn:hover { - background: rgba(212, 165, 116, 0.15); -} - -.morse { - font-size: 0.85rem; - color: var(--dim); - word-break: break-all; - line-height: 1.8; - min-height: 3rem; -} - -.decoded { - margin-top: 1rem; - padding-top: 1rem; - border-top: 1px dashed rgba(212, 165, 116, 0.3); - font-size: 0.95rem; - color: var(--paper); - line-height: 1.6; -} - -.decoded.hidden { display: none; } - -.channels { margin-bottom: 2rem; } - -.channels h2 { - font-size: 0.65rem; - letter-spacing: 0.25em; - color: var(--dim); - margin-bottom: 1rem; -} - -.channels ul { list-style: none; } - -.line-desc { - font-size: 0.7rem; - color: var(--dim); - grid-column: 2; -} - -.channels li { - display: grid; - grid-template-columns: 2.5rem 1fr auto; - gap: 0.25rem 0.5rem; - align-items: center; - padding: 0.65rem 0; - border-bottom: 1px solid rgba(212, 165, 116, 0.1); - font-size: 0.85rem; -} - -.line-id { color: var(--dim); font-size: 0.75rem; } -.line-status { color: var(--amber); font-size: 0.7rem; letter-spacing: 0.1em; } -.line-link { - color: var(--amber); - text-decoration: none; - font-size: 0.75rem; -} - -.line-link:hover { text-decoration: underline; } - -.metrics { - display: flex; - flex-wrap: wrap; - gap: 1rem 1.5rem; - margin-bottom: 1.5rem; - padding: 1rem 0; - border-top: 1px solid rgba(212, 165, 116, 0.15); - border-bottom: 1px solid rgba(212, 165, 116, 0.15); -} - -.metric { - font-size: 0.75rem; - letter-spacing: 0.06em; - color: var(--dim); -} - -.metric strong { - color: var(--amber); - font-weight: 700; -} - -.principles { - font-size: 0.85rem; - color: var(--dim); - margin-bottom: 2.5rem; - line-height: 1.65; -} - -.principles .clients { - margin-top: 0.75rem; - font-size: 0.75rem; - letter-spacing: 0.1em; - color: var(--amber); - opacity: 0.8; -} - -.send { - padding: 1.5rem 0; - border-top: 1px solid rgba(212, 165, 116, 0.25); -} - -.send-label { - font-size: 0.65rem; - letter-spacing: 0.2em; - color: var(--dim); - margin-bottom: 0.5rem; - margin-top: 1rem; -} - -.send-label:first-child { margin-top: 0; } - -.send-addr { - font-size: 1.1rem; - color: var(--amber); - text-decoration: none; - letter-spacing: 0.05em; -} - -.send-addr:hover { text-decoration: underline; } - -footer { - position: relative; - z-index: 1; - display: flex; - justify-content: space-between; - padding: 1.5rem 2rem; - font-size: 0.7rem; - color: var(--dim); - letter-spacing: 0.08em; -} - -footer a { - color: var(--dim); - text-decoration: none; -} - -footer a:hover { color: var(--amber); } diff --git a/relay/relay.js b/relay/relay.js deleted file mode 100644 index 8bb1514..0000000 --- a/relay/relay.js +++ /dev/null @@ -1,66 +0,0 @@ -const MESSAGES = [ - { - morse: '·−·−− · ·−·−− ·−· · ·−·−− −−− · ·−·−− ·−· · ·−·−− ·−−−', - text: 'Levkin — Canadian software practice. Custom apps, automation (n8n, CI/CD, LLMs), CaseWare, SDET. Remote NA & EU. Taking new work.', - }, - { - morse: '−−− ·−·−− · ·−·−− ·−· · ·−·−− ·−−− · ·−·−− ·−· · ·−·−− −−−', - text: 'Proof: 15+ years enterprise. CaseWare releases cut from 8 hours to under 2 minutes. Automation runs 24/7 — not demos.', - }, - { - morse: '·−·−− · ·−·−− ·−· · ·−·−− −−− · ·−·−− ·−· · ·−·−− ·−−−', - text: 'Discover (15 min) → Proposal (fixed scope) → Ship (tested, documented) → Maintain (optional). auto.levkin.ca · caseware.levkin.ca · iliadobkin.com', - }, - { - morse: '−−− ·−·−− · ·−·−− ·−· · ·−·−− ·−−− · ·−·−− ·−· · ·−·−− −−− · ·−·−− ·−· · ·−·−− ·−−−', - text: 'Error handling required. Documentation required. Tests before live data. Pragmatism: 20-line script beats 200-node workflow when it fits.', - }, -]; - -const morseEl = document.getElementById('morse'); -const decodedEl = document.getElementById('decoded'); -const btn = document.getElementById('decode-btn'); -let idx = 0; -let typing = false; - -function typeMorse(text, cb) { - typing = true; - morseEl.textContent = ''; - let i = 0; - const tick = () => { - if (i < text.length) { - morseEl.textContent += text[i]; - i++; - setTimeout(tick, 35 + Math.random() * 40); - } else { - typing = false; - cb?.(); - } - }; - tick(); -} - -function showMessage() { - const msg = MESSAGES[idx % MESSAGES.length]; - decodedEl.classList.add('hidden'); - typeMorse(msg.morse, () => { - btn.disabled = false; - btn.textContent = 'Decode ↵'; - }); - btn._pending = msg; -} - -btn.addEventListener('click', () => { - if (typing) return; - if (btn._pending && decodedEl.classList.contains('hidden')) { - decodedEl.textContent = btn._pending.text; - decodedEl.classList.remove('hidden'); - btn.textContent = 'Next signal →'; - btn._pending = null; - return; - } - idx++; - showMessage(); -}); - -showMessage(); diff --git a/scripts/capture-previews.mjs b/scripts/capture-previews.mjs index c50cbe5..ceeefda 100644 --- a/scripts/capture-previews.mjs +++ b/scripts/capture-previews.mjs @@ -13,7 +13,6 @@ mkdirSync(out, { recursive: true }); const shots = [ ['spec', `${base}/spec/`], - ['stack', `${base}/stack/`], ['auto', 'https://auto.levkin.ca'], ['caseware', 'https://caseware.levkin.ca'], ['iliadobkin', 'https://iliadobkin.com'], diff --git a/scripts/debug-sticky.mjs b/scripts/debug-sticky.mjs deleted file mode 100644 index b0c4f62..0000000 --- a/scripts/debug-sticky.mjs +++ /dev/null @@ -1,34 +0,0 @@ -import { chromium } from 'playwright'; - -const browser = await chromium.launch({ headless: true }); -const page = await browser.newPage({ viewport: { width: 1280, height: 800 } }); -await page.goto('http://localhost:5173/stack-folder/', { waitUntil: 'networkidle' }); - -for (const y of [0, 400, 800, 1200]) { - await page.evaluate((sy) => window.scrollTo(0, sy), y); - await page.waitForTimeout(150); - const data = await page.evaluate(() => { - return [...document.querySelectorAll('.folder')].map((f, i) => { - const cs = getComputedStyle(f); - const r = f.getBoundingClientRect(); - const body = f.querySelector('.body'); - const br = body.getBoundingClientRect(); - const sec = f.closest('.scroll-section'); - const sr = sec.getBoundingClientRect(); - return { - i, - folderTop: Math.round(r.top), - bodyTop: Math.round(br.top), - position: cs.position, - top: cs.top, - zIndex: f.style.zIndex || cs.zIndex, - sectionTop: Math.round(sr.top), - sectionBottom: Math.round(sr.bottom), - sectionH: Math.round(sr.height), - }; - }); - }); - console.log('scroll', y, JSON.stringify(data.filter((d) => d.sectionBottom > 0 && d.sectionTop < 800), null, 2)); -} - -await browser.close(); diff --git a/scripts/test-stack-cover.mjs b/scripts/test-stack-cover.mjs deleted file mode 100644 index 978e3d0..0000000 --- a/scripts/test-stack-cover.mjs +++ /dev/null @@ -1,50 +0,0 @@ -import { chromium } from 'playwright'; - -const BASE = process.env.BASE_URL || 'http://localhost:5173'; -const REVEAL = 48 + 44 * 6 + 32; // approx px - -async function testCover(page, path, bodySel, getTitle) { - await page.goto(BASE + path, { waitUntil: 'networkidle' }); - const results = []; - for (const y of [0, 600, 1200, 1800]) { - await page.evaluate((sy) => window.scrollTo(0, sy), y); - await page.waitForTimeout(250); - const r = await page.evaluate(({ bodySel, reveal }) => { - const bodies = [...document.querySelectorAll(bodySel)]; - const stacked = bodies.filter((b) => { - const br = b.getBoundingClientRect(); - return Math.abs(br.top - reveal) < 30 && br.height > 100; - }); - const top = stacked.sort((a, b) => { - const az = parseInt(getComputedStyle(a.closest('.folder, .layer, .frame, .unit') || a).zIndex) || 0; - const bz = parseInt(getComputedStyle(b.closest('.folder, .layer, .frame, .unit') || b).zIndex) || 0; - return bz - az; - })[0]; - const el = document.elementFromPoint(innerWidth / 2, reveal + 120); - const hit = el?.closest('.folder, .layer, .frame, .unit'); - const layer = hit?.closest('[data-layer]')?.dataset?.layer ?? hit?.className?.match(/f(\d)|layer-(\d)|u(\d)/)?.[1]; - return { - scrollY: scrollY, - stackedCount: stacked.length, - topZ: top ? getComputedStyle(top.closest('.folder, .layer, .frame, .unit')).zIndex : null, - hitLayer: layer, - hitText: hit?.querySelector('h1, h2, strong')?.textContent?.slice(0, 40), - }; - }, { bodySel, reveal: REVEAL }); - results.push(r); - } - return results; -} - -const browser = await chromium.launch({ headless: true }); -const page = await browser.newPage({ viewport: { width: 1280, height: 800 } }); - -const folder = await testCover(page, '/stack-folder/', '.folder .body', (b) => b.querySelector('h1,h2')?.textContent); -console.log('folder', JSON.stringify(folder, null, 2)); - -await page.screenshot({ path: '/tmp/levkin-cover-folder-1200.png' }); -await page.evaluate(() => window.scrollTo(0, 1200)); -await page.waitForTimeout(300); -await page.screenshot({ path: '/tmp/levkin-cover-folder-1200b.png' }); - -await browser.close(); diff --git a/scripts/test-stack-scroll.mjs b/scripts/test-stack-scroll.mjs deleted file mode 100644 index 0753692..0000000 --- a/scripts/test-stack-scroll.mjs +++ /dev/null @@ -1,118 +0,0 @@ -/** - * Automated scroll/blur tests for stack-folder. - * Run: node scripts/test-stack-scroll.mjs - */ -import { chromium } from 'playwright'; - -const URL = process.env.STACK_URL || 'http://localhost:5173/stack-folder/'; -const VIEWPORT = { width: 1280, height: 800 }; - -function fail(msg) { - console.error('FAIL:', msg); - process.exitCode = 1; -} - -function pass(msg) { - console.log('PASS:', msg); -} - -async function readState(page) { - return page.evaluate(() => { - const stick = - parseFloat(getComputedStyle(document.documentElement).fontSize) * 3; - const l7Tab = document.querySelector('.f7 .tab'); - const l0 = document.querySelector('.f0'); - const l0Body = document.querySelector('.f0 .body'); - const folderBlur = l0?.style.getPropertyValue('--stack-blur') || ''; - const filter = l0Body ? getComputedStyle(l0Body).filter : ''; - const blurMatch = filter.match(/blur\(([\d.]+)px\)/); - const blurPx = blurMatch ? parseFloat(blurMatch[1]) : 0; - return { - scrollY: window.scrollY, - docMax: document.documentElement.scrollHeight - innerHeight, - l7TabTop: l7Tab?.getBoundingClientRect().top ?? null, - stick, - l0Covered: l0?.classList.contains('is-covered'), - l0BlurPx: blurPx, - folderBlur, - l0Filter: filter, - runway: getComputedStyle(document.querySelector('.mount')).getPropertyValue( - '--stack-runway', - ), - }; - }); -} - -async function main() { - const browser = await chromium.launch(); - const page = await browser.newPage({ viewport: VIEWPORT }); - await page.goto(URL, { waitUntil: 'networkidle' }); - await page.waitForTimeout(400); - - const top = await readState(page); - if (top.l0Covered || top.l0BlurPx > 0.1) { - fail(`L0 blurred at top (covered=${top.l0Covered}, blur=${top.l0BlurPx})`); - } else { - pass('L0 clear at scroll top'); - } - - await page.evaluate(() => window.scrollTo(0, 999999)); - await page.waitForTimeout(350); - const end = await readState(page); - - if (end.l7TabTop === null) fail('L7 tab missing'); - else if (Math.abs(end.l7TabTop - end.stick) > 8) { - fail(`L7 tab not on stick: top=${end.l7TabTop} stick=${end.stick} scrollY=${end.scrollY}`); - } else { - pass(`L7 on stick at max scroll (y=${end.scrollY}, tabTop=${end.l7TabTop.toFixed(1)})`); - } - - if (end.l0BlurPx < 2) { - fail(`L0 not blurred when stacked (blur=${end.l0BlurPx})`); - } else { - pass(`L0 blurred when stacked (blur=${end.l0BlurPx}px)`); - } - - const midY = Math.floor(end.scrollY * 0.45); - await page.evaluate((y) => window.scrollTo(0, y), midY); - await page.waitForTimeout(200); - const mid = await readState(page); - if (mid.l0BlurPx > 2) { - fail(`L0 still heavily blurred mid-scroll out (blur=${mid.l0BlurPx} at y=${mid.scrollY})`); - } else { - pass(`L0 unfades when scrolling out (blur=${mid.l0BlurPx}px at y=${mid.scrollY})`); - } - - await page.evaluate(() => window.scrollTo(0, 0)); - await page.waitForTimeout(350); - const back = await readState(page); - if (back.l0Covered || back.l0BlurPx > 0.1) { - fail(`L0 still blurred after scroll to top (covered=${back.l0Covered}, blur=${back.l0BlurPx})`); - } else { - pass('L0 unfades after scroll back to top'); - } - - const maxY = end.scrollY; - await page.evaluate((y) => window.scrollTo(0, y), maxY); - await page.waitForTimeout(200); - await page.evaluate(() => window.scrollTo(0, 0)); - await page.waitForTimeout(350); - const back2 = await readState(page); - if (back2.l0Covered || back2.l0BlurPx > 0.1) { - fail(`L0 stuck after down-up cycle (blur=${back2.l0BlurPx})`); - } else { - pass('L0 unfades after full down-up cycle'); - } - - await browser.close(); - if (process.exitCode) { - console.log('\nTests failed.'); - process.exit(1); - } - console.log('\nAll tests passed.'); -} - -main().catch((e) => { - console.error(e); - process.exit(1); -}); diff --git a/scripts/test-stack.mjs b/scripts/test-stack.mjs deleted file mode 100644 index 237d4f6..0000000 --- a/scripts/test-stack.mjs +++ /dev/null @@ -1,76 +0,0 @@ -import { chromium } from 'playwright'; -import { writeFileSync, mkdirSync } from 'fs'; -import { join } from 'path'; - -const BASE = process.env.BASE_URL || 'http://localhost:5173'; -const OUT = '/tmp/levkin-stack-test'; -mkdirSync(OUT, { recursive: true }); - -const pages = [ - { name: 'stack', path: '/stack/', bodySel: '.layer-inner' }, - { name: 'stack-folder', path: '/stack-folder/', bodySel: '.folder .body' }, - { name: 'stack-trace', path: '/stack-trace/', bodySel: '.frame-body' }, - { name: 'stack-rack', path: '/stack-rack/', bodySel: '.unit-body' }, -]; - -const scrollY = [0, 400, 800, 1200, 1800, 2400]; - -async function analyze(page, bodySel) { - const reveal = await page.evaluate(() => { - const s = getComputedStyle(document.documentElement); - return parseFloat(s.getPropertyValue('--stack-reveal')) || - (48 + 44 * 6 + 32); - }); - - return page.evaluate(({ bodySel, reveal }) => { - const bodies = [...document.querySelectorAll(bodySel)]; - const stacked = bodies.filter((b) => { - const r = b.getBoundingClientRect(); - return Math.abs(r.top - reveal) < 35 && r.height > 80; - }); - const visible = bodies.filter((b) => { - const r = b.getBoundingClientRect(); - return r.height > 80 && r.bottom > 0 && r.top < innerHeight; - }); - const el = document.elementFromPoint(innerWidth / 2, reveal + 100); - const panel = el?.closest('.folder, .layer, .frame, .unit'); - const layer = panel?.closest('[data-layer]')?.dataset?.layer; - const title = panel?.querySelector('h1, h2, strong')?.textContent?.trim().slice(0, 50); - return { - scrollY: Math.round(window.scrollY), - reveal: Math.round(reveal), - stackedCount: stacked.length, - visibleCount: visible.length, - topLayer: layer, - topTitle: title, - issues: [], - }; - }, { bodySel, reveal }); -} - -const browser = await chromium.launch({ headless: true }); -const report = []; - -for (const { name, path, bodySel } of pages) { - const page = await browser.newPage({ viewport: { width: 1280, height: 800 } }); - await page.goto(BASE + path, { waitUntil: 'networkidle' }); - const shots = []; - for (const y of scrollY) { - await page.evaluate((sy) => window.scrollTo(0, sy), y); - await page.waitForTimeout(250); - const data = await analyze(page, bodySel); - if (data.stackedCount < 2 && y > 200) data.issues.push('not stacking (need 2+ bodies at reveal line)'); - if (data.visibleCount > 2 && y > 200) data.issues.push(`${data.visibleCount} bodies spread out, not covering`); - await page.screenshot({ path: join(OUT, `${name}-scroll-${y}.png`) }); - shots.push({ y, ...data }); - } - report.push({ name, shots }); - await page.close(); -} - -await browser.close(); -writeFileSync(join(OUT, 'report.json'), JSON.stringify(report, null, 2)); -console.log(JSON.stringify(report.map((r) => ({ - variant: r.name, - checks: r.shots.map((s) => ({ y: s.y, stacked: s.stackedCount, top: s.topTitle, issues: s.issues })), -})), null, 2)); diff --git a/slab/index.html b/slab/index.html deleted file mode 100644 index cfe5193..0000000 --- a/slab/index.html +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - Levkin - - - - - - - - - - -
-
- EST · CA -

LEV
KIN

-

SOFTWARE DEVELOPMENT

-
- -
-

WE BUILD WHAT RUNS.

-
- -
-

AVAILABLE · NEW ENGAGEMENTS · REMOTE NA & EU

-
- -
-
15+YEARS
-
8h→2mRELEASES
-
24/7AUTOMATION
-
- -
-
- 01 -

CUSTOM

-

Apps · APIs · TS · Python · .NET

-
-
- 02 -

AUTO

-

n8n · Zapier · CI/CD · LLMs

- -
-
- 03 -

CASE

-

CaseWare · MNP · JazzIt

- -
-
- 04 -

OPS

-

Job orchestration · auth

- -
-
- 05 -

QE

-

Senior SDET · test automation · traces

- -
-
- -
- -
- -
- BOOK 15 MIN → - HELLO@LEVGINE.CA → -
-
- - - - diff --git a/slab/slab.css b/slab/slab.css deleted file mode 100644 index bc1f102..0000000 --- a/slab/slab.css +++ /dev/null @@ -1,240 +0,0 @@ -:root { - --white: #f5f5f0; - --black: #0a0a0a; - --red: #e63946; - --font-display: 'Archivo Black', system-ui, sans-serif; - --font: 'Archivo', system-ui, sans-serif; -} - -* { box-sizing: border-box; margin: 0; padding: 0; } - -html { scroll-behavior: smooth; } - -body { - font-family: var(--font); - background: var(--white); - color: var(--black); - min-height: 100vh; -} - -.back { - position: fixed; - top: 1.25rem; - left: 1.25rem; - z-index: 10; - font-size: 1.25rem; - color: var(--black); - text-decoration: none; - font-weight: 500; -} - -main { max-width: 100%; } - -.mast { - padding: 4rem 1.5rem 3rem; - border-bottom: 6px solid var(--black); -} - -.year { - font-size: 0.7rem; - letter-spacing: 0.25em; - font-weight: 500; - display: block; - margin-bottom: 2rem; -} - -.mast h1 { - font-family: var(--font-display); - font-size: clamp(4rem, 18vw, 11rem); - line-height: 0.85; - letter-spacing: -0.04em; - font-weight: 400; -} - -.role { - margin-top: 1.5rem; - font-size: 0.8rem; - letter-spacing: 0.35em; - font-weight: 500; -} - -.strip { - padding: 1.25rem 1.5rem; - border-bottom: 4px solid var(--black); -} - -.strip-red { - background: var(--red); - color: var(--white); -} - -.strip-red p { - font-family: var(--font-display); - font-size: clamp(1.25rem, 4vw, 2rem); - letter-spacing: 0.02em; -} - -.strip-avail { - background: var(--black); - color: var(--white); -} - -.strip-avail p { - font-size: 0.7rem; - letter-spacing: 0.2em; - font-weight: 500; -} - -.stats { - display: grid; - grid-template-columns: repeat(3, 1fr); - border-bottom: 4px solid var(--black); -} - -.stat { - padding: 1.5rem 1rem; - text-align: center; - border-right: 4px solid var(--black); -} - -.stat:last-child { border-right: none; } - -.stat-n { - display: block; - font-family: var(--font-display); - font-size: 1.75rem; - letter-spacing: -0.02em; -} - -.stat-l { - font-size: 0.6rem; - letter-spacing: 0.25em; - font-weight: 500; -} - -.block-link { - margin-top: 0.35rem; - font-size: 0.75rem; -} - -.block-link a { - color: inherit; - text-decoration: underline; - text-underline-offset: 2px; -} - -.blocks { - display: grid; - grid-template-columns: 1fr 1fr; -} - -@media (max-width: 500px) { - .blocks { grid-template-columns: 1fr; } -} - -.block { - padding: 2rem 1.5rem; - border-bottom: 4px solid var(--black); - border-right: 4px solid var(--black); - min-height: 160px; -} - -.block:nth-child(2n) { border-right: none; } - -.block-wide { - grid-column: 1 / -1; - border-right: none; -} - -.block-inv { - background: var(--black); - color: var(--white); -} - -.num { - font-size: 0.65rem; - letter-spacing: 0.2em; - opacity: 0.5; - display: block; - margin-bottom: 0.75rem; -} - -.block h2 { - font-family: var(--font-display); - font-size: 2rem; - letter-spacing: -0.02em; - margin-bottom: 0.35rem; -} - -.block p { - font-size: 0.85rem; - letter-spacing: 0.05em; -} - -.block a { - color: inherit; - text-decoration: underline; - text-underline-offset: 3px; -} - -.rules { - list-style: none; - display: grid; - gap: 0.5rem; -} - -.rules li { - font-family: var(--font-display); - font-size: clamp(0.9rem, 2.5vw, 1.1rem); - letter-spacing: 0.08em; -} - -.cta-block { - padding: 3rem 1.5rem; - border-bottom: 6px solid var(--black); - display: flex; - flex-direction: column; - gap: 1.25rem; -} - -.cta { - font-family: var(--font-display); - font-size: clamp(1rem, 3vw, 1.5rem); - color: var(--black); - text-decoration: none; - letter-spacing: 0.04em; - border-bottom: 4px solid var(--red); - padding-bottom: 0.25rem; - transition: color 0.15s, border-color 0.15s; -} - -.cta:hover { - color: var(--red); - border-color: var(--black); -} - -.cta-primary { - background: var(--black); - color: var(--white); - padding: 0.5rem 0; - border-bottom-color: var(--black); -} - -.cta-primary:hover { - color: var(--red); - background: var(--black); -} - -footer { - padding: 1.25rem 1.5rem; - font-size: 0.7rem; - letter-spacing: 0.2em; - font-weight: 500; -} - -footer a { - color: var(--black); - text-decoration: none; -} - -footer a:hover { text-decoration: underline; } diff --git a/stack-folder/previews/cal.png b/stack-folder/previews/cal.png deleted file mode 100644 index 7d4de20..0000000 Binary files a/stack-folder/previews/cal.png and /dev/null differ diff --git a/stack-folder/previews/git.png b/stack-folder/previews/git.png deleted file mode 100644 index 663e6f3..0000000 Binary files a/stack-folder/previews/git.png and /dev/null differ diff --git a/stack-folder/previews/jobs.png b/stack-folder/previews/jobs.png deleted file mode 100644 index abed536..0000000 Binary files a/stack-folder/previews/jobs.png and /dev/null differ diff --git a/stack-folder/previews/stack.png b/stack-folder/previews/stack.png deleted file mode 100644 index c8246df..0000000 Binary files a/stack-folder/previews/stack.png and /dev/null differ diff --git a/stack-rack/index.html b/stack-rack/index.html deleted file mode 100644 index 0f00e9d..0000000 --- a/stack-rack/index.html +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - Levkin — Stack Rack - - - - - - -
-
LEV-RACK-01
-
-
-
-
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

-
- -
-
-
engage()

book · mail

-
-
-
-
- - - - diff --git a/stack-rack/rack.css b/stack-rack/rack.css deleted file mode 100644 index 7d6a083..0000000 --- a/stack-rack/rack.css +++ /dev/null @@ -1,110 +0,0 @@ -@import '../shared/stack-vars.css'; -@import '../shared/stack-layout.css'; - -* { box-sizing: border-box; margin: 0; padding: 0; } - -body { - font-family: 'IBM Plex Mono', ui-monospace, monospace; - background: #08090c; - color: #9ca3af; - font-size: 0.75rem; -} - -.nav { - 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(8,9,12,0.95); border-bottom: 1px solid #1e2430; -} - -.nav a { color: #5a6478; text-decoration: none; } -.nav a:hover { color: #4ade80; } -#depth { margin-left: auto; color: #4ade80; font-weight: 600; } - -.rack-frame { - width: min(600px, 100%); - margin: 0 auto; - padding: var(--stack-nav) 0.5rem 0; - border-left: 4px solid #2a3040; - border-right: 4px solid #2a3040; - background: linear-gradient(90deg, #0c0e14 0%, #12141a 8%, #12141a 92%, #0c0e14 100%); -} - -.rack-label { - 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.25rem; } - -.unit { - position: sticky; - min-height: var(--stack-card-min); - margin-bottom: 1.25rem; - border: 1px solid #2a3448; - background: #161a22; - border-radius: 2px; - box-shadow: 0 6px 0 #0a0c10, 0 12px 24px rgba(0,0,0,0.4); -} - -.unit-head { - position: sticky; - display: flex; align-items: center; gap: 0.5rem; - padding: 0.4rem 0.65rem; - background: #1a2030; - border-bottom: 1px solid #2a3448; - z-index: 80; -} - -.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; } } - -.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.ext { color: #60a5fa; text-decoration: none; font-size: 0.58rem; } - -.unit-body { - padding: 0.6rem 0.7rem 1.5rem; - background: #161a22; -} - -.u0 { top: var(--stack-stick); z-index: 1; } -.u0 .unit-head { top: var(--stack-stick); } - -.u1 { top: calc(var(--stack-stick) + var(--stack-step)); z-index: 2; } -.u1 .unit-head { top: calc(var(--stack-stick) + var(--stack-step)); } - -.u2 { top: calc(var(--stack-stick) + var(--stack-step) * 2); z-index: 3; } -.u2 .unit-head { top: calc(var(--stack-stick) + var(--stack-step) * 2); } - -.u3 { top: calc(var(--stack-stick) + var(--stack-step) * 3); z-index: 4; } -.u3 .unit-head { top: calc(var(--stack-stick) + var(--stack-step) * 3); } - -.u4 { top: calc(var(--stack-stick) + var(--stack-step) * 4); z-index: 5; } -.u4 .unit-head { top: calc(var(--stack-stick) + var(--stack-step) * 4); } - -.u5 { top: calc(var(--stack-stick) + var(--stack-step) * 5); z-index: 6; } -.u5 .unit-head { top: calc(var(--stack-stick) + var(--stack-step) * 5); } - -.u6 { top: calc(var(--stack-stick) + var(--stack-step) * 6); z-index: 7; margin-bottom: 4rem; } -.u6 .unit-head { top: calc(var(--stack-stick) + var(--stack-step) * 6); } - -.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; } - -.foot { - 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-trace/index.html b/stack-trace/index.html deleted file mode 100644 index e3240b8..0000000 --- a/stack-trace/index.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - Levkin — Stack Trace - - - - - - -
-
- -
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 deleted file mode 100644 index 25e7f45..0000000 --- a/stack-trace/trace.css +++ /dev/null @@ -1,90 +0,0 @@ -@import '../shared/stack-vars.css'; -@import '../shared/stack-layout.css'; - -* { box-sizing: border-box; margin: 0; padding: 0; } - -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: 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; -} - -.nav a { color: #5a5854; text-decoration: none; } -.nav a:hover { color: #7eb87a; } -#depth { margin-left: auto; color: #7eb87a; font-weight: 600; } - -.mount { - width: min(600px, 100%); - margin: 0 auto; - padding-top: var(--stack-nav); -} - -.frame { - position: sticky; - min-height: var(--stack-card-min); - margin: 0 0.5rem 1.25rem; - border-left: 3px solid #3a3a44; - background: #141418; - box-shadow: 0 8px 0 #0a0a0c, 0 14px 28px rgba(0,0,0,0.45); -} - -.frame-line { - position: sticky; - display: block; - font-size: 0.66rem; - color: #6b9b6b; - border: none; - font-family: inherit; - cursor: pointer; - text-align: left; - width: 100%; - padding: 0.65rem 0 0.35rem 1rem; - background: #141418; - z-index: 80; -} - -.frame-line:hover { color: #9fdf9f; text-decoration: underline; } - -.frame-body { - padding: 0 0 1.5rem 1rem; - background: #141418; -} - -.f0 { top: var(--stack-stick); z-index: 1; border-color: #c4a574; } -.f0 .frame-line { top: var(--stack-stick); } - -.f1 { top: calc(var(--stack-stick) + var(--stack-step)); z-index: 2; } -.f1 .frame-line { top: calc(var(--stack-stick) + var(--stack-step)); } - -.f2 { top: calc(var(--stack-stick) + var(--stack-step) * 2); z-index: 3; } -.f2 .frame-line { top: calc(var(--stack-stick) + var(--stack-step) * 2); } - -.f3 { top: calc(var(--stack-stick) + var(--stack-step) * 3); z-index: 4; } -.f3 .frame-line { top: calc(var(--stack-stick) + var(--stack-step) * 3); } - -.f4 { top: calc(var(--stack-stick) + var(--stack-step) * 4); z-index: 5; border-color: #6b8b9b; } -.f4 .frame-line { top: calc(var(--stack-stick) + var(--stack-step) * 4); } - -.f5 { top: calc(var(--stack-stick) + var(--stack-step) * 5); z-index: 6; } -.f5 .frame-line { top: calc(var(--stack-stick) + var(--stack-step) * 5); } - -.f6 { top: calc(var(--stack-stick) + var(--stack-step) * 6); z-index: 7; border-color: #7eb87a; margin-bottom: 4rem; } -.f6 .frame-line { top: calc(var(--stack-stick) + var(--stack-step) * 6); } - -.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; } - -.foot { - 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/index.html b/stack/index.html deleted file mode 100644 index 0dea7ac..0000000 --- a/stack/index.html +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - Levkin — Stack - - - - - - - - - - -
-
-
-
company
-

Levkin Inc.

-

LK-SPEC-1.0 · Software development · Canada

-

Builds and maintains production software — custom apps, automation, practice systems. Scoped, documented, handoff-ready.

-

ACTIVE · Taking new engagements · remote NA & EU

-
-
- -
-
-
scope
-

1. Scope

-

Boutique practice — clear start, deliverable, and handoff. Discovery → fixed proposal → delivery → optional support.

-
-
- -
-
-
application
-

2.1 Custom software

-

Web apps, APIs, internal tools — chosen to fit the problem, not a preset stack.

-
-
- -
-
-
automationauto.levkin.ca ↗
-

2.2 Automation

-

Background workflows — reporting, notifications, data sync, ops off your plate.

-
-
- -
-
-
enterprisecaseware.levkin.ca ↗
-

2.3 CaseWare

-

Templates, releases, practice customization — small fixes to full pipeline overhauls.

-
-
- -
-
-
qualityiliadobkin.com ↗
-

2.4 Quality & testing

-

Test strategy, automation, release confidence — experienced QA lead without full-time hire.

-
-
- -
-
-
contact
-

Terms & contact

-

Fixed scope · production-ready · handoff-friendly · right-sized.

- -
-
- - -
- - -
- - - - - - - -
- - - diff --git a/stack/stack.css b/stack/stack.css deleted file mode 100644 index df68aaf..0000000 --- a/stack/stack.css +++ /dev/null @@ -1,119 +0,0 @@ -@import '../shared/stack-vars.css'; -@import '../shared/stack-layout.css'; - -:root { - --mono: 'IBM Plex Mono', ui-monospace, monospace; - --sans: 'Instrument Sans', system-ui, sans-serif; -} - -* { box-sizing: border-box; margin: 0; padding: 0; } - -html { scroll-behavior: smooth; } - -body { - font-family: var(--sans); - background: #0e0e10; - color: #e8e6e3; - line-height: 1.5; -} - -.nav { - position: fixed; top: 0; left: 0; right: 0; z-index: 100; - 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; } -.depth { color: #c4a574; font-weight: 600; } - -.stack-mount { - padding: var(--stack-nav) 1rem 0; - max-width: 560px; - margin: 0 auto; -} - -/* Compact sticky card — click brings to front */ -.layer:not(.layer-6) { - margin-bottom: var(--stack-scroll-slot); -} - -.layer { - position: sticky; - border-radius: 8px; - border: 1px solid rgba(255,255,255,0.08); - box-shadow: 0 12px 36px rgba(0,0,0,0.5); - cursor: pointer; - transition: box-shadow 0.2s; -} - -.layer.is-front { - z-index: 100 !important; - box-shadow: 0 16px 48px rgba(0,0,0,0.65); -} - -.layer-0 { top: var(--stack-stick); z-index: 1; background: #1c1c20; } -.layer-1 { top: calc(var(--stack-stick) + var(--stack-step)); z-index: 2; background: #24242c; } -.layer-2 { top: calc(var(--stack-stick) + var(--stack-step) * 2); z-index: 3; background: #2c2c36; } -.layer-3 { top: calc(var(--stack-stick) + var(--stack-step) * 3); z-index: 4; background: #343440; } -.layer-4 { top: calc(var(--stack-stick) + var(--stack-step) * 4); z-index: 5; background: #3c3c4a; } -.layer-5 { top: calc(var(--stack-stick) + var(--stack-step) * 5); z-index: 6; background: #444454; } -.layer-6 { top: calc(var(--stack-stick) + var(--stack-step) * 6); z-index: 7; background: #4c4c5e; margin-bottom: 0; } - -.layer-inner { padding: 0.95rem 1.15rem 1.05rem; } - -.layer-head { - display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem 0.65rem; - 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; 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 h1 { font-size: 1.5rem; font-weight: 600; letter-spacing: -0.03em; } -.layer h2 { font-size: 1.05rem; font-weight: 600; margin-bottom: 0.3rem; } -.tagline { font-family: var(--mono); font-size: 0.65rem; color: #6b6966; margin-bottom: 0.4rem; } -.layer-copy { 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; -} -.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: 560px; 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: 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 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) { - :root { --stack-step: 1.5rem; } - .variants { display: none; } -} diff --git a/vault/index.html b/vault/index.html deleted file mode 100644 index d00a349..0000000 --- a/vault/index.html +++ /dev/null @@ -1,107 +0,0 @@ - - - - - - Levkin — Software Development - - - - - - - - -
- - -
-

Chartered software practice · Canada

-

Built to hold.
Built to hand off.

-

Levkin develops production systems, automation, and enterprise software for organizations that cannot afford failure in the field. Boutique practice — fixed scope, documented handoff, optional stewardship.

-

Currently accepting new engagements · Remote (North America & Europe)

-
- -
-
-
- 15+ - Years in enterprise software -
-
- 8h → <2m - Release cycle reduction (CaseWare) -
-
- 24/7 - Automation that runs unattended -
-
-

Delivered for teams at CaseWare International, MNP, JazzIt, and private accounting firms.

-
- -
- -
-
-

Custom software

-

TypeScript, Python, C#/.NET — web apps, APIs, internal tools. PostgreSQL, SQL Server.

-
-
-

Automation

-

n8n, Zapier, Make, GitHub Actions, webhooks, LLM workflows. auto.levkin.ca

-
-
-

CaseWare practice

-

Features, CaseView templates, release pipelines — C#, .NET, Jenkins, Azure DevOps. caseware.levkin.ca

-
-
-

Internal operations

-

Job Ops orchestrator — per-user hiring workflows (authenticated). jobs.levkin.ca

-
-
-

Quality engineering

-

Senior SDET — Playwright-style portfolio, CI/CD, trace-driven QA. iliadobkin.com

-
-
-
- -
- - -
- -
- -
    -
  1. Discovery — 15-minute call; honest read on whether software fits
  2. -
  3. Proposal — Fixed scope, tools, timeline, and cost — no surprises
  4. -
  5. Delivery — Survives bad data, retries, and Monday morning
  6. -
  7. Transition — Handoff with runbooks, or ongoing monitoring
  8. -
-
- -
-
-

Open an enquiry

- Book 15 min consultation - hello@levkine.ca -

Remote · North America & Europe

-
-
- - -
- - diff --git a/vault/vault.css b/vault/vault.css deleted file mode 100644 index 45733fb..0000000 --- a/vault/vault.css +++ /dev/null @@ -1,293 +0,0 @@ -:root { - --forest: #0c1410; - --forest-mid: #142820; - --brass: #c9b896; - --brass-dim: rgba(201, 184, 150, 0.5); - --cream: #f0ebe3; - --muted: #7a8f82; - --serif: 'Cormorant Garamond', Georgia, serif; - --sans: 'Source Sans 3', system-ui, sans-serif; -} - -* { box-sizing: border-box; margin: 0; padding: 0; } - -body { - font-family: var(--sans); - background: var(--forest); - color: var(--cream); - min-height: 100vh; - line-height: 1.6; -} - -.frame { - max-width: 680px; - margin: 0 auto; - padding: 0 2rem 4rem; - border-left: 1px solid var(--brass-dim); - border-right: 1px solid var(--brass-dim); - min-height: 100vh; - background: linear-gradient(180deg, var(--forest-mid) 0%, var(--forest) 30%); -} - -.nav { - display: flex; - justify-content: space-between; - align-items: center; - padding: 1.5rem 0; - border-bottom: 1px solid var(--brass-dim); - font-size: 0.8rem; -} - -.nav-back { - color: var(--muted); - text-decoration: none; - letter-spacing: 0.04em; -} - -.nav-back:hover { color: var(--brass); } - -.seal { - font-family: var(--serif); - font-size: 1.1rem; - font-weight: 600; - color: var(--brass); - letter-spacing: 0.12em; -} - -.hero { - padding: 3.5rem 0 3rem; - border-bottom: 1px solid var(--brass-dim); -} - -.charter { - font-size: 0.72rem; - letter-spacing: 0.2em; - text-transform: uppercase; - color: var(--muted); - margin-bottom: 1.25rem; -} - -.hero h1 { - font-family: var(--serif); - font-size: clamp(2rem, 6vw, 2.75rem); - font-weight: 500; - line-height: 1.2; - margin-bottom: 1.25rem; - color: var(--cream); -} - -.hero h1 em { - font-style: italic; - color: var(--brass); -} - -.lead { - font-size: 1.05rem; - color: var(--muted); - max-width: 44ch; - line-height: 1.65; -} - -.avail { - margin-top: 1rem; - font-size: 0.8rem; - letter-spacing: 0.08em; - color: var(--brass); -} - -.proof { - padding-bottom: 1rem; - border-bottom: 1px solid var(--brass-dim); - margin-bottom: 0.5rem; -} - -.proof-grid { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 1rem; - margin-bottom: 1.25rem; -} - -@media (max-width: 500px) { - .proof-grid { grid-template-columns: 1fr; } -} - -.proof-item { - text-align: center; - padding: 1rem 0.5rem; - border: 1px solid var(--brass-dim); -} - -.proof-val { - display: block; - font-family: var(--serif); - font-size: 1.5rem; - color: var(--brass); - margin-bottom: 0.25rem; -} - -.proof-label { - font-size: 0.72rem; - color: var(--muted); - line-height: 1.4; -} - -.clients { - font-size: 0.88rem; - color: var(--muted); - font-style: italic; - text-align: center; -} - -.section-label { - font-family: var(--serif); - font-size: 0.85rem; - font-weight: 600; - letter-spacing: 0.25em; - text-transform: uppercase; - color: var(--brass); - margin-bottom: 1.25rem; - padding-top: 2.5rem; -} - -.holdings { padding-bottom: 1rem; } - -.holding-grid { - display: grid; - gap: 1px; - background: var(--brass-dim); -} - -.holding { - background: var(--forest); - padding: 1.35rem 1.25rem; -} - -.holding h3 { - font-family: var(--serif); - font-size: 1.2rem; - font-weight: 600; - color: var(--brass); - margin-bottom: 0.35rem; -} - -.holding p { - font-size: 0.9rem; - color: var(--muted); -} - -.holding a { - color: var(--brass); - text-decoration: none; -} - -.holding a:hover { text-decoration: underline; } - -.covenant ul { - list-style: none; -} - -.covenant li { - padding: 0.6rem 0; - border-bottom: 1px solid rgba(201, 184, 150, 0.12); - font-size: 0.95rem; - color: var(--muted); - display: flex; - gap: 0.75rem; -} - -.mark { - color: var(--brass); - font-family: var(--serif); -} - -.process ol { - list-style: none; - counter-reset: step; -} - -.process li { - counter-increment: step; - padding: 0.75rem 0; - border-bottom: 1px solid rgba(201, 184, 150, 0.12); - font-size: 0.9rem; - color: var(--muted); - padding-left: 2rem; - position: relative; -} - -.process li::before { - content: counter(step, decimal-leading-zero); - position: absolute; - left: 0; - font-family: var(--serif); - color: var(--brass); - font-size: 0.85rem; -} - -.process strong { - color: var(--cream); - font-weight: 500; -} - -.contact { - padding: 3rem 0 2rem; -} - -.contact-panel { - border: 1px solid var(--brass); - padding: 2rem; - text-align: center; - background: rgba(201, 184, 150, 0.04); -} - -.contact-title { - font-family: var(--serif); - font-size: 0.9rem; - letter-spacing: 0.15em; - text-transform: uppercase; - color: var(--brass); - margin-bottom: 0.75rem; -} - -.contact-email { - font-family: var(--serif); - font-size: 1.5rem; - color: var(--cream); - text-decoration: none; - display: inline-block; - margin-bottom: 0.5rem; -} - -.contact-email:hover { color: var(--brass); } - -.contact-email-secondary { - display: block; - font-size: 1rem; - margin-top: 0.75rem; - color: var(--muted); -} - -.contact-email-secondary:hover { color: var(--brass); } - -.contact-note { - font-size: 0.8rem; - color: var(--muted); -} - -footer { - display: flex; - justify-content: space-between; - padding-top: 2rem; - border-top: 1px solid var(--brass-dim); - font-size: 0.75rem; - color: var(--muted); - letter-spacing: 0.04em; -} - -footer a { - color: var(--muted); - text-decoration: none; -} - -footer a:hover { color: var(--brass); } diff --git a/vite.config.js b/vite.config.js index 0d0a149..b5b0343 100644 --- a/vite.config.js +++ b/vite.config.js @@ -7,7 +7,6 @@ export default defineConfig({ input: { main: resolve(__dirname, 'index.html'), spec: resolve(__dirname, 'spec/index.html'), - stack: resolve(__dirname, 'stack/index.html'), stackFolder: resolve(__dirname, 'stack-folder/index.html'), }, },