From 80134115699d2284fb3200decef51463526631f4 Mon Sep 17 00:00:00 2001 From: Vincent Rubinetti Date: Tue, 19 Dec 2023 14:15:31 -0500 Subject: [PATCH] Init (#1) --- .github/pull_request_template.md | 4 + .nojekyll | 0 README.md | 42 ++++- images/manubot.jpg | Bin 0 -> 33086 bytes index.html | 244 +++++++++++++++++++++++++ list.json | 102 +++++++++++ src/alpine.js | 43 +++++ src/grid.css | 179 +++++++++++++++++++ src/header.css | 89 +++++++++ src/share.jpg | Bin 0 -> 31847 bytes src/styles.css | 118 ++++++++++++ src/util.css | 75 ++++++++ src/viz.js | 297 +++++++++++++++++++++++++++++++ 13 files changed, 1192 insertions(+), 1 deletion(-) create mode 100644 .github/pull_request_template.md create mode 100644 .nojekyll create mode 100644 images/manubot.jpg create mode 100644 index.html create mode 100644 list.json create mode 100644 src/alpine.js create mode 100644 src/grid.css create mode 100644 src/header.css create mode 100644 src/share.jpg create mode 100644 src/styles.css create mode 100644 src/util.css create mode 100644 src/viz.js diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..1399157 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,4 @@ +Submission/review checklist: + +- [] I have followed the submission guidelines in the `README.md`. +- [] I have looked at the preview link to verify that my changes appear correctly. diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md index 2b9388a..8c1c127 100644 --- a/README.md +++ b/README.md @@ -1 +1,41 @@ -# software-registry \ No newline at end of file +# CU DBMI Wall of Software + +The [Department of Biomedical Informatics (DBMI) at the University of Colorado](https://medschool.cuanschutz.edu/dbmi) investigates complex problems in medicine and biology using integrated computational technology. +This repository and website contains a non-comprehensive list of tools, packages, workflows, resources, and other software that have been developed by our diverse group researchers, scientists, developers, and collaborators in support of [our mission](https://medschool.cuanschutz.edu/dbmi/about-us). + +[⭐️⭐️ View the List ⭐️⭐️](https://CU-DBMI.github.io/wall-of-software) + +## Submit Your Software + +If you work for or with the department, you are eligible to submit your software here. +To start, [open a pull request in this repo](https://github.com/CU-DBMI/wall-of-software/pulls) with the following changes: + +- Add an _entry_ to **the top** of `list.json`. + - A short ~3-20 char name. + - A short ~20-60 char description. + - A link to a website or repo. +- Add a _web-friendly_ image to `/images`. + - Filename **must** be lowercase, kebab-case version of entry name, e.g. _Word Lapse_ → `word-lapse`. + - **Must** have extension `.jpg`. + - Roughly square. + - Enough margins for main content to fit nicely inside hexagon. + - Larger than ~400px × 400px , smaller than ~1000px × 1000px + - Better than ~80% jpeg compression level, smaller than ~50 kB. + +Shortly after opening a PR, a link should appear to a preview of the site with your changes. + +Entries are shuffled randomly on each page visit to not give preferential treatment! + +### Printed + +If you'd also like your image to be printed out and posted on the wall of the DBMI office (6th floor Anschutz Health Sciences Building), **also** include these changes: + +- Add a _printable_ image to `/images`. + - Filename should be lowercase, kebab-case version of entry name. + - Should have extension `.png`. + - Enough margins for main content to fit nicely inside hexagon. + - Conforms to the [4" × 4" hexagon template here](https://www.stickermule.com/uses/hexagon-stickers). + +### Help + +If you need help submitting software, you may [contact the Software Engineering Team](https://cu-dbmi.github.io/set-website/about/) for help. diff --git a/images/manubot.jpg b/images/manubot.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b15e770d3f9f17b8a39cfab2d5dac3c084332927 GIT binary patch literal 33086 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 + + + + + + + + + + Wall of Software | CU DBMI + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +

+ +

+ +

+ + + + Department of Biomedical Informatics +

+
+ +
+
+ + + + + + + + +

+ (and counting) + tools, packages, workflows, resources, and other software + developed at or in collaboration with the + Department of Biomedical Informatics (DBMI) at the University of + Colorado. +

+ + + +
+ +
+
+
+ + + + + + diff --git a/list.json b/list.json new file mode 100644 index 0000000..6505f5f --- /dev/null +++ b/list.json @@ -0,0 +1,102 @@ +[ + { + "name": "Manubot", + "description": "Manuscripts, open and automated", + "link": "https://manubot.org" + }, + { + "name": "Dolor Sit Amit", + "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + "link": "" + }, + { + "name": "Lorem Ipsum", + "description": "Lorem ipsum dolor sit amet", + "link": "" + }, + { + "name": "Lorem Ipsum", + "description": "Lorem ipsum dolor sit amet", + "link": "" + }, + { + "name": "Lorem Ipsum", + "description": "Lorem ipsum dolor sit amet", + "link": "" + }, + { + "name": "Lorem Ipsum", + "description": "Lorem ipsum dolor sit amet", + "link": "" + }, + { + "name": "Lorem Ipsum", + "description": "Lorem ipsum dolor sit amet", + "link": "" + }, + { + "name": "Lorem Ipsum", + "description": "Lorem ipsum dolor sit amet", + "link": "" + }, + { + "name": "Lorem Ipsum", + "description": "Lorem ipsum dolor sit amet", + "link": "" + }, + { + "name": "Lorem Ipsum", + "description": "Lorem ipsum dolor sit amet", + "link": "" + }, + { + "name": "Lorem Ipsum", + "description": "Lorem ipsum dolor sit amet", + "link": "" + }, + { + "name": "Lorem Ipsum", + "description": "Lorem ipsum dolor sit amet", + "link": "" + }, + { + "name": "Lorem Ipsum", + "description": "Lorem ipsum dolor sit amet", + "link": "" + }, + { + "name": "Lorem Ipsum", + "description": "Lorem ipsum dolor sit amet", + "link": "" + }, + { + "name": "Lorem Ipsum", + "description": "Lorem ipsum dolor sit amet", + "link": "" + }, + { + "name": "Lorem Ipsum", + "description": "Lorem ipsum dolor sit amet", + "link": "" + }, + { + "name": "Lorem Ipsum", + "description": "Lorem ipsum dolor sit amet", + "link": "" + }, + { + "name": "Lorem Ipsum", + "description": "Lorem ipsum dolor sit amet", + "link": "" + }, + { + "name": "Lorem Ipsum", + "description": "Lorem ipsum dolor sit amet", + "link": "" + }, + { + "name": "Lorem Ipsum", + "description": "Lorem ipsum dolor sit amet", + "link": "" + } +] diff --git a/src/alpine.js b/src/alpine.js new file mode 100644 index 0000000..c1d116d --- /dev/null +++ b/src/alpine.js @@ -0,0 +1,43 @@ +// reactive render html + +document.addEventListener("alpine:init", async () => { + // init store + Alpine.store("store", { + title: [], + list: [], + cols: 1, + set(key, value) { + this[key] = value; + }, + }); + + // page title, split by word and char + let index = 0; + const title = "Wall of Software" + .split(" ") + .map((word) => word.split("").map((char) => ({ char, index: index++ }))); + + // set title + Alpine.store("store").set("title", title); + + // load list + const list = await (await fetch("list.json")).json(); + // shuffle + for (let i = list.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [list[i], list[j]] = [list[j], list[i]]; + } + + // set list + Alpine.store("store").set("list", list); + + // set number of cols on window resize + function updateCols() { + Alpine.store("store").set( + "cols", + Math.min(10, Math.floor(window.innerWidth / 200)) + ); + } + updateCols(); + window.addEventListener("resize", updateCols); +}); diff --git a/src/grid.css b/src/grid.css new file mode 100644 index 0000000..87a9de4 --- /dev/null +++ b/src/grid.css @@ -0,0 +1,179 @@ +.grid { + --ratio: 1.1547; + --gap: 5px; + display: grid; + grid-template-columns: repeat(var(--cols), 1fr); + place-content: center; + width: 100%; + padding: 40px 60px; + gap: calc(var(--gap) / var(--ratio)) var(--gap); +} + +.cell { + display: flex; + position: relative; + align-items: center; + justify-content: center; + aspect-ratio: var(--ratio) / 1; + transform: translateX( + calc(var(--offset) * 25% + (var(--offset) * var(--gap)) / 4) + ); +} + +.cell:has(.hex:hover, .hex:focus) { + z-index: 1; +} + +.hex { + display: flex; + position: absolute; + flex-direction: column; + align-items: center; + justify-content: center; + aspect-ratio: 1 / var(--ratio); + width: 100%; + gap: 5px; + color: var(--white); + text-align: center; + overflow-wrap: anywhere; + animation: hex 1s var(--delay) both; + transition: opacity var(--fast); +} + +.grid:has(.hex:hover, .hex:focus) .hex { + opacity: 0.75; +} + +.hex:hover, +.hex:focus { + opacity: 1 !important; +} + +.hex::after, +.hex::before { + position: absolute; + bottom: calc(100% + 5px); + background: var(--dark); + color: var(--white); + opacity: 0; + pointer-events: none; + transition: opacity var(--fast), transform var(--fast); +} + +.hex::after { + width: 10px; + height: 10px; + transform: translateY(50%) translateY(-0.1px) translateY(0) rotate(45deg); + content: ""; + clip-path: polygon(200% 200%, 200% -100%, -100% 200%); +} + +.hex:hover::after, +.hex:focus::after { + transform: translateY(50%) translateY(-0.1px) translateY(-5px) rotate(45deg); + opacity: 1; +} + +.hex::before { + width: 175%; + max-width: max-content; + padding: 10px 15px; + border-radius: 5px; + content: attr(data-tooltip); +} + +.hex:hover::before, +.hex:focus::before { + transform: translateY(-5px); + opacity: 1; +} + +@keyframes hex { + from { + opacity: 0; + } +} + +.hex > * { + position: absolute; + width: 100%; + height: 100%; + inset: 0; +} + +.shape { + overflow: visible; + pointer-events: none; +} + +.stroke { + fill: none; + stroke: var(--dark); + stroke-width: 0.03; + stroke-dasharray: 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01 + 0.01 0.01 0.01 0.01 0.01 1.5; + stroke-dashoffset: 1.84; + transform: scale(1.1); +} + +.hex:hover .stroke, +.hex:focus .stroke { + animation: stroke 1.25s linear; +} + +@keyframes stroke { + to { + stroke-dashoffset: 0.17; + } +} + +.fill { + fill: var(--mid); +} + +.image { + clip-path: polygon(0% 25%, 0% 75%, 50% 100%, 100% 75%, 100% 25%, 50% 0%); + pointer-events: none; +} + +.img { + position: absolute; + width: 100%; + height: 100%; + inset: 0; + object-fit: cover; + transition: opacity var(--fast), transform var(--fast), filter var(--fast); +} + +svg.img { + fill: currentColor; + text-anchor: middle; + dominant-baseline: middle; +} + +.hex:hover .img, +.hex:focus .img { + transform: scale(1.1); + opacity: 0; +} + +.text { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 20px 10px; + overflow: hidden; + gap: 5px; + transform: scale(0.9); + font-weight: 600; + font-size: 1.1rem; + opacity: 0; + transition: opacity var(--fast), transform var(--fast); +} + +.hex:hover .text, +.hex:focus .text { + transform: scale(1); + opacity: 1; +} diff --git a/src/header.css b/src/header.css new file mode 100644 index 0000000..cbaa624 --- /dev/null +++ b/src/header.css @@ -0,0 +1,89 @@ +h1 { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0 20px; + color: var(--dark); + font-size: 2.5rem; + font-family: "IBM Plex Mono", monospace; +} + +@media (max-width: 400px) { + h1 { + font-size: 2rem; + } +} + +.word { + display: inline-flex; + flex-wrap: wrap; + justify-content: center; +} + +.char { + text-transform: uppercase; + animation: title1 0.5s var(--delay) both, title2 5s var(--delay) infinite; +} + +@keyframes title1 { + from { + display: none; + } +} + +@keyframes title2 { + 0%, + 10% { + color: var(--dark); + } + 5% { + color: var(--mid); + } +} + +h2 { + font-size: 1.2rem; + animation: subtitle 2s 1s both; +} + +h2 .icon { + position: relative; + top: 0.15em; + margin-right: 0.5em; + color: var(--dark); +} + +@keyframes subtitle { + from { + color: var(--light); + opacity: 0; + } +} + +.cursor { + animation: cursor 1s 2s infinite steps(1); +} + +@keyframes cursor { + 0%, + 100% { + opacity: 1; + } + 50% { + opacity: 0; + } +} + +canvas { + position: absolute; + width: 100% !important; + height: 100% !important; + inset: 0; + animation: canvas 3s 1s both; +} + +@keyframes canvas { + from { + opacity: 0; + } +} diff --git a/src/share.jpg b/src/share.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ba19a87cc8e16fc9bd1b9e65c8eeea27e1440e18 GIT binary patch literal 31847 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 section:last-child { + flex-grow: 1; +} + +section { + display: flex; + flex-direction: column; + align-items: center; + padding: 60px max(40px, (100% - 1200px) / 2); + gap: 60px; + background: var(--white); +} + +b, +strong { + font-weight: 600; +} + +i { + font-style: italic; +} + +a { + cursor: pointer; + transition: color var(--fast); +} + +p { + max-width: 820px; + line-height: 2; + text-align: justify; +} + +p a { + text-decoration-line: underline; +} + +p a:is(:hover, :focus) { + color: var(--mid); +} + +header { + display: flex; + position: relative; + flex-direction: column-reverse; + align-items: center; + justify-content: center; + padding: 150px 20px; + gap: 10px; + background: var(--light); + font-weight: 300; + text-align: center; +} + +header > * { + z-index: 1; +} + +footer { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; + padding: 40px; + gap: 30px; + background: var(--dark); + color: var(--white); + text-align: center; +} + +footer a:is(:hover, :focus) { + color: var(--mid); +} diff --git a/src/util.css b/src/util.css new file mode 100644 index 0000000..53c0e73 --- /dev/null +++ b/src/util.css @@ -0,0 +1,75 @@ +.buttons { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 30px; +} + +.button { + display: flex; + position: relative; + align-items: center; + padding: 10px 15px; + gap: 10px; + border-radius: 5px; + background: var(--dark); + color: var(--white); + transition: color var(--fast), background var(--fast); +} + +.button:is(:hover, :focus-visible) { + background: var(--light); + color: var(--dark); +} + +.button::after { + z-index: 1; + position: absolute; + inset: -7.5px; + border: dashed 2px transparent; + border-radius: 10px; + content: ""; + pointer-events: none; +} + +.button:is(:hover, :focus-visible)::after { + animation: button 1s ease-in-out; +} + +@keyframes button { + 0%, + 100% { + clip-path: inset(0 0 97% 0); + } + 25% { + clip-path: inset(0 97% 0 0); + } + 50% { + clip-path: inset(97% 0 0 0); + } + 75% { + clip-path: inset(0 0 0 97%); + } + 0%, + 100% { + border-color: transparent; + } + 10% { + border-color: var(--dark); + } + 50% { + border-color: var(--mid); + } + 90% { + border-color: var(--light); + } +} + +.icon { + flex-shrink: 0; + height: 1em; +} + +.icon path { + fill: currentColor; +} diff --git a/src/viz.js b/src/viz.js new file mode 100644 index 0000000..9de70c7 --- /dev/null +++ b/src/viz.js @@ -0,0 +1,297 @@ +// header background visualization + +import { + BufferGeometry, + CanvasTexture, + Clock, + Color, + Float32BufferAttribute, + PerspectiveCamera, + Points, + PointsMaterial, + Scene, + Vector3, + WebGLRenderer, +} from "three"; +import { LineMaterial } from "three/addons/lines/LineMaterial.js"; +import { LineGeometry } from "three/addons/lines/LineGeometry.js"; +import { LineSegmentsGeometry } from "three/addons/lines/LineSegmentsGeometry.js"; +import { Line2 } from "three/addons/lines/Line2.js"; +import { LineSegments2 } from "three/addons/lines/LineSegments2.js"; + +// helix props +const strandSize = 0.3; +const pairSize = 0.1; +const helixMinZ = -10; +const helixMaxZ = 10; +const helixRadius = 1.75; +const helixStep = 0.1; +const helixTwist = 1 / 15; +const helixTilt = 16; + +// particle props +const particleSize = 0.75; +const particleChars = ["0", "1"]; +const particleCount = 150; +const particleAcceleration = (position) => new Vector3(position.x * 0.5, 1, 0); +const particleDecay = 1; + +// 360 degrees +const tau = 2 * Math.PI; + +// rand between range +const rand = (min = 0, max = 1) => min + Math.random() * (max - min); + +// round to multiple +const round = (value, multiple) => multiple * Math.floor(value / multiple); + +// triangle +const ramp = (value, mid = 0.25) => { + if (value < 0) return 0; + if (value < mid) return value / mid; + if (value < 1) return 1 - (value - mid) / (1 - mid); + return 0; +}; + +// three js main objects +const canvas = document.querySelector("canvas"); +const renderer = new WebGLRenderer({ canvas: canvas, alpha: true }); +const scene = new Scene(); +const camera = new PerspectiveCamera(45, 1, 0.01, 100); +const clock = new Clock(); + +// resize/reset scene/camera +const reset = () => { + camera.aspect = canvas.clientWidth / canvas.clientHeight; + camera.position.set(0, 0, -15); + camera.lookAt(0, 0, 0); + camera.translateX(-5); + camera.updateProjectionMatrix(); + renderer.setSize(canvas.clientWidth, canvas.clientHeight); + renderer.setPixelRatio(window.devicePixelRatio); +}; +reset(); +window.addEventListener("resize", reset); + +// get color palette +const style = window.getComputedStyle(document.documentElement); +const mid = new Color(style.getPropertyValue("--mid")); +const light = new Color(style.getPropertyValue("--light")); + +// generate helix +const leftPoints = []; +const rightPoints = []; +const pairPoints = []; +const colors = []; +const pairColors = []; +for (let z = helixMinZ; z <= helixMaxZ; z += helixStep) { + // percent through length + const percent = (z - helixMinZ) / (helixMaxZ - helixMinZ); + + // position + const x = Math.sin(-helixTwist * tau * z) * helixRadius; + const y = Math.cos(-helixTwist * tau * z) * helixRadius; + leftPoints.push(x, y, z); + rightPoints.push(-x, -y, z); + + // color + const color = mid.clone().lerpHSL(light, percent); + colors.push(color.r, color.g, color.b); + + // connect strands with a pair at z integer intervals + if (Math.abs(z - Math.round(z)) < 0.001) { + pairPoints.push(x, y, z, -x, -y, z); + const { r, g, b } = color; + pairColors.push(r, g, b, r, g, b); + } +} + +// materials +const strandMaterial = new LineMaterial({ + color: 0xffffff, + linewidth: strandSize, + worldUnits: true, + vertexColors: true, +}); +const pairMaterial = new LineMaterial({ + color: 0xffffff, + linewidth: pairSize, + worldUnits: true, + vertexColors: true, +}); + +// left strand object +const leftStrandGeometry = new LineGeometry(); +leftStrandGeometry.setPositions(leftPoints); +leftStrandGeometry.setColors(colors); +const leftStrand = new Line2(leftStrandGeometry, strandMaterial); +leftStrand.name = "helix"; +leftStrand.rotation.x = tau / 4; +scene.add(leftStrand); + +// right strand object +const rightStrandGeometry = new LineGeometry(); +rightStrandGeometry.setPositions(rightPoints); +rightStrandGeometry.setColors(colors); +const rightStrand = new Line2(rightStrandGeometry, strandMaterial); +rightStrand.name = "helix"; +rightStrand.rotation.x = tau / 4; +scene.add(rightStrand); + +// get random point on strands +const getStrandPoint = () => { + const left = Math.random() > 0.5; + const strand = left ? leftStrand : rightStrand; + const points = left ? leftPoints : rightPoints; + const pointIndex = round(Math.random() * points.length, 3); + return strand.localToWorld( + new Vector3(...points.slice(pointIndex, pointIndex + 3)) + ); +}; + +// pairs object +const pairGeometry = new LineSegmentsGeometry(); +pairGeometry.setPositions(pairPoints); +pairGeometry.setColors(pairColors); +const pairs = new LineSegments2(pairGeometry, pairMaterial); +pairs.name = "helix"; +pairs.rotation.x = tau / 4; +scene.add(pairs); + +// particles +for (const char of particleChars) { + // draw char on canvas + const canvas = document.createElement("canvas"); + canvas.width = 100; + canvas.height = 100; + const ctx = canvas.getContext("2d"); + ctx.fillStyle = "black"; + ctx.fillRect(0, 0, 100, 100); + ctx.fillStyle = "white"; + ctx.font = "100px IBM Plex Sans"; + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + ctx.fillText(char, 50, 50); + + // use canvas as texture + const material = new PointsMaterial({ + size: particleSize, + alphaMap: new CanvasTexture(canvas), + alphaTest: 0.5, + transparent: true, + vertexColors: true, + }); + + // init buffer of points and colors + const points = Array(particleCount * 3).fill(0); + const colors = Array(particleCount * 3).fill(0); + const geometry = new BufferGeometry(); + geometry.setAttribute("position", new Float32BufferAttribute(points, 3)); + geometry.setAttribute("color", new Float32BufferAttribute(colors, 3)); + + // particle object + const particle = new Points(geometry, material); + particle.name = "particle"; + particle.userData = Array(particleCount) + .fill() + .map(() => ({ + life: rand(), + acceleration: new Vector3(), + velocity: new Vector3(), + })); + scene.add(particle); +} + +// render frame +const frame = () => { + // time since last tick + const delta = Math.min(0.1, clock.getDelta()); + + // for each element in scene + for (const child of scene.children) { + if (child.name === "helix") { + // spin + child.rotation.z += delta; + } + + if (child.name === "particle") { + // get buffers + const positionAttr = child.geometry.getAttribute("position"); + const colorAttr = child.geometry.getAttribute("color"); + + // loop through other "buffers" + for (let [index, particle] of Object.entries(child.userData)) { + // cast to number + index = +index; + + // get position + let position = new Vector3( + positionAttr.getX(index), + positionAttr.getY(index), + positionAttr.getZ(index) + ); + + // init position + if (!position.length()) position = getStrandPoint(); + + // increment life + particle.life += delta / particleDecay; + + // accelerate + particle.acceleration = particleAcceleration( + position.clone().normalize() + ); + + // increment velocity + particle.velocity.add(particle.acceleration); + + // increment position + position.add(particle.velocity.multiplyScalar(delta)); + + // interpolate color + const [r, g, b] = light + .clone() + .lerpHSL(mid, ramp(particle.life) / 1.25); + colorAttr.setXYZ(index, r, g, b); + + // respawn + if (particle.life > 1) { + position = getStrandPoint(); + particle.life = 0; + } + + // set position + const { x, y, z } = position; + positionAttr.setXYZ(index, x, y, z); + } + + // signal buffers to update + positionAttr.needsUpdate = true; + colorAttr.needsUpdate = true; + } + } + + // update scene + renderer.render(scene, camera); + + // run frame again + window.setTimeout(() => window.requestAnimationFrame(frame), 10); +}; + +frame(); + +// on mouse move +const mouse = (event) => { + // get x/y from -1 to 1 + const x = event ? -1 + 2 * (event.clientX / window.innerWidth) : 0; + const y = event ? -1 + 2 * (event.clientY / window.innerHeight) : 0; + + // tilt helix + for (const child of scene.children) + if (child.name === "helix") { + child.rotation.y = helixTilt + (tau / 4) * (x / 4); + child.rotation.x = tau / 4 + (tau / 4) * (y / 4); + } +}; +mouse(); +window.addEventListener("mousemove", mouse);