Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
version: 2
updates:
# Keep workflow action versions current.
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
commit-message:
prefix: ci

# Dev tooling (Vitest, Playwright, ESLint, Prettier). Runtime libraries
# are vendored under assets/vendor/ and intentionally NOT tracked by
# Dependabot — version drift there is manual via VENDOR.md.
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
commit-message:
prefix: chore
open-pull-requests-limit: 5
84 changes: 84 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: CI

# Single-stage pipeline: Verify.
#
# threadscape has no build step — the deployed artifact is the repo's
# tracked files served as-is. Deploy is handled out-of-band by Digital
# Ocean's auto-pull on push to master, so the only thing CI needs to do
# is gate that push by running the same checks a developer runs locally.
#
# Verify fans out into four named phases via a matrix so they appear as
# child nodes under Verify in the Actions graph view and run in parallel:
#
# Format → prettier --check
# Lint → eslint .
# Unit → vitest run
# E2E → playwright test (algorithm snapshot)

on:
push:
branches: [master]
pull_request:
branches: [master]
workflow_dispatch:

permissions:
contents: read

jobs:
verify:
name: Verify · ${{ matrix.phase }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- phase: Format
command: npm run format:check
needs_browsers: false
- phase: Lint
command: npm run lint
needs_browsers: false
- phase: Unit
command: npm test
needs_browsers: false
- phase: E2E
command: npm run test:e2e
needs_browsers: true
steps:
- uses: actions/checkout@v6

- uses: actions/setup-node@v4
with:
node-version-file: .tool-versions
cache: npm

- name: Install
run: npm ci

# The Playwright browser bundle is ~150 MB. Cache it across runs
# keyed on package-lock so it only redownloads when @playwright/test
# itself changes.
- name: Cache Playwright browsers
if: matrix.needs_browsers
id: playwright-cache
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-${{ hashFiles('package-lock.json') }}
restore-keys: |
${{ runner.os }}-playwright-

- name: Install Playwright browsers
if: matrix.needs_browsers && steps.playwright-cache.outputs.cache-hit != 'true'
run: npx playwright install --with-deps chromium

# `--with-deps` only installs OS packages on a fresh install. When
# the browser comes from cache we still need the system libraries
# (libnss, libasound, etc.) that headless Chromium links against.
- name: Install Playwright OS deps (cache hit)
if: matrix.needs_browsers && steps.playwright-cache.outputs.cache-hit == 'true'
run: npx playwright install-deps chromium

- name: ${{ matrix.phase }}
run: ${{ matrix.command }}
19 changes: 19 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Dependencies (dev tooling only — runtime libs are vendored)
node_modules/

# Build / test artifacts
playwright-report/
test-results/
coverage/

# Editor & OS cruft
.DS_Store
.vscode/
.idea/
*.swp
*.swo

# Logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
22 changes: 22 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
node_modules/
assets/vendor/
assets/js/jquery.min.js
assets/js/jquery.scrollex.min.js
assets/js/jquery.scrolly.min.js
assets/js/browser.min.js
assets/js/breakpoints.min.js
assets/js/cropper.js
assets/js/numjs.js
assets/js/opencv.js
assets/js/util.js
assets/js/main.js
assets/css/cropper.css
assets/css/main.css
assets/css/nav.css
assets/css/nav_panel.css
assets/css/noscript.css
test-results/
playwright-report/
# Snapshot fixtures are compared byte-for-byte against JSON.stringify output —
# Prettier's reformatting would always cause mismatch.
test/e2e/**/*.spec.js-snapshots/
8 changes: 8 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100,
"tabWidth": 2,
"useTabs": false,
"semi": true
}
1 change: 1 addition & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodejs 20.10.0
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# threadscape

A string art generator that also allows you to create templates for the backing board.

## TODO:

1. Make the initialization of the image cropper a singlton outside of image loads.
1. Make the initialization of the image cropper a singlton outside of image loads.
82 changes: 82 additions & 0 deletions VENDOR.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Vendored Dependencies

Everything in `assets/js/`, `assets/css/`, and `assets/vendor/` that is not original
ThreadScape code is vendored here so the deployed site has no runtime fetches and
no dependency on third-party CDNs remaining available.

To refresh a library, re-download from the listed source, verify the SHA256, and
update the version + checksum below.

## Runtime libraries

### jspdf — 2.5.1

- **Path:** `assets/vendor/jspdf/jspdf.umd.min.js`
- **Source:** https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js
- **License:** MIT
- **Used by:** `assets/js/base_generator.js` (`generatePDF`)
- **SHA256:** `98ccf17aa10c20bb1301762618fcc9b6ab3a4e7f26b6071d64d0b41154df3875`

### Font Awesome Free — 6.7.2

- **Paths:**
- `assets/vendor/fontawesome/css/all.min.css`
- `assets/vendor/fontawesome/webfonts/fa-solid-900.{woff2,ttf}`
- `assets/vendor/fontawesome/webfonts/fa-brands-400.{woff2,ttf}`
- **Source:** https://use.fontawesome.com/releases/v6.7.2/fontawesome-free-6.7.2-web.zip
- **License:** Font Awesome Free License (CC BY 4.0 icons, SIL OFL 1.1 fonts, MIT code) — see `assets/vendor/fontawesome/LICENSE.txt`
- **Notes:** Only `solid` and `brands` font families are vendored — the only families
currently referenced from `index.html`. `regular` and other variants are omitted to
keep repo size down.
- **SHA256:**
- `all.min.css` — `74005d7c17d4a02f2f25404ec0655d9bc2fdaa53166874c87d7b7eec69d9088a`
- `fa-solid-900.woff2` — `aa75998623a391e61c6901794ace832e3ecdd288b56d608f21bea0411acc0b8e`
- `fa-solid-900.ttf` — `d2f0593540b0e33ba6de255a54f272d466e31144806956bea8cfdbf7edffc9bd`
- `fa-brands-400.woff2` — `d7236a19bf23cbb2027280e8f51dc99d6c45976a2ed60de73382b034b18a2b68`
- `fa-brands-400.ttf` — `808443ae6c8204395add8543da8a90a60b9376fb0f87ed8e8ea37d109596d805`

