Initial commit
This commit is contained in:
commit
7dbdab4ee8
3 files changed
+555
No files matched your search
+81
@@ -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>
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -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 */
|
||||||
|
}
|
||||||
Reference in new issue
Block a user