Initial commit

This commit is contained in:
dev committed 2025-11-15 18:45:58 +01:00
commit 7dbdab4ee8
3 files changed
+555

No files matched your search

+81
View File
@@ -0,0 +1,81 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Stitches and Rows</title>
<link rel="icon" type="image/x-icon" href="/images/favicon.ico">
<link rel="stylesheet" href="style.css">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
<script src="script.js" defer></script>
</head>
<body>
<h1>
Stiches and Rows!
</h1>
<div id="settings-card">
<h2>Settings</h2>
<div class="settings-group">
<label>
Stitches per Row
<input type="number" id="input-stitches">
</label>
<label class="toggle">
<span>Auto-increase rows</span>
<input type="checkbox" id="input-autoincrease">
<span class="slider"></span>
</label>
</div>
</div>
<textarea id="notes" placeholder="Notes..."></textarea>
<div id="pip-button-container">
<button id="button-start-pip" title="Picture in Picture">
<span class="material-icons">picture_in_picture</span>
</button>
</div>
<div id="div-pip-container">
<div id="div-pip-element">
<div class="counter-buttons">
<button id="button-stitch">
<div id="stitches-counter">0</div>
<div>Stitches</div>
</button>
<button id="button-row">
<div id="rows-counter">0</div>
<div>Rows</div>
</button>
</div>
<div class="control-buttons">
<button id="button-undo">Undo</button>
<button id="button-reset">Reset</button>
</div>
</div>
</div>
<div id="about">
<h3>
About this Site
</h3>
<p>
This is <b>Stiches and Rows</b>, made by Honey.
</p>
<p>
You can support this projects by buying me a Starbucks and giving luvs
</p>
<p style="text-align:center;">
<a class="icon-link" href="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQTz06kj_mq-KXVfbAiItN5iWLoPQ_OQPfacqbV0uvlrIb3mOzwnYIDhiiNdnITap0rTKc&usqp=CAU" target="_blank"><i class="fa-solid fa-cat"></i></a> |
<a class="icon-link" href="https://www.starbucks.de/de/" target="_blank"><i class="fa-solid fa-heart"></i></a>
</p>
</div>
</body>
</html>
+167
View File
@@ -0,0 +1,167 @@
const MAX_HISTORY_LENGTH = 32;
const PIP_WINDOW_SIZE = { width: 280, height: 140 };
class Counter {
constructor(elementId, storageKey) {
this.el = document.getElementById(elementId);
this.key = storageKey;
this.count = parseInt(localStorage.getItem(this.key)) || 0;
this.history = [];
this.render();
}
render = () => {
this.el.textContent = this.count;
}
set = (val) => {
this.history.push(this.count);
if (this.history.length > MAX_HISTORY_LENGTH) this.history.shift();
this.count = val;
localStorage.setItem(this.key, this.count);
this.render();
}
increase = () => {
this.set(this.count + 1);
}
undo = () => {
if (this.history.length > 0) {
this.count = this.history.pop();
localStorage.setItem(this.key, this.count);
this.render();
}
}
}
const stitches = new Counter("stitches-counter", "stitches");
const rows = new Counter("rows-counter", "rows");
const inputAuto = document.getElementById("input-autoincrease");
const inputStitchMax = document.getElementById("input-stitches");
inputAuto.checked = localStorage.getItem("input_autoincrease") === "true";
inputStitchMax.value = localStorage.getItem("input_stitches") || "10";
inputAuto.addEventListener("change", () => {
localStorage.setItem("input_autoincrease", inputAuto.checked);
});
inputStitchMax.addEventListener("change", () => {
localStorage.setItem("input_stitches", inputStitchMax.value);
});
document.getElementById("button-stitch").addEventListener("click", () => {
const max = parseInt(inputStitchMax.value);
if (inputAuto.checked && stitches.count + 1 >= max) {
rows.increase();
stitches.set(0);
} else {
stitches.increase();
}
});
document.getElementById("button-row").addEventListener("click", () => {
rows.increase();
stitches.set(0);
});
document.getElementById("button-undo").addEventListener("click", () => {
stitches.undo();
rows.undo();
});
document.getElementById("button-reset").addEventListener("click", () => {
stitches.set(0);
rows.set(0);
});
class PipManager {
constructor(pipElement, pipContainer, pipButton) {
this.el = pipElement;
this.container = pipContainer;
this.button = pipButton;
this.pipWindow = null;
this.button.addEventListener("click", this.start);
}
start = async () => {
if (this.pipWindow && !this.pipWindow.closed) return;
this.pipWindow = await documentPictureInPicture.requestWindow(PIP_WINDOW_SIZE);
for (const styleSheet of document.styleSheets) {
try {
const style = document.createElement("style");
const rules = Array.from(styleSheet.cssRules).map(r => r.cssText).join("");
style.textContent = rules;
this.pipWindow.document.head.appendChild(style);
} catch {
if (styleSheet.href) {
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = styleSheet.href;
this.pipWindow.document.head.appendChild(link);
}
}
}
this.pipWindow.document.body.appendChild(this.el);
// Add keydown listener inside PiP window
this.pipWindow.addEventListener("keydown", (event) => {
// Ignore if focused on input or textarea in PiP window
const active = this.pipWindow.document.activeElement;
if (active && (active.tagName === "INPUT" || active.tagName === "TEXTAREA")) return;
if (event.code === "Space") {
event.preventDefault();
const max = parseInt(inputStitchMax.value);
if (inputAuto.checked && stitches.count + 1 >= max) {
rows.increase();
stitches.set(0);
} else {
stitches.increase();
}
}
});
this.pipWindow.addEventListener("pagehide", () => this.stop());
}
stop = () => {
this.container.appendChild(this.el);
this.pipWindow = null;
}
}
new PipManager(
document.getElementById("div-pip-element"),
document.getElementById("div-pip-container"),
document.getElementById("button-start-pip")
);
const notesField = document.getElementById("notes");
notesField.value = localStorage.getItem("notes") || "";
notesField.addEventListener("input", () => {
localStorage.setItem("notes", notesField.value);
});
window.addEventListener("keydown", (event) => {
// Ignore if focused on input or textarea, so typing space doesn't increment stitches accidentally
const active = document.activeElement;
if (active && (active.tagName === "INPUT" || active.tagName === "TEXTAREA")) return;
if (event.code === "Space") {
event.preventDefault(); // prevent page scroll on space key
const max = parseInt(inputStitchMax.value);
if (inputAuto.checked && stitches.count + 1 >= max) {
rows.increase();
stitches.set(0);
} else {
stitches.increase();
}
}
});
+307
View File
@@ -0,0 +1,307 @@
:root {
--color-primary: rgb(230, 134, 78);
--color-secondary: rgb(236, 233, 61); /* banana yellow */
--card-bg: rgba(255, 245, 230, 0.8);
--text-dark: #333;
--border-soft: rgba(0, 0, 0, 0.05);
--radius: 1.25rem;
--shadow: 0 6px 24px rgba(0, 0, 0, 0.1);
}
body {
margin: 0;
padding: 2rem 1rem;
font-family: 'Roboto', sans-serif;
background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
color: var(--text-dark);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 1.5rem;
min-height: 100vh;
box-sizing: border-box;
}
#settings-card, #div-pip-element {
width: 100%;
max-width: 360px;
background: rgba(255, 255, 255, 0.15);
border-radius: var(--radius);
box-shadow: var(--shadow);
padding: 1.5rem;
border: 1px solid rgba(255, 255, 255, 0.2);
backdrop-filter: blur(12px);
box-sizing: border-box;
}
#notes {
width: 100%;
max-width: 360px;
background: var(--card-bg);
border-radius: var(--radius);
box-shadow: var(--shadow);
padding: 1.5rem;
border: 1px solid var(--border-soft);
backdrop-filter: blur(8px);
box-sizing: border-box;
}
#notes {
min-height: 100px;
font-size: 1rem;
resize: vertical;
outline: none;
border: none;
font-family: inherit;
background: var(--card-bg);
color: var(--text-dark);
}
#notes::placeholder {
color: rgba(51, 51, 51, 0.7);
}
#settings-card h2 {
font-size: 1.2rem;
margin-bottom: 1rem;
text-align: center;
font-weight: 600;
color: var(--text-dark);
}
.settings-group {
display: flex;
flex-direction: column;
gap: 1rem;
}
.settings-group label {
font-size: 0.95rem;
font-weight: 500;
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.settings-group input[type="number"] {
padding: 0.5rem;
border-radius: 0.5rem;
border: 1px solid #aaa;
text-align: center;
font-size: 1rem;
}
.toggle {
display: flex;
justify-content: space-between;
align-items: center;
flex-direction: row !important;
}
.toggle input {
display: none;
}
.toggle .slider {
width: 42px;
height: 24px;
background-color: #ccc;
border-radius: 999px;
position: relative;
cursor: pointer;
transition: background-color 0.2s;
}
.toggle .slider::before {
content: "";
position: absolute;
left: 2px;
top: 2px;
width: 20px;
height: 20px;
background: white;
border-radius: 50%;
transition: transform 0.2s;
}
.toggle input:checked + .slider {
background-color: var(--color-primary);
}
.toggle input:checked + .slider::before {
transform: translateX(18px);
}
#pip-button-container {
position: absolute;
top: 1rem;
right: 1rem;
}
#button-start-pip {
background: none;
border: none;
font-size: 2rem;
cursor: pointer;
}
#div-pip-container {
width: 100%;
max-width: 360px;
}
#div-pip-element {
display: flex;
flex-direction: column;
align-items: center;
gap: 1.5rem;
min-width: 200px; /* smaller min width */
min-height: 100px; /* smaller min height */
}
.counter-buttons {
display: flex;
gap: 1.5rem;
width: 100%;
justify-content: center;
}
.counter-buttons button {
background-color: var(--color-primary);
color: black;
border: none;
padding: 1.5rem;
border-radius: 1rem;
font-size: 1.2rem;
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
box-shadow: var(--shadow);
width: 120px;
height: 80px;
flex-shrink: 0;
}
.control-buttons {
display: flex;
gap: 1.5rem;
width: 100%;
justify-content: center;
}
.control-buttons button {
background-color: var(--color-primary);
color: black;
border: none;
padding: 0.75rem;
border-radius: 0.75rem;
font-size: 1rem;
cursor: pointer;
box-shadow: var(--shadow);
width: 120px;
height: 45px;
flex-shrink: 0;
}
@media (display-mode: picture-in-picture) {
body {
background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
justify-content: center;
padding: 1rem;
margin: 0;
}
#pip-button-container,
#settings-card,
#notes {
display: none;
}
#div-pip-element {
background: rgba(255, 255, 255, 0.2);
border-radius: 1rem;
padding: 1rem;
backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.3);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
width: 100%;
max-width: none;
}
.counter-buttons {
gap: 1rem;
}
.counter-buttons button {
width: 80px;
height: 60px;
font-size: 1rem;
padding: 0.5rem;
}
.counter-buttons button div:first-child {
font-size: 1.4rem;
margin-bottom: 0.2rem;
}
.counter-buttons button div:last-child {
font-size: 1rem;
opacity: 0.8;
}
.control-buttons {
gap: 1rem;
}
.control-buttons button {
width: 80px;
height: 30px;
font-size: 0.85rem;
padding: 0.5rem;
}
}
#div-button-help {
position: fixed;
bottom: 2vh;
right: 2vw;
}
#button-help {
color: var(--color-primary);
font-size: 4vh;
text-shadow: 0px 0px 2px black;
background: transparent;
border: none;
padding: 1vh 1vw;
}
.icon-link {
font-size: 2vh;
color: black;
text-decoration: none;
}
#about {
text-align: center;
max-width: 400px;
margin: 0 auto;
}
/* Subtle hover effect */
button:hover {
filter: brightness(1.03); /* Slightly lighter */
transform: scale(1.01); /* Barely grows */
transition: transform 0.12s ease, filter 0.12s ease;
}
/* Subtle press (active) effect */
button:active {
transform: scale(0.985); /* Slight shrink */
filter: brightness(0.98); /* Slightly darker */
}