-
-
-
-
- 3. Documented Outcomes
-
-
- | Metric | Value | Context |
-
-
- release_time | 8h → <2min | CaseWare template pipeline rebuild |
- experience | 15+ years | CaseWare, automation, enterprise CI/CD |
- automation_uptime | 24/7 | Pipelines monitored, not happy-path demos |
- engagement_model | Fixed scope | Quoted per project after discovery — no hourly surprises |
-
-
- Engagement flow: Discover (15 min) → Design (proposal) → Ship (tested, documented) → Maintain (optional).
-
-
-
- 4. Required Properties
- All Levkin deliverables MUST satisfy the following constraints unless explicitly waived in writing.
-
-
- | Property | Requirement | Rationale |
-
-
- reliability | Retries, alerts, graceful degradation | Production ≠ demo |
- documentation | Runbook or README sufficient for handoff | Bus factor > 1 |
- testability | Automated tests before live data | Regressions are expensive |
- pragmatism | Smallest solution that solves the problem | 20-line script > 200-node workflow |
-
-
-
-
-
-
-
-
+
© Levkin · Canadian software development
+
+
+
diff --git a/spec/spec.css b/spec/spec.css
index 0a5aa9f..aecbc87 100644
--- a/spec/spec.css
+++ b/spec/spec.css
@@ -1,19 +1,89 @@
-:root {
+/* --- themes --- */
+:root,
+[data-theme="light"] {
+ color-scheme: light;
--paper: #f4f1ec;
+ --surface: #ffffff;
--ink: #1a1a18;
- --muted: #5c5a56;
- --rule: #d4cfc6;
- --accent: #2a4a6b;
- --accent-bg: #e8eef4;
+ --muted: #4a4844;
+ --rule: #c8c2b8;
+ --accent: #1e4a72;
+ --accent-hover: #163a5c;
+ --accent-bg: #e4ecf4;
+ --link: #1e4a72;
+ --link-hover: #163a5c;
+ --badge-ok-bg: #c8e6ce;
+ --badge-ok-fg: #14532d;
+ --badge-post-bg: #f5dcc8;
+ --badge-post-fg: #6b3410;
+ --focus: #1e4a72;
+ --skip-bg: #1a1a18;
+ --skip-fg: #f4f1ec;
+}
+
+[data-theme="dim"] {
+ color-scheme: light;
+ --paper: #ddd8cf;
+ --surface: #ece8e0;
+ --ink: #1f1d1a;
+ --muted: #45423c;
+ --rule: #b5aea2;
+ --accent: #2a5078;
+ --accent-hover: #1e3d5c;
+ --accent-bg: #d4dde8;
+ --link: #2a5078;
+ --link-hover: #1e3d5c;
+ --badge-ok-bg: #b8d4be;
+ --badge-ok-fg: #14532d;
+ --badge-post-bg: #e8cdb8;
+ --badge-post-fg: #5c3010;
+ --focus: #2a5078;
+ --skip-bg: #1f1d1a;
+ --skip-fg: #ece8e0;
+}
+
+[data-theme="dark"] {
+ color-scheme: dark;
+ --paper: #141412;
+ --surface: #1e1e1c;
+ --ink: #ece9e4;
+ --muted: #b8b4ac;
+ --rule: #3a3834;
+ --accent: #7eb8e8;
+ --accent-hover: #a8d4f8;
+ --accent-bg: #243040;
+ --link: #8ec8f0;
+ --link-hover: #b8e0ff;
+ --badge-ok-bg: #1e4a2e;
+ --badge-ok-fg: #a8e8b8;
+ --badge-post-bg: #4a3020;
+ --badge-post-fg: #f0d0b0;
+ --focus: #8ec8f0;
+ --skip-bg: #ece9e4;
+ --skip-fg: #141412;
+}
+
+:root {
+ --font-scale: 1;
--mono: 'IBM Plex Mono', ui-monospace, monospace;
--serif: 'Literata', Georgia, serif;
+ --focus-ring: 0 0 0 3px color-mix(in srgb, var(--focus) 35%, transparent);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
+html {
+ font-size: calc(100% * var(--font-scale));
+ scroll-behavior: smooth;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ html { scroll-behavior: auto; }
+}
+
body {
display: grid;
- grid-template-columns: 200px 1fr;
+ grid-template-columns: minmax(12rem, 220px) 1fr;
min-height: 100vh;
background: var(--paper);
color: var(--ink);
@@ -22,10 +92,64 @@ body {
line-height: 1.65;
}
+/* screen reader only */
+.sr-only {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ white-space: nowrap;
+ border: 0;
+}
+
+.skip-link {
+ position: absolute;
+ top: 0.5rem;
+ left: 0.5rem;
+ z-index: 100;
+ padding: 0.6rem 1rem;
+ background: var(--skip-bg);
+ color: var(--skip-fg);
+ font-family: var(--mono);
+ font-size: 0.8rem;
+ text-decoration: none;
+ border-radius: 2px;
+ transform: translateY(-200%);
+ transition: transform 0.15s;
+}
+
+.skip-link:focus {
+ transform: translateY(0);
+ outline: 3px solid var(--focus);
+ outline-offset: 2px;
+}
+
+a:focus-visible,
+button:focus-visible,
+input:focus-visible,
+.code-block:focus-visible {
+ outline: 3px solid var(--focus);
+ outline-offset: 2px;
+}
+
+a {
+ color: var(--link);
+ text-decoration-thickness: 1px;
+ text-underline-offset: 0.15em;
+}
+
+a:hover {
+ color: var(--link-hover);
+}
+
@media (max-width: 800px) {
body { grid-template-columns: 1fr; }
.toc {
position: static !important;
+ height: auto !important;
border-bottom: 1px solid var(--rule);
padding: 1rem 1.5rem !important;
}
@@ -37,40 +161,188 @@ body {
position: sticky;
top: 0;
height: 100vh;
+ overflow-y: auto;
padding: 2rem 1.25rem;
border-right: 1px solid var(--rule);
font-family: var(--mono);
font-size: 0.7rem;
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
}
.back {
display: block;
color: var(--muted);
text-decoration: none;
- margin-bottom: 1.5rem;
+ margin-bottom: 0.5rem;
+ min-height: 2.75rem;
+ line-height: 2.75rem;
}
.back:hover { color: var(--accent); }
+.toc-title {
+ font-size: 0.65rem;
+ font-weight: 500;
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+ color: var(--muted);
+ margin-bottom: 0.25rem;
+}
+
.toc nav {
display: flex;
flex-direction: column;
- gap: 0.35rem;
+ gap: 0.15rem;
}
.toc nav a {
color: var(--ink);
text-decoration: none;
+ padding: 0.35rem 0.25rem;
+ min-height: 2.75rem;
+ display: flex;
+ align-items: center;
+ border-radius: 2px;
}
.toc nav a:hover { color: var(--accent); }
+.toc nav a.is-active {
+ font-weight: 600;
+ color: var(--accent);
+ background: var(--accent-bg);
+}
+
+/* preferences */
+.prefs {
+ margin-top: auto;
+ padding-top: 1rem;
+ border-top: 1px solid var(--rule);
+}
+
+.prefs-heading {
+ font-size: 0.65rem;
+ font-weight: 500;
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+ color: var(--muted);
+ margin-bottom: 0.6rem;
+}
+
+.theme-field {
+ border: 0;
+ margin: 0 0 0.75rem;
+ padding: 0;
+}
+
+.theme-legend {
+ font-size: 0.62rem;
+ color: var(--muted);
+ margin-bottom: 0.35rem;
+}
+
+.theme-options {
+ display: flex;
+ gap: 0.25rem;
+}
+
+.theme-option {
+ flex: 1;
+ cursor: pointer;
+}
+
+.theme-option input {
+ position: absolute;
+ opacity: 0;
+ width: 0;
+ height: 0;
+}
+
+.theme-option span {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-height: 2.75rem;
+ padding: 0.35rem 0.25rem;
+ border: 1px solid var(--rule);
+ background: var(--surface);
+ color: var(--ink);
+ text-align: center;
+ border-radius: 2px;
+ transition: background 0.12s, border-color 0.12s;
+}
+
+.theme-option input:checked + span {
+ border-color: var(--accent);
+ background: var(--accent-bg);
+ font-weight: 600;
+ color: var(--accent);
+}
+
+.theme-option input:focus-visible + span {
+ outline: 3px solid var(--focus);
+ outline-offset: 2px;
+}
+
+.font-controls {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 0.35rem 0.5rem;
+}
+
+.font-label {
+ width: 100%;
+ font-size: 0.62rem;
+ color: var(--muted);
+}
+
+.font-buttons {
+ display: flex;
+ gap: 0.25rem;
+}
+
+.font-btn {
+ font-family: var(--mono);
+ font-size: 0.75rem;
+ min-width: 2.75rem;
+ min-height: 2.75rem;
+ padding: 0.35rem 0.5rem;
+ border: 1px solid var(--rule);
+ background: var(--surface);
+ color: var(--ink);
+ cursor: pointer;
+ border-radius: 2px;
+}
+
+.font-btn:hover:not(:disabled) {
+ background: var(--accent-bg);
+ border-color: var(--accent);
+}
+
+.font-btn:disabled {
+ opacity: 0.45;
+ cursor: not-allowed;
+}
+
+.font-scale-readout {
+ font-size: 0.62rem;
+ color: var(--muted);
+ min-width: 2.5rem;
+}
+
.meta {
- margin-top: 2rem;
+ margin-top: 0.75rem;
color: var(--muted);
line-height: 1.5;
}
+.main {
+ min-width: 0;
+}
+
.rfc {
max-width: 42rem;
padding: 3rem 2.5rem 5rem;
@@ -121,8 +393,8 @@ body {
font-family: var(--mono);
font-size: 0.65rem;
padding: 0.15rem 0.45rem;
- background: #d4edda;
- color: #1e5631;
+ background: var(--badge-ok-bg);
+ color: var(--badge-ok-fg);
letter-spacing: 0.04em;
}
@@ -167,7 +439,7 @@ section p { margin-bottom: 0.75rem; color: var(--ink); }
.endpoint {
border: 1px solid var(--rule);
margin: 1.25rem 0;
- background: #fff;
+ background: var(--surface);
}
.endpoint-head {
@@ -184,8 +456,8 @@ section p { margin-bottom: 0.75rem; color: var(--ink); }
.method {
font-weight: 600;
- color: #1e5631;
- background: #d4edda;
+ color: var(--badge-ok-fg);
+ background: var(--badge-ok-bg);
padding: 0.15rem 0.4rem;
font-size: 0.7rem;
}
@@ -194,6 +466,7 @@ section p { margin-bottom: 0.75rem; color: var(--ink); }
.ext { margin-left: auto; font-size: 0.75rem; }
.ext a { color: var(--muted); }
+.ext a:hover { color: var(--link); }
.endpoint > p,
.endpoint > ul {
@@ -208,11 +481,17 @@ section p { margin-bottom: 0.75rem; color: var(--ink); }
font-size: 0.85rem;
}
+.table-wrap {
+ overflow-x: auto;
+ margin-top: 0.75rem;
+ -webkit-overflow-scrolling: touch;
+}
+
.spec-table {
width: 100%;
+ min-width: 20rem;
border-collapse: collapse;
font-size: 0.9rem;
- margin-top: 0.75rem;
}
.spec-table th,
@@ -235,7 +514,7 @@ section p { margin-bottom: 0.75rem; color: var(--ink); }
color: var(--accent);
}
-.spec-table a { color: var(--accent); }
+.spec-table a { color: var(--link); }
.contact-grid {
display: grid;
@@ -259,16 +538,18 @@ section p { margin-bottom: 0.75rem; color: var(--ink); }
font-family: var(--mono);
font-size: 0.85rem;
transition: background 0.15s;
+ min-height: 2.75rem;
}
-.contact-card:hover {
+.contact-card:hover,
+.contact-card:focus-visible {
background: var(--accent-bg);
}
.method.post {
font-weight: 600;
- color: #8b4513;
- background: #fde8d8;
+ color: var(--badge-post-fg);
+ background: var(--badge-post-bg);
align-self: flex-start;
padding: 0.1rem 0.35rem;
font-size: 0.65rem;
diff --git a/spec/spec.js b/spec/spec.js
index 076660d..d9af9f2 100644
--- a/spec/spec.js
+++ b/spec/spec.js
@@ -1,17 +1,114 @@
-const sections = document.querySelectorAll('section[id]');
-const links = document.querySelectorAll('.toc nav a');
+const STORAGE_KEY = 'levkin-spec-prefs';
+const FONT_STEPS = [0.875, 1, 1.125, 1.25, 1.375, 1.5];
+const DEFAULT_FONT_INDEX = 1;
-const observer = new IntersectionObserver(
- (entries) => {
- entries.forEach((entry) => {
- if (!entry.isIntersecting) return;
- links.forEach((a) => {
- a.style.fontWeight = a.getAttribute('href') === `#${entry.target.id}` ? '600' : '';
- a.style.color = a.getAttribute('href') === `#${entry.target.id}` ? 'var(--accent)' : '';
- });
+function loadPrefs() {
+ try {
+ return JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}');
+ } catch {
+ return {};
+ }
+}
+
+function savePrefs(prefs) {
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(prefs));
+}
+
+function fontIndexFromScale(scale) {
+ const idx = FONT_STEPS.indexOf(scale);
+ return idx === -1 ? DEFAULT_FONT_INDEX : idx;
+}
+
+function applyTheme(theme) {
+ document.documentElement.dataset.theme = theme;
+ document.documentElement.style.colorScheme = theme === 'dark' ? 'dark' : theme === 'dim' ? 'light' : 'light';
+ const input = document.querySelector(`input[name="theme"][value="${theme}"]`);
+ if (input) input.checked = true;
+}
+
+function applyFontScale(scale) {
+ document.documentElement.style.setProperty('--font-scale', String(scale));
+ const readout = document.getElementById('font-scale-readout');
+ if (readout) readout.textContent = `${Math.round(scale * 100)}%`;
+ document.querySelectorAll('.font-btn').forEach((btn) => {
+ const action = btn.dataset.action;
+ if (action === 'decrease') btn.disabled = scale <= FONT_STEPS[0];
+ if (action === 'increase') btn.disabled = scale >= FONT_STEPS[FONT_STEPS.length - 1];
+ });
+}
+
+function initPreferences() {
+ const prefs = loadPrefs();
+ const theme = prefs.theme || 'light';
+ const fontScale = prefs.fontScale || FONT_STEPS[DEFAULT_FONT_INDEX];
+ let fontIndex = fontIndexFromScale(fontScale);
+
+ applyTheme(theme);
+ applyFontScale(fontScale);
+
+ document.querySelectorAll('input[name="theme"]').forEach((input) => {
+ input.addEventListener('change', () => {
+ const next = { ...loadPrefs(), theme: input.value };
+ savePrefs(next);
+ applyTheme(input.value);
});
- },
- { rootMargin: '-30% 0px -60% 0px' }
-);
+ });
-sections.forEach((s) => observer.observe(s));
+ document.querySelectorAll('.font-btn').forEach((btn) => {
+ btn.addEventListener('click', () => {
+ const action = btn.dataset.action;
+ if (action === 'reset') fontIndex = DEFAULT_FONT_INDEX;
+ else if (action === 'decrease' && fontIndex > 0) fontIndex -= 1;
+ else if (action === 'increase' && fontIndex < FONT_STEPS.length - 1) fontIndex += 1;
+
+ const scale = FONT_STEPS[fontIndex];
+ const next = { ...loadPrefs(), fontScale: scale };
+ savePrefs(next);
+ applyFontScale(scale);
+ });
+ });
+}
+
+function initScrollSpy() {
+ const sections = document.querySelectorAll('section[id]');
+ const links = document.querySelectorAll('.toc nav a');
+ if (!sections.length || !links.length) return;
+
+ const setActive = (id) => {
+ links.forEach((a) => {
+ const active = a.getAttribute('href') === `#${id}`;
+ a.classList.toggle('is-active', active);
+ if (active) a.setAttribute('aria-current', 'location');
+ else a.removeAttribute('aria-current');
+ });
+ };
+
+ if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
+ const onScroll = () => {
+ let current = sections[0]?.id;
+ const offset = 120;
+ sections.forEach((section) => {
+ if (section.getBoundingClientRect().top - offset <= 0) current = section.id;
+ });
+ if (current) setActive(current);
+ };
+ window.addEventListener('scroll', onScroll, { passive: true });
+ onScroll();
+ return;
+ }
+
+ const observer = new IntersectionObserver(
+ (entries) => {
+ entries.forEach((entry) => {
+ if (!entry.isIntersecting) return;
+ setActive(entry.target.id);
+ });
+ },
+ { rootMargin: '-30% 0px -60% 0px' }
+ );
+
+ sections.forEach((s) => observer.observe(s));
+}
+
+initPreferences();
+initScrollSpy();
diff --git a/stack-folder/folder.css b/stack-folder/folder.css
index 9fe978c..10a6f7a 100644
--- a/stack-folder/folder.css
+++ b/stack-folder/folder.css
@@ -60,18 +60,14 @@ body { font-family: var(--sans); background: #2a2824; color: #1a1814; }
padding: var(--stack-nav) 1rem 0;
}
-/* One sticky unit: tab + body move together */
.folder {
- position: sticky;
+ position: relative;
width: 100%;
- margin: 0;
- padding: 0;
- background: transparent;
}
-/* Tab on top — NOT sticky alone; offset per layer */
+/* Tabs stick in staggered rail — always above folder bodies */
.tab {
- position: relative;
+ position: sticky;
display: block;
width: fit-content;
max-width: calc(100% - 1rem);
@@ -87,41 +83,31 @@ body { font-family: var(--sans); background: #2a2824; color: #1a1814; }
text-align: left;
box-shadow: 0 -2px 8px rgba(0,0,0,0.12);
transition: filter 0.15s;
+ z-index: 60;
}
.tab:hover { filter: brightness(1.06); }
+/* Bodies share one stick line; active layer z-index set in JS */
.body {
+ position: sticky;
+ top: var(--stack-reveal);
background: #e8e2d4;
border: 1px solid #c4b8a8;
- border-top: none;
border-radius: 0 10px 10px 10px;
padding: 1.25rem 1.4rem 2rem;
- min-height: calc(100vh - var(--stack-nav) - 5rem);
- box-shadow: 0 10px 32px rgba(0,0,0,0.2);
+ min-height: var(--stack-body-h);
+ box-shadow: 0 10px 32px rgba(0,0,0,0.25);
+ z-index: 10;
}
-/* Sticky top steps — tabs stay visible above lower folders */
-.f0 { top: calc(var(--stack-stick) + var(--stack-step) * 0); z-index: 10; }
-.f0 .tab { margin-left: calc(var(--tab-offset) * 0); background: #c9a86c; color: #2a2824; }
-
-.f1 { top: calc(var(--stack-stick) + var(--stack-step) * 1); z-index: 11; }
-.f1 .tab { margin-left: calc(var(--tab-offset) * 1); background: #a8c4d4; color: #1a2830; }
-
-.f2 { top: calc(var(--stack-stick) + var(--stack-step) * 2); z-index: 12; }
-.f2 .tab { margin-left: calc(var(--tab-offset) * 2); background: #b8d4a8; color: #1a2818; }
-
-.f3 { top: calc(var(--stack-stick) + var(--stack-step) * 3); z-index: 13; }
-.f3 .tab { margin-left: calc(var(--tab-offset) * 3); background: #d4b8c4; color: #2a1820; }
-
-.f4 { top: calc(var(--stack-stick) + var(--stack-step) * 4); z-index: 14; }
-.f4 .tab { margin-left: calc(var(--tab-offset) * 4); background: #d4c8a8; color: #2a2418; }
-
-.f5 { top: calc(var(--stack-stick) + var(--stack-step) * 5); z-index: 15; }
-.f5 .tab { margin-left: calc(var(--tab-offset) * 5); background: #c4c4c4; color: #2a2a2a; }
-
-.f6 { top: calc(var(--stack-stick) + var(--stack-step) * 6); z-index: 16; }
-.f6 .tab { margin-left: calc(var(--tab-offset) * 6); background: #2a4a6b; color: #e8e2d4; }
+.f0 .tab { top: calc(var(--stack-stick) + var(--stack-step) * 0); margin-left: calc(var(--tab-offset) * 0); background: #c9a86c; color: #2a2824; }
+.f1 .tab { top: calc(var(--stack-stick) + var(--stack-step) * 1); margin-left: calc(var(--tab-offset) * 1); background: #a8c4d4; color: #1a2830; }
+.f2 .tab { top: calc(var(--stack-stick) + var(--stack-step) * 2); margin-left: calc(var(--tab-offset) * 2); background: #b8d4a8; color: #1a2818; }
+.f3 .tab { top: calc(var(--stack-stick) + var(--stack-step) * 3); margin-left: calc(var(--tab-offset) * 3); background: #d4b8c4; color: #2a1820; }
+.f4 .tab { top: calc(var(--stack-stick) + var(--stack-step) * 4); margin-left: calc(var(--tab-offset) * 4); background: #d4c8a8; color: #2a2418; }
+.f5 .tab { top: calc(var(--stack-stick) + var(--stack-step) * 5); margin-left: calc(var(--tab-offset) * 5); background: #c4c4c4; color: #2a2a2a; }
+.f6 .tab { top: calc(var(--stack-stick) + var(--stack-step) * 6); margin-left: calc(var(--tab-offset) * 6); background: #2a4a6b; color: #e8e2d4; }
.body h1 { font-size: 1.65rem; margin-bottom: 0.35rem; }
.body h2 { font-size: 1.25rem; margin-bottom: 0.4rem; }
diff --git a/stack-rack/rack.css b/stack-rack/rack.css
index 8a9c707..2de8ba6 100644
--- a/stack-rack/rack.css
+++ b/stack-rack/rack.css
@@ -37,16 +37,17 @@ body {
.mount { padding: 0 0.25rem; }
.unit {
- position: sticky;
+ position: relative;
margin: 0;
border: 1px solid #2a3448;
background: #161a22;
border-radius: 2px;
- overflow: hidden;
box-shadow: 0 6px 0 #0a0c10, 0 12px 24px rgba(0,0,0,0.4);
}
.unit-head {
+ position: sticky;
+ z-index: 60;
display: flex; align-items: center; gap: 0.5rem;
padding: 0.4rem 0.65rem;
background: #1a2030;
@@ -71,21 +72,25 @@ body {
.unit-head a.ext { color: #60a5fa; text-decoration: none; font-size: 0.58rem; }
.unit-body {
+ position: sticky;
+ top: var(--stack-reveal);
padding: 0.6rem 0.7rem 2rem;
- min-height: calc(100vh - var(--stack-nav) - 5rem);
+ min-height: var(--stack-body-h);
+ z-index: 10;
+ background: #161a22;
}
.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; }
-.u0 { top: calc(var(--stack-stick) + var(--stack-step) * 0); z-index: 10; }
-.u1 { top: calc(var(--stack-stick) + var(--stack-step) * 1); z-index: 11; }
-.u2 { top: calc(var(--stack-stick) + var(--stack-step) * 2); z-index: 12; }
-.u3 { top: calc(var(--stack-stick) + var(--stack-step) * 3); z-index: 13; }
-.u4 { top: calc(var(--stack-stick) + var(--stack-step) * 4); z-index: 14; }
-.u5 { top: calc(var(--stack-stick) + var(--stack-step) * 5); z-index: 15; }
-.u6 { top: calc(var(--stack-stick) + var(--stack-step) * 6); z-index: 16; }
+.u0 .unit-head { top: calc(var(--stack-stick) + var(--stack-step) * 0); }
+.u1 .unit-head { top: calc(var(--stack-stick) + var(--stack-step) * 1); }
+.u2 .unit-head { top: calc(var(--stack-stick) + var(--stack-step) * 2); }
+.u3 .unit-head { top: calc(var(--stack-stick) + var(--stack-step) * 3); }
+.u4 .unit-head { top: calc(var(--stack-stick) + var(--stack-step) * 4); }
+.u5 .unit-head { top: calc(var(--stack-stick) + var(--stack-step) * 5); }
+.u6 .unit-head { top: calc(var(--stack-stick) + var(--stack-step) * 6); }
.foot {
display: flex; justify-content: space-between;
diff --git a/stack-trace/trace.css b/stack-trace/trace.css
index 35506a1..960da83 100644
--- a/stack-trace/trace.css
+++ b/stack-trace/trace.css
@@ -27,26 +27,37 @@ body {
}
.frame {
- position: sticky;
+ position: relative;
margin: 0 0.5rem;
border-left: 3px solid #3a3a44;
background: #141418;
- padding: 0.65rem 0 0.65rem 1rem;
- min-height: calc(100vh - var(--stack-nav) - 5rem);
box-shadow: 0 8px 0 #0a0a0c, 0 14px 28px rgba(0,0,0,0.45);
}
+.frame-line {
+ position: sticky;
+ z-index: 60;
+ padding: 0.65rem 0 0.35rem 1rem;
+ background: #141418;
+}
+
+.frame-body {
+ position: sticky;
+ top: var(--stack-reveal);
+ padding: 0 0 2rem 1rem;
+ min-height: var(--stack-body-h);
+ z-index: 10;
+ background: #141418;
+}
+
.frame-line {
display: block;
font-size: 0.66rem;
color: #6b9b6b;
- margin-bottom: 0.4rem;
- background: none;
border: none;
font-family: inherit;
cursor: pointer;
text-align: left;
- padding: 0;
width: 100%;
}
@@ -56,13 +67,16 @@ body {
.frame-body p { color: #6b6966; font-size: 0.74rem; }
.frame-body a { color: #8b9cb3; text-decoration: none; }
-.f0 { top: calc(var(--stack-stick) + var(--stack-step) * 0); z-index: 10; border-color: #c4a574; }
-.f1 { top: calc(var(--stack-stick) + var(--stack-step) * 1); z-index: 11; }
-.f2 { top: calc(var(--stack-stick) + var(--stack-step) * 2); z-index: 12; }
-.f3 { top: calc(var(--stack-stick) + var(--stack-step) * 3); z-index: 13; }
-.f4 { top: calc(var(--stack-stick) + var(--stack-step) * 4); z-index: 14; border-color: #6b8b9b; }
-.f5 { top: calc(var(--stack-stick) + var(--stack-step) * 5); z-index: 15; }
-.f6 { top: calc(var(--stack-stick) + var(--stack-step) * 6); z-index: 16; border-color: #7eb87a; }
+.f0 .frame-line { top: calc(var(--stack-stick) + var(--stack-step) * 0); }
+.f0 { border-color: #c4a574; }
+.f1 .frame-line { top: calc(var(--stack-stick) + var(--stack-step) * 1); }
+.f2 .frame-line { top: calc(var(--stack-stick) + var(--stack-step) * 2); }
+.f3 .frame-line { top: calc(var(--stack-stick) + var(--stack-step) * 3); }
+.f4 .frame-line { top: calc(var(--stack-stick) + var(--stack-step) * 4); }
+.f4 { border-color: #6b8b9b; }
+.f5 .frame-line { top: calc(var(--stack-stick) + var(--stack-step) * 5); }
+.f6 .frame-line { top: calc(var(--stack-stick) + var(--stack-step) * 6); }
+.f6 { border-color: #7eb87a; }
.foot {
display: flex; justify-content: space-between;
diff --git a/stack/stack.css b/stack/stack.css
index 8d0ecd0..f359633 100644
--- a/stack/stack.css
+++ b/stack/stack.css
@@ -35,16 +35,26 @@ body {
}
.layer {
- position: sticky;
+ position: relative;
margin: 0;
border-radius: 8px 8px 6px 6px;
border: 1px solid rgba(255,255,255,0.08);
box-shadow: 0 12px 36px rgba(0,0,0,0.5);
}
+.layer-inner {
+ position: sticky;
+ top: var(--stack-reveal);
+ z-index: 10;
+}
+
.layer-tab {
- position: absolute;
- top: -6px; left: 10px; right: 10px; height: 6px;
+ position: sticky;
+ z-index: 55;
+ left: 10px;
+ right: 10px;
+ height: 6px;
+ margin-bottom: -6px;
border-radius: 5px 5px 0 0;
background: inherit;
filter: brightness(1.12);
@@ -53,17 +63,24 @@ body {
pointer-events: none;
}
-.layer-0 { z-index: 10; background: #1c1c20; top: calc(var(--stack-stick) + var(--stack-step) * 0); }
-.layer-1 { z-index: 11; background: #24242c; top: calc(var(--stack-stick) + var(--stack-step) * 1); }
-.layer-2 { z-index: 12; background: #2c2c36; top: calc(var(--stack-stick) + var(--stack-step) * 2); }
-.layer-3 { z-index: 13; background: #343440; top: calc(var(--stack-stick) + var(--stack-step) * 3); }
-.layer-4 { z-index: 14; background: #3c3c4a; top: calc(var(--stack-stick) + var(--stack-step) * 4); }
-.layer-5 { z-index: 15; background: #444454; top: calc(var(--stack-stick) + var(--stack-step) * 5); }
-.layer-6 { z-index: 16; background: #4c4c5e; top: calc(var(--stack-stick) + var(--stack-step) * 6); }
+.layer-0 { background: #1c1c20; }
+.layer-0 .layer-tab { top: calc(var(--stack-stick) + var(--stack-step) * 0); }
+.layer-1 { background: #24242c; }
+.layer-1 .layer-tab { top: calc(var(--stack-stick) + var(--stack-step) * 1); }
+.layer-2 { background: #2c2c36; }
+.layer-2 .layer-tab { top: calc(var(--stack-stick) + var(--stack-step) * 2); }
+.layer-3 { background: #343440; }
+.layer-3 .layer-tab { top: calc(var(--stack-stick) + var(--stack-step) * 3); }
+.layer-4 { background: #3c3c4a; }
+.layer-4 .layer-tab { top: calc(var(--stack-stick) + var(--stack-step) * 4); }
+.layer-5 { background: #444454; }
+.layer-5 .layer-tab { top: calc(var(--stack-stick) + var(--stack-step) * 5); }
+.layer-6 { background: #4c4c5e; }
+.layer-6 .layer-tab { top: calc(var(--stack-stick) + var(--stack-step) * 6); }
.layer-inner {
padding: 1.2rem 1.35rem 2rem;
- min-height: calc(100vh - var(--stack-nav) - 5rem);
+ min-height: var(--stack-body-h);
}
.layer-head {