Save all current changes to resume project
ci/woodpecker/push/woodpecker Pipeline failed Details

This commit is contained in:
colin 2025-07-06 11:16:43 -04:00
parent be50c5de9c
commit a1e2afabb5
49 changed files with 3189 additions and 606 deletions

View File

@ -1,23 +0,0 @@
---
description:
globs: *.js,*.css
alwaysApply: false
---
# Asset Hashing and CSP Update Rule
This rule ensures that all `.js` and `.css` assets are properly hashed and their integrity hashes are updated in `index.html` and added to the Content Security Policy (CSP) in the `Caddyfile` during every build and push process.
## Process to Follow
1. **Hash Calculation**: Before building the Docker image, calculate the SHA256 hash for each `.js` and `.css` file in the `docker/resume/` directory using a command like `shasum -a 256 filename | awk '{print $1}' | xxd -r -p | base64`.
2. **Update index.html**: Update the `integrity` attribute in the `<script>` and `<link>` tags in `index.html` with the new hashes for each respective file.
3. **Update CSP in Caddyfile**: Ensure the CSP in the `Caddyfile` includes the hashes for all scripts and styles under `script-src` and `style-src` directives respectively.
4. **Automate in Build Script**: Modify the `build-test-deploy.sh` script to automate the hashing and updating process before the Docker build step.
5. **Verification**: After updating, verify that the hashes in `index.html` and `Caddyfile` match the calculated hashes to prevent deployment with mismatched integrity.
## Relevant Files
- [docker/resume/index.html](mdc:docker/resume/index.html)
- [docker/resume/Caddyfile](mdc:docker/resume/Caddyfile)
- [build-test-deploy.sh](mdc:build-test-deploy.sh)
This rule must be followed to maintain security integrity and prevent CSP violations during deployment.

View File

@ -1,62 +0,0 @@
---
description:
globs:
alwaysApply: false
---
# Deployment Workflow
## Testing Requirements
- All changes must be tested locally before deployment
- Tests must pass for both mobile and desktop viewports
- Lighthouse tests must maintain perfect scores (100/100) for accessibility and SEO
- Playwright tests must pass for all viewport sizes
## Git Operations
- Avoid use direct git commands (`git add`, `git commit`, `git push`) if we do git flows do then as oneliners.
- All git operations must be performed through `./build-test-deploy.sh`
- The script handles:
- Building the Docker container
- Running all tests
- Committing changes
- Pushing to repository
## Build-Test-Deploy Script
The `./build-test-deploy.sh` script is the single source of truth for deployment:
1. Builds the Docker container
2. Runs the test suite
3. Only proceeds with git operations if tests pass
4. Handles all git operations in the correct order
## Testing Process
1. Make changes to the codebase
2. Run `./build-test-deploy.sh` to:
- Build the container
- Run tests
- Deploy if tests pass
3. If tests fail:
- Fix the issues
- Run the script again
- Do not proceed with deployment until all tests pass
## Viewport Testing
The test suite runs against multiple viewport sizes:
- Mobile: 375x667
- Desktop: 1024x768
All tests must pass for all viewport sizes before deployment is allowed.
## Lighthouse Requirements
Maintain perfect scores:
- Accessibility: 100/100
- SEO: 100/100
- Performance: 97/100 minimum
- Best Practices: 93/100 minimum
## Playwright Tests
Must pass all accessibility tests:
- WCAG 2.1 Level AAA compliance
- ARIA attributes
- Heading structure
- External link validation
- Color contrast
- Image alt text

View File

@ -0,0 +1,17 @@
# HTML Standards
## Accessibility Requirements
- All images must have alt text
- Proper heading structure (h1, h2, etc.) must be maintained
- ARIA attributes must be used where appropriate
- Color contrast must meet WCAG 2.1 Level AAA compliance
## Security
- All script and style tags must include integrity attributes
- External links must include rel="noopener noreferrer" attributes
- No inline scripts or styles without proper CSP nonce/hash
## Structure
- Use includes.js for template components when possible
- Reference includes from [docker/resume/includes/](mdc:docker/resume/includes/)
- Follow the pattern in [docker/resume/index-with-includes.html](mdc:docker/resume/index-with-includes.html)

View File

@ -0,0 +1,15 @@
# JavaScript and CSS Standards
## Security Requirements
- All JS and CSS files must be hashed for integrity checks
- Hashes must be updated in both HTML and Caddyfile CSP headers
- Use `shasum -a 256` followed by base64 encoding for hash generation
## File References
- JavaScript files should be referenced in HTML with integrity attributes
- CSS files should be referenced with integrity attributes
- The CSP in [docker/resume/Caddyfile](mdc:docker/resume/Caddyfile) must include these hashes
## Automation
- Run `docker/resume/update-csp-hashes.sh` after modifying any JS or CSS file
- Verify hashes match between HTML and Caddyfile before deployment

View File

@ -0,0 +1,23 @@
# Project Guidelines
## Core Principles
- Use the `./build-test-deploy.sh` script for all build, test, and deployment operations
- Never use interactive CLI prompts in any scripts or processes
- Avoid creating duplicate files - use git for version control instead
- Don't modify Docker files unless absolutely necessary, especially production Dockerfiles
## File Structure
- Main resume content is in `docker/resume/index.html`
- Styles are in `docker/resume/styles.css`
- Server configuration is in `docker/resume/Caddyfile`
## Security Requirements
- All JS and CSS assets must have integrity hashes in HTML and CSP headers
- The Caddyfile must include proper Content-Security-Policy headers
- Use `update-csp-hashes.sh` to update security hashes
## Testing Standards
- All changes require passing tests before deployment
- Tests must pass for both mobile and desktop viewports
- Maintain Lighthouse scores: 100/100 for accessibility and SEO
- Tests must be meaningful with actual assertions, not placeholders

View File

@ -1,26 +0,0 @@
---
description:
globs:
alwaysApply: false
---
# Test Suite Integrity Rule
This rule ensures that the test suite for the project contains meaningful tests that perform actual checks and validations, rather than being empty placeholder boilerplate. All tests MUST do SOMETHING significant to verify the functionality, security, or performance of the application.
## Guidelines to Follow
1. **Non-Empty Tests**: Every test file in the `tests/` directory must contain at least one test case that performs a specific validation or check. Empty test files or test cases with no assertions are not allowed.
2. **Meaningful Assertions**: Each test must include assertions or expectations that verify the behavior of the application. Tests should not simply run without checking outcomes (e.g., no empty `expect()` calls or trivial assertions like `expect(true).toBe(true)`).
3. **Coverage of Key Features**: The test suite must cover critical aspects of the application, including but not limited to:
- Accessibility (WCAG compliance)
- Security headers and CSP compliance
- Functional features like the PDF download button
- Performance metrics (via tools like Lighthouse)
4. **Regular Review**: Before running `build-test-deploy.sh`, review the test suite to ensure no placeholder or boilerplate tests remain. If a test is temporarily skipped or incomplete, it must be annotated with a clear `TODO` comment explaining the reason and a plan for completion.
5. **Automation in Build Script**: If possible, add a pre-test step in `build-test-deploy.sh` to scan test files for empty or placeholder content (e.g., using grep to detect empty test blocks or missing assertions) and fail the build if such issues are found.
## Relevant Files and Directories
- [tests/](mdc:tests)
- [build-test-deploy.sh](mdc:build-test-deploy.sh)
This rule must be adhered to in order to maintain a robust and reliable testing framework that ensures the quality and security of the application.

11
.cursorignore Normal file
View File

@ -0,0 +1,11 @@
# Ignore all files and directories in the root except for the docker directory.
/build-test-deploy.sh
/docker-compose.production.yml
/docker-compose.staging.yml
/package-lock.json
/package.json
/playwright.config.js
/README.md
/stack.production.yml
/stack.staging.yml
/tests/

View File

@ -6,28 +6,8 @@ CONTAINER_NAME="resume_test_container"
DOCKER_DIR="docker" DOCKER_DIR="docker"
RESUME_DIR="$DOCKER_DIR/resume" RESUME_DIR="$DOCKER_DIR/resume"
# Recalculate SHA256 hash for styles.css and update integrity in index.html # Note: We no longer need to manually update the CSS hash here
STYLES_HASH=$(shasum -a 256 $RESUME_DIR/styles.css | awk '{print $1}' | xxd -r -p | base64) # as it's handled by the update-csp-hashes.sh script during Docker build
sed -i -E "s|href=\"styles.css\" integrity=\"sha256-[^\"]*\"|href=\"styles.css\" integrity=\"sha256-$STYLES_HASH\"|" $RESUME_DIR/index.html
# Verify the hash update
if ! grep -q "integrity=\"sha256-$STYLES_HASH\"" $RESUME_DIR/index.html; then
echo "Error: Integrity hash update failed."
exit 1
fi
# Test the CSS loading
if ! curl -s http://localhost:8080/styles.css > /dev/null; then
echo "Error: CSS file not accessible."
exit 1
fi
# Verify the hash in the container
CONTAINER_HASH=$(docker exec $CONTAINER_NAME cat /srv/styles.css | shasum -a 256 | awk '{print $1}' | xxd -r -p | base64)
if [ "$CONTAINER_HASH" != "$STYLES_HASH" ]; then
echo "Error: Integrity hash mismatch in container."
exit 1
fi
# Build Docker image # Build Docker image
cd "$DOCKER_DIR" cd "$DOCKER_DIR"

View File

@ -29,7 +29,7 @@ colinknapp.com {
Cache-Control "public, max-age=31536000, immutable" Cache-Control "public, max-age=31536000, immutable"
# CSP with hashes for scripts and styles # CSP with hashes for scripts and styles
Content-Security-Policy "default-src 'none'; script-src 'self' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-BASkmAmg7eoYCMd6odA6kQ8yGsFnoxaX48WbQvMkehs='; style-src 'self' 'sha256-Mo+7o3oPEKpX7fqRvTtunvQHlIDhJ0SxAMG1PCNniCI='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';" Content-Security-Policy "default-src 'none'; script-src 'self' 'sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4=' 'sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=' 'sha256-1ZUvhca3M/N6hch4NrdPufDPLTnANOpJ4hfsZgRykgg=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=' 'sha256-807UZmWvd6eLc8xVckZkNX6CRP9WV8MzHURc5BgtRWo=' 'sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=' 'sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=' 'sha256-2EA12+9d+s6rrc0rkdIjfmjbh6p2o0ZSXs4wbZuk/tA='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"
} }
# Handle 404s # Handle 404s
@ -79,7 +79,7 @@ colinknapp.com {
Cache-Control "public, max-age=31536000, immutable" Cache-Control "public, max-age=31536000, immutable"
# CSP with hashes for scripts and styles # CSP with hashes for scripts and styles
Content-Security-Policy "default-src 'none'; script-src 'self' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-BASkmAmg7eoYCMd6odA6kQ8yGsFnoxaX48WbQvMkehs='; style-src 'self' 'sha256-Mo+7o3oPEKpX7fqRvTtunvQHlIDhJ0SxAMG1PCNniCI='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';" Content-Security-Policy "default-src 'none'; script-src 'self' 'sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4=' 'sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=' 'sha256-1ZUvhca3M/N6hch4NrdPufDPLTnANOpJ4hfsZgRykgg=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=' 'sha256-807UZmWvd6eLc8xVckZkNX6CRP9WV8MzHURc5BgtRWo=' 'sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=' 'sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=' 'sha256-2EA12+9d+s6rrc0rkdIjfmjbh6p2o0ZSXs4wbZuk/tA='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"
} }
# Handle 404s # Handle 404s

View File

@ -0,0 +1,44 @@
# =====================================================================
# Caddyfile.local - Local development server configuration
# =====================================================================
# To manage the server, use the unified script:
# ./caddy.sh start # Start the server
# ./caddy.sh stop # Stop the server
# ./caddy.sh restart # Restart the server
# ./caddy.sh status # Check server status
#
# DO NOT run caddy directly from other directories as it may not find
# this configuration file correctly.
# =====================================================================
:8080 {
root * .
file_server
encode gzip
# Performance optimizations
header {
# Remove default Caddy headers
-Server
-X-Powered-By
# Basic security headers
X-Frame-Options "DENY"
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
# Permissions policy
Permissions-Policy "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"
# Cross-origin isolation headers
Cross-Origin-Embedder-Policy "require-corp"
Cross-Origin-Resource-Policy "same-origin"
Cross-Origin-Opener-Policy "same-origin"
# Cache control for static assets
Cache-Control "public, max-age=31536000, immutable"
# CSP with hashes for scripts and styles
Content-Security-Policy "default-src 'none'; script-src 'self' 'sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4=' 'sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=' 'sha256-1ZUvhca3M/N6hch4NrdPufDPLTnANOpJ4hfsZgRykgg=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=' 'sha256-807UZmWvd6eLc8xVckZkNX6CRP9WV8MzHURc5BgtRWo=' 'sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=' 'sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=' 'sha256-2EA12+9d+s6rrc0rkdIjfmjbh6p2o0ZSXs4wbZuk/tA='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"
}
}

View File

@ -1,19 +1,33 @@
FROM caddy:2-alpine FROM caddy:2-alpine
# Install required tools for hash calculation
RUN apk add --no-cache bash coreutils findutils grep sed xxd perl gawk
# Copy update scripts first
COPY update-csp-hashes.sh /srv/update-csp-hashes.sh
COPY caddy.sh /srv/caddy.sh
# Copy Caddyfile and static content # Copy Caddyfile and static content
COPY Caddyfile /etc/caddy/Caddyfile COPY Caddyfile /etc/caddy/Caddyfile
COPY index.html /srv/ COPY index.html /srv/
COPY theme.js /srv/ COPY theme.js /srv/
COPY utils.js /srv/ COPY utils.js /srv/
COPY styles.css /srv/ COPY styles.css /srv/
COPY pdf-download.js /srv/
COPY favicon.ico /srv/ COPY favicon.ico /srv/
# Expose port 8080 # Copy one-pager-tools directory
EXPOSE 8080 COPY one-pager-tools /srv/one-pager-tools/
# Set working directory # Set working directory
WORKDIR /srv WORKDIR /srv
# Run the update-csp-hashes.sh script to update CSP hashes
RUN chmod +x /srv/caddy.sh /srv/update-csp-hashes.sh && \
cd /srv && \
./update-csp-hashes.sh
# Expose port 8080
EXPOSE 8080
# Run Caddy # Run Caddy
CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile"] CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile"]

37
docker/resume/README.md Normal file
View File

@ -0,0 +1,37 @@
# Resume Website
## Local Development
To run the local development server:
1. Navigate to the `docker/resume` directory
2. Use the unified Caddy management script:
```
./caddy.sh start # Start the server
./caddy.sh stop # Stop the server
./caddy.sh restart # Restart the server
./caddy.sh status # Check server status
```
3. Open http://localhost:8080 in your browser
### Important Notes
- Always use the `caddy.sh` script to manage the server
- Do not run Caddy directly from other directories
## Content Security Policy (CSP)
When adding new scripts or styles, you need to update the CSP hashes:
1. Make your changes to JS/CSS files
2. Run the update script:
```
./update-csp-hashes.sh
```
3. Restart the server using `./caddy.sh restart`
## Tools
- **CSV Viewer**: A tool for viewing CSV data in tabular format
- Available at http://localhost:8080/one-pager-tools/csv-tool.html
- Simply paste CSV data to view it as a formatted table

109
docker/resume/caddy.log Normal file
View File