### Cropper.js — 1.6.2

- **Paths:** `assets/js/cropper.js`, `assets/css/cropper.css`
- **Source:** https://github.com/fengyuanchen/cropperjs/releases/tag/v1.6.2
- **License:** MIT
- **SHA256:**
- `cropper.js` — `62974724fe7ed174e095fe2a0d5d69b83661c4c80c569cb420d674fed9a870b3`
- `cropper.css` — `0a10b9e7d1d995d8ce513819834e158b833e3cddb4508b68c715b09d83b78dda`

### numjs

- **Path:** `assets/js/numjs.js`
- **Source:** https://github.com/nicolaspanel/numjs (browser bundle)
- **License:** MIT
- **Notes:** Slated for removal in Phase 3 — only ~6 narrow operations are used and
they can be replaced with plain `Uint8Array` / `Float64Array`.
- **SHA256:** `b2f4be7072d04ffb90955b8e865f6ef41688af3bfa70e6c186b16af5ab3787b3`

### OpenCV.js

- **Path:** `assets/js/opencv.js`
- **Source:** https://docs.opencv.org/4.x/opencv.js
- **License:** Apache 2.0
- **Notes:** Slated for removal in Phase 3 — only `Mat`, `matFromArray`, `Size`,
`line`, `resize`, `Point`, `Scalar`, `imshow` are used, all replaceable with raw
2D canvas.
- **SHA256:** `5293c8e503bf5bcb8499371efb4fe9e664eb052e149dfc0ff31dbf1bc02772cc`

## HTML5 UP "Massively" template

`assets/js/main.js`, `assets/js/util.js`, `assets/css/main.css`, `assets/css/nav.css`,
`assets/css/nav_panel.css`, `assets/css/noscript.css`, plus the bundled jQuery
plugins (`jquery.min.js`, `jquery.scrollex.min.js`, `jquery.scrolly.min.js`,
`browser.min.js`, `breakpoints.min.js`) are from the
[Massively](https://html5up.net/massively) template by HTML5 UP / @ajlkn,
licensed CC BY 3.0.

These files are treated as upstream and not modified by hand.

- **SHA256:**
- `jquery.min.js` — `ff1523fb7389539c84c65aba19260648793bb4f5e29329d2ee8804bc37a3fe6e`
- `jquery.scrollex.min.js` — `fc25b75fb3fc8b42756413be387e0d7a602813125283d2384551961d73ea784e`
- `jquery.scrolly.min.js` — `8b6571ea2c3631ff50bb4b96e7f9081c6e33ebaadef9cb2ca5955d5e0b625a02`
- `browser.min.js` — `c4217feebdd357e8a952e0ffbaa02791e5323482b8e3d80b3f714b84b1664103`
- `breakpoints.min.js` — `309febcd6d6e0cf092201532215f03a6a9f30b30f26203272a4861d704e7cd52`
Binary file removed assets/.DS_Store
Binary file not shown.
42 changes: 21 additions & 21 deletions assets/css/custom.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
input[type="range"] {
input[type='range'] {
-webkit-appearance: none;
/* Remove default styling */
width: 100%;
Expand All @@ -14,11 +14,11 @@ input[type="range"] {
transition: opacity 0.2s;
}

input[type="range"]:hover {
input[type='range']:hover {
opacity: 1;
}

input[type="range"]::-webkit-slider-thumb {
input[type='range']::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
Expand All @@ -29,7 +29,7 @@ input[type="range"]::-webkit-slider-thumb {
cursor: pointer;
}

input[type="range"]::-moz-range-thumb {
input[type='range']::-moz-range-thumb {
width: 20px;
height: 20px;
border-radius: 50%;
Expand Down Expand Up @@ -90,8 +90,8 @@ canvas {
.center-all {
display: flex;
justify-content: center; /* Horizontally centers the button */
align-items: center; /* Vertically centers the button */
height: 100%; /* Ensure the parent container has height to center vertically */
align-items: center; /* Vertically centers the button */
height: 100%; /* Ensure the parent container has height to center vertically */
}

.marign_right {
Expand Down Expand Up @@ -120,7 +120,7 @@ canvas {
padding: 10px;
}

.centerImage{
.centerImage {
padding-left: 0;
padding-right: 0;
margin-left: auto;
Expand All @@ -130,21 +130,21 @@ canvas {
}

.centerCanvasMedium {
padding-left: 0;
padding-right: 0;
margin-left: auto;
margin-right: auto;
display: block;
width: 500px;
padding-left: 0;
padding-right: 0;
margin-left: auto;
margin-right: auto;
display: block;
width: 500px;
}

.centerCanvasLarge {
padding-left: 0;
padding-right: 0;
margin-left: auto;
margin-right: auto;
display: block;
width: 800px;
padding-left: 0;
padding-right: 0;
margin-left: auto;
margin-right: auto;
display: block;
width: 800px;
}

.not_yet_implemented {
Expand Down Expand Up @@ -185,7 +185,7 @@ width: 800px;

/* Add an arrow pointing to the left */
.tooltip-text::after {
content: "";
content: '';
position: absolute;
top: 50%;
right: 100%; /* Position the arrow on the left */
Expand All @@ -199,4 +199,4 @@ width: 800px;
.tooltip:hover .tooltip-text {
visibility: visible;
opacity: 1; /* Make it visible */
}
}
Loading
Loading