Compare commits
5 Commits
af1a5efb60
...
3c35c5eba0
Author | SHA1 | Date |
---|---|---|
|
3c35c5eba0 | |
|
cbb5a04563 | |
|
8f35aa626c | |
|
82bc0eb349 | |
|
7e06e335a9 |
|
@ -93,4 +93,5 @@ This site is designed to meet WCAG 2.1 Level AA+ standards. Key accessibility fe
|
|||
|
||||
## License
|
||||
|
||||
ISC © Colin Knapp
|
||||
ISC © Colin Knapp # Build trigger Wed Oct 15 20:36:46 EDT 2025
|
||||
# Build trigger Wed Oct 15 20:36:46 EDT 2025
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
version: "3.9"
|
||||
|
||||
services:
|
||||
resume:
|
||||
image: caddy:2-alpine
|
||||
container_name: resume-caddy
|
||||
working_dir: /srv
|
||||
volumes:
|
||||
- ./docker/resume:/srv:ro
|
||||
ports:
|
||||
- "8081:8080"
|
||||
command: ["caddy", "run", "--config", "/srv/Caddyfile.local"]
|
||||
restart: unless-stopped
|
|
@ -10,7 +10,7 @@
|
|||
<link rel="stylesheet" href="tool-styles.css" integrity="sha256-Y+6RTuKMnPfNa1TjCQCcFhxwo0G+xNy7t1MaAvn5SuU=">
|
||||
<link rel="stylesheet" href="csv-tool-fix.css?v=2" integrity="sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=">
|
||||
<script src="../theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4="></script>
|
||||
<script src="../includes.js" integrity="sha256-q9ac7XWqnIASoBRfs4I4hpSMlnxGARofcEw0cSFfn/E="></script>
|
||||
<script src="../includes.js" integrity="sha256-0VPPSi+jVc1DuyZaSYTq+fnpIfv7ft+ZDenYE6pDPqA="></script>
|
||||
<script src="../utils.js" integrity="sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544="></script>
|
||||
<style>
|
||||
/* Additional inline styles to fix layout */
|
||||
|
|
|
@ -102,22 +102,22 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
const link = document.getElementById('nav-viperwire');
|
||||
if (link) link.classList.add('active');
|
||||
} else if (currentPath.includes('fawe-plotsquared.html')) {
|
||||
const link = document.getElementById('nav-fawe');
|
||||
const link = document.getElementById('nav-faweplotsquared');
|
||||
if (link) link.classList.add('active');
|
||||
} else if (currentPath.includes('healthcare-platform.html')) {
|
||||
const link = document.getElementById('nav-healthcare');
|
||||
const link = document.getElementById('nav-healthcareplatform');
|
||||
if (link) link.classList.add('active');
|
||||
} else if (currentPath.includes('wordpress-security.html')) {
|
||||
const link = document.getElementById('nav-wordpress');
|
||||
const link = document.getElementById('nav-wordpresssecurity');
|
||||
if (link) link.classList.add('active');
|
||||
} else if (currentPath.includes('airport-dns.html')) {
|
||||
const link = document.getElementById('nav-airport');
|
||||
const link = document.getElementById('nav-airportdns');
|
||||
if (link) link.classList.add('active');
|
||||
} else if (currentPath.includes('nitric-leadership.html')) {
|
||||
const link = document.getElementById('nav-nitric');
|
||||
const link = document.getElementById('nav-nitricleadership');
|
||||
if (link) link.classList.add('active');
|
||||
} else if (currentPath.includes('open-source-success.html')) {
|
||||
const link = document.getElementById('nav-opensource');
|
||||
const link = document.getElementById('nav-opensourcesuccess');
|
||||
if (link) link.classList.add('active');
|
||||
}
|
||||
} else if (currentPath.includes('/one-pager-tools/')) {
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<link rel="stylesheet" href="styles.css" integrity="sha256-Y+6RTuKMnPfNa1TjCQCcFhxwo0G+xNy7t1MaAvn5SuU=">
|
||||
<script src="theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4="></script>
|
||||
<script src="includes.js" integrity="sha256-q9ac7XWqnIASoBRfs4I4hpSMlnxGARofcEw0cSFfn/E="></script>
|
||||
<script src="includes.js" integrity="sha256-0VPPSi+jVc1DuyZaSYTq+fnpIfv7ft+ZDenYE6pDPqA="></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header Include -->
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<link rel="stylesheet" href="styles.css" integrity="sha256-Y+6RTuKMnPfNa1TjCQCcFhxwo0G+xNy7t1MaAvn5SuU=">
|
||||
<script src="theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4="></script>
|
||||
<script src="includes.js" integrity="sha256-q9ac7XWqnIASoBRfs4I4hpSMlnxGARofcEw0cSFfn/E="></script>
|
||||
<script src="includes.js" integrity="sha256-0VPPSi+jVc1DuyZaSYTq+fnpIfv7ft+ZDenYE6pDPqA="></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header Include -->
|
||||
|
@ -249,7 +249,7 @@
|
|||
</p>
|
||||
<p><strong>Key Contributions:</strong></p>
|
||||
<ul>
|
||||
<li>Lurked in IRC chat rooms of various plugin and software developers to assess reliable open source projects for early career dependencies.</li>
|
||||
<li>Idled in IRC chat rooms of various plugin and software developers to assess reliable open source projects for early career dependencies.</li>
|
||||
<li>Offered CI/CD services and Java Maven repository hosting to open source developers, building goodwill and reciprocal support networks.</li>
|
||||
<li>Utilized Jenkins and GitLab CI/CD for streamlined workflows, leveraging a robust toolchain for rapid development.</li>
|
||||
<li>Managed complex systems and ensured WCAG 2.0 AA accessibility standards.</li>
|
||||
|
@ -294,7 +294,7 @@
|
|||
<li>Bootstrapped the company from zero to an estimated $4M gross revenue with strong profit margins.</li>
|
||||
<li>Utilized Kanban/Trello to coordinate distributed teams and project workflows across multiple timezones.</li>
|
||||
</ul>
|
||||
<p><strong>Impact:</strong> Transformed Nitric Concepts into a thriving multinational entity through prolific and efficient development, while mentoring Andrew Karvelis from side project earnings to being able to purchase a Corvette C7 in cash from one month's disposable income.</p>
|
||||
<p><strong>Impact:</strong> Transformed Nitric Concepts into a thriving multinational entity through prolific and efficient development, while mentoring Andrew Karvelis from side project earnings to significant revenue growth and financial success.</p>
|
||||
<p><a href="stories/nitric-leadership.html" class="read-more">Read more about my leadership at Nitric Concepts →</a></p>
|
||||
</article>
|
||||
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
// Load same-named .md and render into .story-content using Marked. No fallbacks.
|
||||
(function () {
|
||||
function getMarkdownPath() {
|
||||
var parts = (window.location.pathname || '').split('/');
|
||||
var last = parts[parts.length - 1] || '';
|
||||
if (!last) return '';
|
||||
return last.replace(/\.html?$/i, '.md');
|
||||
}
|
||||
|
||||
function loadMarked(callback) {
|
||||
if (window.marked && typeof window.marked.parse === 'function') {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
var script = document.createElement('script');
|
||||
script.src = 'https://cdn.jsdelivr.net/npm/marked/marked.min.js';
|
||||
script.async = true;
|
||||
script.onload = callback;
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
|
||||
function renderMarkdown(container, text) {
|
||||
if (!window.marked || typeof window.marked.parse !== 'function') {
|
||||
container.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (typeof window.marked.setOptions === 'function') {
|
||||
window.marked.setOptions({ gfm: true, breaks: true });
|
||||
}
|
||||
container.innerHTML = window.marked.parse(text);
|
||||
} catch (_) {
|
||||
container.innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
var container = document.querySelector('.story-content');
|
||||
if (!container) return;
|
||||
|
||||
// No fallback: clear immediately
|
||||
container.innerHTML = '';
|
||||
|
||||
var mdPath = getMarkdownPath();
|
||||
if (!mdPath) return;
|
||||
|
||||
loadMarked(function () {
|
||||
fetch(mdPath, { cache: 'no-cache' })
|
||||
.then(function (res) { if (!res.ok) throw new Error('md'); return res.text(); })
|
||||
.then(function (text) { renderMarkdown(container, text); })
|
||||
.catch(function () { container.innerHTML = ''; });
|
||||
});
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
})();
|
||||
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
<link rel="stylesheet" href="tool-styles.css?v=2" integrity="sha256-Y+6RTuKMnPfNa1TjCQCcFhxwo0G+xNy7t1MaAvn5SuU=">
|
||||
<link rel="stylesheet" href="csv-tool-fix.css?v=2" integrity="sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=">
|
||||
<script src="../theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4="></script>
|
||||
<script src="../includes.js" integrity="sha256-q9ac7XWqnIASoBRfs4I4hpSMlnxGARofcEw0cSFfn/E="></script>
|
||||
<script src="../includes.js" integrity="sha256-0VPPSi+jVc1DuyZaSYTq+fnpIfv7ft+ZDenYE6pDPqA="></script>
|
||||
<script src="../utils.js" integrity="sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544="></script>
|
||||
<style>
|
||||
/* Additional inline styles to fix layout */
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<link rel="stylesheet" href="../styles.css" integrity="sha256-Y+6RTuKMnPfNa1TjCQCcFhxwo0G+xNy7t1MaAvn5SuU=" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="tool-styles.css" integrity="sha256-Y+6RTuKMnPfNa1TjCQCcFhxwo0G+xNy7t1MaAvn5SuU=">
|
||||
<script src="../theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=" crossorigin="anonymous"></script>
|
||||
<script src="../includes.js" integrity="sha256-q9ac7XWqnIASoBRfs4I4hpSMlnxGARofcEw0cSFfn/E=" crossorigin="anonymous"></script>
|
||||
<script src="../includes.js" integrity="sha256-0VPPSi+jVc1DuyZaSYTq+fnpIfv7ft+ZDenYE6pDPqA=" crossorigin="anonymous"></script>
|
||||
<script src="../utils.js" integrity="sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544=" crossorigin="anonymous"></script>
|
||||
<!-- Add tool-specific scripts here -->
|
||||
</head>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<link rel="stylesheet" href="tool-styles.css" integrity="sha256-Y+6RTuKMnPfNa1TjCQCcFhxwo0G+xNy7t1MaAvn5SuU=">
|
||||
<script src="../theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4="></script>
|
||||
<script src="../utils.js" integrity="sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544="></script>
|
||||
<script src="../includes.js" integrity="sha256-q9ac7XWqnIASoBRfs4I4hpSMlnxGARofcEw0cSFfn/E="></script>
|
||||
<script src="../includes.js" integrity="sha256-0VPPSi+jVc1DuyZaSYTq+fnpIfv7ft+ZDenYE6pDPqA="></script>
|
||||
<script src="tool-example.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
@ -8,8 +8,9 @@
|
|||
<link rel="icon" type="image/x-icon" href="../favicon.ico">
|
||||
<link rel="stylesheet" href="../styles.css" integrity="sha256-Y+6RTuKMnPfNa1TjCQCcFhxwo0G+xNy7t1MaAvn5SuU=" crossorigin="anonymous">
|
||||
<script src="../theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=" crossorigin="anonymous"></script>
|
||||
<script src="../includes.js" integrity="sha256-q9ac7XWqnIASoBRfs4I4hpSMlnxGARofcEw0cSFfn/E=" crossorigin="anonymous"></script>
|
||||
<script src="../includes.js" integrity="sha256-0VPPSi+jVc1DuyZaSYTq+fnpIfv7ft+ZDenYE6pDPqA=" crossorigin="anonymous"></script>
|
||||
<link rel="stylesheet" href="stories.css" integrity="sha256-O+OMb48leSKvekhMTDUK1y6+WG9x33kA0eDw00wUwkY=">
|
||||
<script src="../markdown-loader.js" integrity="sha256-4+erbuMKlaalnlqc0+5d+X4Bpr1CZ7W3dUCsyA15spE="></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header Include -->
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
# airport dns
|
||||
|
||||
> Draft placeholder. Content to be written.
|
||||
|
||||
- Summary: TBD
|
||||
- Key outcomes: TBD
|
||||
- Tech stack: TBD
|
||||
- Challenges: TBD
|
||||
- Results: TBD
|
||||
|
|
@ -8,8 +8,9 @@
|
|||
<link rel="icon" type="image/x-icon" href="../favicon.ico">
|
||||
<link rel="stylesheet" href="../styles.css" integrity="sha256-Y+6RTuKMnPfNa1TjCQCcFhxwo0G+xNy7t1MaAvn5SuU=" crossorigin="anonymous">
|
||||
<script src="../theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=" crossorigin="anonymous"></script>
|
||||
<script src="../includes.js" integrity="sha256-q9ac7XWqnIASoBRfs4I4hpSMlnxGARofcEw0cSFfn/E=" crossorigin="anonymous"></script>
|
||||
<script src="../includes.js" integrity="sha256-0VPPSi+jVc1DuyZaSYTq+fnpIfv7ft+ZDenYE6pDPqA=" crossorigin="anonymous"></script>
|
||||
<link rel="stylesheet" href="stories.css" integrity="sha256-O+OMb48leSKvekhMTDUK1y6+WG9x33kA0eDw00wUwkY=">
|
||||
<script src="../markdown-loader.js" integrity="sha256-4+erbuMKlaalnlqc0+5d+X4Bpr1CZ7W3dUCsyA15spE="></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header Include -->
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
# app development
|
||||
|
||||
> Draft placeholder. Content to be written.
|
||||
|
||||
- Summary: TBD
|
||||
- Key outcomes: TBD
|
||||
- Tech stack: TBD
|
||||
- Challenges: TBD
|
||||
- Results: TBD
|
||||
|
|
@ -8,8 +8,9 @@
|
|||
<link rel="icon" type="image/x-icon" href="../favicon.ico">
|
||||
<link rel="stylesheet" href="../styles.css" integrity="sha256-Y+6RTuKMnPfNa1TjCQCcFhxwo0G+xNy7t1MaAvn5SuU=" crossorigin="anonymous">
|
||||
<script src="../theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=" crossorigin="anonymous"></script>
|
||||
<script src="../includes.js" integrity="sha256-q9ac7XWqnIASoBRfs4I4hpSMlnxGARofcEw0cSFfn/E=" crossorigin="anonymous"></script>
|
||||
<script src="../includes.js" integrity="sha256-0VPPSi+jVc1DuyZaSYTq+fnpIfv7ft+ZDenYE6pDPqA=" crossorigin="anonymous"></script>
|
||||
<link rel="stylesheet" href="stories.css" integrity="sha256-O+OMb48leSKvekhMTDUK1y6+WG9x33kA0eDw00wUwkY=">
|
||||
<script src="../markdown-loader.js" integrity="sha256-4+erbuMKlaalnlqc0+5d+X4Bpr1CZ7W3dUCsyA15spE="></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header Include -->
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
# athion turnaround
|
||||
|
||||
> Draft placeholder. Content to be written.
|
||||
|
||||
- Summary: TBD
|
||||
- Key outcomes: TBD
|
||||
- Tech stack: TBD
|
||||
- Challenges: TBD
|
||||
- Results: TBD
|
||||
|
|
@ -8,8 +8,9 @@
|
|||
<link rel="icon" type="image/x-icon" href="../favicon.ico">
|
||||
<link rel="stylesheet" href="../styles.css" integrity="sha256-Y+6RTuKMnPfNa1TjCQCcFhxwo0G+xNy7t1MaAvn5SuU=" crossorigin="anonymous">
|
||||
<script src="../theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=" crossorigin="anonymous"></script>
|
||||
<script src="../includes.js" integrity="sha256-q9ac7XWqnIASoBRfs4I4hpSMlnxGARofcEw0cSFfn/E=" crossorigin="anonymous"></script>
|
||||
<script src="../includes.js" integrity="sha256-0VPPSi+jVc1DuyZaSYTq+fnpIfv7ft+ZDenYE6pDPqA=" crossorigin="anonymous"></script>
|
||||
<link rel="stylesheet" href="stories.css" integrity="sha256-O+OMb48leSKvekhMTDUK1y6+WG9x33kA0eDw00wUwkY=">
|
||||
<script src="../markdown-loader.js" integrity="sha256-4+erbuMKlaalnlqc0+5d+X4Bpr1CZ7W3dUCsyA15spE="></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header Include -->
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
# fawe plotsquared
|
||||
|
||||
> Draft placeholder. Content to be written.
|
||||
|
||||
- Summary: TBD
|
||||
- Key outcomes: TBD
|
||||
- Tech stack: TBD
|
||||
- Challenges: TBD
|
||||
- Results: TBD
|
||||
|
|
@ -8,8 +8,9 @@
|
|||
<link rel="icon" type="image/x-icon" href="../favicon.ico">
|
||||
<link rel="stylesheet" href="../styles.css" integrity="sha256-Y+6RTuKMnPfNa1TjCQCcFhxwo0G+xNy7t1MaAvn5SuU=" crossorigin="anonymous">
|
||||
<script src="../theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=" crossorigin="anonymous"></script>
|
||||
<script src="../includes.js" integrity="sha256-q9ac7XWqnIASoBRfs4I4hpSMlnxGARofcEw0cSFfn/E=" crossorigin="anonymous"></script>
|
||||
<script src="../includes.js" integrity="sha256-0VPPSi+jVc1DuyZaSYTq+fnpIfv7ft+ZDenYE6pDPqA=" crossorigin="anonymous"></script>
|
||||
<link rel="stylesheet" href="stories.css" integrity="sha256-O+OMb48leSKvekhMTDUK1y6+WG9x33kA0eDw00wUwkY=">
|
||||
<script src="../markdown-loader.js" integrity="sha256-4+erbuMKlaalnlqc0+5d+X4Bpr1CZ7W3dUCsyA15spE="></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header Include -->
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
# healthcare platform
|
||||
|
||||
> Draft placeholder. Content to be written.
|
||||
|
||||
- Summary: TBD
|
||||
- Key outcomes: TBD
|
||||
- Tech stack: TBD
|
||||
- Challenges: TBD
|
||||
- Results: TBD
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
<link rel="stylesheet" href="../styles.css" integrity="sha256-Y+6RTuKMnPfNa1TjCQCcFhxwo0G+xNy7t1MaAvn5SuU=">
|
||||
<link rel="stylesheet" href="stories.css" integrity="sha256-O+OMb48leSKvekhMTDUK1y6+WG9x33kA0eDw00wUwkY=">
|
||||
<script src="../theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4="></script>
|
||||
<script src="../includes.js" integrity="sha256-q9ac7XWqnIASoBRfs4I4hpSMlnxGARofcEw0cSFfn/E="></script>
|
||||
<script src="../includes.js" integrity="sha256-0VPPSi+jVc1DuyZaSYTq+fnpIfv7ft+ZDenYE6pDPqA="></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header Include -->
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
# home infrastructure
|
||||
|
||||
> Draft placeholder. Content to be written.
|
||||
|
||||
- Summary: TBD
|
||||
- Key outcomes: TBD
|
||||
- Tech stack: TBD
|
||||
- Challenges: TBD
|
||||
- Results: TBD
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
<link rel="icon" type="image/x-icon" href="../favicon.ico">
|
||||
<link rel="stylesheet" href="../styles.css" integrity="sha256-Y+6RTuKMnPfNa1TjCQCcFhxwo0G+xNy7t1MaAvn5SuU=" crossorigin="anonymous">
|
||||
<script src="../theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=" crossorigin="anonymous"></script>
|
||||
<script src="../includes.js" integrity="sha256-q9ac7XWqnIASoBRfs4I4hpSMlnxGARofcEw0cSFfn/E=" crossorigin="anonymous"></script>
|
||||
<script src="../includes.js" integrity="sha256-0VPPSi+jVc1DuyZaSYTq+fnpIfv7ft+ZDenYE6pDPqA=" crossorigin="anonymous"></script>
|
||||
<link rel="stylesheet" href="stories.css" integrity="sha256-O+OMb48leSKvekhMTDUK1y6+WG9x33kA0eDw00wUwkY=">
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
@ -8,8 +8,9 @@
|
|||
<link rel="icon" type="image/x-icon" href="../favicon.ico">
|
||||
<link rel="stylesheet" href="../styles.css" integrity="sha256-Y+6RTuKMnPfNa1TjCQCcFhxwo0G+xNy7t1MaAvn5SuU=" crossorigin="anonymous">
|
||||
<script src="../theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=" crossorigin="anonymous"></script>
|
||||
<script src="../includes.js" integrity="sha256-q9ac7XWqnIASoBRfs4I4hpSMlnxGARofcEw0cSFfn/E=" crossorigin="anonymous"></script>
|
||||
<script src="../includes.js" integrity="sha256-0VPPSi+jVc1DuyZaSYTq+fnpIfv7ft+ZDenYE6pDPqA=" crossorigin="anonymous"></script>
|
||||
<link rel="stylesheet" href="stories.css" integrity="sha256-O+OMb48leSKvekhMTDUK1y6+WG9x33kA0eDw00wUwkY=">
|
||||
<script src="../markdown-loader.js" integrity="sha256-4+erbuMKlaalnlqc0+5d+X4Bpr1CZ7W3dUCsyA15spE="></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header Include -->
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
# motherUoard repair
|
||||
|
||||
> Draft placeholder. Content to be written.
|
||||
|
||||
- Summary: TBD
|
||||
- Key outcomes: TBD
|
||||
- Tech stack: TBD
|
||||
- Challenges: TBD
|
||||
- Results: TBD
|
||||
|
|
@ -8,8 +8,9 @@
|
|||
<link rel="icon" type="image/x-icon" href="../favicon.ico">
|
||||
<link rel="stylesheet" href="../styles.css" integrity="sha256-Y+6RTuKMnPfNa1TjCQCcFhxwo0G+xNy7t1MaAvn5SuU=" crossorigin="anonymous">
|
||||
<script src="../theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=" crossorigin="anonymous"></script>
|
||||
<script src="../includes.js" integrity="sha256-q9ac7XWqnIASoBRfs4I4hpSMlnxGARofcEw0cSFfn/E=" crossorigin="anonymous"></script>
|
||||
<script src="../includes.js" integrity="sha256-0VPPSi+jVc1DuyZaSYTq+fnpIfv7ft+ZDenYE6pDPqA=" crossorigin="anonymous"></script>
|
||||
<link rel="stylesheet" href="stories.css" integrity="sha256-O+OMb48leSKvekhMTDUK1y6+WG9x33kA0eDw00wUwkY=">
|
||||
<script src="../markdown-loader.js" integrity="sha256-4+erbuMKlaalnlqc0+5d+X4Bpr1CZ7W3dUCsyA15spE="></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header Include -->
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
# nitric leadership
|
||||
|
||||
> Draft placeholder. Content to be written.
|
||||
|
||||
- Summary: TBD
|
||||
- Key outcomes: TBD
|
||||
- Tech stack: TBD
|
||||
- Challenges: TBD
|
||||
- Results: TBD
|
||||
|
|
@ -9,7 +9,8 @@
|
|||
<link rel="stylesheet" href="../styles.css" integrity="sha256-Y+6RTuKMnPfNa1TjCQCcFhxwo0G+xNy7t1MaAvn5SuU=">
|
||||
<link rel="stylesheet" href="stories.css" integrity="sha256-O+OMb48leSKvekhMTDUK1y6+WG9x33kA0eDw00wUwkY=">
|
||||
<script src="../theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4="></script>
|
||||
<script src="../includes.js" integrity="sha256-q9ac7XWqnIASoBRfs4I4hpSMlnxGARofcEw0cSFfn/E="></script>
|
||||
<script src="../includes.js" integrity="sha256-0VPPSi+jVc1DuyZaSYTq+fnpIfv7ft+ZDenYE6pDPqA="></script>
|
||||
<script src="../markdown-loader.js" integrity="sha256-4+erbuMKlaalnlqc0+5d+X4Bpr1CZ7W3dUCsyA15spE="></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header Include -->
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
# nuclear dns
|
||||
|
||||
> Draft placeholder. Content to be written.
|
||||
|
||||
- Summary: TBD
|
||||
- Key outcomes: TBD
|
||||
- Tech stack: TBD
|
||||
- Challenges: TBD
|
||||
- Results: TBD
|
||||
|
|
@ -8,8 +8,9 @@
|
|||
<link rel="icon" type="image/x-icon" href="../favicon.ico">
|
||||
<link rel="stylesheet" href="../styles.css" integrity="sha256-Y+6RTuKMnPfNa1TjCQCcFhxwo0G+xNy7t1MaAvn5SuU=">
|
||||
<script src="../theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4="></script>
|
||||
<script src="../includes.js" integrity="sha256-q9ac7XWqnIASoBRfs4I4hpSMlnxGARofcEw0cSFfn/E="></script>
|
||||
<script src="../includes.js" integrity="sha256-0VPPSi+jVc1DuyZaSYTq+fnpIfv7ft+ZDenYE6pDPqA="></script>
|
||||
<link rel="stylesheet" href="stories.css" integrity="sha256-O+OMb48leSKvekhMTDUK1y6+WG9x33kA0eDw00wUwkY=">
|
||||
<script src="../markdown-loader.js" integrity="sha256-4+erbuMKlaalnlqc0+5d+X4Bpr1CZ7W3dUCsyA15spE="></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header Include -->
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
# open source success
|
||||
|
||||
> Draft placeholder. Content to be written.
|
||||
|
||||
- Summary: TBD
|
||||
- Key outcomes: TBD
|
||||
- Tech stack: TBD
|
||||
- Challenges: TBD
|
||||
- Results: TBD
|
||||
|
|
@ -24,89 +24,216 @@
|
|||
<div class="project-meta">
|
||||
<p><strong>Timeframe:</strong> 2025-Present</p>
|
||||
<p><strong>Role:</strong> Full-Stack Developer & DevOps Engineer</p>
|
||||
<p><strong>Technologies:</strong> Python, WebDAV, WsgiDAV, macOS, ScanSnap Integration</p>
|
||||
<p><strong>Client:</strong> buildersclub.ca</p>
|
||||
<p><strong>Technologies:</strong> Python, WebDAV, WsgiDAV, macOS Integration</p>
|
||||
<p><strong>Client:</strong> <a href="https://buildersclub.ca" target="_blank" rel="noopener noreferrer">buildersclub.ca</a></p>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<section class="project-overview">
|
||||
<h2>Project Overview</h2>
|
||||
<h2>The Challenge</h2>
|
||||
<p>
|
||||
The ScanSnap WebDAV Service is a high-performance document digitization solution specifically designed
|
||||
for buildersclub.ca members who need to rapidly process receipts and documents. The service supports
|
||||
ScanSnap scanners capable of processing up to 50 receipts at nearly 1 scan per second, providing
|
||||
enterprise-grade performance for high-volume document digitization workflows.
|
||||
Running a business means dealing with receipts. Lots of them. And for buildersclub.ca members juggling multiple projects,
|
||||
managing receipt documentation was becoming a serious time sink. Traditional scanning workflows involved multiple steps:
|
||||
scan, save, organize, upload. Multiply that by dozens of receipts, and you're looking at hours of manual work every week.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
I needed a solution that could handle the club's Fujitsu ScanSnap iX1500 scanner—a beast of a machine capable of digitizing
|
||||
50 receipts at nearly one scan per second—but without the usual friction of file management systems.
|
||||
</p>
|
||||
|
||||
<div class="highlight-box">
|
||||
<h3>Key Performance Metrics</h3>
|
||||
<h3>What We Built</h3>
|
||||
<p>
|
||||
A custom WebDAV server optimized specifically for high-speed document scanning. Load 50 receipts, hit scan,
|
||||
and watch them all digitize in under a minute. Files are immediately accessible via macOS Finder (just like
|
||||
a network drive), with automatic daily cleanup to prevent storage bloat. Zero maintenance required.
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>Batch Capacity:</strong> 50 receipts maximum per session</li>
|
||||
<li><strong>Processing Speed:</strong> ~1 receipt per second</li>
|
||||
<li><strong>File Formats:</strong> PDF, JPEG, PNG (ScanSnap supported)</li>
|
||||
<li><strong>Storage Management:</strong> Automatic daily cleanup at 3:00 AM</li>
|
||||
<li><strong>Network Protocol:</strong> WebDAV 1.0/2.0 compatible</li>
|
||||
<li><strong>Batch Capacity:</strong> Up to 50 documents at once</li>
|
||||
<li><strong>File Access:</strong> Native Finder integration</li>
|
||||
<li><strong>Cleanup:</strong> Automated daily at 3:00 AM</li>
|
||||
<li><strong>Network Protocol:</strong> WebDAV 1.0/2.0 compliant</li>
|
||||
</ul>
|
||||
<p class="highlight-note">
|
||||
<strong>For buildersclub.ca members:</strong> <a href="http://192.168.1.119:9876" target="_blank">Access the scanner service here</a> (clubhouse network only)
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="technical-challenges">
|
||||
<h2>Technical Challenges & Solutions</h2>
|
||||
<section class="technical-story">
|
||||
<h2>The Technical Journey</h2>
|
||||
|
||||
<h3>macOS Finder WebDAV Compatibility</h3>
|
||||
<h3>When Finder Refuses to Play Nice</h3>
|
||||
<p>
|
||||
One of the primary challenges was ensuring seamless integration with macOS Finder's WebDAV client.
|
||||
macOS Finder has specific requirements for WebDAV protocol responses that many servers don't meet by default.
|
||||
The first hurdle? Getting macOS Finder to actually connect to our WebDAV server. Turns out, Finder is incredibly
|
||||
picky about WebDAV implementations. It expects very specific protocol responses that many standard WebDAV libraries
|
||||
don't provide out of the box.
|
||||
</p>
|
||||
<p>
|
||||
After digging through Finder's network traffic and WebDAV specs, I discovered it needed three specific "hotfixes"
|
||||
that mimic Windows server behavior:
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>Problem:</strong> Finder refused to connect to the WebDAV server</li>
|
||||
<li><strong>Solution:</strong> Implemented macOS-specific hotfixes including <code>emulate_win32_lastmod</code>,
|
||||
<code>unquote_path_info</code>, and <code>win_accept_anonymous</code> settings</li>
|
||||
<li><strong>Result:</strong> Full Finder compatibility with proper directory browsing and file operations</li>
|
||||
<li><code>emulate_win32_lastmod</code> - Makes file timestamps work like Windows expects</li>
|
||||
<li><code>unquote_path_info</code> - Handles special characters in file names properly</li>
|
||||
<li><code>win_accept_anonymous</code> - Allows Finder to connect without credentials</li>
|
||||
</ul>
|
||||
<p>
|
||||
Once these were in place, Finder connected instantly. The whole experience felt native—just Command+K to connect,
|
||||
and boom, you've got a network drive ready for scanning.
|
||||
</p>
|
||||
|
||||
<h3>Security Without the Headache</h3>
|
||||
<p>
|
||||
Here's the thing about receipt scanners: you want them to be fast and frictionless. Authentication dialogs kill that flow.
|
||||
But you also can't just leave a wide-open file server exposed to the internet.
|
||||
</p>
|
||||
<p>
|
||||
The solution? Custom permissions at the protocol level. The scanner can upload files and delete them when needed,
|
||||
but it can't move, copy, or rename anything. More importantly, the service is completely isolated to its own directory—there's
|
||||
literally no way for it to access files outside <code>~/scansnap-dav/scans</code>, even if someone tried to hack around it.
|
||||
</p>
|
||||
<pre><code>class ScanSnapProvider(FilesystemProvider):
|
||||
def create_collection(self, path):
|
||||
# No creating subdirectories
|
||||
raise DAVError(403, "Creating directories not allowed")
|
||||
|
||||
def copy_resource(self, src_path, dest_path, depth):
|
||||
# No copying files around
|
||||
raise DAVError(403, "Copying not allowed")
|
||||
|
||||
def move_resource(self, src_path, dest_path):
|
||||
# No moving or renaming
|
||||
raise DAVError(403, "Moving/renaming not allowed")</code></pre>
|
||||
<p>
|
||||
For the clubhouse environment, this works perfectly. It's on a trusted network, accessible only to members,
|
||||
and the restricted permissions mean there's no risk of accidentally messing up the file system.
|
||||
</p>
|
||||
|
||||
<h3>The Storage Problem Nobody Thinks About</h3>
|
||||
<p>
|
||||
When you're scanning 50 receipts at a time, storage fills up fast. Even with PDF compression, you're looking at
|
||||
several megabytes per scan session. Do that a few times a day, and suddenly you're managing gigabytes of receipt data.
|
||||
</p>
|
||||
<p>
|
||||
The fix? Automatic cleanup. Every night at 3 AM, a Python scheduler wipes the scans directory clean. Receipts
|
||||
are meant to be temporary anyway—scan them, grab what you need, move on. The cleanup runs silently in the background,
|
||||
and members never have to think about storage management.
|
||||
</p>
|
||||
<pre><code>def cleanup_scans():
|
||||
scans_dir = os.path.expanduser("~/scansnap-dav/scans")
|
||||
for filename in os.listdir(scans_dir):
|
||||
file_path = os.path.join(scans_dir, filename)
|
||||
if os.isfile(file_path):
|
||||
os.remove(file_path)
|
||||
|
||||
# Daily cleanup at 3:00 AM
|
||||
schedule.every().day.at("03:00").do(cleanup_scans)</code></pre>
|
||||
</section>
|
||||
|
||||
<section class="real-world-impact">
|
||||
<h2>Real-World Impact</h2>
|
||||
|
||||
<h3>From Hours to Minutes</h3>
|
||||
<p>
|
||||
Before this system, processing a week's worth of receipts meant:
|
||||
</p>
|
||||
<ol>
|
||||
<li>Scan receipts one by one (or in small batches)</li>
|
||||
<li>Wait for files to save to the local machine</li>
|
||||
<li>Open file manager and organize scans</li>
|
||||
<li>Upload to cloud storage or accounting software</li>
|
||||
<li>Clean up local copies to free up space</li>
|
||||
</ol>
|
||||
<p>
|
||||
That's easily 20-30 minutes of manual work for a typical batch of receipts.
|
||||
</p>
|
||||
<p>
|
||||
Now? Load the scanner hopper, hit scan, wait 60 seconds, grab the PDFs from Finder. Done. The time savings
|
||||
are dramatic—what used to take half an hour now takes maybe two minutes.
|
||||
</p>
|
||||
|
||||
<h3>The Numbers</h3>
|
||||
<ul>
|
||||
<li><strong>Time Reduction:</strong> 95% decrease in manual document processing</li>
|
||||
<li><strong>Batch Efficiency:</strong> 50 receipts in under 60 seconds</li>
|
||||
<li><strong>Storage Overhead:</strong> Zero (automated cleanup handles everything)</li>
|
||||
<li><strong>User Training Required:</strong> Literally just "Command+K, enter the URL"</li>
|
||||
</ul>
|
||||
|
||||
<h3>High-Volume File Processing</h3>
|
||||
<h3>Why It Works</h3>
|
||||
<p>
|
||||
The service needed to handle rapid file uploads from ScanSnap scanners without performance degradation
|
||||
or storage issues.
|
||||
The beauty of this solution is its simplicity. There's no complex web interface, no database, no authentication system
|
||||
to maintain. It's just a WebDAV endpoint that does exactly what the scanner needs and nothing more.
|
||||
</p>
|
||||
<p>
|
||||
For buildersclub.ca members, it means one less thing to think about. Receipts get scanned, files are immediately
|
||||
available, and storage never becomes an issue. The system just works, quietly and reliably, in the background.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="technical-details">
|
||||
<h2>Under the Hood</h2>
|
||||
|
||||
<h3>The Tech Stack</h3>
|
||||
<ul>
|
||||
<li><strong>Problem:</strong> Managing storage space with high-volume scanning operations</li>
|
||||
<li><strong>Solution:</strong> Implemented automated cleanup scheduler with configurable timing</li>
|
||||
<li><strong>Result:</strong> Zero-maintenance storage management with daily automated cleanup</li>
|
||||
<li><strong>Server Framework:</strong> WsgiDAV with Cheroot WSGI server</li>
|
||||
<li><strong>Language:</strong> Python 3.13+</li>
|
||||
<li><strong>Automation:</strong> Python schedule library for cleanup</li>
|
||||
<li><strong>macOS Integration:</strong> launchd for auto-start on boot</li>
|
||||
<li><strong>Protocol:</strong> WebDAV with macOS-specific optimizations</li>
|
||||
</ul>
|
||||
|
||||
<h3>Security & File Isolation</h3>
|
||||
<p>
|
||||
Ensuring the WebDAV service could only access designated directories while preventing unauthorized
|
||||
file operations.
|
||||
</p>
|
||||
<h3>Key Configuration</h3>
|
||||
<pre><code>config = {
|
||||
"host": "0.0.0.0",
|
||||
"port": 9876,
|
||||
"provider_mapping": {
|
||||
"/": ScanSnapProvider(scans_dir)
|
||||
},
|
||||
"hotfixes": {
|
||||
"emulate_win32_lastmod": True,
|
||||
"unquote_path_info": True,
|
||||
"win_accept_anonymous": True,
|
||||
},
|
||||
"property_manager": True,
|
||||
"lock_storage": True,
|
||||
}</code></pre>
|
||||
|
||||
<h3>Security Considerations</h3>
|
||||
<ul>
|
||||
<li><strong>Problem:</strong> Preventing access to system files and unauthorized operations</li>
|
||||
<li><strong>Solution:</strong> Custom provider class with restricted permissions (read, create, delete only)</li>
|
||||
<li><strong>Result:</strong> Secure file isolation with blocked move/copy/directory creation operations</li>
|
||||
<li><strong>Network Scope:</strong> Clubhouse network only, no internet exposure</li>
|
||||
<li><strong>File Isolation:</strong> Cannot access anything outside the scans directory</li>
|
||||
<li><strong>Operation Restrictions:</strong> Upload, read, and delete only—no move/copy/rename</li>
|
||||
<li><strong>Authentication:</strong> None required (trusted network environment)</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="results-impact">
|
||||
<h2>Results & Impact</h2>
|
||||
<section class="lessons-learned">
|
||||
<h2>Lessons Learned</h2>
|
||||
|
||||
<h3>Performance Improvements</h3>
|
||||
<ul>
|
||||
<li><strong>Processing Speed:</strong> 95% reduction in manual document processing time</li>
|
||||
<li><strong>Batch Efficiency:</strong> 50 receipts processed in under 60 seconds</li>
|
||||
<li><strong>Storage Management:</strong> Zero-maintenance automated cleanup</li>
|
||||
<li><strong>User Experience:</strong> Seamless Finder integration with drag-and-drop functionality</li>
|
||||
</ul>
|
||||
<h3>Sometimes Simple is Better</h3>
|
||||
<p>
|
||||
I could have built a full web application with user accounts, file organization features, OCR processing,
|
||||
automatic categorization, cloud sync... but none of that was actually needed. The scanner needed a place to
|
||||
dump files quickly, and users needed to grab those files easily. Mission accomplished with a fraction of the complexity.
|
||||
</p>
|
||||
|
||||
<h3>Business Value</h3>
|
||||
<ul>
|
||||
<li><strong>Cost Reduction:</strong> Eliminated manual document processing overhead</li>
|
||||
<li><strong>Scalability:</strong> Supports high-volume scanning operations</li>
|
||||
<li><strong>Reliability:</strong> Automated cleanup prevents storage issues</li>
|
||||
<li><strong>Integration:</strong> Native macOS compatibility reduces training requirements</li>
|
||||
</ul>
|
||||
<h3>macOS WebDAV is Quirky</h3>
|
||||
<p>
|
||||
Apple's Finder WebDAV client has some very specific expectations that aren't always documented. The solution
|
||||
involved reading through protocol specs, analyzing network traffic, and testing various server configurations.
|
||||
Once you know the tricks (those three hotfix flags), it's actually rock solid.
|
||||
</p>
|
||||
|
||||
<h3>Automatic Cleanup Changes Everything</h3>
|
||||
<p>
|
||||
The daily cleanup feature turned this from a "nice to have" into a "set it and forget it" solution. Nobody
|
||||
thinks about storage, nobody worries about running out of space, and the system stays lean indefinitely.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<hr>
|
||||
|
@ -116,8 +243,9 @@
|
|||
<ul>
|
||||
<li><a href="../index.html">← Back to Portfolio</a></li>
|
||||
<li><a href="https://buildersclub.ca" target="_blank" rel="noopener noreferrer">buildersclub.ca</a></li>
|
||||
<li><strong>For Members:</strong> <a href="http://192.168.1.119:9876" target="_blank">Scanner Service Access</a> (clubhouse network)</li>
|
||||
<li><a href="https://github.com/mar10/wsgidav" target="_blank" rel="noopener noreferrer">WsgiDAV Framework</a></li>
|
||||
<li><a href="https://www.fujitsu.com/us/products/computing/peripheral/scanners/scansnap/" target="_blank" rel="noopener noreferrer">ScanSnap Scanners</a></li>
|
||||
<li><a href="https://www.fujitsu.com/us/products/computing/peripheral/scanners/scansnap/" target="_blank" rel="noopener noreferrer">Fujitsu ScanSnap Scanners</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -8,8 +8,9 @@
|
|||
<link rel="icon" type="image/x-icon" href="../favicon.ico">
|
||||
<link rel="stylesheet" href="../styles.css" integrity="sha256-Y+6RTuKMnPfNa1TjCQCcFhxwo0G+xNy7t1MaAvn5SuU=" crossorigin="anonymous">
|
||||
<script src="../theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=" crossorigin="anonymous"></script>
|
||||
<script src="../includes.js" integrity="sha256-q9ac7XWqnIASoBRfs4I4hpSMlnxGARofcEw0cSFfn/E=" crossorigin="anonymous"></script>
|
||||
<script src="../includes.js" integrity="sha256-0VPPSi+jVc1DuyZaSYTq+fnpIfv7ft+ZDenYE6pDPqA=" crossorigin="anonymous"></script>
|
||||
<link rel="stylesheet" href="stories.css" integrity="sha256-O+OMb48leSKvekhMTDUK1y6+WG9x33kA0eDw00wUwkY=">
|
||||
<script src="../markdown-loader.js" integrity="sha256-4+erbuMKlaalnlqc0+5d+X4Bpr1CZ7W3dUCsyA15spE="></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header Include -->
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
# showerloop
|
||||
|
||||
> Draft placeholder. Content to be written.
|
||||
|
||||
- Summary: TBD
|
||||
- Key outcomes: TBD
|
||||
- Tech stack: TBD
|
||||
- Challenges: TBD
|
||||
- Results: TBD
|
||||
|
|
@ -9,7 +9,8 @@
|
|||
<link rel="stylesheet" href="../styles.css" integrity="sha256-Y+6RTuKMnPfNa1TjCQCcFhxwo0G+xNy7t1MaAvn5SuU=">
|
||||
<link rel="stylesheet" href="stories.css" integrity="sha256-O+OMb48leSKvekhMTDUK1y6+WG9x33kA0eDw00wUwkY=">
|
||||
<script src="../theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4="></script>
|
||||
<script src="../includes.js" integrity="sha256-q9ac7XWqnIASoBRfs4I4hpSMlnxGARofcEw0cSFfn/E="></script>
|
||||
<script src="../includes.js" integrity="sha256-0VPPSi+jVc1DuyZaSYTq+fnpIfv7ft+ZDenYE6pDPqA="></script>
|
||||
<script src="../markdown-loader.js" integrity="sha256-4+erbuMKlaalnlqc0+5d+X4Bpr1CZ7W3dUCsyA15spE="></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header Include -->
|
||||
|
|
|
@ -8,8 +8,9 @@
|
|||
<link rel="icon" type="image/x-icon" href="../favicon.ico">
|
||||
<link rel="stylesheet" href="../styles.css" integrity="sha256-Y+6RTuKMnPfNa1TjCQCcFhxwo0G+xNy7t1MaAvn5SuU=" crossorigin="anonymous">
|
||||
<script src="../theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=" crossorigin="anonymous"></script>
|
||||
<script src="../includes.js" integrity="sha256-q9ac7XWqnIASoBRfs4I4hpSMlnxGARofcEw0cSFfn/E=" crossorigin="anonymous"></script>
|
||||
<script src="../includes.js" integrity="sha256-0VPPSi+jVc1DuyZaSYTq+fnpIfv7ft+ZDenYE6pDPqA=" crossorigin="anonymous"></script>
|
||||
<link rel="stylesheet" href="stories.css" integrity="sha256-O+OMb48leSKvekhMTDUK1y6+WG9x33kA0eDw00wUwkY=">
|
||||
<script src="../markdown-loader.js" integrity="sha256-4+erbuMKlaalnlqc0+5d+X4Bpr1CZ7W3dUCsyA15spE="></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header Include -->
|
||||
|
|
|
@ -8,8 +8,9 @@
|
|||
<link rel="icon" type="image/x-icon" href="../favicon.ico">
|
||||
<link rel="stylesheet" href="../styles.css" integrity="sha256-Y+6RTuKMnPfNa1TjCQCcFhxwo0G+xNy7t1MaAvn5SuU=" crossorigin="anonymous">
|
||||
<script src="../theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=" crossorigin="anonymous"></script>
|
||||
<script src="../includes.js" integrity="sha256-q9ac7XWqnIASoBRfs4I4hpSMlnxGARofcEw0cSFfn/E=" crossorigin="anonymous"></script>
|
||||
<script src="../includes.js" integrity="sha256-0VPPSi+jVc1DuyZaSYTq+fnpIfv7ft+ZDenYE6pDPqA=" crossorigin="anonymous"></script>
|
||||
<link rel="stylesheet" href="stories.css" integrity="sha256-O+OMb48leSKvekhMTDUK1y6+WG9x33kA0eDw00wUwkY=">
|
||||
<script src="../markdown-loader.js" integrity="sha256-4+erbuMKlaalnlqc0+5d+X4Bpr1CZ7W3dUCsyA15spE="></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header Include -->
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
# viperwire
|
||||
|
||||
> Draft placeholder. Content to be written.
|
||||
|
||||
- Summary: TBD
|
||||
- Key outcomes: TBD
|
||||
- Tech stack: TBD
|
||||
- Challenges: TBD
|
||||
- Results: TBD
|
||||
|
|
@ -8,8 +8,9 @@
|
|||
<link rel="icon" type="image/x-icon" href="../favicon.ico">
|
||||
<link rel="stylesheet" href="../styles.css" integrity="sha256-Y+6RTuKMnPfNa1TjCQCcFhxwo0G+xNy7t1MaAvn5SuU=" crossorigin="anonymous">
|
||||
<script src="../theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=" crossorigin="anonymous"></script>
|
||||
<script src="../includes.js" integrity="sha256-q9ac7XWqnIASoBRfs4I4hpSMlnxGARofcEw0cSFfn/E=" crossorigin="anonymous"></script>
|
||||
<script src="../includes.js" integrity="sha256-0VPPSi+jVc1DuyZaSYTq+fnpIfv7ft+ZDenYE6pDPqA=" crossorigin="anonymous"></script>
|
||||
<link rel="stylesheet" href="stories.css" integrity="sha256-O+OMb48leSKvekhMTDUK1y6+WG9x33kA0eDw00wUwkY=">
|
||||
<script src="../markdown-loader.js" integrity="sha256-4+erbuMKlaalnlqc0+5d+X4Bpr1CZ7W3dUCsyA15spE="></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header Include -->
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
# weU design java
|
||||
|
||||
> Draft placeholder. Content to be written.
|
||||
|
||||
- Summary: TBD
|
||||
- Key outcomes: TBD
|
||||
- Tech stack: TBD
|
||||
- Challenges: TBD
|
||||
- Results: TBD
|
||||
|
|
@ -8,8 +8,9 @@
|
|||
<link rel="icon" type="image/x-icon" href="../favicon.ico">
|
||||
<link rel="stylesheet" href="../styles.css" integrity="sha256-Y+6RTuKMnPfNa1TjCQCcFhxwo0G+xNy7t1MaAvn5SuU=" crossorigin="anonymous">
|
||||
<script src="../theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=" crossorigin="anonymous"></script>
|
||||
<script src="../includes.js" integrity="sha256-q9ac7XWqnIASoBRfs4I4hpSMlnxGARofcEw0cSFfn/E=" crossorigin="anonymous"></script>
|
||||
<script src="../includes.js" integrity="sha256-0VPPSi+jVc1DuyZaSYTq+fnpIfv7ft+ZDenYE6pDPqA=" crossorigin="anonymous"></script>
|
||||
<link rel="stylesheet" href="stories.css" integrity="sha256-O+OMb48leSKvekhMTDUK1y6+WG9x33kA0eDw00wUwkY=">
|
||||
<script src="../markdown-loader.js" integrity="sha256-4+erbuMKlaalnlqc0+5d+X4Bpr1CZ7W3dUCsyA15spE="></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header Include -->
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
# wordpress security
|
||||
|
||||
> Draft placeholder. Content to be written.
|
||||
|
||||
- Summary: TBD
|
||||
- Key outcomes: TBD
|
||||
- Tech stack: TBD
|
||||
- Challenges: TBD
|
||||
- Results: TBD
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
<link rel="icon" type="image/x-icon" href="../favicon.ico">
|
||||
<link rel="stylesheet" href="../styles.css" integrity="sha256-Y+6RTuKMnPfNa1TjCQCcFhxwo0G+xNy7t1MaAvn5SuU=" crossorigin="anonymous">
|
||||
<script src="../theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=" crossorigin="anonymous"></script>
|
||||
<script src="../includes.js" integrity="sha256-q9ac7XWqnIASoBRfs4I4hpSMlnxGARofcEw0cSFfn/E=" crossorigin="anonymous"></script>
|
||||
<script src="../includes.js" integrity="sha256-0VPPSi+jVc1DuyZaSYTq+fnpIfv7ft+ZDenYE6pDPqA=" crossorigin="anonymous"></script>
|
||||
<link rel="stylesheet" href="stories.css" integrity="sha256-O+OMb48leSKvekhMTDUK1y6+WG9x33kA0eDw00wUwkY=">
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
# youtuUe game dev
|
||||
|
||||
> Draft placeholder. Content to be written.
|
||||
|
||||
- Summary: TBD
|
||||
- Key outcomes: TBD
|
||||
- Tech stack: TBD
|
||||
- Challenges: TBD
|
||||
- Results: TBD
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<link rel="stylesheet" href="styles.css" integrity="sha256-Y+6RTuKMnPfNa1TjCQCcFhxwo0G+xNy7t1MaAvn5SuU=">
|
||||
<script src="theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4="></script>
|
||||
<script src="includes.js" integrity="sha256-q9ac7XWqnIASoBRfs4I4hpSMlnxGARofcEw0cSFfn/E="></script>
|
||||
<script src="includes.js" integrity="sha256-0VPPSi+jVc1DuyZaSYTq+fnpIfv7ft+ZDenYE6pDPqA="></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header Include -->
|
||||
|
|
Loading…
Reference in New Issue