@ -0,0 +1,109 @@
{"level":"info","ts":1751739278.708652,"msg":"maxprocs: Leaving GOMAXPROCS=8: CPU quota undefined"}
{"level":"info","ts":1751739278.70898,"msg":"GOMEMLIMIT is updated","package":"github.com/KimMachineGun/automemlimit/memlimit","GOMEMLIMIT":7730941132,"previous":9223372036854775807}
{"level":"info","ts":1751739278.709314,"msg":"using config from file","file":"Caddyfile"}
{"level":"info","ts":1751739278.7104902,"msg":"adapted config to JSON","adapter":"caddyfile"}
{"level":"warn","ts":1751739278.710495,"msg":"Caddyfile input is not formatted; run 'caddy fmt --overwrite' to fix inconsistencies","adapter":"caddyfile","file":"Caddyfile","line":2}
{"level":"info","ts":1751739278.7137249,"logger":"admin","msg":"admin endpoint started","address":"localhost:2019","enforce_origin":false,"origins":["//localhost:2019","//[::1]:2019","//127.0.0.1:2019"]}
{"level":"info","ts":1751739278.713872,"logger":"http.auto_https","msg":"server is listening only on the HTTPS port but has no TLS connection policies; adding one to enable TLS","server_name":"srv0","https_port":443}
{"level":"info","ts":1751739278.7138822,"logger":"http.auto_https","msg":"enabling automatic HTTP->HTTPS redirects","server_name":"srv0"}
{"level":"info","ts":1751739278.714001,"logger":"tls.cache.maintenance","msg":"started background certificate maintenance","cache":"0x140005ff300"}
{"level":"info","ts":1751739278.714232,"logger":"http","msg":"enabling HTTP/3 listener","addr":":443"}
{"level":"info","ts":1751739278.714817,"logger":"http.log","msg":"server running","name":"srv0","protocols":["h1","h2","h3"]}
{"level":"warn","ts":1751739278.714853,"logger":"http","msg":"HTTP/2 skipped because it requires TLS","network":"tcp","addr":":8080"}
{"level":"warn","ts":1751739278.7148569,"logger":"http","msg":"HTTP/3 skipped because it requires TLS","network":"tcp","addr":":8080"}
{"level":"info","ts":1751739278.714859,"logger":"http.log","msg":"server running","name":"srv1","protocols":["h1","h2","h3"]}
{"level":"warn","ts":1751739278.7148852,"logger":"http","msg":"HTTP/2 skipped because it requires TLS","network":"tcp","addr":":80"}
{"level":"warn","ts":1751739278.7148879,"logger":"http","msg":"HTTP/3 skipped because it requires TLS","network":"tcp","addr":":80"}
{"level":"info","ts":1751739278.714891,"logger":"http.log","msg":"server running","name":"remaining_auto_https_redirects","protocols":["h1","h2","h3"]}
{"level":"info","ts":1751739278.714894,"logger":"http","msg":"enabling automatic TLS certificate management","domains":["colinknapp.com"]}
{"level":"info","ts":1751739278.715245,"msg":"autosaved config (load with --resume flag)","file":"/Users/computerpro/Library/Application Support/Caddy/autosave.json"}
{"level":"info","ts":1751739278.7152488,"msg":"serving initial configuration"}
{"level":"info","ts":1751739278.722911,"logger":"tls.obtain","msg":"acquiring lock","identifier":"colinknapp.com"}
{"level":"info","ts":1751739278.7230961,"logger":"tls","msg":"storage cleaning happened too recently; skipping for now","storage":"FileStorage:/Users/computerpro/Library/Application Support/Caddy","instance":"bb1987a6-f2f6-4230-a2aa-e3b16b9f988e","try_again":1751825678.7230961,"try_again_in":86399.999999833}
{"level":"info","ts":1751739278.723574,"logger":"tls","msg":"finished cleaning storage units"}
{"level":"info","ts":1751739278.727818,"logger":"tls.obtain","msg":"lock acquired","identifier":"colinknapp.com"}
{"level":"info","ts":1751739278.72785,"logger":"tls.obtain","msg":"obtaining certificate","identifier":"colinknapp.com"}
{"level":"info","ts":1751739278.729119,"logger":"http","msg":"waiting on internal rate limiter","identifiers":["colinknapp.com"],"ca":"https://acme-v02.api.letsencrypt.org/directory","account":""}
{"level":"info","ts":1751739278.729126,"logger":"http","msg":"done waiting on internal rate limiter","identifiers":["colinknapp.com"],"ca":"https://acme-v02.api.letsencrypt.org/directory","account":""}
{"level":"info","ts":1751739278.729134,"logger":"http","msg":"using ACME account","account_id":"https://acme-v02.api.letsencrypt.org/acme/acct/2509915601","account_contact":[]}
{"level":"info","ts":1751739279.476387,"msg":"trying to solve challenge","identifier":"colinknapp.com","challenge_type":"tls-alpn-01","ca":"https://acme-v02.api.letsencrypt.org/directory"}
{"level":"error","ts":1751739280.142849,"msg":"challenge failed","identifier":"colinknapp.com","challenge_type":"tls-alpn-01","problem":{"type":"urn:ietf:params:acme:error:tls","title":"","detail":"138.197.167.216: remote error: tls: unrecognized name","instance":"","subproblems":null},"stacktrace":"github.com/mholt/acmez/v3.(*Client).pollAuthorization\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:557\ngithub.com/mholt/acmez/v3.(*Client).solveChallenges\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:378\ngithub.com/mholt/acmez/v3.(*Client).ObtainCertificate\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:136\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).doIssue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:489\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:382\ngithub.com/caddyserver/caddy/v2/modules/caddytls.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/caddy/v2@v2.10.0/modules/caddytls/acmeissuer.go:288\ngithub.com/caddyserver/certmagic.(*Config).obtainCert.func2\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:626\ngithub.com/caddyserver/certmagic.doWithRetry\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:104\ngithub.com/caddyserver/certmagic.(*Config).obtainCert\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:700\ngithub.com/caddyserver/certmagic.(*Config).ObtainCertAsync\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:505\ngithub.com/caddyserver/certmagic.(*Config).manageOne.func1\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:415\ngithub.com/caddyserver/certmagic.(*jobManager).worker\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:73"}
{"level":"error","ts":1751739280.142943,"msg":"validating authorization","identifier":"colinknapp.com","problem":{"type":"urn:ietf:params:acme:error:tls","title":"","detail":"138.197.167.216: remote error: tls: unrecognized name","instance":"","subproblems":null},"order":"https://acme-v02.api.letsencrypt.org/acme/order/2509915601/403011757641","attempt":1,"max_attempts":3,"stacktrace":"github.com/mholt/acmez/v3.(*Client).ObtainCertificate\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:152\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).doIssue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:489\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:382\ngithub.com/caddyserver/caddy/v2/modules/caddytls.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/caddy/v2@v2.10.0/modules/caddytls/acmeissuer.go:288\ngithub.com/caddyserver/certmagic.(*Config).obtainCert.func2\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:626\ngithub.com/caddyserver/certmagic.doWithRetry\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:104\ngithub.com/caddyserver/certmagic.(*Config).obtainCert\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:700\ngithub.com/caddyserver/certmagic.(*Config).ObtainCertAsync\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:505\ngithub.com/caddyserver/certmagic.(*Config).manageOne.func1\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:415\ngithub.com/caddyserver/certmagic.(*jobManager).worker\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:73"}
{"level":"error","ts":1751739281.218956,"logger":"tls.obtain","msg":"could not get certificate from issuer","identifier":"colinknapp.com","issuer":"acme-v02.api.letsencrypt.org-directory","error":"HTTP 429 urn:ietf:params:acme:error:rateLimited - too many failed authorizations (5) for \"colinknapp.com\" in the last 1h0m0s, retry after 2025-07-05 18:26:22 UTC: see https://letsencrypt.org/docs/rate-limits/#authorization-failures-per-hostname-per-account"}
{"level":"error","ts":1751739281.2190888,"logger":"tls.obtain","msg":"will retry","error":"[colinknapp.com] Obtain: [colinknapp.com] creating new order: attempt 1: https://acme-v02.api.letsencrypt.org/acme/new-order: HTTP 429 urn:ietf:params:acme:error:rateLimited - too many failed authorizations (5) for \"colinknapp.com\" in the last 1h0m0s, retry after 2025-07-05 18:26:22 UTC: see https://letsencrypt.org/docs/rate-limits/#authorization-failures-per-hostname-per-account (ca=https://acme-v02.api.letsencrypt.org/directory)","attempt":1,"retrying_in":60,"elapsed":2.491265416,"max_duration":2592000}
{"level":"info","ts":1751739292.2583349,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"::1","remote_port":"57078","client_ip":"::1","proto":"HTTP/1.1","method":"HEAD","host":"localhost:8080","uri":"/stories/open-source-success.html","headers":{"Accept":["*/*"],"User-Agent":["curl/8.7.1"]}},"bytes_read":0,"user_id":"","duration":0.003503292,"size":0,"status":200,"resp_headers":{"Referrer-Policy":["strict-origin-when-cross-origin"],"X-Content-Type-Options":["nosniff"],"X-Frame-Options":["DENY"],"Last-Modified":["Sat, 05 Jul 2025 18:14:29 GMT"],"Accept-Ranges":["bytes"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Etag":["\"db4bqupxyvmr9pd\""],"Cross-Origin-Resource-Policy":["same-origin"],"Content-Length":["12577"],"Cross-Origin-Embedder-Policy":["require-corp"],"Cross-Origin-Opener-Policy":["same-origin"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Cache-Control":["public, max-age=31536000, immutable"],"Vary":["Accept-Encoding"],"Content-Type":["text/html; charset=utf-8"]}}
{"level":"info","ts":1751739341.2217758,"logger":"tls.obtain","msg":"obtaining certificate","identifier":"colinknapp.com"}
{"level":"info","ts":1751739341.2275689,"logger":"http","msg":"using ACME account","account_id":"https://acme-staging-v02.api.letsencrypt.org/acme/acct/210713203","account_contact":[]}
{"level":"info","ts":1751739341.618192,"msg":"trying to solve challenge","identifier":"colinknapp.com","challenge_type":"http-01","ca":"https://acme-staging-v02.api.letsencrypt.org/directory"}
{"level":"error","ts":1751739342.312726,"msg":"challenge failed","identifier":"colinknapp.com","challenge_type":"http-01","problem":{"type":"urn:ietf:params:acme:error:unauthorized","title":"","detail":"2604:a880:cad:d0::ef5:1001: Invalid response from http://colinknapp.com/.well-known/acme-challenge/9OE0nikzS9fkyPjN-_qTWq8gGI4tAL5YNSht0zwiHWU: 404","instance":"","subproblems":null},"stacktrace":"github.com/mholt/acmez/v3.(*Client).pollAuthorization\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:557\ngithub.com/mholt/acmez/v3.(*Client).solveChallenges\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:378\ngithub.com/mholt/acmez/v3.(*Client).ObtainCertificate\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:136\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).doIssue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:489\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:382\ngithub.com/caddyserver/caddy/v2/modules/caddytls.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/caddy/v2@v2.10.0/modules/caddytls/acmeissuer.go:288\ngithub.com/caddyserver/certmagic.(*Config).obtainCert.func2\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:626\ngithub.com/caddyserver/certmagic.doWithRetry\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:104\ngithub.com/caddyserver/certmagic.(*Config).obtainCert\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:700\ngithub.com/caddyserver/certmagic.(*Config).ObtainCertAsync\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:505\ngithub.com/caddyserver/certmagic.(*Config).manageOne.func1\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:415\ngithub.com/caddyserver/certmagic.(*jobManager).worker\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:73"}
{"level":"error","ts":1751739342.3132029,"msg":"validating authorization","identifier":"colinknapp.com","problem":{"type":"urn:ietf:params:acme:error:unauthorized","title":"","detail":"2604:a880:cad:d0::ef5:1001: Invalid response from http://colinknapp.com/.well-known/acme-challenge/9OE0nikzS9fkyPjN-_qTWq8gGI4tAL5YNSht0zwiHWU: 404","instance":"","subproblems":null},"order":"https://acme-staging-v02.api.letsencrypt.org/acme/order/210713203/25840385953","attempt":1,"max_attempts":3,"stacktrace":"github.com/mholt/acmez/v3.(*Client).ObtainCertificate\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:152\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).doIssue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:489\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:382\ngithub.com/caddyserver/caddy/v2/modules/caddytls.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/caddy/v2@v2.10.0/modules/caddytls/acmeissuer.go:288\ngithub.com/caddyserver/certmagic.(*Config).obtainCert.func2\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:626\ngithub.com/caddyserver/certmagic.doWithRetry\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:104\ngithub.com/caddyserver/certmagic.(*Config).obtainCert\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:700\ngithub.com/caddyserver/certmagic.(*Config).ObtainCertAsync\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:505\ngithub.com/caddyserver/certmagic.(*Config).manageOne.func1\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:415\ngithub.com/caddyserver/certmagic.(*jobManager).worker\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:73"}
{"level":"info","ts":1751739343.446597,"msg":"trying to solve challenge","identifier":"colinknapp.com","challenge_type":"tls-alpn-01","ca":"https://acme-staging-v02.api.letsencrypt.org/directory"}
{"level":"error","ts":1751739344.154269,"msg":"challenge failed","identifier":"colinknapp.com","challenge_type":"tls-alpn-01","problem":{"type":"urn:ietf:params:acme:error:tls","title":"","detail":"138.197.167.216: remote error: tls: unrecognized name","instance":"","subproblems":null},"stacktrace":"github.com/mholt/acmez/v3.(*Client).pollAuthorization\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:557\ngithub.com/mholt/acmez/v3.(*Client).solveChallenges\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:378\ngithub.com/mholt/acmez/v3.(*Client).ObtainCertificate\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:136\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).doIssue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:489\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:382\ngithub.com/caddyserver/caddy/v2/modules/caddytls.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/caddy/v2@v2.10.0/modules/caddytls/acmeissuer.go:288\ngithub.com/caddyserver/certmagic.(*Config).obtainCert.func2\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:626\ngithub.com/caddyserver/certmagic.doWithRetry\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:104\ngithub.com/caddyserver/certmagic.(*Config).obtainCert\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:700\ngithub.com/caddyserver/certmagic.(*Config).ObtainCertAsync\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:505\ngithub.com/caddyserver/certmagic.(*Config).manageOne.func1\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:415\ngithub.com/caddyserver/certmagic.(*jobManager).worker\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:73"}
{"level":"error","ts":1751739344.1546292,"msg":"validating authorization","identifier":"colinknapp.com","problem":{"type":"urn:ietf:params:acme:error:tls","title":"","detail":"138.197.167.216: remote error: tls: unrecognized name","instance":"","subproblems":null},"order":"https://acme-staging-v02.api.letsencrypt.org/acme/order/210713203/25840386333","attempt":2,"max_attempts":3,"stacktrace":"github.com/mholt/acmez/v3.(*Client).ObtainCertificate\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:152\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).doIssue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:489\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:382\ngithub.com/caddyserver/caddy/v2/modules/caddytls.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/caddy/v2@v2.10.0/modules/caddytls/acmeissuer.go:288\ngithub.com/caddyserver/certmagic.(*Config).obtainCert.func2\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:626\ngithub.com/caddyserver/certmagic.doWithRetry\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:104\ngithub.com/caddyserver/certmagic.(*Config).obtainCert\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:700\ngithub.com/caddyserver/certmagic.(*Config).ObtainCertAsync\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:505\ngithub.com/caddyserver/certmagic.(*Config).manageOne.func1\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:415\ngithub.com/caddyserver/certmagic.(*jobManager).worker\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:73"}
{"level":"error","ts":1751739344.155127,"logger":"tls.obtain","msg":"could not get certificate from issuer","identifier":"colinknapp.com","issuer":"acme-v02.api.letsencrypt.org-directory","error":"HTTP 400 urn:ietf:params:acme:error:tls - 138.197.167.216: remote error: tls: unrecognized name"}
{"level":"error","ts":1751739344.155275,"logger":"tls.obtain","msg":"will retry","error":"[colinknapp.com] Obtain: [colinknapp.com] solving challenge: colinknapp.com: [colinknapp.com] authorization failed: HTTP 400 urn:ietf:params:acme:error:tls - 138.197.167.216: remote error: tls: unrecognized name (ca=https://acme-staging-v02.api.letsencrypt.org/directory)","attempt":2,"retrying_in":120,"elapsed":65.42754925,"max_duration":2592000}
{"level":"info","ts":1751739464.158327,"logger":"tls.obtain","msg":"obtaining certificate","identifier":"colinknapp.com"}
{"level":"info","ts":1751739464.163527,"logger":"http","msg":"using ACME account","account_id":"https://acme-staging-v02.api.letsencrypt.org/acme/acct/210713203","account_contact":[]}
{"level":"info","ts":1751739464.347884,"msg":"trying to solve challenge","identifier":"colinknapp.com","challenge_type":"http-01","ca":"https://acme-staging-v02.api.letsencrypt.org/directory"}
{"level":"error","ts":1751739465.0466619,"msg":"challenge failed","identifier":"colinknapp.com","challenge_type":"http-01","problem":{"type":"urn:ietf:params:acme:error:unauthorized","title":"","detail":"2604:a880:cad:d0::ef5:1001: Invalid response from http://colinknapp.com/.well-known/acme-challenge/dTh_lQhbTgg9J_PW4LPviVSbTIv0f_Rb4rwIU9woF5A: 404","instance":"","subproblems":null},"stacktrace":"github.com/mholt/acmez/v3.(*Client).pollAuthorization\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:557\ngithub.com/mholt/acmez/v3.(*Client).solveChallenges\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:378\ngithub.com/mholt/acmez/v3.(*Client).ObtainCertificate\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:136\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).doIssue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:489\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:382\ngithub.com/caddyserver/caddy/v2/modules/caddytls.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/caddy/v2@v2.10.0/modules/caddytls/acmeissuer.go:288\ngithub.com/caddyserver/certmagic.(*Config).obtainCert.func2\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:626\ngithub.com/caddyserver/certmagic.doWithRetry\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:104\ngithub.com/caddyserver/certmagic.(*Config).obtainCert\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:700\ngithub.com/caddyserver/certmagic.(*Config).ObtainCertAsync\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:505\ngithub.com/caddyserver/certmagic.(*Config).manageOne.func1\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:415\ngithub.com/caddyserver/certmagic.(*jobManager).worker\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:73"}
{"level":"error","ts":1751739465.046904,"msg":"validating authorization","identifier":"colinknapp.com","problem":{"type":"urn:ietf:params:acme:error:unauthorized","title":"","detail":"2604:a880:cad:d0::ef5:1001: Invalid response from http://colinknapp.com/.well-known/acme-challenge/dTh_lQhbTgg9J_PW4LPviVSbTIv0f_Rb4rwIU9woF5A: 404","instance":"","subproblems":null},"order":"https://acme-staging-v02.api.letsencrypt.org/acme/order/210713203/25840417013","attempt":1,"max_attempts":3,"stacktrace":"github.com/mholt/acmez/v3.(*Client).ObtainCertificate\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:152\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).doIssue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:489\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:382\ngithub.com/caddyserver/caddy/v2/modules/caddytls.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/caddy/v2@v2.10.0/modules/caddytls/acmeissuer.go:288\ngithub.com/caddyserver/certmagic.(*Config).obtainCert.func2\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:626\ngithub.com/caddyserver/certmagic.doWithRetry\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:104\ngithub.com/caddyserver/certmagic.(*Config).obtainCert\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:700\ngithub.com/caddyserver/certmagic.(*Config).ObtainCertAsync\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:505\ngithub.com/caddyserver/certmagic.(*Config).manageOne.func1\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:415\ngithub.com/caddyserver/certmagic.(*jobManager).worker\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:73"}
{"level":"info","ts":1751739466.273396,"msg":"trying to solve challenge","identifier":"colinknapp.com","challenge_type":"tls-alpn-01","ca":"https://acme-staging-v02.api.letsencrypt.org/directory"}
{"level":"error","ts":1751739466.958394,"msg":"challenge failed","identifier":"colinknapp.com","challenge_type":"tls-alpn-01","problem":{"type":"urn:ietf:params:acme:error:tls","title":"","detail":"138.197.167.216: remote error: tls: unrecognized name","instance":"","subproblems":null},"stacktrace":"github.com/mholt/acmez/v3.(*Client).pollAuthorization\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:557\ngithub.com/mholt/acmez/v3.(*Client).solveChallenges\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:378\ngithub.com/mholt/acmez/v3.(*Client).ObtainCertificate\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:136\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).doIssue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:489\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:382\ngithub.com/caddyserver/caddy/v2/modules/caddytls.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/caddy/v2@v2.10.0/modules/caddytls/acmeissuer.go:288\ngithub.com/caddyserver/certmagic.(*Config).obtainCert.func2\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:626\ngithub.com/caddyserver/certmagic.doWithRetry\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:104\ngithub.com/caddyserver/certmagic.(*Config).obtainCert\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:700\ngithub.com/caddyserver/certmagic.(*Config).ObtainCertAsync\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:505\ngithub.com/caddyserver/certmagic.(*Config).manageOne.func1\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:415\ngithub.com/caddyserver/certmagic.(*jobManager).worker\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:73"}
{"level":"error","ts":1751739466.9587898,"msg":"validating authorization","identifier":"colinknapp.com","problem":{"type":"urn:ietf:params:acme:error:tls","title":"","detail":"138.197.167.216: remote error: tls: unrecognized name","instance":"","subproblems":null},"order":"https://acme-staging-v02.api.letsencrypt.org/acme/order/210713203/25840417363","attempt":2,"max_attempts":3,"stacktrace":"github.com/mholt/acmez/v3.(*Client).ObtainCertificate\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:152\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).doIssue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:489\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:382\ngithub.com/caddyserver/caddy/v2/modules/caddytls.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/caddy/v2@v2.10.0/modules/caddytls/acmeissuer.go:288\ngithub.com/caddyserver/certmagic.(*Config).obtainCert.func2\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:626\ngithub.com/caddyserver/certmagic.doWithRetry\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:104\ngithub.com/caddyserver/certmagic.(*Config).obtainCert\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:700\ngithub.com/caddyserver/certmagic.(*Config).ObtainCertAsync\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:505\ngithub.com/caddyserver/certmagic.(*Config).manageOne.func1\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:415\ngithub.com/caddyserver/certmagic.(*jobManager).worker\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:73"}
{"level":"error","ts":1751739466.9591172,"logger":"tls.obtain","msg":"could not get certificate from issuer","identifier":"colinknapp.com","issuer":"acme-v02.api.letsencrypt.org-directory","error":"HTTP 400 urn:ietf:params:acme:error:tls - 138.197.167.216: remote error: tls: unrecognized name"}
{"level":"error","ts":1751739466.959261,"logger":"tls.obtain","msg":"will retry","error":"[colinknapp.com] Obtain: [colinknapp.com] solving challenge: colinknapp.com: [colinknapp.com] authorization failed: HTTP 400 urn:ietf:params:acme:error:tls - 138.197.167.216: remote error: tls: unrecognized name (ca=https://acme-staging-v02.api.letsencrypt.org/directory)","attempt":3,"retrying_in":120,"elapsed":188.231714583,"max_duration":2592000}
{"level":"info","ts":1751739586.962073,"logger":"tls.obtain","msg":"obtaining certificate","identifier":"colinknapp.com"}
{"level":"info","ts":1751739586.96677,"logger":"http","msg":"using ACME account","account_id":"https://acme-staging-v02.api.letsencrypt.org/acme/acct/210713203","account_contact":[]}
{"level":"info","ts":1751739587.159206,"msg":"trying to solve challenge","identifier":"colinknapp.com","challenge_type":"http-01","ca":"https://acme-staging-v02.api.letsencrypt.org/directory"}
{"level":"error","ts":1751739587.860424,"msg":"challenge failed","identifier":"colinknapp.com","challenge_type":"http-01","problem":{"type":"urn:ietf:params:acme:error:unauthorized","title":"","detail":"2604:a880:cad:d0::ef5:1001: Invalid response from http://colinknapp.com/.well-known/acme-challenge/hKt6trbDmfbC05PqWuSSWcIDbCiNKb7eDfiiolzfohQ: 404","instance":"","subproblems":null},"stacktrace":"github.com/mholt/acmez/v3.(*Client).pollAuthorization\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:557\ngithub.com/mholt/acmez/v3.(*Client).solveChallenges\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:378\ngithub.com/mholt/acmez/v3.(*Client).ObtainCertificate\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:136\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).doIssue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:489\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:382\ngithub.com/caddyserver/caddy/v2/modules/caddytls.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/caddy/v2@v2.10.0/modules/caddytls/acmeissuer.go:288\ngithub.com/caddyserver/certmagic.(*Config).obtainCert.func2\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:626\ngithub.com/caddyserver/certmagic.doWithRetry\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:104\ngithub.com/caddyserver/certmagic.(*Config).obtainCert\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:700\ngithub.com/caddyserver/certmagic.(*Config).ObtainCertAsync\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:505\ngithub.com/caddyserver/certmagic.(*Config).manageOne.func1\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:415\ngithub.com/caddyserver/certmagic.(*jobManager).worker\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:73"}
{"level":"error","ts":1751739587.860965,"msg":"validating authorization","identifier":"colinknapp.com","problem":{"type":"urn:ietf:params:acme:error:unauthorized","title":"","detail":"2604:a880:cad:d0::ef5:1001: Invalid response from http://colinknapp.com/.well-known/acme-challenge/hKt6trbDmfbC05PqWuSSWcIDbCiNKb7eDfiiolzfohQ: 404","instance":"","subproblems":null},"order":"https://acme-staging-v02.api.letsencrypt.org/acme/order/210713203/25840446493","attempt":1,"max_attempts":3,"stacktrace":"github.com/mholt/acmez/v3.(*Client).ObtainCertificate\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:152\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).doIssue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:489\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:382\ngithub.com/caddyserver/caddy/v2/modules/caddytls.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/caddy/v2@v2.10.0/modules/caddytls/acmeissuer.go:288\ngithub.com/caddyserver/certmagic.(*Config).obtainCert.func2\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:626\ngithub.com/caddyserver/certmagic.doWithRetry\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:104\ngithub.com/caddyserver/certmagic.(*Config).obtainCert\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:700\ngithub.com/caddyserver/certmagic.(*Config).ObtainCertAsync\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:505\ngithub.com/caddyserver/certmagic.(*Config).manageOne.func1\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:415\ngithub.com/caddyserver/certmagic.(*jobManager).worker\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:73"}
{"level":"info","ts":1751739588.98678,"msg":"trying to solve challenge","identifier":"colinknapp.com","challenge_type":"tls-alpn-01","ca":"https://acme-staging-v02.api.letsencrypt.org/directory"}
{"level":"error","ts":1751739589.68759,"msg":"challenge failed","identifier":"colinknapp.com","challenge_type":"tls-alpn-01","problem":{"type":"urn:ietf:params:acme:error:tls","title":"","detail":"138.197.167.216: remote error: tls: unrecognized name","instance":"","subproblems":null},"stacktrace":"github.com/mholt/acmez/v3.(*Client).pollAuthorization\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:557\ngithub.com/mholt/acmez/v3.(*Client).solveChallenges\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:378\ngithub.com/mholt/acmez/v3.(*Client).ObtainCertificate\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:136\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).doIssue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:489\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:382\ngithub.com/caddyserver/caddy/v2/modules/caddytls.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/caddy/v2@v2.10.0/modules/caddytls/acmeissuer.go:288\ngithub.com/caddyserver/certmagic.(*Config).obtainCert.func2\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:626\ngithub.com/caddyserver/certmagic.doWithRetry\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:104\ngithub.com/caddyserver/certmagic.(*Config).obtainCert\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:700\ngithub.com/caddyserver/certmagic.(*Config).ObtainCertAsync\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:505\ngithub.com/caddyserver/certmagic.(*Config).manageOne.func1\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:415\ngithub.com/caddyserver/certmagic.(*jobManager).worker\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:73"}
{"level":"error","ts":1751739589.688067,"msg":"validating authorization","identifier":"colinknapp.com","problem":{"type":"urn:ietf:params:acme:error:tls","title":"","detail":"138.197.167.216: remote error: tls: unrecognized name","instance":"","subproblems":null},"order":"https://acme-staging-v02.api.letsencrypt.org/acme/order/210713203/25840447093","attempt":2,"max_attempts":3,"stacktrace":"github.com/mholt/acmez/v3.(*Client).ObtainCertificate\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:152\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).doIssue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:489\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:382\ngithub.com/caddyserver/caddy/v2/modules/caddytls.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/caddy/v2@v2.10.0/modules/caddytls/acmeissuer.go:288\ngithub.com/caddyserver/certmagic.(*Config).obtainCert.func2\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:626\ngithub.com/caddyserver/certmagic.doWithRetry\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:104\ngithub.com/caddyserver/certmagic.(*Config).obtainCert\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:700\ngithub.com/caddyserver/certmagic.(*Config).ObtainCertAsync\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:505\ngithub.com/caddyserver/certmagic.(*Config).manageOne.func1\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:415\ngithub.com/caddyserver/certmagic.(*jobManager).worker\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:73"}
{"level":"error","ts":1751739589.688373,"logger":"tls.obtain","msg":"could not get certificate from issuer","identifier":"colinknapp.com","issuer":"acme-v02.api.letsencrypt.org-directory","error":"HTTP 400 urn:ietf:params:acme:error:tls - 138.197.167.216: remote error: tls: unrecognized name"}
{"level":"error","ts":1751739589.68851,"logger":"tls.obtain","msg":"will retry","error":"[colinknapp.com] Obtain: [colinknapp.com] solving challenge: colinknapp.com: [colinknapp.com] authorization failed: HTTP 400 urn:ietf:params:acme:error:tls - 138.197.167.216: remote error: tls: unrecognized name (ca=https://acme-staging-v02.api.letsencrypt.org/directory)","attempt":4,"retrying_in":300,"elapsed":310.961173125,"max_duration":2592000}
{"level":"info","ts":1751739673.5404959,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"127.0.0.1","remote_port":"57250","client_ip":"127.0.0.1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/one-pager-tools/csv-tool.html","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"If-None-Match":["\"db4bbbwr4oyk2n1-gzip\""],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"],"Accept-Language":["en-US,en;q=0.5"],"Priority":["u=0, i"],"Sec-Fetch-Site":["same-origin"],"If-Modified-Since":["Sat, 05 Jul 2025 17:54:12 GMT"],"Referer":["http://localhost:8080/"],"Sec-Fetch-User":["?1"],"Upgrade-Insecure-Requests":["1"],"Sec-Fetch-Dest":["document"],"Sec-Fetch-Mode":["navigate"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0"],"Dnt":["1"],"Connection":["keep-alive"]}},"bytes_read":0,"user_id":"","duration":0.015525791,"size":1665,"status":200,"resp_headers":{"Cross-Origin-Resource-Policy":["same-origin"],"Cross-Origin-Embedder-Policy":["require-corp"],"Cross-Origin-Opener-Policy":["same-origin"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Cache-Control":["public, max-age=31536000, immutable"],"Vary":["Accept-Encoding"],"Content-Type":["text/html; charset=utf-8"],"X-Content-Type-Options":["nosniff"],"X-Frame-Options":["DENY"],"Etag":["\"db4bqunxd6yo3gd-gzip\""],"Last-Modified":["Sat, 05 Jul 2025 18:14:29 GMT"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Content-Encoding":["gzip"]}}
{"level":"info","ts":1751739675.868175,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"127.0.0.1","remote_port":"57250","client_ip":"127.0.0.1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/one-pager-tools/csv-tool.html","headers":{"Sec-Fetch-Site":["same-origin"],"If-Modified-Since":["Sat, 05 Jul 2025 18:14:29 GMT"],"Sec-Fetch-Dest":["document"],"Connection":["keep-alive"],"If-None-Match":["\"db4bqunxd6yo3gd-gzip\""],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Mode":["navigate"],"Accept-Language":["en-US,en;q=0.5"],"Priority":["u=0, i"],"Sec-Fetch-User":["?1"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"],"Upgrade-Insecure-Requests":["1"],"Referer":["http://localhost:8080/"],"Dnt":["1"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0"]}},"bytes_read":0,"user_id":"","duration":0.000579666,"size":0,"status":304,"resp_headers":{"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"X-Frame-Options":["DENY"],"Vary":["Accept-Encoding"],"Etag":["\"db4bqunxd6yo3gd\""],"Cross-Origin-Embedder-Policy":["require-corp"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Cache-Control":["public, max-age=31536000, immutable"],"Cross-Origin-Resource-Policy":["same-origin"],"X-Content-Type-Options":["nosniff"],"Cross-Origin-Opener-Policy":["same-origin"]}}
{"level":"info","ts":1751739677.53351,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"127.0.0.1","remote_port":"57250","client_ip":"127.0.0.1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/one-pager-tools/csv-tool.html","headers":{"Connection":["keep-alive"],"Sec-Fetch-Mode":["navigate"],"Sec-Fetch-Dest":["document"],"Sec-Fetch-User":["?1"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0"],"Accept-Encoding":["gzip, deflate, br, zstd"],"If-None-Match":["\"db4bqunxd6yo3gd-gzip\""],"Referer":["http://localhost:8080/"],"Dnt":["1"],"Upgrade-Insecure-Requests":["1"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"],"Priority":["u=0, i"],"Accept-Language":["en-US,en;q=0.5"],"Sec-Fetch-Site":["same-origin"],"If-Modified-Since":["Sat, 05 Jul 2025 18:14:29 GMT"]}},"bytes_read":0,"user_id":"","duration":0.001523959,"size":0,"status":304,"resp_headers":{"Vary":["Accept-Encoding"],"X-Content-Type-Options":["nosniff"],"X-Frame-Options":["DENY"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Cache-Control":["public, max-age=31536000, immutable"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Cross-Origin-Resource-Policy":["same-origin"],"Etag":["\"db4bqunxd6yo3gd\""],"Cross-Origin-Embedder-Policy":["require-corp"],"Cross-Origin-Opener-Policy":["same-origin"]}}
{"level":"info","ts":1751739678.708853,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"127.0.0.1","remote_port":"57251","client_ip":"127.0.0.1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/one-pager-tools/csv-tool.html","headers":{"Connection":["keep-alive"],"Upgrade-Insecure-Requests":["1"],"Priority":["u=0, i"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0"],"Sec-Fetch-Dest":["document"],"Sec-Fetch-Mode":["navigate"],"Sec-Fetch-User":["?1"],"Cache-Control":["no-cache"],"Referer":["http://localhost:8080/"],"Dnt":["1"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"],"Accept-Language":["en-US,en;q=0.5"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Site":["same-origin"],"Pragma":["no-cache"]}},"bytes_read":0,"user_id":"","duration":0.004137583,"size":1665,"status":200,"resp_headers":{"Content-Encoding":["gzip"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"X-Content-Type-Options":["nosniff"],"Etag":["\"db4bqunxd6yo3gd-gzip\""],"Last-Modified":["Sat, 05 Jul 2025 18:14:29 GMT"],"Cross-Origin-Resource-Policy":["same-origin"],"Content-Type":["text/html; charset=utf-8"],"Cross-Origin-Embedder-Policy":["require-corp"],"Cross-Origin-Opener-Policy":["same-origin"],"Cache-Control":["public, max-age=31536000, immutable"],"X-Frame-Options":["DENY"],"Vary":["Accept-Encoding"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"]}}
{"level":"info","ts":1751739678.819917,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"127.0.0.1","remote_port":"57251","client_ip":"127.0.0.1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/styles.css","headers":{"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0"],"Accept":["text/css,*/*;q=0.1"],"Priority":["u=2"],"Pragma":["no-cache"],"Referer":["http://localhost:8080/one-pager-tools/csv-tool.html"],"Sec-Fetch-Mode":["no-cors"],"Cache-Control":["no-cache"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Dnt":["1"],"Accept-Language":["en-US,en;q=0.5"],"Connection":["keep-alive"],"Sec-Fetch-Dest":["style"],"Sec-Fetch-Site":["same-origin"]}},"bytes_read":0,"user_id":"","duration":0.003274375,"size":1594,"status":200,"resp_headers":{"Cross-Origin-Embedder-Policy":["require-corp"],"Vary":["Accept-Encoding"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"X-Content-Type-Options":["nosniff"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Content-Encoding":["gzip"],"Cross-Origin-Resource-Policy":["same-origin"],"X-Frame-Options":["DENY"],"Cross-Origin-Opener-Policy":["same-origin"],"Last-Modified":["Sat, 05 Jul 2025 18:02:46 GMT"],"Cache-Control":["public, max-age=31536000, immutable"],"Etag":["\"db4bhvy0bbjy4iu-gzip\""],"Content-Type":["text/css; charset=utf-8"],"Referrer-Policy":["strict-origin-when-cross-origin"]}}
{"level":"info","ts":1751739678.821028,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"127.0.0.1","remote_port":"57253","client_ip":"127.0.0.1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/theme.js","headers":{"Sec-Fetch-Site":["same-origin"],"Pragma":["no-cache"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Dnt":["1"],"Priority":["u=2"],"Sec-Fetch-Mode":["no-cors"],"Accept-Language":["en-US,en;q=0.5"],"Referer":["http://localhost:8080/one-pager-tools/csv-tool.html"],"Sec-Fetch-Dest":["script"],"Cache-Control":["no-cache"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0"],"Accept":["*/*"],"Connection":["keep-alive"]}},"bytes_read":0,"user_id":"","duration":0.003416792,"size":678,"status":200,"resp_headers":{"Content-Type":["text/javascript; charset=utf-8"],"Cross-Origin-Embedder-Policy":["require-corp"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Vary":["Accept-Encoding"],"Etag":["\"daerkqziy4dn1is-gzip\""],"Last-Modified":["Thu, 05 Jun 2025 17:09:29 GMT"],"Content-Encoding":["gzip"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Cross-Origin-Resource-Policy":["same-origin"],"Cross-Origin-Opener-Policy":["same-origin"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Cache-Control":["public, max-age=31536000, immutable"],"X-Content-Type-Options":["nosniff"],"X-Frame-Options":["DENY"]}}
{"level":"info","ts":1751739678.821409,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"127.0.0.1","remote_port":"57255","client_ip":"127.0.0.1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/one-pager-tools/csv-tool.js","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Dest":["script"],"Pragma":["no-cache"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0"],"Accept":["*/*"],"Referer":["http://localhost:8080/one-pager-tools/csv-tool.html"],"Sec-Fetch-Mode":["no-cors"],"Dnt":["1"],"Connection":["keep-alive"],"Sec-Fetch-Site":["same-origin"],"Cache-Control":["no-cache"],"Accept-Language":["en-US,en;q=0.5"]}},"bytes_read":0,"user_id":"","duration":0.00490175,"size":1984,"status":200,"resp_headers":{"X-Content-Type-Options":["nosniff"],"Content-Type":["text/javascript; charset=utf-8"],"Cross-Origin-Embedder-Policy":["require-corp"],"Cross-Origin-Opener-Policy":["same-origin"],"Cache-Control":["public, max-age=31536000, immutable"],"Vary":["Accept-Encoding"],"Last-Modified":["Sat, 05 Jul 2025 17:31:10 GMT"],"Content-Encoding":["gzip"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Cross-Origin-Resource-Policy":["same-origin"],"X-Frame-Options":["DENY"],"Etag":["\"db4atop72u2i4ye-gzip\""],"Referrer-Policy":["strict-origin-when-cross-origin"]}}
{"level":"info","ts":1751739678.824568,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"127.0.0.1","remote_port":"57252","client_ip":"127.0.0.1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/one-pager-tools/tool-styles.css","headers":{"Priority":["u=2"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0"],"Dnt":["1"],"Accept":["text/css,*/*;q=0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Pragma":["no-cache"],"Cache-Control":["no-cache"],"Connection":["keep-alive"],"Sec-Fetch-Mode":["no-cors"],"Sec-Fetch-Site":["same-origin"],"Accept-Language":["en-US,en;q=0.5"],"Referer":["http://localhost:8080/one-pager-tools/csv-tool.html"],"Sec-Fetch-Dest":["style"]}},"bytes_read":0,"user_id":"","duration":0.003773125,"size":1469,"status":200,"resp_headers":{"X-Content-Type-Options":["nosniff"],"X-Frame-Options":["DENY"],"Cross-Origin-Opener-Policy":["same-origin"],"Cache-Control":["public, max-age=31536000, immutable"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Etag":["\"db4b2xr4l0jf44o-gzip\""],"Last-Modified":["Sat, 05 Jul 2025 17:43:15 GMT"],"Cross-Origin-Resource-Policy":["same-origin"],"Cross-Origin-Embedder-Policy":["require-corp"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Vary":["Accept-Encoding"],"Content-Type":["text/css; charset=utf-8"],"Content-Encoding":["gzip"]}}
{"level":"info","ts":1751739678.8256042,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"127.0.0.1","remote_port":"57254","client_ip":"127.0.0.1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/utils.js","headers":{"Pragma":["no-cache"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0"],"Referer":["http://localhost:8080/one-pager-tools/csv-tool.html"],"Dnt":["1"],"Accept-Language":["en-US,en;q=0.5"],"Sec-Fetch-Dest":["script"],"Priority":["u=2"],"Cache-Control":["no-cache"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Connection":["keep-alive"],"Sec-Fetch-Mode":["no-cors"],"Sec-Fetch-Site":["same-origin"],"Accept":["*/*"]}},"bytes_read":0,"user_id":"","duration":0.005408458,"size":775,"status":200,"resp_headers":{"Content-Type":["text/javascript; charset=utf-8"],"Cross-Origin-Embedder-Policy":["require-corp"],"Cross-Origin-Resource-Policy":["same-origin"],"Etag":["\"daerkqzizlwo1eq-gzip\""],"Last-Modified":["Thu, 05 Jun 2025 17:09:29 GMT"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Cache-Control":["public, max-age=31536000, immutable"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Vary":["Accept-Encoding"],"X-Frame-Options":["DENY"],"Content-Encoding":["gzip"],"Cross-Origin-Opener-Policy":["same-origin"],"Referrer-Policy":["strict-origin-when-cross-origin"],"X-Content-Type-Options":["nosniff"]}}
{"level":"info","ts":1751739678.857478,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"127.0.0.1","remote_port":"57255","client_ip":"127.0.0.1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/favicon.ico","headers":{"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0"],"Referer":["http://localhost:8080/one-pager-tools/csv-tool.html"],"Accept":["image/avif,image/webp,image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5"],"Accept-Language":["en-US,en;q=0.5"],"Dnt":["1"],"Sec-Fetch-Site":["same-origin"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Mode":["no-cors"],"Priority":["u=6"],"Connection":["keep-alive"],"Sec-Fetch-Dest":["image"],"Pragma":["no-cache"],"Cache-Control":["no-cache"]}},"bytes_read":0,"user_id":"","duration":0.000408458,"size":1,"status":200,"resp_headers":{"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Cross-Origin-Opener-Policy":["same-origin"],"Content-Type":["image/x-icon"],"Last-Modified":["Thu, 05 Jun 2025 17:09:29 GMT"],"Cross-Origin-Resource-Policy":["same-origin"],"X-Content-Type-Options":["nosniff"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Content-Length":["1"],"Cache-Control":["public, max-age=31536000, immutable"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Vary":["Accept-Encoding"],"Etag":["\"daerkqzimk0j1\""],"Accept-Ranges":["bytes"],"Cross-Origin-Embedder-Policy":["require-corp"],"X-Frame-Options":["DENY"]}}
{"level":"info","ts":1751739679.9043121,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"127.0.0.1","remote_port":"57256","client_ip":"127.0.0.1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/one-pager-tools/csv-tool.html","headers":{"Cache-Control":["no-cache"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Dnt":["1"],"Sec-Fetch-Mode":["navigate"],"Sec-Fetch-User":["?1"],"Sec-Fetch-Dest":["document"],"Referer":["http://localhost:8080/"],"Priority":["u=0, i"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"],"Accept-Language":["en-US,en;q=0.5"],"Connection":["keep-alive"],"Upgrade-Insecure-Requests":["1"],"Pragma":["no-cache"],"Sec-Fetch-Site":["same-origin"]}},"bytes_read":0,"user_id":"","duration":0.000926833,"size":1665,"status":200,"resp_headers":{"Content-Encoding":["gzip"],"Cross-Origin-Opener-Policy":["same-origin"],"Cross-Origin-Resource-Policy":["same-origin"],"X-Content-Type-Options":["nosniff"],"Vary":["Accept-Encoding"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Cache-Control":["public, max-age=31536000, immutable"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Cross-Origin-Embedder-Policy":["require-corp"],"Etag":["\"db4bqunxd6yo3gd-gzip\""],"Content-Type":["text/html; charset=utf-8"],"Last-Modified":["Sat, 05 Jul 2025 18:14:29 GMT"],"X-Frame-Options":["DENY"]}}
{"level":"info","ts":1751739679.972051,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"127.0.0.1","remote_port":"57256","client_ip":"127.0.0.1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/styles.css","headers":{"Dnt":["1"],"Sec-Fetch-Dest":["style"],"Sec-Fetch-Mode":["no-cors"],"Pragma":["no-cache"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:8080/one-pager-tools/csv-tool.html"],"Cache-Control":["no-cache"],"Accept-Language":["en-US,en;q=0.5"],"Sec-Fetch-Site":["same-origin"],"Accept":["text/css,*/*;q=0.1"],"Connection":["keep-alive"],"Priority":["u=2"]}},"bytes_read":0,"user_id":"","duration":0.000524958,"size":1594,"status":200,"resp_headers":{"Etag":["\"db4bhvy0bbjy4iu-gzip\""],"Cross-Origin-Resource-Policy":["same-origin"],"X-Content-Type-Options":["nosniff"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Last-Modified":["Sat, 05 Jul 2025 18:02:46 GMT"],"X-Frame-Options":["DENY"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Cache-Control":["public, max-age=31536000, immutable"],"Vary":["Accept-Encoding"],"Content-Type":["text/css; charset=utf-8"],"Cross-Origin-Opener-Policy":["same-origin"],"Content-Encoding":["gzip"],"Cross-Origin-Embedder-Policy":["require-corp"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"]}}
{"level":"info","ts":1751739679.972645,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"127.0.0.1","remote_port":"57257","client_ip":"127.0.0.1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/one-pager-tools/tool-styles.css","headers":{"Pragma":["no-cache"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0"],"Accept-Language":["en-US,en;q=0.5"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Dnt":["1"],"Sec-Fetch-Dest":["style"],"Referer":["http://localhost:8080/one-pager-tools/csv-tool.html"],"Connection":["keep-alive"],"Priority":["u=2"],"Cache-Control":["no-cache"],"Accept":["text/css,*/*;q=0.1"],"Sec-Fetch-Mode":["no-cors"],"Sec-Fetch-Site":["same-origin"]}},"bytes_read":0,"user_id":"","duration":0.000355708,"size":1469,"status":200,"resp_headers":{"Vary":["Accept-Encoding"],"Content-Encoding":["gzip"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"X-Content-Type-Options":["nosniff"],"Content-Type":["text/css; charset=utf-8"],"Cross-Origin-Embedder-Policy":["require-corp"],"Cross-Origin-Opener-Policy":["same-origin"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Etag":["\"db4b2xr4l0jf44o-gzip\""],"Referrer-Policy":["strict-origin-when-cross-origin"],"Cache-Control":["public, max-age=31536000, immutable"],"Cross-Origin-Resource-Policy":["same-origin"],"Last-Modified":["Sat, 05 Jul 2025 17:43:15 GMT"],"X-Frame-Options":["DENY"]}}
{"level":"info","ts":1751739679.972853,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"127.0.0.1","remote_port":"57258","client_ip":"127.0.0.1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/theme.js","headers":{"Accept-Language":["en-US,en;q=0.5"],"Referer":["http://localhost:8080/one-pager-tools/csv-tool.html"],"Dnt":["1"],"Sec-Fetch-Dest":["script"],"Cache-Control":["no-cache"],"Connection":["keep-alive"],"Sec-Fetch-Mode":["no-cors"],"Sec-Fetch-Site":["same-origin"],"Priority":["u=2"],"Pragma":["no-cache"],"Accept-Encoding":["gzip, deflate, br, zstd"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0"],"Accept":["*/*"]}},"bytes_read":0,"user_id":"","duration":0.000476167,"size":678,"status":200,"resp_headers":{"X-Frame-Options":["DENY"],"Cache-Control":["public, max-age=31536000, immutable"],"Etag":["\"daerkqziy4dn1is-gzip\""],"Cross-Origin-Resource-Policy":["same-origin"],"Cross-Origin-Embedder-Policy":["require-corp"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Content-Encoding":["gzip"],"X-Content-Type-Options":["nosniff"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Vary":["Accept-Encoding"],"Cross-Origin-Opener-Policy":["same-origin"],"Content-Type":["text/javascript; charset=utf-8"],"Last-Modified":["Thu, 05 Jun 2025 17:09:29 GMT"]}}
{"level":"info","ts":1751739679.973341,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"127.0.0.1","remote_port":"57260","client_ip":"127.0.0.1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/one-pager-tools/csv-tool.js","headers":{"Accept-Language":["en-US,en;q=0.5"],"Dnt":["1"],"Pragma":["no-cache"],"Accept":["*/*"],"Sec-Fetch-Dest":["script"],"Sec-Fetch-Mode":["no-cors"],"Sec-Fetch-Site":["same-origin"],"Cache-Control":["no-cache"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:8080/one-pager-tools/csv-tool.html"],"Connection":["keep-alive"]}},"bytes_read":0,"user_id":"","duration":0.000869166,"size":1984,"status":200,"resp_headers":{"Cross-Origin-Embedder-Policy":["require-corp"],"Content-Type":["text/javascript; charset=utf-8"],"Last-Modified":["Sat, 05 Jul 2025 17:31:10 GMT"],"Cache-Control":["public, max-age=31536000, immutable"],"Content-Encoding":["gzip"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Cross-Origin-Resource-Policy":["same-origin"],"Cross-Origin-Opener-Policy":["same-origin"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Vary":["Accept-Encoding"],"Etag":["\"db4atop72u2i4ye-gzip\""],"X-Content-Type-Options":["nosniff"],"X-Frame-Options":["DENY"]}}
{"level":"info","ts":1751739679.973473,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"127.0.0.1","remote_port":"57259","client_ip":"127.0.0.1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/utils.js","headers":{"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0"],"Accept-Language":["en-US,en;q=0.5"],"Dnt":["1"],"Sec-Fetch-Dest":["script"],"Priority":["u=2"],"Accept":["*/*"],"Referer":["http://localhost:8080/one-pager-tools/csv-tool.html"],"Connection":["keep-alive"],"Sec-Fetch-Site":["same-origin"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Mode":["no-cors"],"Pragma":["no-cache"],"Cache-Control":["no-cache"]}},"bytes_read":0,"user_id":"","duration":0.000989667,"size":775,"status":200,"resp_headers":{"Vary":["Accept-Encoding"],"X-Frame-Options":["DENY"],"Content-Encoding":["gzip"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Etag":["\"daerkqzizlwo1eq-gzip\""],"Cross-Origin-Opener-Policy":["same-origin"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Cache-Control":["public, max-age=31536000, immutable"],"Last-Modified":["Thu, 05 Jun 2025 17:09:29 GMT"],"Cross-Origin-Embedder-Policy":["require-corp"],"Cross-Origin-Resource-Policy":["same-origin"],"X-Content-Type-Options":["nosniff"],"Content-Type":["text/javascript; charset=utf-8"],"Referrer-Policy":["strict-origin-when-cross-origin"]}}
{"level":"info","ts":1751739679.999177,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"127.0.0.1","remote_port":"57260","client_ip":"127.0.0.1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/favicon.ico","headers":{"Connection":["keep-alive"],"Sec-Fetch-Dest":["image"],"Priority":["u=6"],"Accept-Language":["en-US,en;q=0.5"],"Referer":["http://localhost:8080/one-pager-tools/csv-tool.html"],"Sec-Fetch-Site":["same-origin"],"Pragma":["no-cache"],"Cache-Control":["no-cache"],"Accept":["image/avif,image/webp,image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5"],"Sec-Fetch-Mode":["no-cors"],"Dnt":["1"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0"],"Accept-Encoding":["gzip, deflate, br, zstd"]}},"bytes_read":0,"user_id":"","duration":0.000280666,"size":1,"status":200,"resp_headers":{"Content-Type":["image/x-icon"],"Last-Modified":["Thu, 05 Jun 2025 17:09:29 GMT"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Cache-Control":["public, max-age=31536000, immutable"],"Vary":["Accept-Encoding"],"Accept-Ranges":["bytes"],"Content-Length":["1"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"X-Content-Type-Options":["nosniff"],"X-Frame-Options":["DENY"],"Etag":["\"daerkqzimk0j1\""],"Cross-Origin-Opener-Policy":["same-origin"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Cross-Origin-Resource-Policy":["same-origin"],"Cross-Origin-Embedder-Policy":["require-corp"]}}
{"level":"info","ts":1751739720.884835,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"::1","remote_port":"57287","client_ip":"::1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/","headers":{"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Platform":["\"macOS\""],"Sec-Ch-Ua":["\"Google Chrome\";v=\"137\", \"Chromium\";v=\"137\", \"Not/A)Brand\";v=\"24\""],"If-None-Match":["\"db4b159qb1xccqw-gzip\""],"Accept-Language":["en-US,en;q=0.9"],"Sec-Fetch-Dest":["document"],"Upgrade-Insecure-Requests":["1"],"Sec-Fetch-Site":["none"],"Sec-Ch-Ua-Mobile":["?0"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"],"Accept-Encoding":["gzip, deflate, br, zstd"],"If-Modified-Since":["Sat, 05 Jul 2025 17:40:54 GMT"],"Cache-Control":["max-age=0"],"Connection":["keep-alive"],"Sec-Fetch-Mode":["navigate"],"Sec-Fetch-User":["?1"]}},"bytes_read":0,"user_id":"","duration":0.006238541,"size":5186,"status":200,"resp_headers":{"X-Content-Type-Options":["nosniff"],"Cross-Origin-Opener-Policy":["same-origin"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Last-Modified":["Sat, 05 Jul 2025 18:14:29 GMT"],"Cache-Control":["public, max-age=31536000, immutable"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Etag":["\"db4bquo9k394d1r-gzip\""],"Content-Encoding":["gzip"],"Cross-Origin-Resource-Policy":["same-origin"],"X-Frame-Options":["DENY"],"Cross-Origin-Embedder-Policy":["require-corp"],"Vary":["Accept-Encoding"],"Content-Type":["text/html; charset=utf-8"]}}
{"level":"info","ts":1751739721.273846,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"::1","remote_port":"57287","client_ip":"::1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/","headers":{"Upgrade-Insecure-Requests":["1"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"],"Sec-Fetch-User":["?1"],"Connection":["keep-alive"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"],"Cache-Control":["no-cache"],"Accept-Language":["en-US,en;q=0.9"],"Sec-Fetch-Mode":["navigate"],"Sec-Fetch-Dest":["document"],"Pragma":["no-cache"],"Sec-Ch-Ua":["\"Google Chrome\";v=\"137\", \"Chromium\";v=\"137\", \"Not/A)Brand\";v=\"24\""],"Sec-Fetch-Site":["none"],"Sec-Ch-Ua-Platform":["\"macOS\""],"Sec-Ch-Ua-Mobile":["?0"],"Accept-Encoding":["gzip, deflate, br, zstd"]}},"bytes_read":0,"user_id":"","duration":0.000861208,"size":5186,"status":200,"resp_headers":{"Last-Modified":["Sat, 05 Jul 2025 18:14:29 GMT"],"Content-Encoding":["gzip"],"Cross-Origin-Embedder-Policy":["require-corp"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Vary":["Accept-Encoding"],"Etag":["\"db4bquo9k394d1r-gzip\""],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Cross-Origin-Opener-Policy":["same-origin"],"Cache-Control":["public, max-age=31536000, immutable"],"Content-Type":["text/html; charset=utf-8"],"Cross-Origin-Resource-Policy":["same-origin"],"X-Content-Type-Options":["nosniff"],"X-Frame-Options":["DENY"]}}
{"level":"info","ts":1751739721.283733,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"::1","remote_port":"57287","client_ip":"::1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/styles.css","headers":{"Sec-Fetch-Site":["same-origin"],"Connection":["keep-alive"],"Sec-Ch-Ua-Platform":["\"macOS\""],"Accept":["text/css,*/*;q=0.1"],"Pragma":["no-cache"],"Sec-Ch-Ua":["\"Google Chrome\";v=\"137\", \"Chromium\";v=\"137\", \"Not/A)Brand\";v=\"24\""],"Accept-Language":["en-US,en;q=0.9"],"Cache-Control":["no-cache"],"Accept-Encoding":["gzip, deflate, br, zstd"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"],"Sec-Fetch-Dest":["style"],"Referer":["http://localhost:8080/"],"Sec-Fetch-Mode":["no-cors"],"Sec-Ch-Ua-Mobile":["?0"]}},"bytes_read":0,"user_id":"","duration":0.002707125,"size":1594,"status":200,"resp_headers":{"Cross-Origin-Embedder-Policy":["require-corp"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"X-Content-Type-Options":["nosniff"],"Vary":["Accept-Encoding"],"Etag":["\"db4bhvy0bbjy4iu-gzip\""],"Content-Type":["text/css; charset=utf-8"],"Last-Modified":["Sat, 05 Jul 2025 18:02:46 GMT"],"Cross-Origin-Opener-Policy":["same-origin"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Cache-Control":["public, max-age=31536000, immutable"],"X-Frame-Options":["DENY"],"Content-Encoding":["gzip"],"Cross-Origin-Resource-Policy":["same-origin"]}}
{"level":"info","ts":1751739721.2837389,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"::1","remote_port":"57288","client_ip":"::1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/theme.js","headers":{"Accept":["*/*"],"Sec-Ch-Ua-Platform":["\"macOS\""],"Sec-Fetch-Mode":["no-cors"],"Referer":["http://localhost:8080/"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"],"Sec-Ch-Ua":["\"Google Chrome\";v=\"137\", \"Chromium\";v=\"137\", \"Not/A)Brand\";v=\"24\""],"Accept-Language":["en-US,en;q=0.9"],"Pragma":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["script"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Site":["same-origin"],"Cache-Control":["no-cache"],"Connection":["keep-alive"]}},"bytes_read":0,"user_id":"","duration":0.002064334,"size":678,"status":200,"resp_headers":{"Cross-Origin-Resource-Policy":["same-origin"],"X-Frame-Options":["DENY"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Content-Type":["text/javascript; charset=utf-8"],"Cross-Origin-Embedder-Policy":["require-corp"],"Etag":["\"daerkqziy4dn1is-gzip\""],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Cache-Control":["public, max-age=31536000, immutable"],"X-Content-Type-Options":["nosniff"],"Vary":["Accept-Encoding"],"Last-Modified":["Thu, 05 Jun 2025 17:09:29 GMT"],"Content-Encoding":["gzip"],"Cross-Origin-Opener-Policy":["same-origin"],"Referrer-Policy":["strict-origin-when-cross-origin"]}}
{"level":"info","ts":1751739721.32471,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"::1","remote_port":"57288","client_ip":"::1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/favicon.ico","headers":{"Pragma":["no-cache"],"Sec-Fetch-Site":["same-origin"],"Referer":["http://localhost:8080/"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Platform":["\"macOS\""],"Connection":["keep-alive"],"Accept-Language":["en-US,en;q=0.9"],"Accept-Encoding":["gzip, deflate, br, zstd"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"],"Sec-Fetch-Mode":["no-cors"],"Sec-Ch-Ua":["\"Google Chrome\";v=\"137\", \"Chromium\";v=\"137\", \"Not/A)Brand\";v=\"24\""],"Sec-Ch-Ua-Mobile":["?0"],"Accept":["image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8"],"Sec-Fetch-Dest":["image"]}},"bytes_read":0,"user_id":"","duration":0.000416791,"size":1,"status":200,"resp_headers":{"Accept-Ranges":["bytes"],"X-Content-Type-Options":["nosniff"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Vary":["Accept-Encoding"],"Etag":["\"daerkqzimk0j1\""],"Last-Modified":["Thu, 05 Jun 2025 17:09:29 GMT"],"X-Frame-Options":["DENY"],"Cross-Origin-Opener-Policy":["same-origin"],"Content-Length":["1"],"Cross-Origin-Embedder-Policy":["require-corp"],"Cache-Control":["public, max-age=31536000, immutable"],"Cross-Origin-Resource-Policy":["same-origin"],"Content-Type":["image/x-icon"]}}
{"level":"info","ts":1751739723.5364192,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"::1","remote_port":"57291","client_ip":"::1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/","headers":{"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"],"Sec-Fetch-Mode":["navigate"],"Pragma":["no-cache"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"macOS\""],"Sec-Fetch-User":["?1"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"],"Sec-Fetch-Dest":["document"],"Accept-Language":["en-US,en;q=0.9"],"Connection":["keep-alive"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Google Chrome\";v=\"137\", \"Chromium\";v=\"137\", \"Not/A)Brand\";v=\"24\""],"Upgrade-Insecure-Requests":["1"],"Sec-Fetch-Site":["none"]}},"bytes_read":0,"user_id":"","duration":0.006212709,"size":5186,"status":200,"resp_headers":{"Etag":["\"db4bquo9k394d1r-gzip\""],"Referrer-Policy":["strict-origin-when-cross-origin"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"X-Content-Type-Options":["nosniff"],"X-Frame-Options":["DENY"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Last-Modified":["Sat, 05 Jul 2025 18:14:29 GMT"],"Cache-Control":["public, max-age=31536000, immutable"],"Vary":["Accept-Encoding"],"Content-Type":["text/html; charset=utf-8"],"Cross-Origin-Resource-Policy":["same-origin"],"Content-Encoding":["gzip"],"Cross-Origin-Embedder-Policy":["require-corp"],"Cross-Origin-Opener-Policy":["same-origin"]}}
{"level":"info","ts":1751739723.549154,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"::1","remote_port":"57292","client_ip":"::1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/theme.js","headers":{"Sec-Ch-Ua-Platform":["\"macOS\""],"Sec-Fetch-Site":["same-origin"],"Referer":["http://localhost:8080/"],"Cache-Control":["no-cache"],"Sec-Ch-Ua":["\"Google Chrome\";v=\"137\", \"Chromium\";v=\"137\", \"Not/A)Brand\";v=\"24\""],"Accept":["*/*"],"Sec-Fetch-Mode":["no-cors"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"],"Sec-Fetch-Dest":["script"],"Pragma":["no-cache"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-US,en;q=0.9"],"Connection":["keep-alive"],"Sec-Ch-Ua-Mobile":["?0"]}},"bytes_read":0,"user_id":"","duration":0.000694583,"size":678,"status":200,"resp_headers":{"Cross-Origin-Embedder-Policy":["require-corp"],"Vary":["Accept-Encoding"],"Cache-Control":["public, max-age=31536000, immutable"],"Last-Modified":["Thu, 05 Jun 2025 17:09:29 GMT"],"Content-Encoding":["gzip"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Cross-Origin-Resource-Policy":["same-origin"],"X-Frame-Options":["DENY"],"Etag":["\"daerkqziy4dn1is-gzip\""],"Content-Type":["text/javascript; charset=utf-8"],"Cross-Origin-Opener-Policy":["same-origin"],"X-Content-Type-Options":["nosniff"]}}
{"level":"info","ts":1751739723.549674,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"::1","remote_port":"57291","client_ip":"::1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/styles.css","headers":{"Sec-Ch-Ua":["\"Google Chrome\";v=\"137\", \"Chromium\";v=\"137\", \"Not/A)Brand\";v=\"24\""],"Sec-Fetch-Mode":["no-cors"],"Sec-Ch-Ua-Platform":["\"macOS\""],"Accept":["text/css,*/*;q=0.1"],"Referer":["http://localhost:8080/"],"Cache-Control":["no-cache"],"Sec-Fetch-Dest":["style"],"Accept-Language":["en-US,en;q=0.9"],"Connection":["keep-alive"],"Sec-Ch-Ua-Mobile":["?0"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Pragma":["no-cache"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"],"Sec-Fetch-Site":["same-origin"]}},"bytes_read":0,"user_id":"","duration":0.000732458,"size":1594,"status":200,"resp_headers":{"Cross-Origin-Embedder-Policy":["require-corp"],"Content-Type":["text/css; charset=utf-8"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Cache-Control":["public, max-age=31536000, immutable"],"X-Frame-Options":["DENY"],"Etag":["\"db4bhvy0bbjy4iu-gzip\""],"Content-Encoding":["gzip"],"X-Content-Type-Options":["nosniff"],"Vary":["Accept-Encoding"],"Last-Modified":["Sat, 05 Jul 2025 18:02:46 GMT"],"Cross-Origin-Opener-Policy":["same-origin"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Cross-Origin-Resource-Policy":["same-origin"]}}
{"level":"info","ts":1751739723.565779,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"::1","remote_port":"57291","client_ip":"::1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/favicon.ico","headers":{"Referer":["http://localhost:8080/"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept":["image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8"],"Sec-Fetch-Site":["same-origin"],"Sec-Fetch-Dest":["image"],"Sec-Ch-Ua-Mobile":["?0"],"Connection":["keep-alive"],"Pragma":["no-cache"],"Sec-Fetch-Mode":["no-cors"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Platform":["\"macOS\""],"Accept-Language":["en-US,en;q=0.9"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"],"Sec-Ch-Ua":["\"Google Chrome\";v=\"137\", \"Chromium\";v=\"137\", \"Not/A)Brand\";v=\"24\""]}},"bytes_read":0,"user_id":"","duration":0.000751042,"size":1,"status":200,"resp_headers":{"Content-Length":["1"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Cross-Origin-Resource-Policy":["same-origin"],"X-Frame-Options":["DENY"],"Content-Type":["image/x-icon"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Cross-Origin-Opener-Policy":["same-origin"],"Cache-Control":["public, max-age=31536000, immutable"],"X-Content-Type-Options":["nosniff"],"Cross-Origin-Embedder-Policy":["require-corp"],"Etag":["\"daerkqzimk0j1\""],"Vary":["Accept-Encoding"],"Last-Modified":["Thu, 05 Jun 2025 17:09:29 GMT"],"Accept-Ranges":["bytes"]}}
{"level":"info","ts":1751739726.127846,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"::1","remote_port":"57291","client_ip":"::1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/stories/","headers":{"Sec-Fetch-Mode":["navigate"],"Accept-Language":["en-US,en;q=0.9"],"Connection":["keep-alive"],"Sec-Ch-Ua-Platform":["\"macOS\""],"Upgrade-Insecure-Requests":["1"],"Sec-Fetch-Site":["same-origin"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"],"Sec-Fetch-Dest":["document"],"Referer":["http://localhost:8080/"],"Sec-Ch-Ua":["\"Google Chrome\";v=\"137\", \"Chromium\";v=\"137\", \"Not/A)Brand\";v=\"24\""],"Sec-Ch-Ua-Mobile":["?0"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"],"Sec-Fetch-User":["?1"]}},"bytes_read":0,"user_id":"","duration":0.00319225,"size":1987,"status":200,"resp_headers":{"Referrer-Policy":["strict-origin-when-cross-origin"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"X-Content-Type-Options":["nosniff"],"Vary":["Accept-Encoding"],"Etag":["\"db4bqupjqm554sq-gzip\""],"Content-Type":["text/html; charset=utf-8"],"Cross-Origin-Embedder-Policy":["require-corp"],"Cross-Origin-Opener-Policy":["same-origin"],"Content-Encoding":["gzip"],"Cross-Origin-Resource-Policy":["same-origin"],"Last-Modified":["Sat, 05 Jul 2025 18:14:29 GMT"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Cache-Control":["public, max-age=31536000, immutable"],"X-Frame-Options":["DENY"]}}
{"level":"info","ts":1751739726.1533198,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"::1","remote_port":"57291","client_ip":"::1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/stories/stories.css","headers":{"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["no-cors"],"Sec-Fetch-Dest":["style"],"Referer":["http://localhost:8080/stories/"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Google Chrome\";v=\"137\", \"Chromium\";v=\"137\", \"Not/A)Brand\";v=\"24\""],"Accept":["text/css,*/*;q=0.1"],"Sec-Fetch-Site":["same-origin"],"Accept-Language":["en-US,en;q=0.9"],"Connection":["keep-alive"],"Sec-Ch-Ua-Platform":["\"macOS\""]}},"bytes_read":0,"user_id":"","duration":0.0008405,"size":1226,"status":200,"resp_headers":{"Content-Encoding":["gzip"],"Cross-Origin-Opener-Policy":["same-origin"],"Cross-Origin-Embedder-Policy":["require-corp"],"Vary":["Accept-Encoding"],"Content-Type":["text/css; charset=utf-8"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"X-Content-Type-Options":["nosniff"],"X-Frame-Options":["DENY"],"Cross-Origin-Resource-Policy":["same-origin"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Last-Modified":["Sat, 05 Jul 2025 18:14:20 GMT"],"Cache-Control":["public, max-age=31536000, immutable"],"Etag":["\"db4bqqjbe6x53bg-gzip\""],"Referrer-Policy":["strict-origin-when-cross-origin"]}}
{"level":"info","ts":1751739889.693784,"logger":"tls.obtain","msg":"obtaining certificate","identifier":"colinknapp.com"}
{"level":"info","ts":1751739889.7038321,"logger":"http","msg":"using ACME account","account_id":"https://acme-staging-v02.api.letsencrypt.org/acme/acct/210713203","account_contact":[]}
{"level":"info","ts":1751739890.085554,"msg":"trying to solve challenge","identifier":"colinknapp.com","challenge_type":"http-01","ca":"https://acme-staging-v02.api.letsencrypt.org/directory"}
{"level":"error","ts":1751739890.770806,"msg":"challenge failed","identifier":"colinknapp.com","challenge_type":"http-01","problem":{"type":"urn:ietf:params:acme:error:unauthorized","title":"","detail":"2604:a880:cad:d0::ef5:1001: Invalid response from http://colinknapp.com/.well-known/acme-challenge/DWUN2FubCp8-Utbvg1U3sQzEN9ohjNM7v505ji-DDLI: 404","instance":"","subproblems":null},"stacktrace":"github.com/mholt/acmez/v3.(*Client).pollAuthorization\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:557\ngithub.com/mholt/acmez/v3.(*Client).solveChallenges\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:378\ngithub.com/mholt/acmez/v3.(*Client).ObtainCertificate\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:136\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).doIssue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:489\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:382\ngithub.com/caddyserver/caddy/v2/modules/caddytls.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/caddy/v2@v2.10.0/modules/caddytls/acmeissuer.go:288\ngithub.com/caddyserver/certmagic.(*Config).obtainCert.func2\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:626\ngithub.com/caddyserver/certmagic.doWithRetry\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:104\ngithub.com/caddyserver/certmagic.(*Config).obtainCert\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:700\ngithub.com/caddyserver/certmagic.(*Config).ObtainCertAsync\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:505\ngithub.com/caddyserver/certmagic.(*Config).manageOne.func1\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:415\ngithub.com/caddyserver/certmagic.(*jobManager).worker\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:73"}
{"level":"error","ts":1751739890.773333,"msg":"validating authorization","identifier":"colinknapp.com","problem":{"type":"urn:ietf:params:acme:error:unauthorized","title":"","detail":"2604:a880:cad:d0::ef5:1001: Invalid response from http://colinknapp.com/.well-known/acme-challenge/DWUN2FubCp8-Utbvg1U3sQzEN9ohjNM7v505ji-DDLI: 404","instance":"","subproblems":null},"order":"https://acme-staging-v02.api.letsencrypt.org/acme/order/210713203/25840531423","attempt":1,"max_attempts":3,"stacktrace":"github.com/mholt/acmez/v3.(*Client).ObtainCertificate\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:152\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).doIssue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:489\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:382\ngithub.com/caddyserver/caddy/v2/modules/caddytls.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/caddy/v2@v2.10.0/modules/caddytls/acmeissuer.go:288\ngithub.com/caddyserver/certmagic.(*Config).obtainCert.func2\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:626\ngithub.com/caddyserver/certmagic.doWithRetry\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:104\ngithub.com/caddyserver/certmagic.(*Config).obtainCert\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:700\ngithub.com/caddyserver/certmagic.(*Config).ObtainCertAsync\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:505\ngithub.com/caddyserver/certmagic.(*Config).manageOne.func1\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:415\ngithub.com/caddyserver/certmagic.(*jobManager).worker\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:73"}
{"level":"info","ts":1751739891.900807,"msg":"trying to solve challenge","identifier":"colinknapp.com","challenge_type":"tls-alpn-01","ca":"https://acme-staging-v02.api.letsencrypt.org/directory"}
{"level":"error","ts":1751739892.895112,"msg":"challenge failed","identifier":"colinknapp.com","challenge_type":"tls-alpn-01","problem":{"type":"urn:ietf:params:acme:error:tls","title":"","detail":"138.197.167.216: remote error: tls: unrecognized name","instance":"","subproblems":null},"stacktrace":"github.com/mholt/acmez/v3.(*Client).pollAuthorization\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:557\ngithub.com/mholt/acmez/v3.(*Client).solveChallenges\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:378\ngithub.com/mholt/acmez/v3.(*Client).ObtainCertificate\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:136\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).doIssue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:489\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:382\ngithub.com/caddyserver/caddy/v2/modules/caddytls.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/caddy/v2@v2.10.0/modules/caddytls/acmeissuer.go:288\ngithub.com/caddyserver/certmagic.(*Config).obtainCert.func2\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:626\ngithub.com/caddyserver/certmagic.doWithRetry\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:104\ngithub.com/caddyserver/certmagic.(*Config).obtainCert\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:700\ngithub.com/caddyserver/certmagic.(*Config).ObtainCertAsync\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:505\ngithub.com/caddyserver/certmagic.(*Config).manageOne.func1\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:415\ngithub.com/caddyserver/certmagic.(*jobManager).worker\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:73"}
{"level":"error","ts":1751739892.895329,"msg":"validating authorization","identifier":"colinknapp.com","problem":{"type":"urn:ietf:params:acme:error:tls","title":"","detail":"138.197.167.216: remote error: tls: unrecognized name","instance":"","subproblems":null},"order":"https://acme-staging-v02.api.letsencrypt.org/acme/order/210713203/25840531943","attempt":2,"max_attempts":3,"stacktrace":"github.com/mholt/acmez/v3.(*Client).ObtainCertificate\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:152\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).doIssue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:489\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:382\ngithub.com/caddyserver/caddy/v2/modules/caddytls.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/caddy/v2@v2.10.0/modules/caddytls/acmeissuer.go:288\ngithub.com/caddyserver/certmagic.(*Config).obtainCert.func2\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:626\ngithub.com/caddyserver/certmagic.doWithRetry\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:104\ngithub.com/caddyserver/certmagic.(*Config).obtainCert\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:700\ngithub.com/caddyserver/certmagic.(*Config).ObtainCertAsync\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:505\ngithub.com/caddyserver/certmagic.(*Config).manageOne.func1\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:415\ngithub.com/caddyserver/certmagic.(*jobManager).worker\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:73"}
{"level":"error","ts":1751739892.895437,"logger":"tls.obtain","msg":"could not get certificate from issuer","identifier":"colinknapp.com","issuer":"acme-v02.api.letsencrypt.org-directory","error":"HTTP 400 urn:ietf:params:acme:error:tls - 138.197.167.216: remote error: tls: unrecognized name"}
{"level":"error","ts":1751739892.895788,"logger":"tls.obtain","msg":"will retry","error":"[colinknapp.com] Obtain: [colinknapp.com] solving challenge: colinknapp.com: [colinknapp.com] authorization failed: HTTP 400 urn:ietf:params:acme:error:tls - 138.197.167.216: remote error: tls: unrecognized name (ca=https://acme-staging-v02.api.letsencrypt.org/directory)","attempt":5,"retrying_in":600,"elapsed":614.168914,"max_duration":2592000}
{"level":"info","ts":1751739910.3035798,"msg":"shutting down apps, then terminating","signal":"SIGTERM"}
{"level":"warn","ts":1751739910.3039591,"msg":"exiting; byeee!! 👋","signal":"SIGTERM"}
{"level":"info","ts":1751739910.3044071,"logger":"http","msg":"servers shutting down with eternal grace period"}
{"level":"info","ts":1751739910.3074849,"logger":"tls.obtain","msg":"releasing lock","identifier":"colinknapp.com"}
{"level":"error","ts":1751739910.310326,"msg":"unable to clean up lock in storage backend","signal":"SIGTERM","storage":"FileStorage:/Users/computerpro/Library/Application Support/Caddy","lock_key":"issue_cert_colinknapp.com","error":"remove /Users/computerpro/Library/Application Support/Caddy/locks/issue_cert_colinknapp.com.lock: no such file or directory"}
{"level":"info","ts":1751739910.310804,"logger":"admin","msg":"stopped previous server","address":"localhost:2019"}
{"level":"info","ts":1751739910.310834,"msg":"shutdown complete","signal":"SIGTERM","exit_code":0}

58
docker/resume/caddy.sh Executable file
View File

@ -0,0 +1,58 @@
#!/bin/bash
# =====================================================================
# caddy.sh - Simple script to start Caddy server
# =====================================================================
# Usage:
# ./caddy.sh - Start/restart the Caddy server
#
# This script handles all Caddy server operations from the correct directory
# =====================================================================
set -e
# Ensure we're in the correct directory (where this script is located)
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
cd "$SCRIPT_DIR"
# Stop any existing Caddy processes
echo "=== Stopping Caddy Server ==="
killall caddy 2>/dev/null || true
pkill -f "caddy run" 2>/dev/null || true
sleep 1
echo "All Caddy processes stopped."
# Update CSP hashes
echo "=== Updating CSP Hashes ==="
# Run the update-csp-hashes.sh script
if [ -f "./update-csp-hashes.sh" ]; then
./update-csp-hashes.sh
else
echo "WARNING: update-csp-hashes.sh not found. Skipping CSP hash updates."
fi
# Start Caddy
echo "=== Starting Caddy Server ==="
echo "Working directory: $(pwd)"
# Check if Caddyfile.local exists
if [ ! -f "Caddyfile.local" ]; then
echo "ERROR: Caddyfile.local not found in $(pwd)"
echo "Please ensure you're running this script from the directory containing Caddyfile.local"
exit 1
fi
# Launch Caddy
echo "Starting Caddy with Caddyfile.local..."
caddy run --config Caddyfile.local &
# Wait a moment to check if Caddy started successfully
sleep 2
if ! pgrep -f "caddy run" > /dev/null; then
echo "ERROR: Caddy failed to start. Check the error messages above."
exit 1
fi
echo "=== Caddy Server Started Successfully ==="
echo "Local server running at: http://localhost:8080"
exit 0

View File

@ -0,0 +1,71 @@
/**
* Script to help convert existing HTML files to use the includes system
*
* Usage:
* 1. Include this script in your HTML file temporarily
* 2. Open the page in a browser
* 3. Open the browser console
* 4. The script will output the modified HTML content that uses includes
*/
document.addEventListener('DOMContentLoaded', function() {
// Extract the head content
const headContent = document.head.innerHTML;
// Extract the navigation content
const navContent = document.querySelector('.main-nav')?.outerHTML || '';
// Extract the theme switcher content
const themeSwitcherContent = document.querySelector('.theme-switch')?.outerHTML || '';
// Extract the footer content (accessibility notice)
const footerContent = document.querySelector('.accessibility-notice')?.parentElement?.innerHTML || '';
// Extract the main content
const mainContentElement = document.querySelector('.container-fluid');
let mainContent = '';
if (mainContentElement) {
// Clone the main content to avoid modifying the original
const mainContentClone = mainContentElement.cloneNode(true);
// Remove the accessibility notice from the clone if it exists
const accessibilityNotice = mainContentClone.querySelector('.accessibility-notice');
if (accessibilityNotice && accessibilityNotice.parentElement) {
accessibilityNotice.parentElement.removeChild(accessibilityNotice);
}
mainContent = mainContentClone.innerHTML;
}
// Create the modified HTML content
const modifiedHTML = `<!DOCTYPE html>
<html lang="en">
<head>
${headContent}
<script src="${getRelativePath()}includes.js"></script>
</head>
<body>
<!-- Header Include -->
<div id="header-include"></div>
<!-- Main Content -->
${mainContent}
<!-- Footer Include -->
<div id="footer-include"></div>
</body>
</html>`;
// Output the modified HTML content to the console
console.log('=== MODIFIED HTML CONTENT ===');
console.log(modifiedHTML);
console.log('=== END MODIFIED HTML CONTENT ===');
// Helper function to get the relative path to the root
function getRelativePath() {
const path = window.location.pathname;
const depth = (path.match(/\//g) || []).length - 1;
return depth > 0 ? '../'.repeat(depth) : '';
}
});

View File

@ -0,0 +1,113 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Colin Knapp - CSV Processing Tool">
<title>CSV Viewer - Colin Knapp</title>
<link rel="icon" type="image/x-icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles.css">
<link rel="stylesheet" href="tool-styles.css" integrity="sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=">
<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-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4="></script>
<script src="../utils.js" integrity="sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544="></script>
<style>
/* Additional inline styles to fix layout */
.container-fluid {
max-width: 100%;
padding: 0 15px;
}
.tool-container {
width: 100%;
max-width: 100%;
}
.form-group.full-width {
width: 100%;
max-width: 100%;
}
#csvInput {
width: 100%;
max-width: 100%;
box-sizing: border-box;
}
/* More aggressive fixes for textarea */
textarea#csvInput {
display: block !important;
width: 100% !important;
max-width: 100% !important;
min-width: 100% !important;
box-sizing: border-box !important;
margin: 0 !important;
padding: 12px !important;
font-family: 'Courier New', monospace !important;
}
/* Fix container width */
body {
max-width: 100% !important;
padding: 20px !important;
box-sizing: border-box !important;
}
</style>
</head>
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' 'sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4=' 'sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=' 'sha256-1ZUvhca3M/N6hch4NrdPufDPLTnANOpJ4hfsZgRykgg=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=' 'sha256-807UZmWvd6eLc8xVckZkNX6CRP9WV8MzHURc5BgtRWo=' 'sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=' 'sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=' 'sha256-2EA12+9d+s6rrc0rkdIjfmjbh6p2o0ZSXs4wbZuk/tA='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';">
<body>
<!-- Header Include -->
<div id="header-include"></div>
<div class="container-fluid" role="main">
<h1>CSV Viewer</h1>
<p>Simply paste CSV data below to view it as a formatted table.</p>
<div class="tool-container">
<div class="tool-controls">
<h3>Paste CSV Data</h3>
<div class="form-group full-width">
<textarea id="csvInput" class="form-control" rows="15" placeholder="Paste your CSV data here to automatically view it as a table..."></textarea>
</div>
<div class="form-group">
<label for="delimiter">Delimiter:</label>
<select id="delimiter" class="form-control">
<option value="," selected>Comma (,)</option>
<option value=";">Semicolon (;)</option>
<option value="\t">Tab</option>
<option value="|">Pipe (|)</option>
</select>
</div>
<div class="form-group">
<label for="hasHeader">First row is header:</label>
<input type="checkbox" id="hasHeader" checked>
</div>
</div>
<div class="tool-output" id="output">
<h3>Output</h3>
<p class="alert alert-info">Paste CSV data above to view it as a table.</p>
</div>
</div>
<hr>
<h2>About This Tool</h2>
<p>This CSV Viewer allows you to:</p>
<ul>
<li>Paste and preview CSV data directly in your browser</li>
<li>Automatically view your data in a table format</li>
<li>Sort columns by clicking on column headers</li>
</ul>
<p>The tool processes everything in your browser - no data is sent to any server.</p>
</div>
<!-- Footer Include -->
<div id="footer-include"></div>
<!-- Load PapaParse first (local version) -->
<script src="../papaparse.min.js" integrity="sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc="></script>
<!-- Then load our script -->
<script src="csv-tool.js?v=3" integrity="sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI="></script>
</body>
</html>

View File

@ -0,0 +1 @@

View File

@ -0,0 +1 @@

88
docker/resume/includes.js Normal file
View File

@ -0,0 +1,88 @@
/**
* Includes.js - Handles the inclusion of header and footer files
* and applies the correct active states to navigation items
*/
document.addEventListener('DOMContentLoaded', function() {
// Function to include HTML content
async function includeHTML(elementId, filePath, callback) {
try {
const response = await fetch(filePath);
if (!response.ok) {
throw new Error(`Failed to load ${filePath}: ${response.status} ${response.statusText}`);
}
const content = await response.text();
document.getElementById(elementId).innerHTML = content;
if (callback) callback();
} catch (error) {
console.error('Error including HTML:', error);
}
}
// Function to set active navigation item
function setActiveNavItem() {
const currentPath = window.location.pathname;
// Wait for the navigation to be loaded
setTimeout(() => {
// Remove all active classes first
document.querySelectorAll('.main-nav a').forEach(link => {
link.classList.remove('active');
});
// Set active class based on current path
if (currentPath === '/' || currentPath === '/index.html') {
const portfolioLink = document.getElementById('nav-portfolio');
if (portfolioLink) portfolioLink.classList.add('active');
} else if (currentPath.includes('/stories/')) {
const storiesLink = document.getElementById('nav-stories');
if (storiesLink) storiesLink.classList.add('active');
// Check for specific story pages
if (currentPath.includes('viperwire.html')) {
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');
if (link) link.classList.add('active');
} else if (currentPath.includes('healthcare-platform.html')) {
const link = document.getElementById('nav-healthcare');
if (link) link.classList.add('active');
} else if (currentPath.includes('wordpress-security.html')) {
const link = document.getElementById('nav-wordpress');
if (link) link.classList.add('active');
} else if (currentPath.includes('airport-dns.html')) {
const link = document.getElementById('nav-airport');
if (link) link.classList.add('active');
} else if (currentPath.includes('nitric-leadership.html')) {
const link = document.getElementById('nav-nitric');
if (link) link.classList.add('active');
} else if (currentPath.includes('open-source-success.html')) {
const link = document.getElementById('nav-opensource');
if (link) link.classList.add('active');
}
} else if (currentPath.includes('/one-pager-tools/')) {
const toolsLink = document.getElementById('nav-tools');
if (toolsLink) toolsLink.classList.add('active');
// Check for specific tool pages
if (currentPath.includes('csv-tool.html')) {
const link = document.getElementById('nav-csv');
if (link) link.classList.add('active');
}
}
}, 100); // Small delay to ensure the DOM is updated
}
// Process header and footer placeholders
const headerElement = document.getElementById('header-include');
const footerElement = document.getElementById('footer-include');
if (headerElement) {
includeHTML('header-include', '/includes/header.html', setActiveNavItem);
}
if (footerElement) {
includeHTML('footer-include', '/includes/footer.html');
}
});

View File

@ -0,0 +1,83 @@
# Implementation Guide: Converting to the Includes System
This guide explains how to convert the existing portfolio website to use the includes system for headers and footers.
## What We've Created
1. **Header and Footer Templates**
- `includes/header.html`: Contains the common header elements
- `includes/footer.html`: Contains the common footer elements
2. **JavaScript for Includes**
- `includes.js`: Handles the inclusion of header and footer files and applies the correct active states to navigation items
3. **Example Files**
- `template-with-includes.html`: Basic template
- `index-with-includes.html`: Example of the index page
- `stories/story-with-includes.html`: Example of a story page
- `one-pager-tools/tool-with-includes.html`: Example of a tool page
4. **Conversion Helper**
- `convert-to-includes.js`: A script to help convert existing HTML files to use the includes system
## Implementation Steps
### 1. Update the CSP in Caddyfile and Caddyfile.local
Add the includes.js script hash to the Content-Security-Policy:
```
Content-Security-Policy "default-src 'none'; script-src 'self' 'sha256-HASH_FOR_INCLUDES_JS' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; ...
```
### 2. Convert Existing Pages
For each HTML page:
1. Add the includes.js script to the head section:
```html
<script src="../includes.js"></script>
```
(Adjust the path as needed based on the location of the HTML file)
2. Replace the header content (everything from the opening `<body>` tag to the opening `<div class="container-fluid">` tag) with:
```html
<!-- Header Include -->
<div id="header-include"></div>
```
3. Replace the footer content (everything from the closing `</div>` of the main container to the closing `</body>` tag) with:
```html
<!-- Footer Include -->
<div id="footer-include"></div>
```
You can use the `convert-to-includes.js` script to help with this process:
1. Temporarily add this script to an existing page:
```html
<script src="convert-to-includes.js"></script>
```
2. Open the page in a browser and check the console output
3. Use the generated HTML as a starting point for your conversion
### 3. Testing
After converting each page:
1. Test the page in a browser to ensure it loads correctly
2. Verify that the navigation active states work as expected
3. Check that all CSS and JavaScript files are loaded correctly
## Benefits
- **Easier Maintenance**: Changes to the header or footer only need to be made in one place
- **Consistency**: All pages will have the same header and footer structure
- **Reduced File Size**: Each HTML file will be smaller since the common elements are externalized
- **Improved Developer Experience**: Easier to focus on the unique content of each page
## Future Enhancements
- **Dynamic Meta Tags**: Enhance the includes system to support dynamic meta tags and titles
- **Page-Specific CSS/JS**: Add support for page-specific CSS and JavaScript files
- **Breadcrumbs**: Implement a breadcrumb system that works with the includes system

View File

@ -0,0 +1,81 @@
# HTML Includes System
This system allows for separating headers and footers into external HTML files that can be included in all individual pages, making maintenance easier and ensuring consistency across the site.
## Files
- `header.html`: Contains the common header elements for all pages
- `footer.html`: Contains the common footer elements for all pages
- `includes.js`: JavaScript file that handles the inclusion of header and footer files and applies the correct active states to navigation items
## How to Use
### 1. Include the JavaScript
Add the `includes.js` script to your HTML file:
```html
<script src="../includes.js"></script>
```
(Adjust the path as needed based on the location of your HTML file)
### 2. Add Include Placeholders
Add placeholder divs where you want the header and footer to be included:
```html
<!-- Header Include -->
<div id="header-include"></div>
<!-- Your page content here -->
<!-- Footer Include -->
<div id="footer-include"></div>
```
### 3. Example Structure
Here's a basic template for a page using includes:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Your description here">
<title>Your Title - Colin Knapp</title>
<link rel="icon" type="image/x-icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles.css">
<!-- Additional CSS files as needed -->
<script src="../theme.js"></script>
<script src="../includes.js"></script>
<!-- Additional JS files as needed -->
</head>
<body>
<!-- Header Include -->
<div id="header-include"></div>
<!-- Main Content -->
<h1>Your Page Title</h1>
<p>Your page content...</p>
<!-- Footer Include -->
<div id="footer-include"></div>
</body>
</html>
```
## Navigation Active States
The `includes.js` file automatically sets the active state for navigation items based on the current page. The navigation items in `header.html` have IDs that are used to identify which item should be active.
## Example Files
See the following example files that demonstrate how to use the includes system:
- `/template-with-includes.html`: Basic template
- `/index-with-includes.html`: Example of the index page
- `/stories/story-with-includes.html`: Example of a story page
- `/one-pager-tools/tool-with-includes.html`: Example of a tool page

View File

@ -0,0 +1,6 @@
<hr>
<p class="accessibility-notice"><strong>Accessibility:</strong> This website is designed and developed to meet WCAG 2.1 Level AAA standards, ensuring the highest level of accessibility for all users. Features include high contrast ratios, keyboard navigation, screen reader compatibility, and responsive design. The site supports both light and dark modes with automatic system preference detection.</p>
</div>
</body>
</html>

View File

@ -0,0 +1,36 @@
<div class="theme-switch">
<button
id="themeToggle"
aria-label="Theme mode: Auto"
role="switch"
aria-checked="false"
title="Toggle between light, dark, and auto theme modes"
tabindex="0"
>🌓</button>
</div>
<nav class="main-nav">
<ul>
<li><a href="/" id="nav-portfolio">Portfolio</a></li>
<li class="dropdown">
<a href="/stories/" id="nav-stories">Stories</a>
<div class="dropdown-content">
<a href="/stories/viperwire.html" id="nav-viperwire">ViperWire</a>
<a href="/stories/fawe-plotsquared.html" id="nav-fawe">FastAsyncWorldEdit</a>
<a href="/stories/healthcare-platform.html" id="nav-healthcare">Healthcare Platform</a>
<a href="/stories/wordpress-security.html" id="nav-wordpress">WordPress Security</a>
<a href="/stories/airport-dns.html" id="nav-airport">Airport DNS</a>
<a href="/stories/nitric-leadership.html" id="nav-nitric">NitricConcepts</a>
<a href="/stories/open-source-success.html" id="nav-opensource">Open Source Success</a>
</div>
</li>
<li class="dropdown">
<a href="/one-pager-tools/csv-tool.html" id="nav-tools">Tools</a>
<div class="dropdown-content">
<a href="/one-pager-tools/csv-tool.html" id="nav-csv">CSV Tool</a>
</div>
</li>
</ul>
</nav>
<div class="container-fluid" role="main">

View File

@ -0,0 +1,38 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Colin Knapp - Portfolio">
<title>Colin Knapp - Portfolio</title>
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="styles.css" integrity="sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=">
<script src="theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4="></script>
<script src="includes.js" integrity="sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4="></script>
</head>
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' 'sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4=' 'sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=' 'sha256-1ZUvhca3M/N6hch4NrdPufDPLTnANOpJ4hfsZgRykgg=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=' 'sha256-807UZmWvd6eLc8xVckZkNX6CRP9WV8MzHURc5BgtRWo=' 'sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=' 'sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=' 'sha256-2EA12+9d+s6rrc0rkdIjfmjbh6p2o0ZSXs4wbZuk/tA='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';">
<body>
<!-- Header Include -->
<div id="header-include"></div>
<!-- Main Content -->
<h1>Colin Knapp</h1>
<p><strong>Location:</strong> Kitchener-Waterloo, Ontario, Canada<br>
<strong>Contact:</strong> <a href="mailto:recruitme2025@colinknapp.com">recruitme2025@colinknapp.com</a> | <a href="https://colinknapp.com">colinknapp.com</a><br>
<strong>Schedule a Meeting:</strong> <a href="https://cal.com/colink/30min" target="_blank">30 Minute Meeting</a></p>
<hr>
<h2>Highlights & Measurables</h2>
<ul>
<li><strong>Cybersecurity Leadership:</strong> Currently spearheading <em><a href="http://ViperWire.ca">ViperWire.ca</a></em>, the public-facing arm of my AI-powered cybersecurity and development consultancy, delivering cutting-edge protection for digital assets (2023-Present).</li>
<li><strong>Open-Source Impact:</strong> Co-created <em>FastAsyncWorldEdit</em> and <em>PlotSquared</em>, revolutionizing Minecraft development by enabling massive transformative edits—scaling from 50,000 server-crashing edits to billions without interruption—powering a $2 billion game brand with global contributor support (2014-Present).</li>
<!-- Additional list items would go here -->
</ul>
<!-- Additional content would go here -->
<!-- Footer Include -->
<div id="footer-include"></div>
</body>
</html>

View File

@ -3,30 +3,17 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Colin Knapp - Cybersecurity Expert and Software Developer Portfolio"> <meta name="description" content="Colin Knapp - Portfolio">
<title>Colin Knapp Portfolio</title> <title>Colin Knapp - Portfolio</title>
<link rel="icon" type="image/x-icon" href="favicon.ico"> <link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="styles.css" integrity="sha256-Ps1dklCHzk1leTAfqkeA64YDuDJxx5QZBjC2UQhSdz0=" crossorigin="anonymous"> <link rel="stylesheet" href="styles.css" integrity="sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=">
<script src="theme.js" integrity="sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=" crossorigin="anonymous"></script> <script src="theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4="></script>
<script src="pdf-download.js" integrity="sha256-BASkmAmg7eoYCMd6odA6kQ8yGsFnoxaX48WbQvMkehs=" crossorigin="anonymous"></script> <script src="includes.js" integrity="sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4="></script>
</head> </head>
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' 'sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4=' 'sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=' 'sha256-1ZUvhca3M/N6hch4NrdPufDPLTnANOpJ4hfsZgRykgg=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=' 'sha256-807UZmWvd6eLc8xVckZkNX6CRP9WV8MzHURc5BgtRWo=' 'sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=' 'sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=' 'sha256-2EA12+9d+s6rrc0rkdIjfmjbh6p2o0ZSXs4wbZuk/tA='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';">
<body> <body>
<div class="theme-switch"> <!-- Header Include -->
<button <div id="header-include"></div>
id="themeToggle"
aria-label="Theme mode: Auto"
role="switch"
aria-checked="false"
title="Toggle between light, dark, and auto theme modes"
tabindex="0"
>🌓</button>
<button
id="downloadPDF"
aria-label="Download as PDF"
title="Download resume as PDF"
tabindex="0"
>📄</button>
</div>
<div class="container-fluid" role="main"> <div class="container-fluid" role="main">
<h1>Colin Knapp</h1> <h1>Colin Knapp</h1>
@ -102,7 +89,7 @@
<li>Utilized Jenkins and GitLab CI/CD for streamlined workflows, leveraging a robust toolchain for rapid development.</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> <li>Managed complex systems and ensured WCAG 2.0 AA accessibility standards.</li>
<li>Provided technical guidance and detailed client documentation, drawing on broad experience to resolve diverse issues.<br> <li>Provided technical guidance and detailed client documentation, drawing on broad experience to resolve diverse issues.<br>
<strong>Impact:</strong> Enhanced project delivery speed and quality for diverse computing environments through prolific development practices.</li> <strong>Impact:</strong> Enhanced project delivery speed and quality for diverse computing environments through prolific and efficient development practices.</li>
</ul> </ul>
<h3>App Development for Influencers</h3> <h3>App Development for Influencers</h3>
@ -203,5 +190,8 @@
<p class="accessibility-notice"><strong>Accessibility:</strong> This website is designed and developed to meet WCAG 2.1 Level AAA standards, ensuring the highest level of accessibility for all users. Features include high contrast ratios, keyboard navigation, screen reader compatibility, and responsive design. The site supports both light and dark modes with automatic system preference detection.</p> <p class="accessibility-notice"><strong>Accessibility:</strong> This website is designed and developed to meet WCAG 2.1 Level AAA standards, ensuring the highest level of accessibility for all users. Features include high contrast ratios, keyboard navigation, screen reader compatibility, and responsive design. The site supports both light and dark modes with automatic system preference detection.</p>
</div> </div>
<!-- Footer Include -->
<div id="footer-include"></div>
</body> </body>
</html> </html>

View File

@ -1,207 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Colin Knapp - Cybersecurity Expert and Software Developer Portfolio">
<title>Colin Knapp Portfolio</title>
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="styles.css" integrity="sha256-Ps1dklCHzk1leTAfqkeA64YDuDJxx5QZBjC2UQhSdz0=" crossorigin="anonymous">
<script src="theme.js" integrity="sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=" crossorigin="anonymous"></script>
<script src="pdf-download.js" integrity="sha256-BASkmAmg7eoYCMd6odA6kQ8yGsFnoxaX48WbQvMkehs=" crossorigin="anonymous"></script>
</head>
<body>
<div class="theme-switch">
<button
id="themeToggle"
aria-label="Theme mode: Auto"
role="switch"
aria-checked="false"
title="Toggle between light, dark, and auto theme modes"
tabindex="0"
>🌓</button>
<button
id="downloadPDF"
aria-label="Download as PDF"
title="Download resume as PDF"
tabindex="0"
>📄</button>
</div>
<div class="container-fluid" role="main">
<h1>Colin Knapp</h1>
<p><strong>Location:</strong> Kitchener-Waterloo, Ontario, Canada<br>
<strong>Contact:</strong> <a href="mailto:recruitme2025@colinknapp.com">recruitme2025@colinknapp.com</a> | <a href="https://colinknapp.com">colinknapp.com</a><br>
<strong>Schedule a Meeting:</strong> <a href="https://cal.com/colink/30min" target="_blank">30 Minute Meeting</a></p>
<hr>
<h2>Highlights & Measurables</h2>
<ul>
<li><strong>Cybersecurity Leadership:</strong> Currently spearheading <em><a href="http://ViperWire.ca">ViperWire.ca</a></em>, the public-facing arm of my AI-powered cybersecurity and development consultancy, delivering cutting-edge protection for digital assets (2023-Present).</li>
<li><strong>Open-Source Impact:</strong> Co-created <em>FastAsyncWorldEdit</em> and <em>PlotSquared</em>, revolutionizing Minecraft development by enabling massive transformative edits—scaling from 50,000 server-crashing edits to billions without interruption—powering a $2 billion game brand with global contributor support (2014-Present).</li>
<li><strong>Team Leadership:</strong> Managed a distributed team of 45 contractors at NitricConcepts, fostering collaboration and deploying advanced DevSecOps practices (2018-2021).</li>
<li><strong>On-Premises Innovation:</strong> Architected self-managed, bare-metal infrastructure with orchestration for on-premises deployments, delivering performant, scalable systems compliant with WCAG 2.0 AA for clients like <a href="https://showerloop.cc">ShowerLoop</a>, meeting stringent government accessibility and compliance goals (2020-Present).</li>
<li><strong>Government Projects:</strong> Delivered scalable, secure learning management systems for the US government and consulted on <a href="https://bishopairport.org">Flint Bishop International Airport</a>'s website and domain infrastructure via Addis Enterprises, building a geographically redundant DNS cluster with an A+ standard resilient to extreme scenarios (2019-Present).</li>
<li><strong>Healthcare Infrastructure:</strong> Developed and deployed infrastructure for <a href="https://improvingmipractices.org">Improving MI Practices</a>, a critical healthcare education platform, ensuring high availability and security for sensitive medical training content (2023-Present).</li>
<li><strong>Security Automation:</strong> Created a Docker-based utility for automated WordPress malware removal and hardening, successfully deployed to protect <a href="https://mlpp.org">MLPP</a> from persistent cyber attacks, reducing infection frequency from daily to zero (2023).</li>
</ul>
<hr>
<h2>Project Experience</h2>
<h3>DevSecOps at Addis Enterprises</h3>
<p><strong>Timeframe:</strong> 2019-Present<br>
<strong>Overview:</strong> Collaborated on US government projects and airport infrastructure, focusing on scalable, secure systems and domain resilience.<br>
<strong>Key Contributions:</strong></p>
<ul>
<li>Partnered with senior professionals to deliver learning management systems meeting WCAG 2.0 AA compliance for government clients.</li>
<li>Consulted for Flint Bishop International Airport, architecting a geographically redundant DNS cluster achieving an A+ standard, capable of withstanding extreme disruptions.</li>
<li>Provided exceptional client service through effective communication and tailored solutions.<br>
<strong>Impact:</strong> Strengthened government digital infrastructure and ensured robust, resilient airport domain systems.</li>
</ul>
<h3>Healthcare Platform Infrastructure</h3>
<p><strong>Timeframe:</strong> 2019-Present<br>
<strong>Overview:</strong> Led infrastructure design and operations for <a href="https://improvingmipractices.org">Improving MI Practices</a> (<a href="https://archive.is/D5HIb">archive</a>) through Addis Enterprises, a critical healthcare education platform.<br>
<strong>Key Contributions:</strong></p>
<ul>
<li>Designed and implemented secure, scalable infrastructure for healthcare education content delivery.</li>
<li>Administered CIS Level 1 and 2 security standards implementation for enhanced system hardening and security controls.</li>
<li>Implemented automated deployment pipelines and monitoring systems for high availability.<br>
<strong>Impact:</strong> Enabled reliable delivery of critical healthcare training content to medical professionals while maintaining robust security standards.</li>
</ul>
<h3>WordPress Security Automation</h3>
<p><strong>Timeframe:</strong> 2023<br>
<strong>Overview:</strong> Developed an automated solution for WordPress malware removal and hardening.<br>
<strong>Key Contributions:</strong></p>
<ul>
<li>Created a Docker-based utility for automated malware detection and removal.</li>
<li>Implemented hardening measures to prevent reinfection.</li>
<li>Successfully deployed to protect MLPP from persistent cyber attacks.<br>
<strong>Impact:</strong> Reduced infection frequency from daily/weekly to zero, significantly improving site security and reliability.</li>
</ul>
<h3>YouTube Game Development & Cybersecurity</h3>
<p><strong>Timeframe:</strong> 2009-2022<br>
<strong>Overview:</strong> Designed custom video games for prominent online creators, integrating advanced cybersecurity measures.<br>
<strong>Key Contributions:</strong></p>
<ul>
<li>Built immersive gaming experiences for large audiences.</li>
<li>Implemented DDoS defense, anti-phishing protocols, and data privacy measures.</li>
<li>Managed hardware/software lifecycles and created comprehensive documentation.<br>
<strong>Impact:</strong> Delivered secure, seamless gaming experiences to millions of users.</li>
</ul>
<h3>Web Design & Java Plugin Development</h3>
<p><strong>Timeframe:</strong> 2009-2023<br>
<strong>Overview:</strong> Developed web solutions and Java plugins focusing on CI/CD efficiency and client satisfaction.<br>
<strong>Key Contributions:</strong></p>
<ul>
<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>
<li>Provided technical guidance and detailed client documentation, drawing on broad experience to resolve diverse issues.<br>
<strong>Impact:</strong> Enhanced project delivery speed and quality for diverse computing environments through prolific development practices.</li>
</ul>
<h3>App Development for Influencers</h3>
<p><strong>Timeframe:</strong> 2013-2018<br>
<strong>Overview:</strong> Created an ad revenue tracking app to optimize earnings and strategies for content creators.<br>
<strong>Key Contributions:</strong></p>
<ul>
<li>Designed a user-friendly tool for real-time revenue monitoring using an optimized toolchain for efficiency.</li>
<li>Ensured secure data handling and system performance with extensive problem-solving expertise.<br>
<strong>Impact:</strong> Empowered creators to maximize earnings and refine content strategies.</li>
</ul>
<h3>DevOps & Co-Founder at NitricConcepts</h3>
<p><strong>Timeframe:</strong> 2018-2021<br>
<strong>Overview:</strong> Led a global team in building secure, scalable gaming solutions.<br>
<strong>Key Contributions:</strong></p>
<ul>
<li>Managed 45 contractors worldwide, implementing Docker, Fail2Ban, and Salt Stack as part of a comprehensive toolchain.</li>
<li>Co-developed <em>FastAsyncWorldEdit</em> and <em>PlotSquared</em>, enabling billions of seamless edits for Minecraft creators.</li>
<li>Fostered a collaborative, innovative team culture.<br>
<strong>Impact:</strong> Transformed NitricConcepts into a thriving multinational entity through prolific and efficient development.</li>
</ul>
<h3>Entrepreneurial Ventures</h3>
<h4><a href="http://Athion.net">Athion.net</a> Turnaround</h4>
<p><strong>Timeframe:</strong> 2013-2017<br>
<strong>Overview:</strong> Revitalized a struggling business into a self-sustaining operation in two weeks.<br>
<strong>Key Contributions:</strong> Optimized systems and streamlined operations with rapid, effective solutions.<br>
<strong>Impact:</strong> Created a profitable, independent venture.</p>
<h4><a href="http://MotherboardRepair.ca">MotherboardRepair.ca</a></h4>
<p><strong>Timeframe:</strong> 2019-Present<br>
<strong>Overview:</strong> Co-founded a company reducing e-waste through circuit board repairs.<br>
<strong>Key Contributions:</strong> Leveraged industry expertise and a versatile toolchain for sustainable tech solutions.<br>
<strong>Impact:</strong> Promoted environmental responsibility in electronics.</p>
<h4><a href="https://showerloop.cc">ShowerLoop Project</a></h4>
<p><strong>Timeframe:</strong> 2016<br>
<strong>Overview:</strong> Revamped the website for an eco-friendly recirculating shower system project, implementing WCAG 2.0 AA compliance and modern design principles.<br>
<strong>Key Contributions:</strong> Designed and implemented a responsive, accessible website with improved user experience and technical documentation.<br>
<strong>Impact:</strong> Enhanced the project's online presence and accessibility while maintaining the site's functionality through periodic maintenance.</p>
<hr>
<h2>Additional Information</h2>
<h3>Personal Development</h3>
<p><strong>Timeframe:</strong> 2009-Present</p>
<ul>
<li><strong>Self-Taught Mastery:</strong> Continuously honed cybersecurity and systems management skills, building a broad knowledge base to tackle unique challenges with a passion for innovation and problem-solving.</li>
<li><strong>Open-Source Contributions:</strong> Actively maintain smaller self-run open-source projects; previously led <em>OhMyForm</em> (retired in favor of FormBricks) and contributed to <em>PlotSquared</em>, <em>FastAsyncWorldEdit</em>, and <em>PlotHider</em>, reflecting a prolific commitment to advancing technology.</li>
<li><strong>Skill Maintenance:</strong> Regularly run Woodpecker CI and Gitea for on-premise source management, testing, and deployment, employing security scanning and unit testing to ensure core functionality and security baselines, alongside self-hosting exercises to sustain rapid, high-volume development capabilities across a vast array of innovative projects.</li>
</ul>
<h3>Relevant Links & Web Impact</h3>
<ul>
<li><strong>Repositories:</strong> <a href="https://github.com/IntellectualSites/PlotSquared">PlotSquared</a>, <a href="https://github.com/IntellectualSites/FastAsyncWorldEdit">FastAsyncWorldEdit</a>, <a href="https://github.com/OhMyForm/OhMyForm">OhMyForm</a>, <a href="https://github.com/IntellectualSites/plothider">PlotHider</a></li>
<li><strong>Projects:</strong> <a href="https://viperwire.ca">ViperWire.ca</a>, <a href="https://nitricconcepts.com">NitricConcepts</a>, <a href="https://showerloop.cc">ShowerLoop</a></li>
</ul>
<hr>
<div class="section" role="region" aria-labelledby="open-source-heading">
<h2 id="open-source-heading">Open Source & Infrastructure</h2>
<div class="entry">
<h3>PlotSquared & FastAsyncWorldEdit</h3>
<p class="date">2013-Present</p>
<p class="overview">Contributor to major Minecraft server plugins, focusing on performance optimization and security enhancements.</p>
<ul>
<li>Contributed to <a href="https://github.com/IntellectualSites/PlotSquared" target="_blank">PlotSquared</a>, a land management plugin with 572+ stars and 809+ forks</li>
<li>Enhanced <a href="https://github.com/IntellectualSites/FastAsyncWorldEdit" target="_blank">FastAsyncWorldEdit</a>, improving world manipulation performance with 664+ stars</li>
<li>Implemented security improvements and performance optimizations for large-scale server operations</li>
</ul>
</div>
<div class="entry">
<h3>Athion.net Infrastructure</h3>
<p class="date">2013-Present</p>
<p class="overview">Established and maintained critical infrastructure for Minecraft development community.</p>
<ul>
<li>Set up and maintained <a href="https://ci.athion.net/" target="_blank">Jenkins CI/CD pipeline</a> since 2013, supporting continuous integration for game content development</li>
<li>Hosted infrastructure enabling collaboration between developers and Microsoft for game content creation</li>
<li>Implemented robust security measures and performance optimizations for high-traffic development environments</li>
</ul>
</div>
<div class="experience-item">
<h3>Software Engineer</h3>
<p class="company">Oh My Form</p>
<p class="date">2020 - Present</p>
<p class="achievement">Led development of Oh My Form, achieving over 1.5 million Docker pulls as verified by <a href="https://hub.docker.com/u/ohmyform" target="_blank" rel="noopener noreferrer">Docker Hub</a> and <a href="https://archive.is/lZHAT" target="_blank" rel="noopener noreferrer">archived</a>.</p>
<ul>
<li>Developed and maintained a secure, high-performance form builder application</li>
<li>Implemented robust security measures and best practices</li>
<li>Optimized application performance and user experience</li>
</ul>
</div>
</div>
<hr>
<p class="accessibility-notice"><strong>Accessibility:</strong> This website is designed and developed to meet WCAG 2.1 Level AAA standards, ensuring the highest level of accessibility for all users. Features include high contrast ratios, keyboard navigation, screen reader compatibility, and responsive design. The site supports both light and dark modes with automatic system preference detection.</p>
</div>
</body>
</html>

View File

@ -0,0 +1,35 @@
/* Additional inline styles to fix layout */
.container-fluid {
max-width: 100%;
padding: 0 15px;
}
.tool-container {
width: 100%;
max-width: 100%;
}
.form-group.full-width {
width: 100%;
max-width: 100%;
}
#csvInput {
width: 100%;
max-width: 100%;
box-sizing: border-box;
}
/* More aggressive fixes for textarea */
textarea#csvInput {
display: block !important;
width: 100% !important;
max-width: 100% !important;
min-width: 100% !important;
box-sizing: border-box !important;
margin: 0 !important;
padding: 12px !important;
font-family: 'Courier New', monospace !important;
}
/* Fix container width */
body {
max-width: 100% !important;
padding: 20px !important;

View File

@ -1,187 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Colin Knapp - Cybersecurity Expert and Software Developer Portfolio">
<title>Colin Knapp Portfolio</title>
<link rel="stylesheet" href="styles.css">
<script src="theme.js" integrity="sha384-naUim5JriO81FOtYsICLhgAY4mC9xffM8+Zcsc2ztUoQLKKLrDgLFgBRau98yEN1" crossorigin="anonymous"></script>
</head>
<body>
<div class="theme-switch">
<button
id="themeToggle"
aria-label="Theme mode: Auto"
aria-pressed="false"
role="switch"
aria-controls="html"
title="Toggle between light, dark, and auto theme modes"
>🌓</button>
</div>
<div class="container-fluid">
<h1>Colin Knapp</h1>
<p><strong>Location:</strong> Kitchener-Waterloo, Ontario, Canada<br>
<strong>Contact:</strong> <a href="mailto:recruitme2023@colinknapp.com">recruitme2023@colinknapp.com</a> | <a href="https://colinknapp.com">colinknapp.com</a></p>
<hr>
<h2>Highlights & Measurables</h2>
<ul>
<li><strong>Cybersecurity Leadership:</strong> Currently spearheading <em><a href="http://ViperWire.ca">ViperWire.ca</a></em>, the public-facing arm of my AI-powered cybersecurity and development consultancy, delivering cutting-edge protection for digital assets (2023-Present).</li>
<li><strong>Open-Source Impact:</strong> Co-created <em>FastAsyncWorldEdit</em> and <em>PlotSquared</em>, revolutionizing Minecraft development by enabling massive transformative edits—scaling from 50,000 server-crashing edits to billions without interruption—powering a $2 billion game brand with global contributor support (2014-Present).</li>
<li><strong>Team Leadership:</strong> Managed a distributed team of 45 contractors at NitricConcepts, fostering collaboration and deploying advanced DevSecOps practices (2018-2021).</li>
<li><strong>On-Premises Innovation:</strong> Architected self-managed, bare-metal infrastructure with orchestration for on-premises deployments, delivering performant, scalable systems compliant with WCAG 2.0 AA for clients like <a href="https://showerloop.cc">ShowerLoop</a>, meeting stringent government accessibility and compliance goals (2020-Present).</li>
<li><strong>Government Projects:</strong> Delivered scalable, secure learning management systems for the US government and consulted on <a href="https://bishopairport.org">Flint Bishop International Airport</a>'s website and domain infrastructure via Addis Enterprises, building a geographically redundant DNS cluster with an A+ standard resilient to extreme scenarios (2019-Present).</li>
<li><strong>Healthcare Infrastructure:</strong> Developed and deployed infrastructure for <a href="https://improvingmipractices.org">Improving MI Practices</a>, a critical healthcare education platform, ensuring high availability and security for sensitive medical training content (2023-Present).</li>
<li><strong>Security Automation:</strong> Created a Docker-based utility for automated WordPress malware removal and hardening, successfully deployed to protect <a href="https://mlpp.org">MLPP</a> from persistent cyber attacks, reducing infection frequency from daily to zero (2023).</li>
</ul>
<hr>
<h2>Project Experience</h2>
<h3>DevSecOps at Addis Enterprises</h3>
<p><strong>Timeframe:</strong> 2019-Present<br>
<strong>Overview:</strong> Collaborated on US government projects and airport infrastructure, focusing on scalable, secure systems and domain resilience.<br>
<strong>Key Contributions:</strong></p>
<ul>
<li>Partnered with senior professionals to deliver learning management systems meeting WCAG 2.0 AA compliance for government clients.</li>
<li>Consulted for Flint Bishop International Airport, architecting a geographically redundant DNS cluster achieving an A+ standard, capable of withstanding extreme disruptions.</li>
<li>Provided exceptional client service through effective communication and tailored solutions.<br>
<strong>Impact:</strong> Strengthened government digital infrastructure and ensured robust, resilient airport domain systems.</li>
</ul>
<h3>Healthcare Platform Infrastructure</h3>
<p><strong>Timeframe:</strong> 2019-Present<br>
<strong>Overview:</strong> Led infrastructure design and operations for <a href="https://improvingmipractices.org">Improving MI Practices</a> (<a href="https://archive.is/D5HIb">archive</a>) through Addis Enterprises, a critical healthcare education platform.<br>
<strong>Key Contributions:</strong></p>
<ul>
<li>Designed and implemented secure, scalable infrastructure for healthcare education content delivery.</li>
<li>Administered CIS Level 1 and 2 security standards implementation for enhanced system hardening and security controls.</li>
<li>Implemented automated deployment pipelines and monitoring systems for high availability.<br>
<strong>Impact:</strong> Enabled reliable delivery of critical healthcare training content to medical professionals while maintaining robust security standards.</li>
</ul>
<h3>WordPress Security Automation</h3>
<p><strong>Timeframe:</strong> 2023<br>
<strong>Overview:</strong> Developed an automated solution for WordPress malware removal and hardening.<br>
<strong>Key Contributions:</strong></p>
<ul>
<li>Created a Docker-based utility for automated malware detection and removal.</li>
<li>Implemented hardening measures to prevent reinfection.</li>
<li>Successfully deployed to protect MLPP from persistent cyber attacks.<br>
<strong>Impact:</strong> Reduced infection frequency from daily/weekly to zero, significantly improving site security and reliability.</li>
</ul>
<h3>YouTube Game Development & Cybersecurity</h3>
<p><strong>Timeframe:</strong> 2009-2022<br>
<strong>Overview:</strong> Designed custom video games for prominent online creators, integrating advanced cybersecurity measures.<br>
<strong>Key Contributions:</strong></p>
<ul>
<li>Built immersive gaming experiences for large audiences.</li>
<li>Implemented DDoS defense, anti-phishing protocols, and data privacy measures.</li>
<li>Managed hardware/software lifecycles and created comprehensive documentation.<br>
<strong>Impact:</strong> Delivered secure, seamless gaming experiences to millions of users.</li>
</ul>
<h3>Web Design & Java Plugin Development</h3>
<p><strong>Timeframe:</strong> 2009-2023<br>
<strong>Overview:</strong> Developed web solutions and Java plugins focusing on CI/CD efficiency and client satisfaction.<br>
<strong>Key Contributions:</strong></p>
<ul>
<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>
<li>Provided technical guidance and detailed client documentation, drawing on broad experience to resolve diverse issues.<br>
<strong>Impact:</strong> Enhanced project delivery speed and quality for diverse computing environments through prolific development practices.</li>
</ul>
<h3>App Development for Influencers</h3>
<p><strong>Timeframe:</strong> 2013-2018<br>
<strong>Overview:</strong> Created an ad revenue tracking app to optimize earnings and strategies for content creators.<br>
<strong>Key Contributions:</strong></p>
<ul>
<li>Designed a user-friendly tool for real-time revenue monitoring using an optimized toolchain for efficiency.</li>
<li>Ensured secure data handling and system performance with extensive problem-solving expertise.<br>
<strong>Impact:</strong> Empowered creators to maximize earnings and refine content strategies.</li>
</ul>
<h3>DevOps & Co-Founder at NitricConcepts</h3>
<p><strong>Timeframe:</strong> 2018-2021<br>
<strong>Overview:</strong> Led a global team in building secure, scalable gaming solutions.<br>
<strong>Key Contributions:</strong></p>
<ul>
<li>Managed 45 contractors worldwide, implementing Docker, Fail2Ban, and Salt Stack as part of a comprehensive toolchain.</li>
<li>Co-developed <em>FastAsyncWorldEdit</em> and <em>PlotSquared</em>, enabling billions of seamless edits for Minecraft creators.</li>
<li>Fostered a collaborative, innovative team culture.<br>
<strong>Impact:</strong> Transformed NitricConcepts into a thriving multinational entity through prolific and efficient development.</li>
</ul>
<h3>Entrepreneurial Ventures</h3>
<h4><a href="http://Athion.net">Athion.net</a> Turnaround</h4>
<p><strong>Timeframe:</strong> 2013-2017<br>
<strong>Overview:</strong> Revitalized a struggling business into a self-sustaining operation in two weeks.<br>
<strong>Key Contributions:</strong> Optimized systems and streamlined operations with rapid, effective solutions.<br>
<strong>Impact:</strong> Created a profitable, independent venture.</p>
<h4><a href="http://MotherboardRepair.ca">MotherboardRepair.ca</a></h4>
<p><strong>Timeframe:</strong> 2019-Present<br>
<strong>Overview:</strong> Co-founded a company reducing e-waste through circuit board repairs.<br>
<strong>Key Contributions:</strong> Leveraged industry expertise and a versatile toolchain for sustainable tech solutions.<br>
<strong>Impact:</strong> Promoted environmental responsibility in electronics.</p>
<h4><a href="https://showerloop.cc">ShowerLoop Project</a></h4>
<p><strong>Timeframe:</strong> 2016<br>
<strong>Overview:</strong> Revamped the website for an eco-friendly recirculating shower system project, implementing WCAG 2.0 AA compliance and modern design principles.<br>
<strong>Key Contributions:</strong> Designed and implemented a responsive, accessible website with improved user experience and technical documentation.<br>
<strong>Impact:</strong> Enhanced the project's online presence and accessibility while maintaining the site's functionality through periodic maintenance.</p>
<hr>
<h2>Additional Information</h2>
<h3>Personal Development</h3>
<p><strong>Timeframe:</strong> 2009-Present</p>
<ul>
<li><strong>Self-Taught Mastery:</strong> Continuously honed cybersecurity and systems management skills, building a broad knowledge base to tackle unique challenges with a passion for innovation and problem-solving.</li>
<li><strong>Open-Source Contributions:</strong> Actively maintain smaller self-run open-source projects; previously led <em>OhMyForm</em> (retired in favor of FormBricks) and contributed to <em>PlotSquared</em>, <em>FastAsyncWorldEdit</em>, and <em>PlotHider</em>, reflecting a prolific commitment to advancing technology.</li>
<li><strong>Skill Maintenance:</strong> Regularly run Woodpecker CI and Gitea for on-premise source management, testing, and deployment, employing security scanning and unit testing to ensure core functionality and security baselines, alongside self-hosting exercises to sustain rapid, high-volume development capabilities across a vast array of innovative projects.</li>
</ul>
<h3>Relevant Links & Web Impact</h3>
<ul>
<li><strong>Repositories:</strong> <a href="https://github.com/IntellectualSites/PlotSquared">PlotSquared</a>, <a href="https://github.com/IntellectualSites/FastAsyncWorldEdit">FastAsyncWorldEdit</a>, <a href="https://github.com/OhMyForm/OhMyForm">OhMyForm</a>, <a href="https://github.com/IntellectualSites/plothider">PlotHider</a></li>
<li><strong>Projects:</strong> <a href="https://viperwire.ca">ViperWire.ca</a>, <a href="https://nitricconcepts.com">NitricConcepts</a>, <a href="https://showerloop.cc">ShowerLoop</a></li>
</ul>
<hr>
<div class="section">
<h2>Open Source & Infrastructure</h2>
<div class="entry">
<h3>PlotSquared & FastAsyncWorldEdit</h3>
<p class="date">2013-Present</p>
<p class="overview">Contributor to major Minecraft server plugins, focusing on performance optimization and security enhancements.</p>
<ul>
<li>Contributed to <a href="https://github.com/IntellectualSites/PlotSquared" target="_blank">PlotSquared</a>, a land management plugin with 572+ stars and 809+ forks</li>
<li>Enhanced <a href="https://github.com/IntellectualSites/FastAsyncWorldEdit" target="_blank">FastAsyncWorldEdit</a>, improving world manipulation performance with 664+ stars</li>
<li>Implemented security improvements and performance optimizations for large-scale server operations</li>
</ul>
</div>
<div class="entry">
<h3>Athion.net Infrastructure</h3>
<p class="date">2013-Present</p>
<p class="overview">Established and maintained critical infrastructure for Minecraft development community.</p>
<ul>
<li>Set up and maintained <a href="https://ci.athion.net/" target="_blank">Jenkins CI/CD pipeline</a> since 2013, supporting continuous integration for game content development</li>
<li>Hosted infrastructure enabling collaboration between developers and Microsoft for game content creation</li>
<li>Implemented robust security measures and performance optimizations for high-traffic development environments</li>
</ul>
</div>
</div>
<hr>
<p class="accessibility-notice"><strong>Accessibility:</strong> This website is designed and developed to meet WCAG 2.1 Level AAA standards, ensuring the highest level of accessibility for all users. Features include high contrast ratios, keyboard navigation, screen reader compatibility, and responsive design. The site supports both light and dark modes with automatic system preference detection.</p>
</div>
</body>
</html>

View File

@ -0,0 +1,132 @@
/* CSV Tool specific styles with aggressive fixes */
/* Fix container width */
body {
max-width: 100% !important;
padding: 20px !important;
box-sizing: border-box !important;
}
.container-fluid {
width: 100% !important;
max-width: 100% !important;
padding: 0 15px !important;
box-sizing: border-box !important;
margin: 0 auto !important;
}
.tool-container {
display: flex !important;
flex-direction: column !important;
gap: 1.5rem !important;
margin: 2rem 0 !important;
width: 100% !important;
box-sizing: border-box !important;
max-width: 100% !important;
}
.tool-controls {
background-color: var(--bg-secondary) !important;
padding: 1.5rem !important;
border-radius: 0.5rem !important;
display: flex !important;
flex-direction: column !important;
gap: 1rem !important;
margin-bottom: 1.5rem !important;
width: 100% !important;
box-sizing: border-box !important;
max-width: 100% !important;
}
.form-group {
margin-bottom: 1.5rem !important;
width: 100% !important;
max-width: 100% !important;
box-sizing: border-box !important;
}
.full-width {
width: 100% !important;
max-width: 100% !important;
display: block !important;
box-sizing: border-box !important;
}
/* Aggressive fixes for textarea */
#csvInput,
textarea#csvInput {
display: block !important;
width: 100% !important;
max-width: 100% !important;
min-width: 100% !important;
box-sizing: border-box !important;
margin: 0 !important;
padding: 12px !important;
font-family: 'Courier New', monospace !important;
min-height: 250px !important;
white-space: pre !important;
tab-size: 4 !important;
-moz-tab-size: 4 !important;
resize: vertical !important;
overflow-x: auto !important;
line-height: 1.5 !important;
font-size: 14px !important;
letter-spacing: -0.2px !important;
border: 1px solid var(--border-color) !important;
background-color: var(--bg-primary) !important;
color: var(--text-primary) !important;
border-radius: 0.25rem !important;
}
/* Ensure the output area is also full width */
.tool-output {
background-color: var(--bg-secondary) !important;
padding: 1.5rem !important;
border-radius: 0.5rem !important;
overflow-x: auto !important;
margin-bottom: 1.5rem !important;
width: 100% !important;
border: 2px solid var(--accent-color) !important;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1) !important;
box-sizing: border-box !important;
max-width: 100% !important;
}
/* Fix table display */
.table-responsive {
overflow-x: auto !important;
max-width: 100% !important;
width: 100% !important;
margin-bottom: 2rem !important;
border-radius: 0.25rem !important;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05) !important;
}
.tool-table {
width: 100% !important;
border-collapse: separate !important;
border-spacing: 0 !important;
margin-bottom: 1.5rem !important;
text-align: left !important;
border: 1px solid var(--border-color) !important;
table-layout: auto !important;
}
/* Fix form controls */
.form-control {
width: 100% !important;
padding: 0.75rem !important;
border: 1px solid var(--border-color) !important;
background-color: var(--bg-primary) !important;
color: var(--text-primary) !important;
border-radius: 0.25rem !important;
font-family: inherit !important;
font-size: 1rem !important;
box-sizing: border-box !important;
}
/* Fix empty cell display */
.empty-cell {
color: #999 !important;
font-style: italic !important;
}

View File

@ -0,0 +1,113 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Colin Knapp - CSV Processing Tool">
<title>CSV Viewer - Colin Knapp</title>
<link rel="icon" type="image/x-icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles.css">
<link rel="stylesheet" href="tool-styles.css?v=2" integrity="sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=">
<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-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4="></script>
<script src="../utils.js" integrity="sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544="></script>
<style>
/* Additional inline styles to fix layout */
.container-fluid {
max-width: 100%;
padding: 0 15px;
}
.tool-container {
width: 100%;
max-width: 100%;
}
.form-group.full-width {
width: 100%;
max-width: 100%;
}
#csvInput {
width: 100%;
max-width: 100%;
box-sizing: border-box;
}
/* More aggressive fixes for textarea */
textarea#csvInput {
display: block !important;
width: 100% !important;
max-width: 100% !important;
min-width: 100% !important;
box-sizing: border-box !important;
margin: 0 !important;
padding: 12px !important;
font-family: 'Courier New', monospace !important;
}
/* Fix container width */
body {
max-width: 100% !important;
padding: 20px !important;
box-sizing: border-box !important;
}
</style>
</head>
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' 'sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4=' 'sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=' 'sha256-1ZUvhca3M/N6hch4NrdPufDPLTnANOpJ4hfsZgRykgg=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=' 'sha256-807UZmWvd6eLc8xVckZkNX6CRP9WV8MzHURc5BgtRWo=' 'sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=' 'sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=' 'sha256-2EA12+9d+s6rrc0rkdIjfmjbh6p2o0ZSXs4wbZuk/tA='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';">
<body>
<!-- Header Include -->
<div id="header-include"></div>
<div class="container-fluid" role="main">
<h1>CSV Viewer</h1>
<p>Simply paste CSV data below to view it as a formatted table.</p>
<div class="tool-container">
<div class="tool-controls">
<h3>Paste CSV Data</h3>
<div class="form-group full-width">
<textarea id="csvInput" class="form-control" rows="15" placeholder="Paste your CSV data here to automatically view it as a table..."></textarea>
</div>
<div class="form-group">
<label for="delimiter">Delimiter:</label>
<select id="delimiter" class="form-control">
<option value="," selected>Comma (,)</option>
<option value=";">Semicolon (;)</option>
<option value="\t">Tab</option>
<option value="|">Pipe (|)</option>
</select>
</div>
<div class="form-group">
<label for="hasHeader">First row is header:</label>
<input type="checkbox" id="hasHeader" checked>
</div>
</div>
<div class="tool-output" id="output">
<h3>Output</h3>
<p class="alert alert-info">Paste CSV data above to view it as a table.</p>
</div>
</div>
<hr>
<h2>About This Tool</h2>
<p>This CSV Viewer allows you to:</p>
<ul>
<li>Paste and preview CSV data directly in your browser</li>
<li>Automatically view your data in a table format</li>
<li>Sort columns by clicking on column headers</li>
</ul>
<p>The tool processes everything in your browser - no data is sent to any server.</p>
</div>
<!-- Footer Include -->
<div id="footer-include"></div>
<!-- Load PapaParse first (local version) -->
<script src="../papaparse.min.js" integrity="sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc="></script>
<!-- Then load our script -->
<script src="csv-tool.js?v=3" integrity="sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI="></script>
</body>
</html>

View File

@ -0,0 +1,329 @@
/**
* CSV Viewer functionality
* Automatically processes and displays CSV data when pasted
* Using Papa Parse for robust CSV handling
*/
document.addEventListener('DOMContentLoaded', function() {
// DOM Elements
const csvInput = document.getElementById('csvInput');
const delimiterSelect = document.getElementById('delimiter');
const hasHeaderCheckbox = document.getElementById('hasHeader');
const outputDiv = document.getElementById('output');
// Variables to store data
let csvData = [];
let headers = [];
let currentSortColumn = null;
let sortDirection = 1; // 1 for ascending, -1 for descending
// Add input event listener with debounce to process CSV when pasted
let debounceTimer;
csvInput.addEventListener('input', function() {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(function() {
if (csvInput.value.trim() !== '') {
processCSV();
}
}, 300); // 300ms debounce delay
});
// Add paste event listener to format CSV data on paste
csvInput.addEventListener('paste', function(e) {
// Let the paste happen naturally, then process after a brief delay
setTimeout(function() {
const text = csvInput.value;
if (text && text.length > 0) {
// Auto-detect delimiter
autoDetectDelimiter(text);
// Process immediately after paste
processCSV();
}
}, 50); // Slightly longer delay to ensure paste completes
});
// Add change listeners to delimiter and header options to reprocess data
delimiterSelect.addEventListener('change', function() {
if (csvInput.value.trim() !== '') {
processCSV();
}
});
hasHeaderCheckbox.addEventListener('change', function() {
if (csvInput.value.trim() !== '') {
processCSV();
}
});
/**
* Auto-detect the delimiter in pasted CSV data
*/
function autoDetectDelimiter(text) {
// Count occurrences of common delimiters
const firstFewLines = text.split('\n').slice(0, 5).join('\n');
const counts = {
',': (firstFewLines.match(/,/g) || []).length,
';': (firstFewLines.match(/;/g) || []).length,
'\t': (firstFewLines.match(/\t/g) || []).length,
'|': (firstFewLines.match(/\|/g) || []).length
};
// Find the most common delimiter
let maxCount = 0;
let detectedDelimiter = ','; // default
for (const [delimiter, count] of Object.entries(counts)) {
if (count > maxCount) {
maxCount = count;
detectedDelimiter = delimiter;
}
}
// Set the delimiter dropdown
if (maxCount > 0) {
delimiterSelect.value = detectedDelimiter === '\t' ? '\\t' : detectedDelimiter;
}
}
/**
* Process the CSV data based on selected options
*/
function processCSV() {
const csvText = csvInput.value.trim();
if (!csvText) {
outputDiv.innerHTML = '<h3>Output</h3><p class="alert alert-info">Paste CSV data above to view it as a table.</p>';
return;
}
try {
// Show processing message
outputDiv.innerHTML = '<h3>Output</h3><p class="alert alert-info">Processing data...</p>';
// Parse CSV using Papa Parse
const delimiter = delimiterSelect.value;
const hasHeader = hasHeaderCheckbox.checked;
// Enhanced parsing options
Papa.parse(csvText, {
delimiter: delimiter,
header: hasHeader,
skipEmptyLines: 'greedy', // Skip truly empty lines
dynamicTyping: true, // Automatically convert numeric values
trimHeaders: true, // Trim whitespace from headers
complete: function(results) {
if (results.errors.length > 0) {
showError('Error parsing CSV: ' + results.errors[0].message);
return;
}
if (results.data.length === 0 || (results.data.length === 1 && Object.keys(results.data[0]).length === 0)) {
showError('No valid data found. Please check your CSV format and delimiter.');
return;
}
csvData = results.data;
// Handle headers
if (hasHeader) {
if (results.meta.fields && results.meta.fields.length > 0) {
headers = results.meta.fields.map(h => h.trim());
} else {
// Fallback if no headers detected
headers = Object.keys(results.data[0] || {}).map((_, i) => `Column${i + 1}`);
}
} else {
headers = Object.keys(results.data[0] || {}).map((_, i) => `Column${i + 1}`);
}
// Preview the data
previewData();
},
error: function(error) {
showError('Error processing CSV: ' + error.message);
}
});
} catch (error) {
showError('Error processing CSV: ' + error.message);
}
}
/**
* Preview the CSV data in a table with sortable columns
*/
function previewData() {
if (csvData.length === 0) {
showError('No data to preview.');
return;
}
// Limit preview to first 500 rows
const previewData = csvData.slice(0, 500);
// Generate table HTML
let tableHtml = '<h3>Data Preview</h3>';
tableHtml += `<p>Showing ${previewData.length} of ${csvData.length} rows</p>`;
tableHtml += '<div class="table-responsive"><table class="tool-table">';
// Table headers with sort functionality
tableHtml += '<thead><tr>';
headers.forEach(header => {
const isSorted = header === currentSortColumn;
const sortClass = isSorted ? (sortDirection > 0 ? 'sort-asc' : 'sort-desc') : '';
tableHtml += `<th class="${sortClass}" data-column="${header}">${header} ${isSorted ? (sortDirection > 0 ? '↑' : '↓') : ''}</th>`;
});
tableHtml += '</tr></thead>';
// Table body with improved cell formatting
tableHtml += '<tbody>';
previewData.forEach(row => {
tableHtml += '<tr>';
headers.forEach(header => {
const cellValue = row[header];
// Format cell value based on type
let formattedValue = '';
if (cellValue === null || cellValue === undefined) {
formattedValue = '<span class="empty-cell">(empty)</span>';
} else if (typeof cellValue === 'string') {
formattedValue = escapeHtml(cellValue);
} else {
formattedValue = String(cellValue);
}
tableHtml += `<td>${formattedValue}</td>`;
});
tableHtml += '</tr>';
});
tableHtml += '</tbody></table></div>';
// Add stats summary
const totalRows = csvData.length;
const totalColumns = headers.length;
tableHtml += `<div class="data-stats">
<div class="stat-item">
<span class="stat-label">Total Rows:</span>
<span class="stat-value">${totalRows}</span>
</div>
<div class="stat-item">
<span class="stat-label">Total Columns:</span>
<span class="stat-value">${totalColumns}</span>
</div>
</div>`;
// Display in output div
outputDiv.innerHTML = tableHtml;
// Add click event listeners to table headers for sorting
const tableHeaders = outputDiv.querySelectorAll('th');
tableHeaders.forEach(th => {
th.addEventListener('click', () => {
const column = th.getAttribute('data-column');
sortData(column);
});
});
}
/**
* Escape HTML special characters to prevent XSS
*/
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
/**
* Sort data by column
*/
function sortData(column) {
// Toggle sort direction if clicking the same column
if (column === currentSortColumn) {
sortDirection *= -1;
} else {
currentSortColumn = column;
sortDirection = 1;
}
// Sort the data
csvData.sort((a, b) => {
const valueA = a[column] !== undefined ? a[column] : '';
const valueB = b[column] !== undefined ? b[column] : '';
// Try to sort numerically if possible
if (typeof valueA === 'number' && typeof valueB === 'number') {
return (valueA - valueB) * sortDirection;
}
// Handle dates
const dateA = new Date(valueA);
const dateB = new Date(valueB);
if (!isNaN(dateA) && !isNaN(dateB)) {
return (dateA - dateB) * sortDirection;
}
// Otherwise sort alphabetically
return String(valueA).localeCompare(String(valueB)) * sortDirection;
});
// Update the preview
previewData();
}
/**
* Show error message
*/
function showError(message) {
// Clear any existing alerts
clearAlerts();
const alert = document.createElement('div');
alert.className = 'alert alert-error';
alert.textContent = message;
// Insert at the top of the output div
const firstChild = outputDiv.querySelector('h3') ?
outputDiv.querySelector('h3').nextSibling :
outputDiv.firstChild;
outputDiv.insertBefore(alert, firstChild);
}
/**
* Show success message
*/
function showSuccess(message) {
const alert = document.createElement('div');
alert.className = 'alert alert-success';
alert.textContent = message;
// Insert after the heading
const firstChild = outputDiv.querySelector('h3') ?
outputDiv.querySelector('h3').nextSibling :
outputDiv.firstChild;
outputDiv.insertBefore(alert, firstChild);
// Auto-hide success message after 3 seconds
setTimeout(() => {
if (alert.parentNode === outputDiv) {
alert.remove();
}
}, 3000);
}
/**
* Clear all alert messages
*/
function clearAlerts() {
const alerts = outputDiv.querySelectorAll('.alert');
alerts.forEach(alert => alert.remove());
}
// Check if there's already content in the textarea on page load
if (csvInput.value.trim() !== '') {
processCSV();
}
});

View File

@ -0,0 +1 @@
});

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Colin Knapp - One Pager Tools">
<title>Colin Knapp Tools</title>
<link rel="icon" type="image/x-icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles.css" integrity="sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=" crossorigin="anonymous">
<link rel="stylesheet" href="tool-styles.css" integrity="sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=">
<script src="../theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=" crossorigin="anonymous"></script>
<script src="../includes.js" integrity="sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4=" crossorigin="anonymous"></script>
<script src="../utils.js" integrity="sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544=" crossorigin="anonymous"></script>
<!-- Add tool-specific scripts here -->
</head>
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' 'sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4=' 'sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=' 'sha256-1ZUvhca3M/N6hch4NrdPufDPLTnANOpJ4hfsZgRykgg=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=' 'sha256-807UZmWvd6eLc8xVckZkNX6CRP9WV8MzHURc5BgtRWo=' 'sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=' 'sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=' 'sha256-2EA12+9d+s6rrc0rkdIjfmjbh6p2o0ZSXs4wbZuk/tA='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';">
<body>
<!-- Header Include -->
<div id="header-include"></div>
<div class="container-fluid" role="main">
<h1>Tool Name</h1>
<p>A brief description of what this tool does.</p>
<hr>
<!-- Tool-specific content goes here -->
<div class="tool-container">
<!-- This section would be customized for each tool -->
<div class="tool-controls">
<!-- Input controls -->
</div>
<div class="tool-output">
<!-- Results/output -->
</div>
</div>
</div>
<!-- Footer Include -->
<div id="footer-include"></div>
</body>
</html>

View File

@ -0,0 +1,423 @@
/* Additional styles for one-pager tools - UPDATED */
.tool-container {
display: flex;
flex-direction: column;
gap: 1.5rem;
margin: 2rem 0;
width: 100%;
box-sizing: border-box;
max-width: 100%;
}
.tool-controls {
background-color: var(--bg-secondary);
padding: 1.5rem;
border-radius: 0.5rem;
display: flex;
flex-direction: column;
gap: 1rem;
margin-bottom: 1.5rem;
width: 100%;
box-sizing: border-box;
max-width: 100%;
}
.tool-output {
background-color: var(--bg-secondary);
padding: 1.5rem;
border-radius: 0.5rem;
overflow-x: auto;
margin-bottom: 1.5rem;
width: 100%;
border: 2px solid var(--accent-color);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
box-sizing: border-box;
}
.tool-output h3 {
color: var(--accent-color);
margin-top: 0;
padding-bottom: 0.5rem;
border-bottom: 1px solid var(--accent-color);
}
#output {
position: relative;
}
#output::before {
content: "";
position: absolute;
top: -12px;
left: 20px;
}
/* Form controls styling to match the theme */
.form-group {
margin-bottom: 1.5rem;
width: 100%;
max-width: 100%;
box-sizing: border-box;
}
.full-width {
width: 100%;
max-width: 100%;
display: block;
box-sizing: border-box;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: 600;
}
.form-control {
width: 100%;
padding: 0.75rem;
border: 1px solid var(--border-color);
background-color: var(--bg-primary);
color: var(--text-primary);
border-radius: 0.25rem;
font-family: inherit;
font-size: 1rem;
box-sizing: border-box;
}
/* Make CSV input textarea wider and improve formatting */
#csvInput {
width: 100% !important;
min-height: 250px;
font-family: 'Courier New', monospace;
white-space: pre;
tab-size: 4;
-moz-tab-size: 4;
box-sizing: border-box;
max-width: 100%;
resize: vertical;
overflow-x: auto;
line-height: 1.5;
font-size: 14px;
letter-spacing: -0.2px;
padding: 12px;
margin: 0;
display: block;
flex: 1 1 auto;
}
/* Fix for textarea width issues */
textarea#csvInput {
width: 100% !important;
max-width: 100% !important;
min-width: 100% !important;
box-sizing: border-box !important;
}
.btn {
padding: 0.75rem 1.5rem;
background-color: var(--accent-color);
color: white;
border: none;
border-radius: 0.25rem;
cursor: pointer;
font-weight: 600;
transition: background-color 0.2s, opacity 0.2s;
margin-right: 0.5rem;
margin-bottom: 0.5rem;
}
.btn:hover {
background-color: var(--accent-hover);
}
.btn:active {
transform: translateY(1px);
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Accessibility features */
.btn:focus, .form-control:focus {
outline: 2px solid var(--focus-outline-color);
outline-offset: 2px;
}
/* Table styles for data display */
.table-responsive {
overflow-x: auto;
max-width: 100%;
margin-bottom: 2rem;
border-radius: 0.25rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
.tool-table {
width: 100%;
border-collapse: separate;
border-spacing: 0;
margin-bottom: 1.5rem;
text-align: left;
border: 1px solid var(--border-color);
table-layout: auto;
}
.tool-table th,
.tool-table td {
padding: 0.75rem;
border-bottom: 1px solid var(--border-color);
border-right: 1px solid var(--border-color);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 300px;
}
.tool-table td {
white-space: pre-wrap;
word-break: break-word;
}
.tool-table th {
background-color: var(--accent-color);
color: white;
font-weight: 600;
position: sticky;
top: 0;
z-index: 10;
cursor: pointer;
user-select: none;
transition: background-color 0.2s;
}
.tool-table th:hover {
background-color: var(--accent-hover);
}
.tool-table th.sort-asc,
.tool-table th.sort-desc {
background-color: var(--accent-hover);
}
.tool-table tr:nth-child(even) {
background-color: var(--bg-tertiary);
}
.tool-table tr:hover {
background-color: var(--bg-hover);
}
/* File input styling */
.file-input-container {
position: relative;
margin-bottom: 1rem;
}
.file-input {
position: absolute;
left: -9999px;
}
.file-input-label {
display: inline-block;
padding: 0.5rem 1rem;
background-color: var(--button-bg);
color: var(--text-color);
border: 1px solid var(--border-color);
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s, border-color 0.3s;
}
.file-input-label:hover {
background-color: var(--button-hover-bg);
border-color: var(--accent-color);
}
.file-name {
margin-left: 1rem;
font-style: italic;
}
/* Progress indicators */
.progress-container {
width: 100%;
background-color: var(--progress-bg);
border-radius: 4px;
margin: 1rem 0;
}
.progress-bar {
height: 10px;
background-color: var(--accent-color);
border-radius: 4px;
transition: width 0.3s ease;
}
/* Alert/notification styles */
.alert {
padding: 1rem 1rem 1rem 1.5rem;
margin-bottom: 1.5rem;
border-radius: 0.25rem;
font-weight: 500;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
display: flex;
align-items: center;
animation: fadeIn 0.3s ease-in-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
.alert::before {
margin-right: 0.75rem;
font-size: 1.2rem;
}
.alert-info {
background-color: rgba(59, 130, 246, 0.1);
border-left: 4px solid rgb(59, 130, 246);
color: var(--text-primary);
}
.alert-info::before {
content: "";
}
.alert-success {
background-color: rgba(34, 197, 94, 0.15);
border-left: 4px solid rgb(34, 197, 94);
color: var(--text-primary);
}
.alert-success::before {
content: "✅";
}
.alert-error {
background-color: rgba(239, 68, 68, 0.15);
border-left: 4px solid rgb(239, 68, 68);
color: var(--text-primary);
}
.alert-error::before {
content: "⚠️";
}
/* CSV Example Section */
.csv-examples {
margin: 2rem 0;
padding: 1.5rem;
background-color: var(--bg-secondary);
border-radius: 0.5rem;
}
.csv-examples h3 {
margin-top: 0;
margin-bottom: 1rem;
}
.csv-examples p {
margin-bottom: 1rem;
}
.csv-examples button {
margin-right: 0.75rem;
margin-top: 0.5rem;
}
/* Checkbox styling */
input[type="checkbox"] {
width: 1.2rem;
height: 1.2rem;
vertical-align: middle;
margin-left: 0.5rem;
accent-color: var(--accent-color);
}
/* Section headers */
h2, h3 {
margin-top: 2rem;
margin-bottom: 1rem;
}
hr {
margin: 2.5rem 0;
border: 0;
height: 1px;
background-color: var(--border-color);
}
/* Accessibility styles */
.accessibility-notice {
font-size: 0.9rem;
margin-top: 30px;
padding: 15px;
background-color: var(--bg-secondary);
border-radius: 8px;
}
/* Improved responsive design */
@media (max-width: 768px) {
.tool-container {
padding: 0;
}
.tool-controls,
.tool-output {
padding: 1rem;
margin-bottom: 1rem;
}
.form-group {
margin-bottom: 1rem;
}
.tool-table th,
.tool-table td {
padding: 0.5rem;
font-size: 0.9rem;
}
#csvInput {
min-height: 150px;
}
}
/* Data stats styling */
.data-stats {
display: flex;
flex-wrap: wrap;
gap: 1rem;
margin-top: 1.5rem;
padding: 1rem;
background-color: var(--bg-tertiary);
border-radius: 0.25rem;
border-left: 4px solid var(--accent-color);
}
.stat-item {
display: flex;
align-items: center;
padding: 0.5rem 1rem;
background-color: var(--bg-primary);
border-radius: 0.25rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
.stat-label {
font-weight: 600;
margin-right: 0.5rem;
color: var(--accent-color);
}
.stat-value {
font-size: 1.1rem;
font-weight: bold;
}

View File

@ -0,0 +1,51 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Colin Knapp - Tool Example">
<title>Tool Example - Colin Knapp</title>
<link rel="icon" type="image/x-icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles.css">
<link rel="stylesheet" href="tool-styles.css" integrity="sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=">
<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-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4="></script>
<script src="tool-example.js" defer></script>
</head>
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' 'sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4=' 'sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=' 'sha256-1ZUvhca3M/N6hch4NrdPufDPLTnANOpJ4hfsZgRykgg=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=' 'sha256-807UZmWvd6eLc8xVckZkNX6CRP9WV8MzHURc5BgtRWo=' 'sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=' 'sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=' 'sha256-2EA12+9d+s6rrc0rkdIjfmjbh6p2o0ZSXs4wbZuk/tA='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';">
<body>
<!-- Header Include -->
<div id="header-include"></div>
<!-- Main Content -->
<h1>Tool Example</h1>
<p>A simple example tool to demonstrate the includes system.</p>
<div class="tool-container">
<div class="tool-controls">
<h3>Tool Controls</h3>
<div class="form-group">
<label for="exampleInput">Example Input:</label>
<input type="text" id="exampleInput" class="form-control" placeholder="Enter some text...">
</div>
<div class="form-group">
<button id="exampleButton" class="btn">Process</button>
</div>
</div>
<div class="tool-output" id="output">
<p class="alert alert-info">Output will appear here.</p>
</div>
</div>
<hr>
<h2>About This Tool</h2>
<p>This is an example tool page that demonstrates how to use the includes system.</p>
<!-- Footer Include -->
<div id="footer-include"></div>
</body>
</html>

7
docker/resume/papaparse.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,22 +0,0 @@
document.addEventListener('DOMContentLoaded', function() {
const downloadButton = document.getElementById('downloadPDF');
if (downloadButton) {
downloadButton.addEventListener('click', function() {
// Store current theme
const currentTheme = document.body.getAttribute('data-theme');
// Force light theme for PDF
document.body.setAttribute('data-theme', 'light');
// Wait for theme change to apply
setTimeout(function() {
window.print();
// Restore original theme
setTimeout(function() {
document.body.setAttribute('data-theme', currentTheme);
}, 100);
}, 100);
});
}
});

View File

@ -0,0 +1,84 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Colin Knapp - Portfolio Stories and Case Studies">
<title>Colin Knapp - Stories & Case Studies</title>
<link rel="icon" type="image/x-icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles.css" integrity="sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=" crossorigin="anonymous">
<script src="../theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=" crossorigin="anonymous"></script>
<script src="../includes.js" integrity="sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4=" crossorigin="anonymous"></script>
<link rel="stylesheet" href="stories.css" integrity="sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=">
</head>
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' 'sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4=' 'sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=' 'sha256-1ZUvhca3M/N6hch4NrdPufDPLTnANOpJ4hfsZgRykgg=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=' 'sha256-807UZmWvd6eLc8xVckZkNX6CRP9WV8MzHURc5BgtRWo=' 'sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=' 'sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=' 'sha256-2EA12+9d+s6rrc0rkdIjfmjbh6p2o0ZSXs4wbZuk/tA='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';">
<body>
<!-- Header Include -->
<div id="header-include"></div>
<div class="container-fluid" role="main">
<h1>Project Stories & Case Studies</h1>
<p>Detailed stories and elaborations of projects from my portfolio.</p>
<hr>
<div class="stories-grid">
<div class="story-card">
<h2>Open Source Community Success</h2>
<p class="story-excerpt">How I revitalized an abandoned open source project, built a thriving community of 32,000+ members, and established sustainable funding.</p>
<p class="story-meta">Category: Open Source | Date: 2019-Present</p>
<a href="open-source-success.html" class="story-link">Read Full Story</a>
</div>
<div class="story-card">
<h2>ViperWire Cybersecurity</h2>
<p class="story-excerpt">How I built an AI-powered cybersecurity consultancy from the ground up, focusing on cutting-edge protection for digital assets.</p>
<p class="story-meta">Category: Cybersecurity | Date: 2023</p>
<a href="viperwire.html" class="story-link">Read Full Story</a>
</div>
<div class="story-card">
<h2>FastAsyncWorldEdit & PlotSquared</h2>
<p class="story-excerpt">The technical challenges overcome in scaling Minecraft world editing from crashing at 50,000 edits to seamlessly handling billions.</p>
<p class="story-meta">Category: Open Source | Date: 2014-Present</p>
<a href="fawe-plotsquared.html" class="story-link">Read Full Story</a>
</div>
<div class="story-card">
<h2>Healthcare Platform Infrastructure</h2>
<p class="story-excerpt">An in-depth look at the infrastructure design and security implementation for the Improving MI Practices healthcare platform.</p>
<p class="story-meta">Category: Infrastructure | Date: 2019-Present</p>
<a href="healthcare-platform.html" class="story-link">Read Full Story</a>
</div>
<div class="story-card">
<h2>WordPress Security Automation</h2>
<p class="story-excerpt">How I developed a Docker-based solution that eliminated persistent malware attacks on a high-profile website.</p>
<p class="story-meta">Category: Security | Date: 2023</p>
<a href="wordpress-security.html" class="story-link">Read Full Story</a>
</div>
<div class="story-card">
<h2>Airport DNS Infrastructure</h2>
<p class="story-excerpt">Building a geographically redundant DNS cluster for Flint Bishop International Airport that achieves A+ reliability standards.</p>
<p class="story-meta">Category: Infrastructure | Date: 2019-Present</p>
<a href="airport-dns.html" class="story-link">Read Full Story</a>
</div>
<div class="story-card">
<h2>NitricConcepts Leadership</h2>
<p class="story-excerpt">Managing a distributed team of 45 contractors and implementing DevSecOps practices across multiple timezones.</p>
<p class="story-meta">Category: Leadership | Date: 2018-2021</p>
<a href="nitric-leadership.html" class="story-link">Read Full Story</a>
</div>
</div>
<hr>
<p class="accessibility-notice"><strong>Accessibility:</strong> This website is designed and developed to meet WCAG 2.1 Level AAA standards, ensuring the highest level of accessibility for all users. Features include high contrast ratios, keyboard navigation, screen reader compatibility, and responsive design. The site supports both light and dark modes with automatic system preference detection.</p>
</div>
<!-- Footer Include -->
<div id="footer-include"></div>
</body>
</html>

View File

@ -0,0 +1,161 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Colin Knapp - Open Source Community Success Case Study">
<title>Open Source Community Success - Colin Knapp Case Study</title>
<link rel="icon" type="image/x-icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles.css" integrity="sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=">
<script src="../theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4="></script>
<script src="../includes.js" integrity="sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4="></script>
<link rel="stylesheet" href="stories.css" integrity="sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=">
</head>
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' 'sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4=' 'sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=' 'sha256-1ZUvhca3M/N6hch4NrdPufDPLTnANOpJ4hfsZgRykgg=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=' 'sha256-807UZmWvd6eLc8xVckZkNX6CRP9WV8MzHURc5BgtRWo=' 'sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=' 'sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=' 'sha256-2EA12+9d+s6rrc0rkdIjfmjbh6p2o0ZSXs4wbZuk/tA='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';">
<body>
<!-- Header Include -->
<div id="header-include"></div>
<div class="container-fluid" role="main">
<div class="story-header">
<h1>Building a Thriving Open Source Community</h1>
<p class="story-meta">Category: Open Source | Date: 2019-Present</p>
<hr>
</div>
<div class="story-content">
<p>In 2019, I had the opportunity to take over a promising open source project that was at risk of becoming abandoned. The original developer had created a solid foundation but was unable to continue maintaining it. Recognizing the project's potential and the community's need, I stepped in to not only maintain but significantly expand both the codebase and the community around it.</p>
<div class="story-image-container">
<img src="../images/docker-hub-stats.jpg" alt="Docker Hub statistics showing over 10 million pulls" class="story-image">
<p class="image-caption">The project's Docker Hub statistics showing over 10 million pulls</p>
</div>
<div class="story-image-container">
<img src="../images/discord-community.jpg" alt="Discord community statistics showing over 32,000 members with 4,297 online" class="story-image">
<p class="image-caption">Our thriving Discord community with over 32,000 members</p>
</div>
<h2>The Challenge</h2>
<p>When I took over the project, it faced several critical challenges:</p>
<ul>
<li>A codebase that was functional but needed significant modernization</li>
<li>Limited infrastructure for continuous integration, testing, and deployment</li>
<li>A small but passionate community with no structured way to communicate or collaborate</li>
<li>No sustainable funding model to support ongoing development and maintenance</li>
<li>Technical debt that was hindering new feature development and stability</li>
</ul>
<blockquote>
"The true measure of an open source project's success isn't just code quality or feature completeness—it's the health and engagement of the community that forms around it."
</blockquote>
<h2>The Approach</h2>
<p>I developed a comprehensive strategy that addressed both technical excellence and community building:</p>
<h3>1. Technical Infrastructure</h3>
<p>My first priority was establishing robust infrastructure to support sustainable development:</p>
<ul>
<li>Implemented a comprehensive CI/CD pipeline using GitHub Actions</li>
<li>Created Docker containers for easy deployment, which have now been pulled over 10 million times</li>
<li>Established automated testing with coverage requirements for all new code</li>
<li>Developed clear documentation for both users and contributors</li>
<li>Set up monitoring and observability tools to track usage and identify issues</li>
</ul>
<h3>2. Community Building</h3>
<p>In parallel with technical improvements, I focused on creating a welcoming, active community:</p>
<ul>
<li>Established a Discord server that has grown to over 32,000 members, with typically 4,000+ active at any time</li>
<li>Created structured channels for support, feature requests, showcase, and general discussion</li>
<li>Implemented community guidelines and moderation systems to maintain a positive environment</li>
<li>Organized regular community calls and update announcements</li>
<li>Recognized and celebrated community contributions</li>
</ul>
<h3>3. Sustainable Funding</h3>
<p>To ensure long-term viability, I established a sustainable funding model:</p>
<ul>
<li>Set up an OpenCollective account for transparent community funding</li>
<li>Developed clear funding goals tied to specific development milestones</li>
<li>Created a sponsorship program with appropriate recognition for corporate supporters</li>
<li>Established a governance model for fund allocation</li>
<li>Built several years of runway to ensure project stability</li>
</ul>
<h2>The Team</h2>
<p>As the project grew, I recruited and mentored a team of over 10 developers who now contribute regularly:</p>
<ul>
<li>Established clear contribution guidelines and review processes</li>
<li>Created an onboarding process for new contributors</li>
<li>Implemented a mentorship system pairing experienced developers with newcomers</li>
<li>Developed a governance structure that distributes decision-making authority</li>
<li>Set up regular team meetings and coordination channels</li>
</ul>
<h2>Results & Impact</h2>
<p>The project has achieved remarkable success under this new structure:</p>
<ul>
<li>Over 10 million Docker image pulls, demonstrating widespread adoption</li>
<li>A thriving Discord community with 32,000+ members and 4,000+ regularly active users</li>
<li>Sustainable funding through OpenCollective with several years of runway</li>
<li>A stable team of 10+ regular contributors</li>
<li>Significant expansion of features and capabilities</li>
<li>Improved code quality, test coverage, and documentation</li>
<li>Regular release cycles with clear roadmaps</li>
</ul>
<h2>Lessons Learned</h2>
<p>This experience provided valuable insights into open source project management:</p>
<h3>Technical Lessons</h3>
<p>Infrastructure investments pay dividends. The early focus on CI/CD, containerization, and automated testing dramatically improved both code quality and contributor experience. By making it easy to deploy, test, and contribute, we lowered barriers to entry and increased the pace of innovation.</p>
<p>Additionally, the decision to containerize the application early proved prescient, as it significantly simplified deployment across diverse environments and contributed to the project's widespread adoption.</p>
<h3>Community Lessons</h3>
<p>Community building requires intentional design. By creating structured spaces for different types of interactions (support, development, showcase, etc.), we enabled community members to engage in ways that matched their interests and expertise. The regular rhythm of updates and community calls helped maintain momentum and excitement.</p>
<p>Transparency in decision-making and fund management built trust with the community, which proved essential for sustainable growth and support.</p>
<h2>Future Directions</h2>
<p>Looking ahead, the project continues to evolve with several key initiatives:</p>
<ul>
<li>Expanding the core team through formalized mentorship programs</li>
<li>Developing educational resources to lower the barrier to entry for new users</li>
<li>Exploring integration opportunities with complementary projects</li>
<li>Implementing a more structured feature planning process based on community input</li>
<li>Expanding the project's scope based on evolving user needs</li>
</ul>
<p>The success of this project demonstrates how technical excellence combined with intentional community building can transform an at-risk open source project into a thriving ecosystem that benefits thousands of users while providing sustainable opportunities for contributors.</p>
</div>
<div class="story-footer">
<div class="story-nav">
<a href="nitric-leadership.html" class="story-nav-link prev">NitricConcepts Leadership</a>
<span class="story-nav-link next" style="visibility: hidden;">Next Story</span>
</div>
<div class="related-stories">
<h3>Related Stories</h3>
<div class="related-stories-list">
<div class="story-card">
<h2>FastAsyncWorldEdit & PlotSquared</h2>
<p class="story-excerpt">The technical challenges overcome in scaling Minecraft world editing from crashing at 50,000 edits to seamlessly handling billions.</p>
<a href="fawe-plotsquared.html" class="story-link">Read Story</a>
</div>
<div class="story-card">
<h2>NitricConcepts Leadership</h2>
<p class="story-excerpt">Managing a distributed team of 45 contractors and implementing DevSecOps practices across multiple timezones.</p>
<a href="nitric-leadership.html" class="story-link">Read Story</a>
</div>
</div>
</div>
</div>
</div>
<!-- Footer Include -->
<div id="footer-include"></div>
</body>
</html>

View File

@ -0,0 +1,204 @@
/* Additional styles for stories and case studies */
.stories-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 2rem;
margin: 2rem 0;
}
.story-card {
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 1.5rem;
background-color: var(--card-bg);
transition: transform 0.3s, box-shadow 0.3s;
height: auto;
display: flex;
flex-direction: column;
box-sizing: border-box;
margin: 0;
}
.story-card:hover {
transform: translateY(-3px);
box-shadow: 0 8px 15px rgba(0, 0, 0, 0.1);
}
.story-card h2 {
margin-top: 0;
font-size: 1.5rem;
color: var(--heading-color);
}
.story-excerpt {
flex-grow: 1;
margin-bottom: 1rem;
}
.story-meta {
font-size: 0.85rem;
color: var(--meta-text-color);
margin-bottom: 1rem;
font-style: italic;
}
.story-link {
display: inline-block;
padding: 0.5rem 1rem;
background-color: var(--accent-color);
color: white;
text-decoration: none;
border-radius: 4px;
font-weight: 500;
transition: background-color 0.3s;
text-align: center;
align-self: flex-start;
}
.story-link:hover {
background-color: var(--accent-color-darker);
}
/* Individual story page styles */
.story-header {
margin-bottom: 2rem;
}
.story-header h1 {
margin-bottom: 0.5rem;
}
.story-header .story-meta {
margin-top: 0;
margin-bottom: 1rem;
}
/* Story image container and caption styles */
.story-image-container {
margin: 2rem 0;
text-align: center;
}
.story-image {
width: 100%;
max-width: 800px;
height: auto;
border-radius: 4px;
margin: 0 0 0.5rem;
border: 1px solid var(--border-color);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.image-caption {
font-size: 0.9rem;
color: var(--meta-text-color);
margin: 0.5rem 0 1.5rem;
font-style: italic;
text-align: center;
}
.story-content {
line-height: 1.7;
max-width: 800px;
margin: 0 auto;
}
.story-content h2 {
margin-top: 2rem;
margin-bottom: 1rem;
}
.story-content p {
margin-bottom: 1.5rem;
}
.story-content blockquote {
border-left: 4px solid var(--accent-color);
padding-left: 1rem;
margin-left: 0;
margin-right: 0;
font-style: italic;
color: var(--blockquote-color);
}
.story-footer {
margin-top: 3rem;
padding-top: 1rem;
border-top: 1px solid var(--border-color);
}
.related-stories {
margin-top: 2rem;
}
.related-stories h3 {
margin-bottom: 1rem;
}
.related-stories-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 1rem;
}
.story-nav {
display: flex;
justify-content: space-between;
margin-top: 2rem;
margin-bottom: 2rem;
}
.story-nav-link {
padding: 0.5rem 1rem;
border: 1px solid var(--border-color);
border-radius: 4px;
text-decoration: none;
color: var(--text-color);
transition: background-color 0.3s, color 0.3s;
}
.story-nav-link:hover {
background-color: var(--accent-color);
color: white;
border-color: var(--accent-color);
}
.story-nav-link.prev::before {
content: "← ";
}
.story-nav-link.next::after {
content: " →";
}
/* Responsive adjustments */
@media (max-width: 768px) {
.stories-grid {
grid-template-columns: 1fr;
}
.related-stories-list {
grid-template-columns: 1fr;
}
.story-nav {
flex-direction: column;
gap: 1rem;
}
.story-nav-link {
text-align: center;
}
}
/* Accessibility enhancements */
.story-link:focus {
outline: 2px solid var(--focus-outline-color);
outline-offset: 2px;
}
/* Utility classes */
.hidden {
visibility: hidden;
}

View File

@ -0,0 +1,56 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Colin Knapp - Story Example">
<title>Story Example - Colin Knapp</title>
<link rel="icon" type="image/x-icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles.css" integrity="sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=">
<link rel="stylesheet" href="stories.css" integrity="sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=">
<script src="../theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4="></script>
<script src="../includes.js" integrity="sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4="></script>
</head>
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' 'sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4=' 'sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=' 'sha256-1ZUvhca3M/N6hch4NrdPufDPLTnANOpJ4hfsZgRykgg=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=' 'sha256-807UZmWvd6eLc8xVckZkNX6CRP9WV8MzHURc5BgtRWo=' 'sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=' 'sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=' 'sha256-2EA12+9d+s6rrc0rkdIjfmjbh6p2o0ZSXs4wbZuk/tA='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';">
<body>
<!-- Header Include -->
<div id="header-include"></div>
<!-- Main Content -->
<div class="story-header">
<h1>Story Title Example</h1>
<p class="story-meta">Category: Example | Date: 2023-Present</p>
<hr>
</div>
<div class="story-content">
<p>This is an example story content paragraph.</p>
<h2>Story Section</h2>
<p>This is a section of the story.</p>
<!-- Additional content would go here -->
</div>
<div class="story-footer">
<div class="story-nav">
<a href="#" class="story-nav-link prev">Previous Story</a>
<a href="#" class="story-nav-link next">Next Story</a>
</div>
<div class="related-stories">
<h3>Related Stories</h3>
<div class="related-stories-list">
<div class="story-card">
<h2>Related Story Title</h2>
<p class="story-excerpt">Brief description of the related story content.</p>
<a href="#" class="story-link">Read Story</a>
</div>
</div>
</div>
</div>
<!-- Footer Include -->
<div id="footer-include"></div>
</body>
</html>

View File

@ -0,0 +1,80 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Colin Knapp - [Story Title] Case Study">
<title>[Story Title] - Colin Knapp Case Study</title>
<link rel="icon" type="image/x-icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles.css" integrity="sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=" crossorigin="anonymous">
<script src="../theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=" crossorigin="anonymous"></script>
<script src="../includes.js" integrity="sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4=" crossorigin="anonymous"></script>
<link rel="stylesheet" href="stories.css" integrity="sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=">
</head>
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' 'sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4=' 'sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=' 'sha256-1ZUvhca3M/N6hch4NrdPufDPLTnANOpJ4hfsZgRykgg=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=' 'sha256-807UZmWvd6eLc8xVckZkNX6CRP9WV8MzHURc5BgtRWo=' 'sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=' 'sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=' 'sha256-2EA12+9d+s6rrc0rkdIjfmjbh6p2o0ZSXs4wbZuk/tA='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';">
<body>
<!-- Header Include -->
<div id="header-include"></div>
<div class="container-fluid" role="main">
<div class="story-header">
<h1>[Story Title]</h1>
<p class="story-meta">Category: [Category] | Date: [Date Range]</p>
<hr>
</div>
<div class="story-content">
<!-- Replace with actual content -->
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam vehicula, nisl vel ultricies aliquet, nisi urna posuere nibh, vel dapibus sapien libero ac nisi. Phasellus non volutpat orci, eu eleifend diam. Integer scelerisque euismod sem, vel euismod orci viverra a.</p>
<h2>The Challenge</h2>
<p>Cras consectetur dolor vel arcu luctus, a hendrerit tortor ultricies. Fusce eu eros vel mauris fermentum volutpat. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer rhoncus tortor vitae turpis tincidunt, sed congue lacus tempor.</p>
<blockquote>
"This project represented a significant challenge that required innovative thinking and cutting-edge technology solutions."
</blockquote>
<h2>The Approach</h2>
<p>Praesent feugiat tortor non tortor maximus, at congue nibh tincidunt. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Morbi vulputate velit non enim euismod, sit amet maximus mauris consectetur. Donec placerat arcu non faucibus condimentum.</p>
<!-- Sample image placement -->
<!-- <img src="images/story-image.jpg" alt="Description of the image" class="story-image"> -->
<h2>Technical Implementation</h2>
<p>Aenean non auctor mauris, id dapibus turpis. Fusce vestibulum mi sit amet sem commodo, id aliquam magna sollicitudin. Donec vel libero cursus, venenatis justo sit amet, faucibus nisi. Vestibulum nec suscipit mi. Ut fringilla scelerisque eros, id vestibulum lectus vehicula id.</p>
<h2>Results & Impact</h2>
<p>Suspendisse potenti. Curabitur pharetra neque quis dolor pretium, nec feugiat metus varius. Nulla facilisi. Nam ut justo sed leo viverra iaculis. Aliquam eget risus vitae quam dapibus dignissim at vel lectus. Proin convallis velit non dictum faucibus.</p>
<h2>Lessons Learned</h2>
<p>Proin malesuada facilisis felis, quis placerat erat dignissim nec. Morbi hendrerit elit vitae orci interdum mattis. Curabitur imperdiet velit ut libero egestas, at accumsan arcu vehicula. Nullam ac orci et velit efficitur condimentum.</p>
</div>
<div class="story-footer">
<div class="story-nav">
<a href="#" class="story-nav-link prev">Previous Story</a>
<a href="#" class="story-nav-link next">Next Story</a>
</div>
<div class="related-stories">
<h3>Related Stories</h3>
<div class="related-stories-list">
<div class="story-card">
<h2>Related Story Title</h2>
<p class="story-excerpt">Brief description of the related story content.</p>
<a href="#" class="story-link">Read Story</a>
</div>
<div class="story-card">
<h2>Another Related Story</h2>
<p class="story-excerpt">Brief description of another related story.</p>
<a href="#" class="story-link">Read Story</a>
</div>
</div>
</div>
</div>
</div>
<!-- Footer Include -->
<div id="footer-include"></div>
</body>
</html>

View File

@ -0,0 +1,126 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Colin Knapp - ViperWire Cybersecurity Case Study">
<title>ViperWire Cybersecurity - Colin Knapp Case Study</title>
<link rel="icon" type="image/x-icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles.css" integrity="sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=" crossorigin="anonymous">
<script src="../includes.js" integrity="sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4=" crossorigin="anonymous"></script>
<link rel="stylesheet" href="stories.css" integrity="sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=">
</head>
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' 'sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4=' 'sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=' 'sha256-1ZUvhca3M/N6hch4NrdPufDPLTnANOpJ4hfsZgRykgg=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=' 'sha256-807UZmWvd6eLc8xVckZkNX6CRP9WV8MzHURc5BgtRWo=' 'sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=' 'sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=' 'sha256-2EA12+9d+s6rrc0rkdIjfmjbh6p2o0ZSXs4wbZuk/tA='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';">
<body>
<!-- Header Include -->
<div id="header-include"></div>
<div class="container-fluid" role="main">
<div class="story-header">
<h1>Building ViperWire: An AI-Powered Cybersecurity Consultancy</h1>
<p class="story-meta">Category: Cybersecurity | Date: 2023-Present</p>
<hr>
</div>
<div class="story-content">
<p>In early 2023, I identified a critical gap in the cybersecurity market: small to medium-sized businesses were increasingly becoming targets for sophisticated cyber attacks, but lacked access to enterprise-grade security solutions that could adapt to rapidly evolving threats. This observation led to the creation of ViperWire, an AI-powered cybersecurity consultancy designed to democratize access to advanced security measures.</p>
<h2>The Challenge</h2>
<p>The cybersecurity landscape in 2023 presented several unique challenges:</p>
<ul>
<li>The increasing sophistication of attacks targeting SMBs with limited security budgets</li>
<li>A shortage of cybersecurity professionals capable of addressing modern threats</li>
<li>The rapid evolution of attack vectors requiring constant vigilance and adaptation</li>
<li>The need for solutions that could scale from small businesses to larger enterprises</li>
</ul>
<blockquote>
"The typical SMB faces the same threat actors as Fortune 500 companies, but with a fraction of the resources to defend themselves. This asymmetry creates a perfect storm where businesses are increasingly vulnerable while security solutions remain inaccessible."
</blockquote>
<h2>The Approach</h2>
<p>I built ViperWire around three core principles that would differentiate it in the market:</p>
<h3>1. AI-Augmented Security Analysis</h3>
<p>Rather than attempting to replace human expertise with AI, I designed systems where AI tools augment human analysts, dramatically increasing their efficiency and effectiveness. This approach began with custom-built monitoring tools that use machine learning to identify behavioral anomalies and prioritize potential threats, allowing human experts to focus on the most critical issues.</p>
<h3>2. Accessible Enterprise-Grade Protection</h3>
<p>By leveraging containerization, infrastructure-as-code, and modular security components, I created scalable security systems that could be rapidly deployed across organizations of varying sizes. This technical architecture allowed ViperWire to deliver enterprise-caliber protection at price points accessible to smaller organizations.</p>
<h3>3. Continuous Adaptation</h3>
<p>I implemented a continuous security improvement cycle that incorporated threat intelligence feeds, regular penetration testing, and automated vulnerability scanning. This approach ensured that security postures evolved in tandem with emerging threats rather than reacting after incidents occurred.</p>
<h2>Technical Implementation</h2>
<p>The technical architecture of ViperWire comprises several innovative components:</p>
<h3>Threat Detection Infrastructure</h3>
<p>I built a distributed monitoring system using a combination of open-source tools (Wazuh, Suricata, OSSEC) enhanced with custom machine learning models to detect anomalous network and system behaviors. The architecture utilizes Kubernetes for orchestration and Prometheus/Grafana for metrics visualization, with custom alerting thresholds tuned to each client's environment.</p>
<h3>Response Automation</h3>
<p>To counter the speed of modern attacks, I developed an automated response framework using Python and Ansible that could isolate compromised systems, revoke credentials, and implement temporary access controls within seconds of a confirmed threat detection. This system reduced the mean time to respond from hours to minutes, significantly limiting potential damage.</p>
<h3>Security Assessment Pipeline</h3>
<p>For proactive security, I created an assessment pipeline incorporating static analysis, dynamic testing, and configuration auditing. This suite leverages Docker containers for consistent, reproducible security tests across different environments and includes custom scanners for emerging vulnerabilities not yet covered by commercial tools.</p>
<h2>Results & Impact</h2>
<p>In its first year, ViperWire has achieved several notable successes:</p>
<ul>
<li>Successfully prevented ransomware attacks at two clients who had been targeted, saving an estimated $500,000 in potential losses</li>
<li>Reduced security alert noise by 87% through improved detection algorithms, allowing for more focused attention on genuine threats</li>
<li>Decreased mean time to detection of security incidents from 24+ hours to under 15 minutes</li>
<li>Enabled five small businesses to achieve compliance with industry security standards that were previously beyond their reach</li>
</ul>
<h2>Lessons Learned</h2>
<p>Building ViperWire has provided valuable insights into both technical and business aspects of cybersecurity:</p>
<h3>Technical Lessons</h3>
<p>The most effective security solutions combine multiple detection methodologies rather than relying on any single approach. Our hybrid model of behavioral analysis, signature detection, and anomaly identification proved far more effective than any individual method alone.</p>
<p>Additionally, I discovered that properly tuned automation dramatically reduces false positives—the bane of many security operations—while still capturing genuine threats. The key was implementing progressive verification steps that validate alerts before triggering high-impact responses.</p>
<h3>Business Lessons</h3>
<p>Perhaps most importantly, I learned that transparency builds trust in security services. By providing clients with clear visibility into threat detection processes and plainly explaining technical concepts, ViperWire was able to build stronger relationships and encourage better security practices within client organizations.</p>
<h2>Future Directions</h2>
<p>Looking ahead, ViperWire is expanding into several promising areas:</p>
<ul>
<li>Developing specialized security solutions for IoT environments in manufacturing settings</li>
<li>Creating educational resources to help clients build internal security capabilities</li>
<li>Expanding AI capabilities to provide predictive threat intelligence specific to each client's industry</li>
</ul>
<p>The founding principle of ViperWire—that sophisticated security should be accessible to organizations of all sizes—continues to guide its evolution and growth.</p>
</div>
<div class="story-footer">
<div class="story-nav">
<span class="story-nav-link prev hidden">Previous Story</span>
<a href="fawe-plotsquared.html" class="story-nav-link next">FastAsyncWorldEdit & PlotSquared</a>
</div>
<div class="related-stories">
<h3>Related Stories</h3>
<div class="related-stories-list">
<div class="story-card">
<h2>WordPress Security Automation</h2>
<p class="story-excerpt">How I developed a Docker-based solution that eliminated persistent malware attacks on a high-profile website.</p>
<a href="wordpress-security.html" class="story-link">Read Story</a>
</div>
<div class="story-card">
<h2>Healthcare Platform Infrastructure</h2>
<p class="story-excerpt">An in-depth look at the infrastructure design and security implementation for the Improving MI Practices healthcare platform.</p>
<a href="healthcare-platform.html" class="story-link">Read Story</a>
</div>
</div>
</div>
</div>
</div>
<!-- Footer Include -->
<div id="footer-include"></div>
<!-- Theme script moved to end of body -->
<script src="../theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=" crossorigin="anonymous"></script>
</body>
</html>

View File

@ -8,6 +8,16 @@
--theme-border: #ddd; --theme-border: #ddd;
--theme-hover: #e0e0e0; --theme-hover: #e0e0e0;
--date-color: #555555; --date-color: #555555;
--bg-primary: #ffffff;
--bg-secondary: #f5f5f5;
--bg-tertiary: #eaeaea;
--bg-hover: #f0f0f0;
--text-primary: #333333;
--button-bg: #f5f5f5;
--button-hover-bg: #e0e0e0;
--focus-outline-color: #0056b3;
--progress-bg: #e0e0e0;
--accent-hover: #003d82;
} }
/* Dark theme variables when system prefers dark mode (auto setting) */ /* Dark theme variables when system prefers dark mode (auto setting) */
@ -22,6 +32,16 @@
--theme-border: #404040; --theme-border: #404040;
--theme-hover: #3d3d3d; --theme-hover: #3d3d3d;
--date-color: #a0a0a0; --date-color: #a0a0a0;
--bg-primary: #1a1a1a;
--bg-secondary: #2d2d2d;
--bg-tertiary: #3d3d3d;
--bg-hover: #333333;
--text-primary: #e0e0e0;
--button-bg: #2d2d2d;
--button-hover-bg: #3d3d3d;
--focus-outline-color: #5fa9ff;
--progress-bg: #404040;
--accent-hover: #8ac2ff;
} }
} }
@ -36,6 +56,16 @@ html[data-theme='light'] {
--theme-border: #ddd; --theme-border: #ddd;
--theme-hover: #e0e0e0; --theme-hover: #e0e0e0;
--date-color: #555555; --date-color: #555555;
--bg-primary: #ffffff;
--bg-secondary: #f5f5f5;
--bg-tertiary: #eaeaea;
--bg-hover: #f0f0f0;
--text-primary: #333333;
--button-bg: #f5f5f5;
--button-hover-bg: #e0e0e0;
--focus-outline-color: #0056b3;
--progress-bg: #e0e0e0;
--accent-hover: #003d82;
} }
/* Dark theme variables when manually selected */ /* Dark theme variables when manually selected */
@ -49,6 +79,16 @@ html[data-theme='dark'] {
--theme-border: #404040; --theme-border: #404040;
--theme-hover: #3d3d3d; --theme-hover: #3d3d3d;
--date-color: #a0a0a0; --date-color: #a0a0a0;
--bg-primary: #1a1a1a;
--bg-secondary: #2d2d2d;
--bg-tertiary: #3d3d3d;
--bg-hover: #333333;
--text-primary: #e0e0e0;
--button-bg: #2d2d2d;
--button-hover-bg: #3d3d3d;
--focus-outline-color: #5fa9ff;
--progress-bg: #404040;
--accent-hover: #8ac2ff;
} }
body { body {
@ -62,6 +102,17 @@ body {
margin: 0 auto; margin: 0 auto;
} }
/* Container that can expand to full width */
.container-fluid {
width: 100%;
max-width: 100%;
padding-right: 15px;
padding-left: 15px;
margin-right: auto;
margin-left: auto;
box-sizing: border-box;
}
h1, h2, h3 { h1, h2, h3 {
color: var(--text-color); color: var(--text-color);
margin-top: 1.5em; margin-top: 1.5em;
@ -163,33 +214,6 @@ hr {
background-color: rgba(128, 128, 128, 0.2); background-color: rgba(128, 128, 128, 0.2);
} }
/* Print styles */
@media print {
.theme-switch {
display: none;
}
body {
background: white !important;
color: black !important;
}
a {
color: black !important;
text-decoration: none !important;
}
.container-fluid {
max-width: 100% !important;
margin: 0 !important;
padding: 0 !important;
}
hr {
border-color: black !important;
}
}
@media (max-width: 600px) { @media (max-width: 600px) {
body { body {
padding: 10px; padding: 10px;
@ -207,3 +231,111 @@ hr {
font-size: 1.2em; font-size: 1.2em;
} }
} }
/* Navigation styles */
.main-nav {
display: flex;
justify-content: center;
margin: 1rem 0;
}
.main-nav ul {
display: flex;
list-style: none;
margin: 0;
padding: 0;
gap: 1rem;
border-radius: 4px;
background-color: var(--theme-bg);
padding: 0.5rem 1rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.main-nav li {
margin: 0;
padding: 0;
position: relative;
}
.main-nav a {
display: block;
padding: 0.5rem 1rem;
text-decoration: none;
color: var(--text-color);
font-weight: 500;
border-radius: 4px;
transition: background-color 0.3s, color 0.3s;
}
.main-nav a:hover {
background-color: var(--theme-hover);
color: var(--accent-color);
}
.main-nav a.active {
background-color: var(--accent-color);
color: white;
}
/* Dropdown styles */
.main-nav .dropdown {
position: relative;
cursor: pointer;
}
.main-nav .dropdown > a::after {
content: "▼";
font-size: 0.7em;
margin-left: 0.5em;
vertical-align: middle;
}
.main-nav .dropdown-content {
display: none;
position: absolute;
top: 100%;
left: 0;
background-color: var(--theme-bg);
min-width: 160px;
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
z-index: 1000;
border-radius: 4px;
padding: 0.5rem 0;
margin-top: 0.5rem;
}
.main-nav .dropdown:hover .dropdown-content,
.main-nav .dropdown:focus-within .dropdown-content {
display: block;
}
.main-nav .dropdown-content a {
padding: 0.5rem 1rem;
display: block;
text-align: left;
white-space: nowrap;
}
.main-nav .dropdown-content a.active {
background-color: var(--accent-color);
color: white;
}
/* Responsive navigation */
@media (max-width: 600px) {
.main-nav ul {
flex-direction: column;
gap: 0.5rem;
}
.main-nav a {
text-align: center;
}
.main-nav .dropdown-content {
position: static;
box-shadow: none;
margin-top: 0;
padding-left: 1rem;
}
}

View File

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Colin Knapp - Template with Includes">
<title>Template with Includes - Colin Knapp</title>
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="styles.css" integrity="sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=">
<script src="theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4="></script>
<script src="includes.js" integrity="sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4="></script>
</head>
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' 'sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4=' 'sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=' 'sha256-1ZUvhca3M/N6hch4NrdPufDPLTnANOpJ4hfsZgRykgg=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=' 'sha256-807UZmWvd6eLc8xVckZkNX6CRP9WV8MzHURc5BgtRWo=' 'sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=' 'sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=' 'sha256-2EA12+9d+s6rrc0rkdIjfmjbh6p2o0ZSXs4wbZuk/tA='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';">
<body>
<!-- Header Include -->
<div id="header-include"></div>
<!-- Main Content -->
<h1>Page Title</h1>
<p>This is the main content of the page.</p>
<hr>
<h2>Section Title</h2>
<p>This is a section of content.</p>
<!-- Footer Include -->
<div id="footer-include"></div>
</body>
</html>

44
docker/resume/test-csv-tool.sh Executable file
View File

@ -0,0 +1,44 @@
#!/bin/bash
# =====================================================================
# test-csv-tool.sh - Test the CSV tool functionality
# =====================================================================
# This script checks if the CSV tool page loads without CSP errors
# =====================================================================
echo "=== Testing CSV Tool ==="
# Create a test CSV file
echo "Name,Age,City
John,30,New York
Jane,25,San Francisco
Bob,40,Chicago" > test.csv
# Check if the page loads properly
echo "Checking if the CSV tool page loads properly..."
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/one-pager-tools/csv-tool.html)
if [ "$RESPONSE" -eq 200 ]; then
echo "✅ CSV tool page loads successfully (HTTP $RESPONSE)"
else
echo "❌ CSV tool page failed to load (HTTP $RESPONSE)"
exit 1
fi
# Check for CSP errors in the response headers
echo "Checking for CSP errors in response headers..."
CSP_HEADER=$(curl -s -I http://localhost:8080/one-pager-tools/csv-tool.html | grep -i "Content-Security-Policy")
if [ -n "$CSP_HEADER" ]; then
echo "✅ CSP header found in response"
else
echo "❌ CSP header not found in response"
exit 1
fi
# Clean up
rm -f test.csv
echo "=== CSV Tool Test Completed Successfully ==="
echo "The CSV tool appears to be working correctly."
echo "You can manually test it by visiting: http://localhost:8080/one-pager-tools/csv-tool.html"
echo "and pasting CSV data into the textarea."

View File

@ -2,6 +2,12 @@ document.addEventListener('DOMContentLoaded', function() {
const themeToggle = document.getElementById('themeToggle'); const themeToggle = document.getElementById('themeToggle');
const html = document.documentElement; const html = document.documentElement;
// Check if themeToggle exists before proceeding
if (!themeToggle) {
console.log('Theme toggle button not found on this page');
return;
}
// Check for saved theme preference, default to auto // Check for saved theme preference, default to auto
const savedTheme = localStorage.getItem('theme') || 'auto'; const savedTheme = localStorage.getItem('theme') || 'auto';

View File

@ -0,0 +1,230 @@
#!/bin/bash
# =====================================================================
# update-csp-hashes.sh - Update Content Security Policy hashes
# =====================================================================
# This script updates the CSP hashes for:
# 1. All JavaScript and CSS files
# 2. All inline style attributes in HTML files
# 3. Adds CSP meta tags to HTML files
# After running this script, restart the server using:
# ./caddy.sh
# =====================================================================
set -e
echo "Updating CSP hashes for all JavaScript, CSS files, and inline styles..."
# Directory containing the files
BASE_DIR="$(pwd)"
CADDYFILE="$BASE_DIR/Caddyfile"
TEMP_INLINE_HASHES_FILE=$(mktemp)
# Arrays to store hashes
SCRIPT_HASHES=()
STYLE_HASHES=()
# Calculate hash for a file
calculate_hash() {
local file=$1
shasum -a 256 "$file" | awk '{print $1}' | xxd -r -p | base64
}
# Calculate hash for inline style
calculate_inline_hash() {
local style_content=$1
echo -n "$style_content" | shasum -a 256 | awk '{print $1}' | xxd -r -p | base64
}
# Process JavaScript files
echo "Processing JavaScript files..."
for js_file in $(find "$BASE_DIR" -name "*.js" -type f); do
echo "Processing $js_file"
file_name=$(basename "$js_file")
hash=$(calculate_hash "$js_file")
SCRIPT_HASHES+=("'sha256-$hash'")
# Update HTML files that reference this JS file
for html_file in $(find "$BASE_DIR" -name "*.html" -type f); do
if grep -q "$file_name" "$html_file"; then
echo "Updating $file_name in $html_file"
# Create a temporary file for the replacement
tmp_file=$(mktemp)
# For files with existing integrity attribute
if grep -q "$file_name.*integrity" "$html_file"; then
# Use awk for safer text processing
awk -v fname="$file_name" -v newhash="$hash" '
{
if ($0 ~ fname && $0 ~ /integrity/) {
gsub(/integrity="sha256-[^"]*"/, "integrity=\"sha256-" newhash "\"");
}
print;
}' "$html_file" > "$tmp_file"
else
# Add integrity attribute if it doesn't exist
awk -v fname="$file_name" -v newhash="$hash" '
{
if ($0 ~ fname && $0 ~ /src/ && !($0 ~ /integrity/)) {
gsub(/src="[^"]*"/, "&" " integrity=\"sha256-" newhash "\"");
}
print;
}' "$html_file" > "$tmp_file"
fi
# Replace original file with modified content
mv "$tmp_file" "$html_file"
fi
done
done
# Process CSS files
echo "Processing CSS files..."
for css_file in $(find "$BASE_DIR" -name "*.css" -type f); do
echo "Processing $css_file"
file_name=$(basename "$css_file")
hash=$(calculate_hash "$css_file")
STYLE_HASHES+=("'sha256-$hash'")
# Update HTML files that reference this CSS file
for html_file in $(find "$BASE_DIR" -name "*.html" -type f); do
if grep -q "$file_name" "$html_file"; then
echo "Updating $file_name in $html_file"
# Create a temporary file for the replacement
tmp_file=$(mktemp)
# For files with existing integrity attribute
if grep -q "$file_name.*integrity" "$html_file"; then
# Use awk for safer text processing
awk -v fname="$file_name" -v newhash="$hash" '
{
if ($0 ~ fname && $0 ~ /integrity/) {
gsub(/integrity="sha256-[^"]*"/, "integrity=\"sha256-" newhash "\"");
}
print;
}' "$html_file" > "$tmp_file"
else
# Add integrity attribute if it doesn't exist
awk -v fname="$file_name" -v newhash="$hash" '
{
if ($0 ~ fname && $0 ~ /href/ && !($0 ~ /integrity/)) {
gsub(/href="[^"]*"/, "&" " integrity=\"sha256-" newhash "\"");
}
print;
}' "$html_file" > "$tmp_file"
fi
# Replace original file with modified content
mv "$tmp_file" "$html_file"
fi
done
done
# Find and process inline styles - using a more thorough approach
echo "Processing HTML files for inline styles..."
find "$BASE_DIR" -name "*.html" -type f | while read -r html_file; do
echo "Processing $html_file for inline styles..."
# Use a more comprehensive grep pattern to catch all inline styles
# This includes both style="..." and style = "..." patterns
grep -o 'style\s*=\s*"[^"]*"' "$html_file" | sed 's/style\s*=\s*"\(.*\)"/\1/' | while read -r style_content; do
if [ -n "$style_content" ]; then
hash=$(calculate_inline_hash "$style_content")
echo "Found inline style: '$style_content'"
echo "Calculated hash: sha256-$hash"
echo "'sha256-$hash'" >> "$TEMP_INLINE_HASHES_FILE"
fi
done
done
# Sort and remove duplicates from inline style hashes
if [ -f "$TEMP_INLINE_HASHES_FILE" ]; then
sort -u "$TEMP_INLINE_HASHES_FILE" > "${TEMP_INLINE_HASHES_FILE}.sorted"
mv "${TEMP_INLINE_HASHES_FILE}.sorted" "$TEMP_INLINE_HASHES_FILE"
# Add inline style hashes to the STYLE_HASHES array
while read -r hash; do
STYLE_HASHES+=("$hash")
done < "$TEMP_INLINE_HASHES_FILE"
# Clean up
rm -f "$TEMP_INLINE_HASHES_FILE"
fi
# Combine all hashes for CSP
echo "Updating Caddyfile CSP headers..."
SCRIPT_HASHES_STR=$(printf " %s" "${SCRIPT_HASHES[@]}")
STYLE_HASHES_STR=$(printf " %s" "${STYLE_HASHES[@]}")
# Create the CSP string
CSP_STRING="default-src 'none'; script-src 'self'$SCRIPT_HASHES_STR; style-src 'self'$STYLE_HASHES_STR; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"
# Create a temporary file for the Caddyfile update
tmp_file=$(mktemp)
# Update CSP in Caddyfile using awk for more reliable text processing
awk -v csp_string="$CSP_STRING" '
{
if ($0 ~ /Content-Security-Policy/) {
gsub(/Content-Security-Policy "[^"]*"/, "Content-Security-Policy \"" csp_string "\"");
}
print;
}' "$CADDYFILE" > "$tmp_file"
# Replace original Caddyfile with modified content
mv "$tmp_file" "$CADDYFILE"
# Also update Caddyfile.local if it exists
if [ -f "$BASE_DIR/Caddyfile.local" ]; then
echo "Updating Caddyfile.local CSP headers..."
tmp_file=$(mktemp)
awk -v csp_string="$CSP_STRING" '
{
if ($0 ~ /Content-Security-Policy/) {
gsub(/Content-Security-Policy "[^"]*"/, "Content-Security-Policy \"" csp_string "\"");
}
print;
}' "$BASE_DIR/Caddyfile.local" > "$tmp_file"
# Replace original Caddyfile.local with modified content
mv "$tmp_file" "$BASE_DIR/Caddyfile.local"
fi
# Add CSP meta tags to HTML files
echo "Adding CSP meta tags to HTML files..."
for html_file in $(find "$BASE_DIR" -name "*.html" -type f); do
echo "Adding CSP meta tag to $html_file"
# Create a temporary file for the replacement
tmp_file=$(mktemp)
# Check if the file already has a CSP meta tag
if grep -q '<meta http-equiv="Content-Security-Policy"' "$html_file"; then
# Update existing CSP meta tag
awk -v csp_string="$CSP_STRING" '
{
if ($0 ~ /<meta http-equiv="Content-Security-Policy"/) {
gsub(/<meta http-equiv="Content-Security-Policy" content="[^"]*"/, "<meta http-equiv=\"Content-Security-Policy\" content=\"" csp_string "\"");
}
print;
}' "$html_file" > "$tmp_file"
else
# Add CSP meta tag after the last meta tag
awk -v csp_string="$CSP_STRING" '
{
print;
if ($0 ~ /<\/head>/ && !added_csp) {
print " <meta http-equiv=\"Content-Security-Policy\" content=\"" csp_string "\">";
added_csp = 1;
}
}' "$html_file" > "$tmp_file"
fi
# Replace original file with modified content
mv "$tmp_file" "$html_file"
done
echo "CSP hashes updated successfully!"
echo "To apply changes, restart the server using: ./caddy.sh"