junkerstock
 マイ電卓 

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>マイ電卓</title>
<style>
/* 基本設定 */
* {
box-sizing: border-box;
touch-action: manipulation;
-webkit-tap-highlight-color: transparent;
}
body {
margin: 0;
padding: 0;
height: 100dvh;
width: 100vw;
display: flex;
flex-direction: column;
background-color: #000;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
overflow: hidden;
}

/* 画面上部エリア */
#display-container {
flex: 1;
display: flex;
flex-direction: column;
justify-content: flex-end;
padding: 20px;
padding-bottom: 10px;
overflow: hidden;
}

/* 履歴表示エリア */
#history {
width: 100%;
flex: 1;
overflow-y: auto;
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: flex-end;
color: #888;
font-size: 0.9rem;
line-height: 1.4;
padding-bottom: 5px;
margin-bottom: 5px;
border-bottom: 1px solid #333;
}

.history-item {
font-family: monospace;
display: flex;
align-items: center;
gap: 10px;
width: 100%;
justify-content: flex-end;
}

.m-tag {
color: #FFD60A;
font-weight: bold;
display: inline-block;
min-width: 30px;
text-align: right;
}

/* メイン数値表示部分 */
#display {
width: 100%;
color: white;
font-size: 3.5rem;
font-weight: 300;
text-align: right;
word-break: break-all;
line-height: 1.1;
max-height: calc(3.5rem * 1.1 * 2);
overflow-y: auto;
flex-shrink: 0;
}

#display::-webkit-scrollbar, #history::-webkit-scrollbar {
display: none;
}

/* ボタンエリア */
.buttons {
height: 55%;
padding-bottom: env(safe-area-inset-bottom);
margin-bottom: 10px;
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: repeat(5, 1fr);
gap: 8px;
padding-left: 15px;
padding-right: 15px;
}

button {
border: none;
font-size: 1.7rem;
border-radius: 1000px;
cursor: pointer;
color: #fff;
display: flex;
justify-content: center;
align-items: center;
transition: background-color 0.3s ease;
height: 100%;
}

button:active {
filter: brightness(1.2);
transform: scale(0.98);
transition: none;
}

