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