diff --git a/TODO.md b/TODO.md
index 6af7f1b..5c33350 100644
--- a/TODO.md
+++ b/TODO.md
@@ -20,6 +20,9 @@
- Keep placeholder and demo stuff
- Move other things
- Prevent styles to be global
+- Adapt bootstrap utility API
+ - https://github.com/twbs/bootstrap/blob/main/scss/utilities/_api.scss
+ - https://github.com/twbs/bootstrap/blob/main/scss/_utilities.scss
# JS
diff --git a/source/code/game.js b/source/code/game.js
new file mode 100644
index 0000000..b961fdb
--- /dev/null
+++ b/source/code/game.js
@@ -0,0 +1,313 @@
+class HippieCrosshair {
+ constructor(canvas, options = {}) {
+ this.canvas = canvas;
+ this.ctx = canvas.getContext('2d');
+
+ this.debug = options.debug || false;
+
+ const defaults = {
+ crosshair: {
+ size: 16,
+ thickness: 2,
+ color: '#000',
+ gapSize: 8,
+ style: 'cross'
+ },
+ connector: {
+ distance: 128, // Distance to draw next symbol
+ spacing: 64, // Space between symbols
+ size: 8,
+ color: '#000',
+ style: 'arrow',
+ visibility: true
+ },
+ line: {
+ color: 'rgba(0, 0, 0, 0.1)',
+ width: 1
+ }
+ };
+ const merged = this.mergeOptions(defaults, options);
+ const { crosshair, connector, line } = merged;
+
+ // Crosshair options
+ this.size = crosshair.size;
+ this.thickness = crosshair.thickness;
+ this.color = crosshair.color;
+ this.gapSize = crosshair.gapSize;
+ this.style = crosshair.style;
+
+ // Connector options
+ this.distance = connector.distance;
+ this.spacing = connector.spacing;
+ this.connectorSize = connector.size;
+ this.connectorColor = connector.color;
+ this.connectorStyle = connector.style;
+ this.connectorShow = connector.visibility;
+
+ // Line options
+ this.lineColor = line.color || '#fff';
+ this.lineWidth = line.width || 1;
+
+ this.mouseX = canvas.width / 2;
+ this.mouseY = canvas.height / 2;
+
+ // Animation control
+ this.isAnimating = true;
+ this.animationFrameId = null;
+
+ this.setupEventListeners();
+ this.animate();
+ }
+
+ mergeOptions(defaults, options) {
+ const merged = JSON.parse(JSON.stringify(defaults));
+
+ if (options.crosshair) {
+ Object.assign(merged.crosshair, options.crosshair);
+ }
+ if (options.connector) {
+ Object.assign(merged.connector, options.connector);
+ }
+ if (options.line) {
+ Object.assign(merged.line, options.line);
+ }
+
+ return merged;
+ }
+
+ setupEventListeners() {
+ document.addEventListener('mousemove', (event) => {
+ this.mouseX = event.clientX;
+ this.mouseY = event.clientY;
+ });
+ }
+
+ animate() {
+ if (!this.isAnimating) {
+ this.animationFrameId = requestAnimationFrame(() => this.animate());
+ return;
+ }
+
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
+
+ if (this.connectorShow) {
+ if (this.debug) this.drawLine();
+ this.drawConnector();
+ }
+
+ // TODO: Autom. Zug zum Zentrum hin ermöglichen
+ this.drawCrosshair();
+
+ this.animationFrameId = requestAnimationFrame(() => this.animate());
+ }
+
+ drawLine() {
+ const centerX = this.canvas.width / 2;
+ const centerY = this.canvas.height / 2;
+
+ this.ctx.strokeStyle = this.lineColor;
+ this.ctx.lineWidth = this.lineWidth;
+ this.ctx.beginPath();
+ this.ctx.moveTo(centerX, centerY);
+ this.ctx.lineTo(this.mouseX, this.mouseY);
+ this.ctx.stroke();
+ }
+
+ // TODO: Ausblenden nach Distanz
+ drawConnector() {
+ const centerX = this.canvas.width / 2;
+ const centerY = this.canvas.height / 2;
+
+ const dx = this.mouseX - centerX;
+ const dy = this.mouseY - centerY;
+ const delta = Math.sqrt(dx * dx + dy * dy);
+
+ // Only draw connectors if cursor is far enough from center
+ if (delta < this.distance) {
+ return;
+ }
+
+ const angle = Math.atan2(dy, dx);
+ const count = Math.floor((delta - this.distance) / this.spacing);
+
+ for (let i = 0; i < count; i++) {
+ const distance = this.distance + (i * this.spacing);
+ const x = centerX + Math.cos(angle) * distance;
+ const y = centerY + Math.sin(angle) * distance;
+
+ this.drawSymbol(x, y, angle);
+ }
+ }
+
+ drawSymbol(x, y, angle = 0) {
+ this.ctx.fillStyle = this.connectorColor;
+ this.ctx.strokeStyle = this.connectorColor;
+ this.ctx.lineWidth = 1;
+
+ switch (this.connectorStyle) {
+ case 'circle':
+ this.ctx.beginPath();
+ this.ctx.arc(x, y, this.connectorSize / 2, 0, Math.PI * 2);
+ this.ctx.fill();
+ break;
+ case 'diamond':
+ const size = this.connectorSize - (this.connectorSize / 4);
+ this.ctx.beginPath();
+ this.ctx.moveTo(x, y - size);
+ this.ctx.lineTo(x + size, y);
+ this.ctx.lineTo(x, y + size);
+ this.ctx.lineTo(x - size, y);
+ this.ctx.closePath();
+ this.ctx.fill();
+ break;
+ case 'square':
+ this.ctx.fillRect(
+ x - this.connectorSize / 2,
+ y - this.connectorSize / 2,
+ this.connectorSize,
+ this.connectorSize
+ );
+ break;
+ case 'arrow':
+ this.arrow(x, y, angle);
+ break;
+ }
+ }
+
+ arrow(x, y, angle) {
+ const size = this.connectorSize - (this.connectorSize / 4);
+
+ this.ctx.save();
+ this.ctx.translate(x, y);
+ this.ctx.rotate(angle);
+
+ // Arrow pointing right
+ this.ctx.beginPath();
+ this.ctx.moveTo(size, 0); // Tip
+ this.ctx.lineTo(-size, -size); // Back left
+ // this.ctx.lineTo(-size * 0.4, 0); // Middle
+ this.ctx.lineTo(-size, size); // Back right
+ this.ctx.closePath();
+ this.ctx.fill();
+
+ this.ctx.restore();
+ }
+
+ drawCrosshair() {
+ this.ctx.strokeStyle = this.color;
+ this.ctx.lineWidth = this.thickness;
+ this.ctx.lineCap = 'round';
+
+ switch (this.style) {
+ case 'cross':
+ this.cross();
+ break;
+ case 'circle':
+ this.circle();
+ break;
+ case 'dot':
+ this.dot();
+ break;
+ case 'level':
+ this.level();
+ break;
+ }
+ }
+
+ cross() {
+ // Horizontal line
+ this.ctx.beginPath();
+ this.ctx.moveTo(this.mouseX - this.size, this.mouseY);
+ this.ctx.lineTo(this.mouseX - this.gapSize, this.mouseY);
+ this.ctx.stroke();
+
+ this.ctx.beginPath();
+ this.ctx.moveTo(this.mouseX + this.gapSize, this.mouseY);
+ this.ctx.lineTo(this.mouseX + this.size, this.mouseY);
+ this.ctx.stroke();
+
+ // Vertical line
+ this.ctx.beginPath();
+ this.ctx.moveTo(this.mouseX, this.mouseY - this.size);
+ this.ctx.lineTo(this.mouseX, this.mouseY - this.gapSize);
+ this.ctx.stroke();
+
+ this.ctx.beginPath();
+ this.ctx.moveTo(this.mouseX, this.mouseY + this.gapSize);
+ this.ctx.lineTo(this.mouseX, this.mouseY + this.size);
+ this.ctx.stroke();
+ }
+
+ circle() {
+ // Outer circle
+ this.ctx.beginPath();
+ this.ctx.arc(this.mouseX, this.mouseY, this.size, 0, Math.PI * 2);
+ this.ctx.stroke();
+
+ // Inner dot
+ this.ctx.fillStyle = this.color;
+ this.ctx.beginPath();
+ this.ctx.arc(this.mouseX, this.mouseY, this.thickness, 0, Math.PI * 2);
+ this.ctx.fill();
+ }
+
+ dot() {
+ this.ctx.fillStyle = this.color;
+ this.ctx.beginPath();
+ this.ctx.arc(this.mouseX, this.mouseY, this.size / 4, 0, Math.PI * 2);
+ this.ctx.fill();
+ }
+
+ level() {
+ this.ctx.beginPath();
+ this.ctx.moveTo(this.mouseX - this.size * 2, this.mouseY);
+ this.ctx.lineTo(this.mouseX - this.gapSize * 2, this.mouseY);
+ this.ctx.stroke();
+
+ this.ctx.beginPath();
+ this.ctx.arc(this.mouseX, this.mouseY, this.size, 0, Math.PI);
+ this.ctx.stroke();
+
+ this.ctx.beginPath();
+ this.ctx.moveTo(this.mouseX + this.gapSize * 2, this.mouseY);
+ this.ctx.lineTo(this.mouseX + this.size * 2, this.mouseY);
+ this.ctx.stroke();
+
+ this.ctx.fillStyle = this.color;
+ this.ctx.beginPath();
+ this.ctx.arc(this.mouseX, this.mouseY, this.thickness, 0, Math.PI * 2);
+ this.ctx.fill();
+ }
+
+ setCrosshairStyle(style) {
+ this.style = style;
+ }
+
+ setCrosshairColor(color) {
+ this.color = color;
+ }
+
+ setConnectorStyle(style) {
+ this.connectorStyle = style;
+ }
+
+ setConnectorColor(color) {
+ this.connectorColor = color;
+ }
+
+ setConnectorVisibility(visible) {
+ this.connectorShow = visible;
+ }
+
+ startAnimation() {
+ this.isAnimating = true;
+ }
+
+ stopAnimation() {
+ this.isAnimating = false;
+ }
+
+ toggleAnimation() {
+ this.isAnimating = !this.isAnimating;
+ }
+}
diff --git a/source/code/hippie/app.js b/source/code/hippie/app.js
index 7cda8b6..eb6eda9 100644
--- a/source/code/hippie/app.js
+++ b/source/code/hippie/app.js
@@ -540,6 +540,15 @@ function mapRange(value, inMin, inMax, outMin, outMax, reverse = false, clamp =
return mapped;
}
+function zeroFill(number, width) {
+ width -= number.toString().length;
+
+ if (width > 0) {
+ return new Array(width + (/\./.test(number) ? 2 : 1)).join('0') + number;
+ }
+ return number + ''; // always return a string
+}
+
// Source - https://stackoverflow.com/a/47480429
// Posted by Etienne Martin, modified by community. See post 'Timeline' for change history
// Retrieved 2026-03-08, License - CC BY-SA 4.0
@@ -603,91 +612,6 @@ Clock.prototype.formatDigits = function (val) {
return val;
};
-function ongoing() {
-
- var now = new Date();
-
- var w = Math.floor(now.getDay());
- var D = ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'];
- var DNumb = Math.floor(now.getDate());
- var MNumb = Math.floor(now.getMonth());
- var M = ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'MaiOktober', 'November', 'Dezember'];
- var y = Math.floor(now.getYear());
- if (y < 999) y += 1900;
-
- var ms = Math.floor(now.getMilliseconds());
- var s = Math.floor(now.getSeconds());
- var m = Math.floor(now.getMinutes() + s / 60);
- var h = Math.floor(now.getHours() + m / 60);
-
- var j2000 = new Date(); // Bezugspunkt ist der 1.1.2000 0:00 UT (entspricht JD 2451544,5)
- j2000.setUTCFullYear(2000, 0, 1);
- j2000.setUTCHours(0, 0, 0, 0);
-
- var utc = new Date();
- utc.setUTCFullYear(y, MNumb, DNumb); // Monate müssen im Wertebereich 0...11 übergeben werden
- utc.setUTCHours(h, m, s, ms);
-
- var utc0 = new Date();
- utc0.setUTCFullYear(y, MNumb, DNumb);
- utc0.setUTCHours(0, 0, 0, 0);
-
- var jd = 2451544.5 + (utc - j2000) / 86400000; // Zählung erfolgt in Millisekunden, 1 Tag = 86.400.000 ms
- var jdUTC0 = 2451544.5 + (utc0 - j2000) / 86400000;
-
- var N = jd - 2451545.0;
- var L = 280.460 + 0.9856474 * N; // mittlere ekliptikale Länge der Sonne
- var g = 357.528 + 0.9856003 * N; // mittlere Anomalie
- var el = L + 1.915 * Math.sin(g) + 0.020 * Math.sin(2 * g);
- var e = 23.439 - 0.0000004 * N;
- var rektaszension = Math.atan((Math.cos(e) * Math.sin(el)) / Math.cos(el));
-
- var T = (jdUTC0 - 2451545.0) / 36525;
- var stGMT = (((6 * 3600) + (41 * 60) + 50.54841) + (8640184.812866 * T) + (0.093104 * Math.pow(T, 2)) - (0.0000062 * Math.pow(T, 3))) / 3600;
-
- var stGMT2 = 6.697376 + 2400.05134 * T + 1.002738 * T;
- var hWGMT = stGMT2 * 15;
- var hW = hWGMT + 11.9566185772;
-
- var st = (stGMT + (now.getUTCHours() * 1.00273790935)) + (11.9566185772 / 15); // Sommerzeit muss noch berücksichtigt werden
- var st24 = Math.abs(st - (Math.round(st / 24) * 24));
- var stH = Math.floor(st24);
- var stM = Math.floor((st24 % 1) * 60);
- var stS = zeroFill(Math.floor((((st24 % 1) * 60) % 1) * 60), 2);
-
- var travelWidth = document.body.clientWidth;
- var travelHeight = document.body.clientHeight;
- var sunPosX = 0;
- var sunPosY = 0;
- var moonPosX = 0;
- var moonPosY = 0;
-
- var sun = $('#sun').css({
- 'left': (s / 60) * travelWidth,
- 'top': (m / 60) * travelHeight
- });
-
- $('#day').text(D[w]);
- $('#dayNumb').text(DNumb);
- $('#month').text(M[MNumb]);
- $('#year').text(y);
- $('#time').text('' + zeroFill(h, 2) + ':' + zeroFill(m, 2) + ':' + zeroFill(s, 2));
-
- $('#julian').text(jd.toFixed(6));
- //$('#star').text(stH + ':' + stM + ':' + stS);
- $('#star').text(stH + ':' + stM);
- $('#star1').text(stGMT);
- $('#star2').text(stGMT2);
-}
-
-function zeroFill(number, width) {
- width -= number.toString().length;
- if (width > 0) {
- return new Array(width + (/\./.test(number) ? 2 : 1)).join('0') + number;
- }
- return number + ''; // always return a string
-}
-
//Länge der Balken im Diagram berechnen
function barwidth(size, G, W) {
var s = size;
diff --git a/source/data/start.json b/source/data/start.json
index cc0da22..65bfb14 100644
--- a/source/data/start.json
+++ b/source/data/start.json
@@ -8,7 +8,7 @@
"href": "/demo/basics.html"
},
{
- "text": "Drag",
- "href": "/demo/examples/ui/drag.html"
+ "text": "Portal",
+ "href": "/demo/examples/portal.html"
}
]
\ No newline at end of file
diff --git a/source/screens/demo/basics.liquid b/source/screens/demo/basics.liquid
index d3c7a08..f1c8560 100644
--- a/source/screens/demo/basics.liquid
+++ b/source/screens/demo/basics.liquid
@@ -801,7 +801,7 @@ order: 2
<fieldset>
realisiert.