.number { background-color: #333333; }
.func { background-color: #a5a5a5; color: black; font-weight: 500; }

/* 切替ボタン */
.shift-btn {
background-color: #a5a5a5;
color: black;
font-size: 1.2rem;
font-weight: bold;
}

.m-key {
background-color: #FFD60A;
color: black;
font-weight: bold;
}

.mode-active-green {
background-color: #30d158 !important;
color: white !important;
font-weight: bold;
}

.shift-active {
background-color: #fff !important;
color: #000 !important;
}

/* 無効化時のスタイル */
.dimmed {
opacity: 0.3;
pointer-events: none;
}

/* === 右列グラデーション設定 === */

/* 1. 通常モード(赤 -> 紫) */
#btn-div { background-color: #FF3B30; color: white; } /* 赤 */
#btn-mul { background-color: #FF2D55; color: white; } /* 赤ピンク */
#btn-sub { background-color: #E040D0; color: white; } /* マゼンタ */
#btn-add { background-color: #C644FC; color: white; } /* オーキッド紫 */

/* 2. 切替モード(青 -> 紫) */
.style-base { background-color: #007AFF !important; color: white !important; font-size: 1.2rem; font-weight: bold; } /* 青 */
.style-time { background-color: #4054EB !important; color: white !important; font-size: 1.2rem; font-weight: bold; } /* インディゴ */
.style-paren-open { background-color: #6848F3 !important; color: white !important; font-weight: bold; } /* 青紫 */
.style-paren-close { background-color: #8E44FC !important; color: white !important; font-weight: bold; } /* 紫 */

/* 共通ゴール(=ボタン) */
#btn-equal {
background-color: #AF52DE;
color: white;
}

/* . */
#btn-dot { background-color: #333333; color: white; }

</style>
</head>
<body>

<div id="display-container">
<div id="history"></div>
<div id="display">0</div>
</div>

<div class="buttons" id="keypad">
<button class="func" id="btn-clear" onclick="handleClear()">C</button>
<button class="func" onclick="deleteLast()">←</button>

<button class="shift-btn" id="btn-shift" onclick="toggleShiftMode()">切替</button>

<button class="operator" id="btn-div" onclick="appendDisplay('/')">÷</button>

<button class="number" onclick="appendDisplay('7')">7</button>
<button class="number" onclick="appendDisplay('8')">8</button>
<button class="number" onclick="appendDisplay('9')">9</button>
<button class="operator" id="btn-mul" onclick="appendDisplay('*')">×</button>

<button class="number" onclick="appendDisplay('4')">4</button>
<button class="number" onclick="appendDisplay('5')">5</button>
<button class="number" onclick="appendDisplay('6')">6</button>
<button class="operator" id="btn-sub" onclick="appendDisplay('-')">−</button>

<button class="number" onclick="appendDisplay('1')">1</button>
<button class="number" onclick="appendDisplay('2')">2</button>
<button class="number" onclick="appendDisplay('3')">3</button>
<button class="operator" id="btn-add" onclick="appendDisplay('+')">+</button>

<button class="m-key" id="btn-m" onclick="toggleMemoryMode()">M</button>
<button class="number" onclick="appendDisplay('0')">0</button>
<button class="number" id="btn-dot" onclick="appendDisplay('.')">.</button>
<button class="operator" id="btn-equal" onclick="handleEqual()">=</button>
</div>

<script>
const display = document.getElementById('display');
const historyDiv = document.getElementById('history');
const keypad = document.getElementById('keypad');

const btnShift = document.getElementById('btn-shift');
const btnDiv = document.getElementById('btn-div');
const btnMul = document.getElementById('btn-mul');
const btnSub = document.getElementById('btn-sub');
const btnAdd = document.getElementById('btn-add');
const btnDot = document.getElementById('btn-dot');
const btnEqual = document.getElementById('btn-equal');
const btnClear = document.getElementById('btn-clear');
const btnM = document.getElementById('btn-m');

const operators = ['/', '*', '-', '+'];

// 修正:秒を先頭にし、週と月を追加
const unitNames = ["秒", "分", "時", "日", "週", "月", "年"];
let timeState = 0; // 0=秒, 1=分 ...

let historyList = [];
const STORAGE_KEY = 'my_calculator_history_v1';

let isMemoryMode = false;
let isShiftMode = false;
let memoryInput = "";

// 進数変換用ステート
let conversionState = 0; // 0:なし, 1:Hex, 2:Bin
let conversionTimer = null;
let tempDecimalValue = null;

// 履歴ロード
window.addEventListener('DOMContentLoaded', () => {
const savedData = localStorage.getItem(STORAGE_KEY);
if (savedData) {
try {
historyList = JSON.parse(savedData);
renderHistory();
} catch (e) {
historyList = [];
}
}
});

function saveHistory() {
localStorage.setItem(STORAGE_KEY, JSON.stringify(historyList));
}

function getDisplayValue() {
return display.innerText;
}

function setDisplayValue(val) {
display.innerText = val;
}

function renderHistory() {
historyDiv.innerHTML = "";
historyList.forEach((item, index) => {
const div = document.createElement('div');
div.className = 'history-item';
const mNumber = index + 1;
div.innerHTML = `<span>${item.text} = ${item.result}</span> <span class="m-tag">M${mNumber}</span>`;
historyDiv.appendChild(div);
});
historyDiv.scrollTop = historyDiv.scrollHeight;
}

function appendDisplay(input) {
if (getDisplayValue() === "ERROR") {
setDisplayValue("0");
}

if (isMemoryMode) {
if (!operators.includes(input) && input !== '.' && input !== '(' && input !== ')') {
memoryInput += input;
updateMemoryButtons();
}
return;
}

const currentVal = getDisplayValue();
const lastChar = currentVal.slice(-1);

if (currentVal === '0' && !operators.includes(input) && input !== '.') {
setDisplayValue(input);
return;
}

if (operators.includes(input) && operators.includes(lastChar)) {
return;
}

setDisplayValue(currentVal + input);
}

// ■ 切替(Shift)モード機能 ■
function toggleShiftMode() {
// 変換モード中に切替ボタンが押された場合
if (conversionState !== 0) {
resetConversion();
return;
}

isShiftMode = !isShiftMode;

if (isShiftMode) {
btnShift.classList.add("shift-active");

// --- デザイン変更(青→紫グラデーション) ---
btnDiv.className = "operator style-base";
btnMul.className = "operator style-time";
btnSub.className = "operator style-paren-open";
btnAdd.className = "operator style-paren-close";

// --- 機能変更 ---

// 1. ÷ -> 進数
btnDiv.innerText = "進数";
btnDiv.onclick = handleBaseConversion;

// 2. × -> 時間
// 修正:「現在の単位」を表示する
btnMul.innerText = unitNames[timeState];
btnMul.onclick = cycleTime;

// 3. - -> (
btnSub.innerText = "(";
btnSub.onclick = () => {
appendDisplay('(');
toggleShiftMode(); // 自動切替
};

// 4. + -> )
btnAdd.innerText = ")";
btnAdd.onclick = () => {
appendDisplay(')');
toggleShiftMode(); // 自動切替
};

} else {
btnShift.classList.remove("shift-active");

// --- デザイン戻し ---
btnDiv.className = "operator";
btnMul.className = "operator";
btnSub.className = "operator";
btnAdd.className = "operator";

// --- 機能戻し ---
btnDiv.innerText = "÷";
btnDiv.onclick = () => appendDisplay('/');

btnMul.innerText = "×";
btnMul.onclick = () => appendDisplay('*');

btnSub.innerText = "−";
btnSub.onclick = () => appendDisplay('-');

btnAdd.innerText = "+";
btnAdd.onclick = () => appendDisplay('+');
}
}

// ■ 進数変換ロジック ■
function handleBaseConversion() {
if (!isShiftMode) return;

if (conversionTimer) {
clearTimeout(conversionTimer);
conversionTimer = null;
}

if (conversionState === 0) {
try {
let expression = getDisplayValue();
let result = new Function('return ' + expression)();
result = parseFloat(result.toPrecision(12));

if (!isFinite(result) || isNaN(result)) throw new Error();

tempDecimalValue = result;
let intVal = parseInt(result, 10);
setDisplayValue(intVal.toString(16).toUpperCase());

conversionState = 1;
btnDiv.innerText = "16進";
disableAllButtonsExceptBase(true);

} catch (e) {
showError();
return;
}

} else if (conversionState === 1) {
let intVal = parseInt(tempDecimalValue, 10);
setDisplayValue(intVal.toString(2));
conversionState = 2;
btnDiv.innerText = "2進";

} else {
resetConversion();
return;
}

conversionTimer = setTimeout(() => {
resetConversion();
}, 1000);
}

function resetConversion() {
if (conversionTimer) {
clearTimeout(conversionTimer);
conversionTimer = null;
}
if (tempDecimalValue !== null) {
setDisplayValue(tempDecimalValue);
tempDecimalValue = null;
}
conversionState = 0;
disableAllButtonsExceptBase(false);

if (isShiftMode) {
toggleShiftMode();
}
}

function disableAllButtonsExceptBase(shouldDisable) {
const allButtons = keypad.querySelectorAll('button');
allButtons.forEach(btn => {
if (btn.id !== 'btn-div') {
if (shouldDisable) {
btn.classList.add('dimmed');
} else {
btn.classList.remove('dimmed');
}
}
});
}

// --- 時間変換 (修正版) ---
function cycleTime() {
if (!isShiftMode) return;
if (isMemoryMode) return;
try {
// 現在の値を評価
let rawResult = eval(getDisplayValue());
let val = parseFloat(rawResult.toPrecision(12));

// 現在の状態から「次の単位」へ変換
// 0:秒 -> 1:分
if (timeState === 0) val = val / 60;
// 1:分 -> 2:時
else if (timeState === 1) val = val / 60;
// 2:時 -> 3:日
else if (timeState === 2) val = val / 24;
// 3:日 -> 4:週
else if (timeState === 3) val = val / 7;
// 4:週 -> 5:月 (月を約30.44日として計算: 365.25 / 12 / 7 = 4.348...週)
else if (timeState === 4) val = val / (365.25 / 12 / 7);
// 5:月 -> 6:年
else if (timeState === 5) val = val / 12;
// 6:年 -> 0:秒 (リセット)
else if (timeState === 6) val = val * 365.25 * 24 * 60 * 60;

// 状態を進める
timeState++;
if (timeState >= unitNames.length) timeState = 0;

setDisplayValue(val);

// ボタンの表示は「現在の単位」(=変換後の新しい単位)にする
btnMul.innerText = unitNames[timeState];

} catch (e) {
showError();
}
}

function toggleMemoryMode() {
if (conversionState !== 0) resetConversion();
if (isShiftMode) toggleShiftMode();

if (isMemoryMode) {
endMemoryMode();
} else {
isMemoryMode = true;
memoryInput = "";
btnM.style.filter = "brightness(0.8)";
btnEqual.classList.add("mode-active-green");
btnClear.classList.add("mode-active-green");
updateMemoryButtons();
}
}

function updateMemoryButtons() {
if (memoryInput === "") {
btnEqual.innerText = "E";
btnClear.innerText = "C";
} else {
btnEqual.innerText = "E(" + memoryInput + ")";
btnClear.innerText = "C(" + memoryInput + ")";
}
}

function endMemoryMode() {
isMemoryMode = false;
memoryInput = "";
btnM.style.filter = "none";
btnEqual.innerText = "=";
btnEqual.classList.remove("mode-active-green");
btnClear.innerText = "C";
btnClear.classList.remove("mode-active-green");
}

function handleClear() {
if (conversionState !== 0) {
resetConversion();
return;
}
if (isMemoryMode) {
if (memoryInput === "") {
if (confirm("MEMORY ALL CLEAR OK?")) {
historyList = [];
saveHistory();
renderHistory();
endMemoryMode();
}
return;
}
if (memoryInput !== "") {
const targetIndex = parseInt(memoryInput) - 1;
if (targetIndex >= 0 && targetIndex < historyList.length) {
historyList.splice(targetIndex, 1);
saveHistory();
renderHistory();
endMemoryMode();
} else {
memoryInput = "";
updateMemoryButtons();
}
}
return;
}
setDisplayValue("0");
}

function handleEqual() {
if (conversionState !== 0) {
resetConversion();
return;
}
if (isMemoryMode) {
if (memoryInput === "") {
if (historyList.length === 0) {
endMemoryMode();
return;
}
if (confirm("MEMORY ALL SUM OK?")) {
let totalSum = 0;
let expressionParts = [];
historyList.forEach(item => {
const val = Number(item.result);
totalSum += val;
expressionParts.push(val);
});
const sumExpression = expressionParts.join("+");

const lastHistory = historyList[historyList.length - 1];
if (!lastHistory || (lastHistory.text !== sumExpression || lastHistory.result != totalSum)) {
historyList.push({ text: sumExpression, result: totalSum });
saveHistory();
}

renderHistory();
endMemoryMode();
const currentVal = getDisplayValue();
if (currentVal === "0") {
setDisplayValue(totalSum);
} else {
setDisplayValue(currentVal + totalSum);
}
}
return;
}
const targetIndex = parseInt(memoryInput) - 1;
if (targetIndex >= 0 && targetIndex < historyList.length) {
endMemoryMode();
const val = historyList[targetIndex].result;
const currentVal = getDisplayValue();
if (currentVal === "0") {
setDisplayValue(val);
} else {
setDisplayValue(currentVal + val);
}
} else {
endMemoryMode();
}
return;
}
calculateResult();
}

function deleteLast() {
if (conversionState !== 0) return;
if (isMemoryMode) {
memoryInput = memoryInput.slice(0, -1);
updateMemoryButtons();
return;
}
const currentVal = getDisplayValue();
if (currentVal === "ERROR") {
setDisplayValue("0");
return;
}
if (currentVal.length > 1) {
setDisplayValue(currentVal.slice(0, -1));
} else {
setDisplayValue("0");
}
}

function showError() {
setDisplayValue("ERROR");
setTimeout(() => {
if (getDisplayValue() === "ERROR") setDisplayValue("0");
}, 1500);
}

function validateExpression(expr) {
const openParens = (expr.match(/\(/g) || []).length;
const closeParens = (expr.match(/\)/g) || []).length;
if (openParens !== closeParens) return false;
if (/[0-9.]\s*\(/.test(expr)) return false;
if (/\)\s*[0-9.]/.test(expr)) return false;
if (/\(\s*\)/.test(expr)) return false;
return true;
}

function calculateResult() {
let expression = getDisplayValue();
if (!validateExpression(expression)) {
showError();
return;
}
try {
let result = new Function('return ' + expression)();
result = parseFloat(result.toPrecision(12));
if (!isFinite(result) || isNaN(result)) {
showError();
return;
}

const lastHistory = historyList[historyList.length - 1];
if (!lastHistory || (lastHistory.text !== expression || lastHistory.result != result)) {
historyList.push({ text: expression, result: result });
saveHistory();
renderHistory();
}

setDisplayValue(result);
} catch (error) {
showError();
}
}
</script>
</body>
</html>


使用変数

*
0
1
7
allButtons
appendDisplay -------( Function )
btn
btnAdd
btnClear
btnDiv
btnDot
btnEqual
btnM
btnMul
btnShift
btnSub
calculateResult -------( Function )
charset
class
className
closeParens
content
conversionState
conversionTimer
currentVal
cycleTime -------( Function )
deleteLast -------( Function )
disableAllButtonsExceptBase -------( Function )
display
div
endMemoryMode -------( Function )
expression
expressionParts
filter
fit
getDisplayValue -------( Function )
handleBaseConversion -------( Function )
handleClear -------( Function )
handleEqual -------( Function )
historyDiv
historyList
id
innerHTML
innerText
intVal
isMemoryMode
isShiftMode
item
keypad
lang
lastChar
lastHistory
length
memoryInput
mNumber
name
onclick
openParens
operators
rawResult
renderHistory -------( Function )
resetConversion -------( Function )
result
savedData
saveHistory -------( Function )
scalable
scale
scrollTop
setDisplayValue -------( Function )
showError -------( Function )
STORAGE_KEY
sumExpression
targetIndex
tempDecimalValue
timeState
toggleMemoryMode -------( Function )
toggleShiftMode -------( Function )
totalSum
unitNames
updateMemoryButtons -------( Function )
val
validateExpression -------( Function )
width
右列グラデーション設定

Content-type: text/html error-smemo8

ERROR !

ファイルの差し替えに失敗しました: ./smemo8.log