自家焙煎コーヒー記録アプリ(1バッチ専用)

こんにちはハッピーサトです。

手焙煎(ドラム、手網、鍋焙煎など)用のシンプルな焙煎記録アプリを作りました。

普段は手書きで焙煎記録を残していますが、焙煎中は手がなかなか離せないですし、スマホ画面で簡単に綺麗に記録を残せたらとの気持ちで開発しました。

良かったら使って下さい☆彡

>8バッチ記録可能なVersionはコチラ<

使い方

イメージ画像

ストップウォッチの要領で各ボタンを押するだけで1、2ハゼ、煎り止め時間をタイマーに記録できます。

使い方

①豆投入時に「Start」ボタンを押す
②1ハゼ時に「1st Crack」ボタンを押す
③2ハゼ時に「2nd Crack」ボタンを押す
④煎り止め時「Stop Reset」ボタンを押す

※リセット機能
「Stop Reset」ボタンは3回押すとタイマーと記録を消去しリセットできます

※1,2ハゼは各2回まで記録可能
「さっきのは正式なハゼじゃない」という時にボタンを再度押せば2回目の記録も残ります

※2ハゼに入れない焙煎の記録
浅・中煎りなど2ハゼに入れない焙煎は「Start」「1st Crack」「Stop Reset」の順に進めればOKです

焙煎指数計算は、焙煎前後のグラム数をそれぞれ入力し「計算ボタン」を押すと指数が出ます。値を変えて何度でも計算可能です。

コーヒー自家焙煎タイマー+指数計算






焙煎タイマー&指数計算


焙煎タイマーアプリ

00:00



 

焙煎指数計算



指数:





プログラム内容

アプリのHTML、CSS、JAVAコードを公開しておきます。

8バッチ記録可能Versionも該当ページに公開しておきます。

HTMLコード

<p> </p>
<p></p>
<p><style>
/* 追加のCSSをここに書きます */
.input-group {
display: flex;
flex-wrap: wrap;
align-items: center;
}
.input-group label {
flex: 1;
margin-right: 5px;
text-align: right;
}
.input-group input {
flex: 1;
max-width: 40%;
}
.input-group button {
flex: 1;
max-width: 20%;
}
.container h1 {
text-align: center;
}
.input-group label::after {
content: "";
display: block;
height: 0;
clear: both;
visibility: hidden;
}
#result {
margin-top: 0;
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
#roastIndex {
margin-left: 5px;
}
#calculationForm {
display: flex;
flex-direction: column;
align-items: center;
}
#calculationForm .input-group {
flex-direction: row;
}
#calculationForm .input-group:last-child {
justify-content: center;
}
</style></p>
<div id="timer-container">
<h1>焙煎タイマーアプリ</h1>
<div id="timer-display">00:00</div>
<div id="buttons-container"><button id="start">Start</button><br /><button id="first-crack">1st<br />Crack</button><br /><button id="second-crack">2nd<br />Crack</button><br /><button id="stop-reset">Stop<br />Reset (×3)</button></div>
<div id="crack-times"> </div>
</div>
<div class="container">
<h1>焙煎指数計算</h1>
<form id="calculationForm">
<div class="input-group"><label for="before">焙煎前(g)<input id="before" maxlength="3" name="before" pattern="\d*\.?\d*" required="" type="text" /></label> <label for="after">焙煎後(g)<input id="after" maxlength="3" name="after" pattern="\d*\.?\d*" required="" type="text" /></label> <button type="button">計算</button></div>
<div id="result">
<h2>指数: <span id="roastIndex"></span></h2>
</div>
</form></div>
<p><script src="script.js"></script> <script src="timer_script.js"></script></p>

CSSコード

#timer-container {
font-family: Arial, sans-serif; /* 焙煎アプリタイマーのフォント指定 */
text-align: center;
}

#timer-title {
font-family: Arial, sans-serif; /* 焙煎アプリタイマーのフォント指定 */
font-size: 24px;
font-weight: bold;
}

#timer-display {
background-color: black;
color: white;
font-size: 36px;
padding: 20px;
margin: 10px 0;
}

#buttons-container {
display: flex;
justify-content: center;
}

button {
background-color: #8B4513;
color: white;
border: none;
padding: 10px 20px;
margin: 5px;
font-size: 16px;
cursor: pointer;
}

button:hover {
background-color: #A0522D;
}

#calculationForm h1 {
font-family: Arial, sans-serif; /* 焙煎指数計算のフォント指定 */
font-size: 24px;
font-weight: bold;
}

JAVAコード

document.addEventListener('DOMContentLoaded', function() {
let startTime = 0;
let timerInterval = null;
let firstCrackTimes = [];
let secondCrackTimes = [];
let stopTime = 0;
let resetCount = 0;

function formatTime(time) {
const minutes = Math.floor(time / 60000).toString().padStart(2, '0');
const seconds = ((time % 60000) / 1000).toFixed(0).padStart(2, '0');
return `${minutes}:${seconds}`;
}

function updateTimerDisplay(time) {
document.getElementById('timer-display').textContent = formatTime(time);
}

function updateCrackTimesDisplay() {
const firstCracks = firstCrackTimes.map(t => formatTime(t)).join(', ');
const secondCracks = secondCrackTimes.map(t => formatTime(t)).join(', ');
const stopDisplay = stopTime ? `Stop: ${formatTime(stopTime)}` : '';

document.getElementById('crack-times').innerHTML = [
firstCracks ? `1st Crack: ${firstCracks}` : '',
secondCracks ? `2nd Crack: ${secondCracks}` : '',
stopDisplay
].filter(Boolean).join('<br>'); // 改行を<br>タグで表現
}

function startTimer() {
if (!timerInterval) {
startTime = Date.now() - (stopTime ? stopTime : 0);
timerInterval = setInterval(() => {
updateTimerDisplay(Date.now() - startTime);
}, 1000);
}
}

function recordCrack(crackArray) {
if (timerInterval && crackArray.length < 2) {
crackArray.push(Date.now() - startTime);
updateCrackTimesDisplay();
}
}

function stopResetTimer() {
if (timerInterval) {
clearInterval(timerInterval);
stopTime = Date.now() - startTime;
timerInterval = null;
updateCrackTimesDisplay();
resetCount = 0;
} else {
resetCount++;
if (resetCount === 3) {
document.getElementById('timer-display').textContent = '00:00';
firstCrackTimes = [];
secondCrackTimes = [];
stopTime = 0;
updateCrackTimesDisplay();
resetCount = 0;
}
}
}

document.getElementById('start').addEventListener('click', startTimer);
document.getElementById('first-crack').addEventListener('click', () => recordCrack(firstCrackTimes));
document.getElementById('second-crack').addEventListener('click', () => recordCrack(secondCrackTimes));
document.getElementById('stop-reset').addEventListener('click', stopResetTimer);
});


function calculate() {
const before = parseFloat(document.getElementById('before').value);
const after = parseFloat(document.getElementById('after').value);

if (isNaN(before) || isNaN(after) || after === 0) {
alert("有効な数値を入力してください");
return;
}

const roastIndex = (before / after).toFixed(3);
document.getElementById('roastIndex').textContent = roastIndex;
}