<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Investment on 跟著 Desolve 學程式</title><link>https://desolve.pages.dev/categories/investment/</link><description>Recent content in Investment on 跟著 Desolve 學程式</description><generator>Hugo -- gohugo.io</generator><language>zh-tw</language><lastBuildDate>Thu, 08 Oct 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://desolve.pages.dev/categories/investment/index.xml" rel="self" type="application/rss+xml"/><item><title>📈 台股即時 K 線查詢系統</title><link>https://desolve.pages.dev/post/tw-stock-chart/</link><pubDate>Sat, 02 May 2026 00:00:00 +0800</pubDate><guid>https://desolve.pages.dev/post/tw-stock-chart/</guid><description>&lt;img src="https://desolve.pages.dev/img/tools.webp" alt="Featured image of post 📈 台股即時 K 線查詢系統" /&gt;&lt;p&gt;歡迎使用台股即時查詢系統！請在下方圖表左上角點擊「股票代號」，輸入您想查詢的台股（例如：&lt;code&gt;2330&lt;/code&gt; 或 &lt;code&gt;鴻海&lt;/code&gt;）。&lt;/p&gt;
&lt;div style="background: var(--card-background); padding: 15px; border-radius: 8px; box-shadow: var(--shadow-l1);"&gt;
&lt;div id="kline-config"
data-symbol="2603"
data-locked="false"
data-support="150"
data-resistance="220"
style="display: none;"&gt;&lt;/div&gt;
&lt;div style="margin-bottom: 15px; display: flex; flex-wrap: wrap; gap: 10px; align-items: center;"&gt;
&lt;div style="display: flex; gap: 5px;"&gt;
&lt;input type="text" id="stockInput" value="2603"
placeholder="台股代號"
style="padding: 6px 10px; width: 100px; border-radius: 4px; border: 1px solid var(--gray-300); background: var(--body-background); color: var(--body-text-color);"
&gt;
&lt;button onclick="bootup()" style="padding: 6px 12px; border-radius: 4px; background: #3b82f6; color: white; border: none; cursor: pointer; font-size: 0.9em;"&gt;查詢&lt;/button&gt;
&lt;/div&gt;
&lt;div style="width: 1px; height: 24px; background: var(--gray-300); margin: 0 5px;"&gt;&lt;/div&gt;
&lt;div style="display: flex; gap: 5px;"&gt;
&lt;input type="number" id="priceLineValue" placeholder="價格" style="padding: 6px 10px; width: 80px; border-radius: 4px; border: 1px solid var(--gray-300); background: var(--body-background); color: var(--body-text-color);"&gt;
&lt;button onclick="addUserLine('支撐', '#26a69a')" style="padding: 6px 12px; border-radius: 4px; background: #26a69a; color: white; border: none; cursor: pointer; font-size: 0.9em;"&gt;畫支撐&lt;/button&gt;
&lt;button onclick="addUserLine('壓力', '#ef5350')" style="padding: 6px 12px; border-radius: 4px; background: #ef5350; color: white; border: none; cursor: pointer; font-size: 0.9em;"&gt;畫壓力&lt;/button&gt;
&lt;button onclick="clearUserLines()" style="padding: 6px 12px; border-radius: 4px; background: var(--gray-500); color: white; border: none; cursor: pointer; font-size: 0.9em;"&gt;清除線條&lt;/button&gt;
&lt;/div&gt;
&lt;span id="loadingMsg" style="display: none; font-size: 0.9em; color: var(--gray-500); margin-left: auto;"&gt;資料載入中...&lt;/span&gt;
&lt;/div&gt;
&lt;div id="tvchart" style="width: 100%; height: 550px;"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;script src="https://cdn.jsdelivr.net/npm/klinecharts/dist/klinecharts.min.js"&gt;&lt;/script&gt;
&lt;script&gt;
let chart = null;
klinecharts.registerOverlay({
name: 'supportResistanceLine',
createPointFigures: function({ bounding, coordinates, overlay }) {
if (!coordinates || coordinates.length === 0) return [];
const y = coordinates[0].y;
const data = overlay.extendData;
return [
{
type: 'line',
attrs: { coordinates: [{ x: 0, y: y }, { x: bounding.width, y: y }] },
ignoreEvent: true,
styles: { style: 'dashed', color: data.color, size: 1.5, dashedValue: [4, 4] }
}
];
},
createYAxisFigures: function({ bounding, coordinates, overlay }) {
if (!coordinates || coordinates.length === 0) return [];
const y = coordinates[0].y;
const data = overlay.extendData;
return [
{
type: 'text',
attrs: {
x: bounding.width / 2,
y: y,
text: data.label,
align: 'center',
baseline: 'middle'
},
ignoreEvent: true,
styles: {
color: '#ffffff',
backgroundColor: data.color,
size: 12,
paddingLeft: 6,
paddingRight: 6,
paddingTop: 4,
paddingBottom: 4,
borderRadius: 2
}
}
];
}
});
function drawOverlayLine(label, price, color) {
if (!chart || isNaN(price)) return;
chart.createOverlay({
name: 'supportResistanceLine',
extendData: { label: `${label}: ${price}`, color: color },
points: [{ value: price }]
});
}
function addUserLine(label, color) {
const priceInput = document.getElementById('priceLineValue').value;
const price = parseFloat(priceInput);
if (!isNaN(price)) {
drawOverlayLine(label, price, color);
document.getElementById('priceLineValue').value = '';
} else {
alert('請輸入有效的數字格式價格！');
}
}
function clearUserLines() {
if (chart) {
chart.removeOverlay();
}
}
function initChart() {
if (chart) {
klinecharts.dispose('tvchart');
}
chart = klinecharts.init('tvchart');
chart.setStyles('dark');
chart.setStyles({
yAxis: {
size: 75
},
grid: {
horizontal: { color: 'rgba(197, 203, 206, 0.1)' },
vertical: { color: 'rgba(197, 203, 206, 0.1)' }
},
candle: {
type: 'candle_solid',
bar: {
upColor: '#ef5350', downColor: '#26a69a', noChangeColor: '#999999',
upBorderColor: '#ef5350', downBorderColor: '#26a69a', noChangeBorderColor: '#999999',
upWickColor: '#ef5350', downWickColor: '#26a69a', noChangeWickColor: '#999999'
}
}
});
chart.createIndicator('MA', false, { id: 'candle_pane' });
chart.createIndicator('VOL');
}
async function fetchStockData() {
const config = document.getElementById('kline-config').dataset;
const stockId = document.getElementById('stockInput').value || config.symbol;
const loadingMsg = document.getElementById('loadingMsg');
const d = new Date();
d.setFullYear(d.getFullYear() - 1);
const startDate = d.toISOString().split('T')[0];
loadingMsg.style.display = 'inline';
try {
const response = await fetch(`https://api.finmindtrade.com/api/v4/data?dataset=TaiwanStockPrice&amp;data_id=${stockId}&amp;start_date=${startDate}`);
const result = await response.json();
if (result.data &amp;&amp; result.data.length &gt; 0) {
const chartData = result.data.map(item =&gt; ({
timestamp: new Date(item.date).getTime(),
open: item.open,
high: item.max,
low: item.min,
close: item.close,
volume: item.Trading_Volume
}));
chart.applyNewData(chartData);
if (config.support) {
drawOverlayLine('支撐', parseFloat(config.support), '#26a69a');
}
if (config.resistance) {
drawOverlayLine('壓力', parseFloat(config.resistance), '#ef5350');
}
} else {
alert('查無資料，請確認代號是否正確。');
}
} catch (error) {
console.error('API 請求失敗:', error);
alert('資料讀取失敗，請稍後再試。');
} finally {
loadingMsg.style.display = 'none';
}
}
function bootup() {
initChart();
fetchStockData();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', bootup);
} else {
setTimeout(bootup, 100);
}
&lt;/script&gt;</description></item><item><title>股市塔羅運勢占卜器：開盤前來抽三張牌吧！</title><link>https://desolve.pages.dev/post/stock-market-tarot-fortune-teller/</link><pubDate>Thu, 08 Oct 2026 00:00:00 +0800</pubDate><guid>https://desolve.pages.dev/post/stock-market-tarot-fortune-teller/</guid><description>&lt;p&gt;各位股海浮沉的戰友們，大家好！XD&lt;/p&gt;
&lt;p&gt;今天來點不一樣的，開盤前，我們先來抽三張牌看看今天的運勢吧！👇&lt;/p&gt;
&lt;div id="stock-tarot-app" class="tarot-wrapper"&gt;
&lt;style&gt;
.tarot-wrapper, .tarot-wrapper * {
box-sizing: border-box;
}
.tarot-wrapper {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background-color: var(--body-background, #f3f4f6);
color: var(--body-text-color, #1f2937);
padding: 30px;
border-radius: 12px;
box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1);
max-width: 900px;
width: 100%;
margin: 0 auto;
}
html[data-scheme="dark"] .tarot-wrapper {
background-color: transparent;
}
.tarot-wrapper h1 {
text-align: center;
font-size: 26px;
margin-top: 0;
margin-bottom: 10px;
color: var(--card-text-color-main, #111827);
}
.disclaimer {
text-align: center;
font-size: 13px;
color: #ef4444;
background: rgba(239, 68, 68, 0.1);
padding: 8px;
border-radius: 6px;
margin-bottom: 25px;
}
.controls {
display: flex;
justify-content: center;
gap: 10px;
margin-bottom: 30px;
flex-wrap: wrap;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: bold;
cursor: pointer;
transition: transform 0.1s, opacity 0.2s;
color: #fff;
touch-action: manipulation;
}
.btn:active { transform: scale(0.95); }
.btn-primary { background: linear-gradient(135deg, #6366f1, #8b5cf6); }
.btn-secondary { background: linear-gradient(135deg, #10b981, #34d399); }
.btn-tertiary { background: linear-gradient(135deg, #f59e0b, #fbbf24); }
.cards-layout {
display: flex;
justify-content: space-around;
gap: 15px;
margin-bottom: 30px;
flex-wrap: wrap;
}
.card-slot {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
min-width: 200px;
}
.slot-title {
font-size: 18px;
margin-bottom: 15px;
color: var(--card-text-color-main, #374151);
}
.tarot-card-container {
perspective: 1000px;
width: 160px;
height: 270px;
margin-bottom: 15px;
}
.tarot-card {
width: 100%;
height: 100%;
position: relative;
transition: transform 0.8s;
transform-style: preserve-3d;
cursor: pointer;
box-shadow: 0 6px 12px rgba(0,0,0,0.15);
border-radius: 10px;
touch-action: manipulation;
}
.tarot-card.flipped {
transform: rotateY(180deg);
}
.tarot-card.empty {
cursor: default;
box-shadow: none;
border: 2px dashed #9ca3af;
background: transparent;
}
@keyframes flyIn {
from { transform: translateY(150px) scale(0.5); opacity: 0; }
to { transform: translateY(0) scale(1); opacity: 1; }
}
.tarot-card-container.fly-in {
animation: flyIn 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
}
.deck-fan-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.85);
z-index: 9999;
display: none;
flex-direction: column;
justify-content: center;
align-items: center;
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
}
.deck-fan-container {
position: absolute;
bottom: 0;
left: 0;
width: 100vw;
height: 100vh;
overflow: hidden;
perspective: 1200px;
pointer-events: none;
}
.deck-fan-instruction {
position: absolute;
top: 18%;
width: 100%;
text-align: center;
color: #fcd34d;
font-weight: bold;
font-size: 26px;
z-index: 100;
text-shadow: 0 2px 10px rgba(0,0,0,0.9);
padding: 0 16px;
}
.fan-card {
position: absolute;
width: var(--fan-card-w, 90px);
height: var(--fan-card-h, 155px);
background-color: #0f172a;
background-image: url("https://img.magnific.com/premium-photo/paper-frames-decorative-backgrounds-vintageinspired-urban-themes-creative-templates_1020495-136298.jpg?w=1060");
background-size: cover;
background-position: center;
border: 3px solid #eab308;
border-radius: 6px;
box-shadow: -3px 3px 10px rgba(0,0,0,0.5);
cursor: pointer;
transition: transform 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity 0.4s;
transform-origin: bottom center;
bottom: var(--fan-bottom, 50px);
left: 50%;
margin-left: calc(var(--fan-card-w, 90px) / -2);
pointer-events: auto;
touch-action: manipulation;
-webkit-tap-highlight-color: transparent;
}
.fan-card:hover, .fan-card:active {
box-shadow: -2px 10px 20px rgba(234, 179, 8, 0.4);
border-color: #fef08a;
z-index: 999 !important;
}
.fan-card.picked {
opacity: 0;
pointer-events: none;
transform: translateY(-100px) scale(1.2) !important;
}
.card-front, .card-back {
position: absolute;
width: 100%;
height: 100%;
backface-visibility: hidden;
border-radius: 10px;
overflow: hidden;
}
.card-front {
background-color: #0f172a;
background-image: url("https://img.magnific.com/premium-photo/paper-frames-decorative-backgrounds-vintageinspired-urban-themes-creative-templates_1020495-136298.jpg?w=1060");
background-size: cover;
background-position: center;
border: 6px solid #eab308;
box-sizing: border-box;
}
.card-back {
background-color: #fff;
transform: rotateY(180deg);
border: 4px solid #f1f5f9;
}
.card-back img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s;
}
.tarot-card.reversed .card-back img {
transform: rotateZ(180deg);
}
.card-desc {
text-align: center;
font-size: 15px;
min-height: 80px;
background: var(--card-background, #fff);
padding: 12px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
width: 100%;
box-sizing: border-box;
border: 1px solid var(--card-separator-color, #e5e7eb);
}
.card-desc .card-name {
font-weight: bold;
color: #6366f1;
margin-bottom: 5px;
display: block;
}
html[data-scheme="dark"] .card-desc .card-name { color: #818cf8; }
.card-desc .card-status {
display: inline-block;
padding: 2px 6px;
border-radius: 4px;
font-size: 12px;
font-weight: bold;
margin-bottom: 5px;
}
.status-up { background: #dcfce7; color: #166534; }
.status-rev { background: #fee2e2; color: #991b1b; }
html[data-scheme="dark"] .status-up { background: rgba(74,222,128,0.2); color: #4ade80; }
html[data-scheme="dark"] .status-rev { background: rgba(248,113,113,0.2); color: #f87171; }
.overall-fortune {
background: var(--card-background, #fff);
padding: 20px;
border-radius: 10px;
border-left: 5px solid #8b5cf6;
margin-top: 20px;
border-right: 1px solid var(--card-separator-color, #e5e7eb);
border-top: 1px solid var(--card-separator-color, #e5e7eb);
border-bottom: 1px solid var(--card-separator-color, #e5e7eb);
}
.overall-fortune h3 {
margin-top: 0;
color: #8b5cf6;
}
html[data-scheme="dark"] .overall-fortune h3 { color: #a78bfa; }
header.article-header .article-title {
display: none !important;
}
@media (max-width: 768px) {
.cards-layout {
gap: 20px;
}
.card-slot {
min-width: 170px;
}
.tarot-card-container {
width: 145px;
height: 245px;
}
}
@media (max-width: 650px) {
.tarot-wrapper {
padding: 16px 12px;
border-radius: 8px;
width: 100%;
}
.tarot-wrapper h1 {
font-size: 19px;
line-height: 1.35;
margin-bottom: 8px;
word-break: break-word;
}
.tarot-wrapper h1 img {
width: 24px !important;
height: 24px !important;
margin-right: 6px !important;
}
.disclaimer {
font-size: 12px;
padding: 8px 10px;
margin-bottom: 16px;
line-height: 1.4;
}
.controls {
flex-direction: column;
gap: 8px;
margin-bottom: 20px;
}
.btn {
width: 100%;
font-size: 15px;
padding: 12px 16px;
}
.cards-layout {
flex-direction: column;
align-items: center;
gap: 24px;
margin-bottom: 25px;
}
.card-slot {
width: 100%;
max-width: 320px;
min-width: unset;
}
.slot-title {
font-size: 17px;
margin-bottom: 10px;
}
.tarot-card-container {
width: 140px;
height: 236px;
margin-bottom: 12px;
}
.card-desc {
font-size: 14px;
padding: 10px;
min-height: 70px;
line-height: 1.5;
}
.overall-fortune {
padding: 16px;
margin-top: 15px;
font-size: 14px;
line-height: 1.6;
}
.overall-fortune h3 {
font-size: 17px;
margin-bottom: 8px;
}
.deck-fan-instruction {
font-size: 18px;
top: 10%;
padding: 0 16px;
line-height: 1.4;
}
.fan-card {
border-width: 2px;
border-radius: 4px;
box-shadow: -2px 2px 6px rgba(0,0,0,0.6);
}
}
&lt;/style&gt;
&lt;h1&gt;&lt;img src="https://cdn-icons-png.flaticon.com/512/8041/8041668.png" alt="tarot" style="width: 32px; height: 32px; vertical-align: middle; margin-right: 8px; margin-bottom: 5px;"&gt;股市塔羅運勢占卜器：開盤前來抽三張牌吧！&lt;/h1&gt;
&lt;p class="disclaimer"&gt;⚠️ 非專業占卜，本工具僅供娛樂與參考。股市有賺有賠，投資請謹慎理財，盈虧自負！&lt;/p&gt;
&lt;div class="controls" data-html2canvas-ignore&gt;
&lt;button id="draw-btn" class="btn btn-primary"&gt;🎴 為我抽三張牌 (開盤/盤中/收盤)&lt;/button&gt;
&lt;button id="reset-btn" class="btn btn-secondary" style="display:none;"&gt;🔄 重新占卜&lt;/button&gt;
&lt;button id="share-btn" class="btn btn-tertiary" style="display:none;"&gt;📸 截圖分享&lt;/button&gt;
&lt;/div&gt;
&lt;div class="deck-fan-overlay" id="deck-fan" data-html2canvas-ignore&gt;
&lt;div class="deck-fan-container" id="deck-fan-inner"&gt;
&lt;div class="deck-fan-instruction" id="fan-instruction"&gt;請憑直覺從牌堆中抽出 3 張牌&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="cards-layout" id="cards-layout"&gt;
&lt;div class="card-slot" id="slot-0"&gt;
&lt;h3 class="slot-title"&gt;🌅 開盤運勢&lt;/h3&gt;
&lt;div class="tarot-card-container"&gt;
&lt;div class="tarot-card empty" id="card-0" onclick="flipCard(0)"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="card-desc" id="desc-0"&gt;等待抽牌...&lt;/div&gt;
&lt;/div&gt;
&lt;div class="card-slot" id="slot-1"&gt;
&lt;h3 class="slot-title"&gt;☀️ 盤中運勢&lt;/h3&gt;
&lt;div class="tarot-card-container"&gt;
&lt;div class="tarot-card empty" id="card-1" onclick="flipCard(1)"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="card-desc" id="desc-1"&gt;等待抽牌...&lt;/div&gt;
&lt;/div&gt;
&lt;div class="card-slot" id="slot-2"&gt;
&lt;h3 class="slot-title"&gt;🌙 收盤運勢&lt;/h3&gt;
&lt;div class="tarot-card-container"&gt;
&lt;div class="tarot-card empty" id="card-2" onclick="flipCard(2)"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="card-desc" id="desc-2"&gt;等待抽牌...&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="overall-fortune" id="overall-fortune" style="display:none;"&gt;
&lt;h3&gt;📊 總體股市運勢解析&lt;/h3&gt;
&lt;p id="overall-text"&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"&gt;&lt;/script&gt;
&lt;script&gt;
const TAROT_DECK = [
{ id: "m00", name: "愚者 (The Fool)", up: "新手無畏、盲目追高、具備爆發潛力", rev: "當韭菜、無視風險、錯失良機" },
{ id: "m01", name: "魔術師 (The Magician)", up: "掌握籌碼、靈活操作、主力拉抬", rev: "遇上假消息、主力誘多/誘空" },
{ id: "m02", name: "女祭司 (The High Priestess)", up: "冷靜觀望、基本面研究、等待時機", rev: "失去直覺、判斷失誤、消息混亂" },
{ id: "m03", name: "皇后 (The Empress)", up: "豐收、高配息、穩健成長股", rev: "過度樂觀、浪費資金、獲利回吐" },
{ id: "m04", name: "皇帝 (The Emperor)", up: "國家隊護盤、大型權值股穩盤、紀律操作", rev: "政策打壓、大環境不佳、失去紀律" },
{ id: "m05", name: "教皇 (The Hierophant)", up: "聽從投顧/外資報告、主流類股", rev: "盲從跟單被套牢、跌破專家眼鏡" },
{ id: "m06", name: "戀人 (The Lovers)", up: "併購題材、資金青睞、完美買點", rev: "投資決策錯誤、合作破局、選錯股" },
{ id: "m07", name: "戰車 (The Chariot)", up: "強勢多頭、突破前高、動能強勁", rev: "追高殺低、失去方向、動能衰退" },
{ id: "m08", name: "力量 (Strength)", up: "抱緊處理、克服恐懼、長線獲利", rev: "信心崩潰、恐慌性拋售、抱不住" },
{ id: "m09", name: "隱者 (The Hermit)", up: "冷門潛力股、逆勢操作、遠離喧囂", rev: "被邊緣化、孤注一擲、錯失主流" },
{ id: "m10", name: "命運之輪 (Wheel of Fortune)", up: "景氣循環向上、風水輪流轉、時機到來", rev: "景氣下行、運氣不佳、被套牢" },
{ id: "m11", name: "正義 (Justice)", up: "財報回歸基本面、合理估值、法規利多", rev: "財報暴雷、估值過高、違規交割" },
{ id: "m12", name: "倒吊人 (The Hanged Man)", up: "暫時套牢但有轉機、以時間換取空間", rev: "無謂的犧牲、死不認錯、凹單" },
{ id: "m13", name: "死神 (Death)", up: "停損出場、下市危機、徹底洗盤", rev: "死裡逃生、破底翻、否極泰來" },
{ id: "m14", name: "節制 (Temperance)", up: "資產配置、資金控管得當、高低拋接", rev: "資金失衡、過度集中、無法調度" },
{ id: "m15", name: "惡魔 (The Devil)", up: "貪婪、無本當沖、高槓桿風險、炒作股", rev: "解除槓桿、斷頭出場、認賠殺出" },
{ id: "m16", name: "高塔 (The Tower)", up: "黑天鵝事件、崩盤、系統性風險、連續跌停", rev: "逃過一劫、危機逐漸解除" },
{ id: "m17", name: "星星 (The Star)", up: "未來展望佳、新興產業、希望無窮", rev: "畫大餅、夢想幻滅、本夢比破裂" },
{ id: "m18", name: "月亮 (The Moon)", up: "前景不明、利空潛伏、市場恐慌", rev: "利空出盡、真相大白、不確定性消除" },
{ id: "m19", name: "太陽 (The Sun)", up: "利多齊發、全面普漲、大牛市", rev: "利多不漲、高檔震盪、熱度稍退" },
{ id: "m20", name: "審判 (Judgement)", up: "財報開獎、重大宣佈、關鍵轉折點", rev: "壞消息成真、錯失逃命波" },
{ id: "m21", name: "世界 (The World)", up: "完美獲利了結、目標價達成、圓滿收場", rev: "延遲達標、尚欠東風、獲利縮水" }
];
const suits = [
{ prefix: "w", name: "權杖", type: "動能面" },
{ prefix: "c", name: "聖杯", type: "情緒面" },
{ prefix: "s", name: "寶劍", type: "風險面" },
{ prefix: "p", name: "星幣", type: "基本面" }
];
const ranks = [
{ num: "01", name: "王牌 (Ace)", up: "趨勢初步成型、新資金湧入", rev: "動能受阻、假突破/假跌破" },
{ num: "02", name: "二 (Two)", up: "多空交戰、陷入盤整觀望", rev: "失去平衡、方向即將表態" },
{ num: "03", name: "三 (Three)", up: "初步獲利、團隊合作/法人共識", rev: "共識破裂、行情面臨考驗" },
{ num: "04", name: "四 (Four)", up: "支撐強勁、防禦型操作、穩健保本", rev: "久盤必跌、過度保守錯失機會" },
{ num: "05", name: "五 (Five)", up: "市場震盪、停損出場、陣痛期", rev: "熬過低谷、跌深反彈" },
{ num: "06", name: "六 (Six)", up: "順風順水、均線多頭排列、穩步向上", rev: "優勢喪失、慎防獲利回吐" },
{ num: "07", name: "七 (Seven)", up: "力抗賣壓、守住關鍵價位", rev: "棄守支撐、籌碼鬆動" },
{ num: "08", name: "八 (Eight)", up: "急拉/急殺、行情進展迅速", rev: "陷入泥沼、流動性陷阱" },
{ num: "09", name: "九 (Nine)", up: "豐收在望、長線佈局開花結果", rev: "臨門一腳失敗、需落袋為安" },
{ num: "10", name: "十 (Ten)", up: "極盛而衰、壓力沉重、行情尾聲", rev: "觸底反彈、卸下重擔" },
{ num: "11", name: "侍者 (Page)", up: "試單階段、留意初期訊號", rev: "情報錯誤、新手遭套牢" },
{ num: "12", name: "騎士 (Knight)", up: "主力急拉、強勢表態、追車時機", rev: "主力出貨、波動劇烈、洗盤" },
{ num: "13", name: "王后 (Queen)", up: "穩健操盤、內資/投信發力", rev: "情緒化交易、失去紀律" },
{ num: "14", name: "國王 (King)", up: "外資控盤、大局底定、權值股領軍", rev: "大戶割韭菜、強硬打壓" }
];
suits.forEach(suit =&gt; {
ranks.forEach(rank =&gt; {
TAROT_DECK.push({
id: `${suit.prefix}${rank.num}`,
name: `${suit.name} ${rank.name}`,
up: `【${suit.type}】 ${rank.up}`,
rev: `【${suit.type}】 ${rank.rev}`
});
});
});
let drawnCards = [];
let pendingCards = [];
let pickedCount = 0;
let flippedCount = 0;
const BASE_IMAGE_URL = "https://raw.githubusercontent.com/metabismuth/tarot-json/master/cards/";
document.getElementById('draw-btn').addEventListener('click', startFanDrawing);
document.getElementById('reset-btn').addEventListener('click', resetBoard);
document.getElementById('share-btn').addEventListener('click', shareScreenshot);
function getFanLayout() {
const w = window.innerWidth;
const h = window.innerHeight;
const isMobile = w &lt;= 650;
const isTablet = w &gt; 650 &amp;&amp; w &lt; 950;
let cardW, cardH, maxAngle, bottomOffset;
if (isMobile) {
cardW = 44;
cardH = 76;
maxAngle = 58;
bottomOffset = Math.max(30, Math.round(h * 0.12));
} else if (isTablet) {
cardW = 65;
cardH = 112;
maxAngle = 70;
bottomOffset = Math.max(40, Math.round(h * 0.08));
} else {
cardW = 90;
cardH = 155;
maxAngle = 80;
bottomOffset = 50;
}
const radMaxAngle = maxAngle * Math.PI / 180;
const halfW = w / 2;
const safeMargin = isMobile ? 12 : 35;
const edgeOffset = (cardW / 2) * Math.cos(radMaxAngle);
const maxRadiusByWidth = ((halfW - safeMargin - edgeOffset) / Math.sin(radMaxAngle)) - cardH;
const topSafeRatio = isMobile ? 0.22 : 0.28;
const maxRadiusByHeight = (h * (1 - topSafeRatio)) - bottomOffset - cardH;
const radius = Math.max(80, Math.min(maxRadiusByWidth, maxRadiusByHeight));
return { isMobile, cardW, cardH, maxAngle, bottomOffset, radius };
}
function startFanDrawing() {
document.getElementById('draw-btn').style.display = 'none';
pendingCards = [];
let deckCopy = [...TAROT_DECK];
for (let i = 0; i &lt; 3; i++) {
const randIndex = Math.floor(Math.random() * deckCopy.length);
const card = deckCopy.splice(randIndex, 1)[0];
const isReversed = Math.random() &gt; 0.5;
pendingCards.push({ ...card, isReversed, flipped: false });
}
const fanOverlay = document.getElementById('deck-fan');
const fanInner = document.getElementById('deck-fan-inner');
document.body.appendChild(fanOverlay);
fanOverlay.style.display = 'block';
document.getElementById('fan-instruction').innerText = "請憑直覺從牌堆中抽出 3 張牌";
const layout = getFanLayout();
fanInner.style.setProperty('--fan-card-w', `${layout.cardW}px`);
fanInner.style.setProperty('--fan-card-h', `${layout.cardH}px`);
fanInner.style.setProperty('--fan-bottom', `${layout.bottomOffset}px`);
fanInner.querySelectorAll('.fan-card').forEach(el =&gt; el.remove());
const totalFanCards = 78;
const angleStep = (layout.maxAngle * 2) / (totalFanCards - 1);
for (let i = 0; i &lt; totalFanCards; i++) {
let card = document.createElement('div');
card.className = 'fan-card';
card.style.transform = `rotate(0deg) translateY(0px)`;
card.style.zIndex = i;
card.addEventListener('click', function() {
if (this.classList.contains('picked') || pickedCount &gt;= 3) return;
this.classList.add('picked');
pickCard();
});
fanInner.appendChild(card);
let angle = -layout.maxAngle + angleStep * i;
setTimeout(() =&gt; {
card.style.transform = `rotate(${angle}deg) translateY(-${layout.radius}px)`;
}, i * (layout.isMobile ? 8 : 14));
}
}
function pickCard() {
if (pickedCount &gt;= 3) return;
const cardIndex = pickedCount;
drawnCards.push(pendingCards[cardIndex]);
const cardEl = document.getElementById(`card-${cardIndex}`);
const containerEl = document.querySelector(`#slot-${cardIndex} .tarot-card-container`);
containerEl.classList.add('fly-in');
const cardUrl = `${BASE_IMAGE_URL}${drawnCards[cardIndex].id}.jpg`;
cardEl.innerHTML = `
&lt;div class="card-front"&gt;&lt;/div&gt;
&lt;div class="card-back"&gt;
&lt;img src="${cardUrl}" alt="Tarot Card" crossorigin="anonymous"&gt;
&lt;/div&gt;
`;
if (drawnCards[cardIndex].isReversed) {
cardEl.classList.add('reversed');
}
document.getElementById(`desc-${cardIndex}`).innerHTML = `&lt;span style="color:#8b5cf6; font-weight:bold;"&gt;✨ 點擊卡牌翻開...&lt;/span&gt;`;
pickedCount++;
if (pickedCount === 3) {
document.getElementById('fan-instruction').innerText = "發牌完成！請點擊下方卡牌檢視運勢";
setTimeout(() =&gt; {
document.getElementById('deck-fan').style.display = 'none';
document.getElementById('reset-btn').style.display = 'inline-block';
const app = document.getElementById('stock-tarot-app');
app.appendChild(document.getElementById('deck-fan'));
if (window.innerWidth &lt;= 650) {
const layoutEl = document.getElementById('cards-layout');
if (layoutEl) {
layoutEl.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}
}, 1000);
}
}
function flipCard(index) {
if (pickedCount &lt; 3) return;
if (!drawnCards[index] || drawnCards[index].flipped) return;
const cardEl = document.getElementById(`card-${index}`);
cardEl.classList.add('flipped');
drawnCards[index].flipped = true;
const card = drawnCards[index];
const statusHtml = card.isReversed
? `&lt;span class="card-status status-rev"&gt;▼ 逆位&lt;/span&gt;`
: `&lt;span class="card-status status-up"&gt;▲ 正位&lt;/span&gt;`;
const meaning = card.isReversed ? card.rev : card.up;
document.getElementById(`desc-${index}`).innerHTML = `
${statusHtml}
&lt;span class="card-name"&gt;${card.name}&lt;/span&gt;
&lt;span&gt;${meaning}&lt;/span&gt;
`;
flippedCount++;
if (flippedCount === 3) {
showOverallFortune();
}
}
function showOverallFortune() {
const upCount = drawnCards.filter(c =&gt; !c.isReversed).length;
let text = "";
if (upCount === 3) {
text = "🎉 **大吉大利！** 今日市場多頭氣盛，三張牌皆為正位，暗示整體趨勢非常健康，適合順勢操作，獲利機率極高！但別忘了設好停利點，讓利潤奔跑。";
} else if (upCount === 2) {
text = "📈 **吉中帶險！** 大方向偏多，但需留意盤中震盪或反轉訊號（逆位牌出現的時段），記得見好就收，避免獲利回吐。";
} else if (upCount === 1) {
text = "⚠️ **逆風操作！** 市場雜音較多，容易被主力洗盤或面臨假突破。建議多看少做，保守為宜，保護本金是今日的首要任務。";
} else {
text = "⛈️ **烏雲罩頂！** 三張牌皆為逆位，空頭來勢洶洶或市場極度恐慌。切忌接刀子，空手觀望是最好的策略，等待風暴過去再進場！";
}
document.getElementById('overall-text').innerHTML = text.replace(/\*\*(.*?)\*\*/g, '&lt;strong&gt;$1&lt;/strong&gt;');
document.getElementById('overall-fortune').style.display = 'block';
document.getElementById('share-btn').style.display = 'inline-block';
}
function resetBoard() {
document.getElementById('deck-fan').style.display = 'none';
const app = document.getElementById('stock-tarot-app');
app.appendChild(document.getElementById('deck-fan'));
for (let i = 0; i &lt; 3; i++) {
const containerEl = document.querySelector(`#slot-${i} .tarot-card-container`);
if(containerEl) containerEl.classList.remove('fly-in');
const cardEl = document.getElementById(`card-${i}`);
cardEl.className = 'tarot-card empty';
cardEl.innerHTML = '';
document.getElementById(`desc-${i}`).innerHTML = '等待抽牌...';
}
document.getElementById('draw-btn').style.display = 'inline-block';
document.getElementById('reset-btn').style.display = 'none';
document.getElementById('share-btn').style.display = 'none';
document.getElementById('overall-fortune').style.display = 'none';
drawnCards = [];
pendingCards = [];
pickedCount = 0;
flippedCount = 0;
}
function shareScreenshot() {
const btn = document.getElementById('share-btn');
const originalText = btn.innerHTML;
btn.innerHTML = '📸 正在產生截圖...';
const app = document.getElementById('stock-tarot-app');
const rootTheme = document.documentElement.getAttribute('data-scheme');
const targetBg = rootTheme === 'dark' ? '#181825' : '#f3f4f6';
const clone = app.cloneNode(true);
clone.style.position = 'absolute';
clone.style.top = '0px';
clone.style.left = '0px';
clone.style.zIndex = '-9999';
clone.style.margin = '0';
clone.style.width = app.offsetWidth + 'px';
clone.style.backgroundColor = targetBg;
document.body.appendChild(clone);
html2canvas(clone, {
useCORS: true,
backgroundColor: targetBg,
scale: window.devicePixelRatio &gt; 1 ? 2 : window.devicePixelRatio
}).then(canvas =&gt; {
document.body.removeChild(clone);
const link = document.createElement('a');
link.download = '股市塔羅占卜結果.png';
link.href = canvas.toDataURL('image/png');
link.click();
btn.innerHTML = originalText;
}).catch(err =&gt; {
if (document.body.contains(clone)) document.body.removeChild(clone);
console.error(err);
alert('截圖產生失敗，可能是因為載入外部圖片遭到 CORS 阻擋。請確保您的瀏覽器支援。');
btn.innerHTML = originalText;
});
}
&lt;/script&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h2 id="-工具介紹為什麼會有這個占卜器"&gt;💡 工具介紹：為什麼會有這個占卜器？
&lt;/h2&gt;&lt;p&gt;有時候除了看基本面、技術面、籌碼面之外，我們似乎也需要一點點&amp;hellip;「玄學面」的指引？（笑）&lt;br&gt;
為此，我特別結合了標準 78 張偉特塔羅牌，將牌義大幅「在地化」為符合台股情境的解釋，開發了這款 &lt;strong&gt;【股市塔羅運勢占卜器】&lt;/strong&gt;！&lt;/p&gt;
&lt;p&gt;不管是新手散戶、當沖客還是長線存股族，都可以來試試手氣：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;台股專屬解析&lt;/strong&gt;：星幣代表「基本面」、寶劍代表「風險/技術面」、聖杯代表「情緒面」、權杖則是「動能與題材」。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;沉浸式抽牌&lt;/strong&gt;：按下抽牌後，請憑直覺從展開的扇形牌堆中選出 3 張牌（對應開盤、盤中、收盤）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一鍵截圖分享&lt;/strong&gt;：抽完後，會根據正逆位比例給你今日的總體解析，還可以一鍵截圖分享到你的 LINE 股友群組！&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="-免責聲明很重要"&gt;⚠️ 免責聲明（很重要！！）
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;&lt;span style="color:#ef4444"&gt;非專業占卜，本工具僅供娛樂與參考！&lt;/span&gt;&lt;/strong&gt;&lt;br&gt;
股市有賺有賠，千萬不要因為抽到「死神」就把台積電全部停損，也不要因為抽到「太陽」就隨便全倉開槓桿！投資請務必謹慎理財，盈虧自負！XD&lt;/p&gt;
&lt;p&gt;希望這個小工具能為大家枯燥的盯盤日常增添一點樂趣，喜歡的話歡迎分享給身邊的股友們測測運氣！我們下次見囉，掰～&lt;/p&gt;</description></item><item><title>📈 台股零整套利計算器</title><link>https://desolve.pages.dev/post/stock-calculator/</link><pubDate>Sun, 09 Aug 2026 03:34:40 +0800</pubDate><guid>https://desolve.pages.dev/post/stock-calculator/</guid><description>&lt;img src="https://desolve.pages.dev/img/tools.webp" alt="Featured image of post 📈 台股零整套利計算器" /&gt;&lt;p&gt;歡迎使用台股零整套利計算器！您可以輸入買進價或賣出價，計算手續費、證交稅，並找尋最佳的獲利賣點或買點。&lt;/p&gt;
&lt;div id="stock-calculator-wrapper"&gt;
&lt;style&gt;
#stock-calculator-wrapper { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background-color: #f3f4f6; padding: 20px; color: #1f2937; line-height: 1.5; border-radius: 12px; }
#stock-calculator-wrapper .card { max-width: 800px; margin: 0 auto; background: #ffffff; padding: 30px; border-radius: 12px; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1); }
#stock-calculator-wrapper h1 { font-size: 24px; text-align: center; margin-bottom: 25px; color: #111827; border-bottom: 2px solid #e5e7eb; padding-bottom: 10px; margin-top: 0; }
#stock-calculator-wrapper .calc-grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 15px; margin-bottom: 20px; }
#stock-calculator-wrapper .form-group { display: flex; flex-direction: column; }
#stock-calculator-wrapper label { font-weight: 600; margin-bottom: 6px; font-size: 14px; color: #4b5563; display: flex; justify-content: space-between; align-items: center; }
#stock-calculator-wrapper .fee-badge { font-size: 11px; background-color: #ede9fe; color: #7c3aed; padding: 2px 6px; border-radius: 4px; font-weight: normal; white-space: nowrap; }
#stock-calculator-wrapper .table-badge { margin-left: 8px; vertical-align: middle; background-color: #e0e7ff; color: #4338ca; }
#stock-calculator-wrapper input { padding: 10px; border: 1px solid #d1d5db; border-radius: 6px; font-size: 16px; outline: none; transition: border-color 0.2s; background: #fafafa; width: 100%; box-sizing: border-box; color: #1f2937; }
#stock-calculator-wrapper input:focus { border-color: #3b82f6; background: #fff; }
#stock-calculator-wrapper input[type=number]::-webkit-inner-spin-button,
#stock-calculator-wrapper input[type=number]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
#stock-calculator-wrapper input[type=number] { -moz-appearance: textfield; }
#stock-calculator-wrapper details { background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px; margin-bottom: 20px; padding: 10px 15px; }
#stock-calculator-wrapper summary { font-weight: bold; cursor: pointer; color: #475569; user-select: none; list-style: none; display: flex; align-items: center; }
#stock-calculator-wrapper summary::-webkit-details-marker { display: none; }
#stock-calculator-wrapper summary::before { content: '⚙️ 展開進階設定 (折數、低消、稅率)'; margin-right: 8px; }
#stock-calculator-wrapper details[open] summary::before { content: '⚙️ 收合進階設定 (折數、低消、稅率)'; }
#stock-calculator-wrapper .advanced-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-top: 15px; padding-top: 15px; border-top: 1px dashed #cbd5e1; }
#stock-calculator-wrapper .result-box { margin-top: 25px; padding: 20px; border-radius: 8px; background-color: #f8fafc; border-left: 5px solid #3b82f6; box-shadow: inset 0 2px 4px rgba(0,0,0,0.02); }
#stock-calculator-wrapper .result-box h3 { margin-top: 0; color: #1e40af; font-size: 18px; display: flex; align-items: center; gap: 8px; }
#stock-calculator-wrapper table { width: 100%; border-collapse: collapse; margin-top: 15px; background: #fff; text-align: center; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.05); }
#stock-calculator-wrapper th, #stock-calculator-wrapper td { border: 1px solid #e5e7eb; padding: 12px; font-size: 15px; color: #1f2937; }
#stock-calculator-wrapper th { background-color: #f1f5f9; color: #475569; font-weight: 600; }
#stock-calculator-wrapper .highlight { color: #2563eb; font-weight: bold; }
#stock-calculator-wrapper .profit { color: #16a34a; font-weight: bold; }
#stock-calculator-wrapper .loss { color: #dc2626; font-weight: bold; }
#stock-calculator-wrapper .note { font-size: 13px; color: #64748b; margin-top: 8px; margin-bottom: 0; }
#stock-calculator-wrapper .empty-state { text-align: center; color: #94a3b8; padding: 30px; font-style: italic; }
@media (max-width: 600px) {
#stock-calculator-wrapper .calc-grid, #stock-calculator-wrapper .advanced-grid { grid-template-columns: 1fr; }
}
html[data-scheme="dark"] #stock-calculator-wrapper { background-color: transparent; color: var(--card-text-color-main); }
html[data-scheme="dark"] #stock-calculator-wrapper .card { background-color: var(--card-background); box-shadow: none; }
html[data-scheme="dark"] #stock-calculator-wrapper h1 { color: var(--card-text-color-main); border-bottom-color: var(--card-separator-color); }
html[data-scheme="dark"] #stock-calculator-wrapper label { color: var(--card-text-color-secondary); }
html[data-scheme="dark"] #stock-calculator-wrapper input { background-color: var(--body-background); border-color: var(--card-separator-color); color: var(--card-text-color-main); }
html[data-scheme="dark"] #stock-calculator-wrapper input:focus { border-color: #60a5fa; background-color: var(--card-background); }
html[data-scheme="dark"] #stock-calculator-wrapper details { background-color: var(--body-background); border-color: var(--card-separator-color); }
html[data-scheme="dark"] #stock-calculator-wrapper summary { color: var(--card-text-color-secondary); }
html[data-scheme="dark"] #stock-calculator-wrapper .advanced-grid { border-top-color: var(--card-separator-color); }
html[data-scheme="dark"] #stock-calculator-wrapper .result-box { background-color: var(--body-background); border-left-color: #3b82f6; }
html[data-scheme="dark"] #stock-calculator-wrapper .result-box h3 { color: #60a5fa; }
html[data-scheme="dark"] #stock-calculator-wrapper table { background-color: transparent; }
html[data-scheme="dark"] #stock-calculator-wrapper th,
html[data-scheme="dark"] #stock-calculator-wrapper td { border-color: var(--table-border-color); color: var(--card-text-color-main); }
html[data-scheme="dark"] #stock-calculator-wrapper th { background-color: var(--tr-even-background-color); color: var(--card-text-color-secondary); }
html[data-scheme="dark"] #stock-calculator-wrapper .highlight { color: #60a5fa; }
html[data-scheme="dark"] #stock-calculator-wrapper .profit { color: #4ade80; }
html[data-scheme="dark"] #stock-calculator-wrapper .loss { color: #f87171; }
html[data-scheme="dark"] #stock-calculator-wrapper .fee-badge { background-color: rgba(124, 58, 237, 0.2); color: #c4b5fd; }
html[data-scheme="dark"] #stock-calculator-wrapper .table-badge { background-color: rgba(67, 56, 202, 0.3); color: #a5b4fc; }
html[data-scheme="dark"] #stock-calculator-wrapper .note { color: var(--card-text-color-tertiary); }
html[data-scheme="dark"] #stock-calculator-wrapper .empty-state { color: var(--card-text-color-tertiary); }
&lt;/style&gt;
&lt;div class="card"&gt;
&lt;h1&gt;📈 台股零整套利計算器&lt;/h1&gt;
&lt;div class="calc-grid"&gt;
&lt;div class="form-group"&gt;
&lt;label&gt;
&lt;span&gt;買進價 (元) &lt;a href="javascript:void(0)" onclick="clearInput('buy')" style="font-size:12px; color:#ef4444; text-decoration:none; margin-left: 4px;"&gt;[清空]&lt;/a&gt;&lt;/span&gt;
&lt;span id="calc-buyFeeBadge" class="fee-badge" style="display:none;"&gt;低消: -- 股&lt;/span&gt;
&lt;/label&gt;
&lt;input type="number" id="calc-buyPrice" step="0.05" placeholder="例: 100"&gt;
&lt;/div&gt;
&lt;div class="form-group"&gt;
&lt;label&gt;
&lt;span&gt;賣出價 (元) &lt;a href="javascript:void(0)" onclick="clearInput('sell')" style="font-size:12px; color:#ef4444; text-decoration:none; margin-left: 4px;"&gt;[清空]&lt;/a&gt;&lt;/span&gt;
&lt;span id="calc-sellFeeBadge" class="fee-badge" style="display:none;"&gt;低消: -- 股&lt;/span&gt;
&lt;/label&gt;
&lt;input type="number" id="calc-sellPrice" step="0.05" placeholder="例: 101"&gt;
&lt;/div&gt;
&lt;div class="form-group"&gt;
&lt;label&gt;交易張數 (1張=1000股)&lt;/label&gt;
&lt;input type="number" id="calc-lots" value="1" min="1" placeholder="未輸入視為 1 張"&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;details&gt;
&lt;summary&gt;&lt;/summary&gt;
&lt;div class="advanced-grid"&gt;
&lt;div class="form-group"&gt;
&lt;label&gt;手續費折數 (預設2.3折)&lt;/label&gt;
&lt;input type="number" id="calc-discount" value="2.3" step="0.1" min="0.1" max="10"&gt;
&lt;/div&gt;
&lt;div class="form-group"&gt;
&lt;label&gt;
&lt;span&gt;證交稅率 (%)&lt;/span&gt;
&lt;label style="display:inline-flex; align-items:center; gap:4px; font-weight:normal; cursor:pointer; margin:0; color:#3b82f6;"&gt;
&lt;input type="checkbox" id="calc-dayTrade" style="width:auto; margin:0; padding:0;"&gt;
當沖(0.15)
&lt;/label&gt;
&lt;/label&gt;
&lt;input type="number" id="calc-tax" value="0.3" step="0.05" min="0"&gt;
&lt;/div&gt;
&lt;div class="form-group"&gt;
&lt;label&gt;零股手續費低消 (預設20元)&lt;/label&gt;
&lt;input type="number" id="calc-minFeeOdd" value="20" min="1"&gt;
&lt;/div&gt;
&lt;div class="form-group"&gt;
&lt;label&gt;整股手續費低消 (預設20元)&lt;/label&gt;
&lt;input type="number" id="calc-minFeeRound" value="20" min="1"&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;div id="calc-result"&gt;
&lt;div class="empty-state"&gt;請輸入「買進價」或「賣出價」開始試算...&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;script&gt;
const buyInput = document.getElementById('calc-buyPrice');
const sellInput = document.getElementById('calc-sellPrice');
const taxInput = document.getElementById('calc-tax');
const dayTradeCheck = document.getElementById('calc-dayTrade');
let shouldClearOtherOnEdit = false;
function clearInput(type) {
if (type === 'buy') {
buyInput.value = '';
} else if (type === 'sell') {
sellInput.value = '';
}
shouldClearOtherOnEdit = false;
calculate();
}
buyInput.addEventListener('focus', () =&gt; {
if (buyInput.value !== '' &amp;&amp; sellInput.value !== '') {
shouldClearOtherOnEdit = 'sell';
} else {
shouldClearOtherOnEdit = false;
}
});
sellInput.addEventListener('focus', () =&gt; {
if (buyInput.value !== '' &amp;&amp; sellInput.value !== '') {
shouldClearOtherOnEdit = 'buy';
} else {
shouldClearOtherOnEdit = false;
}
});
buyInput.addEventListener('input', (e) =&gt; {
if (shouldClearOtherOnEdit === 'sell') {
sellInput.value = '';
shouldClearOtherOnEdit = false;
}
calculate();
});
sellInput.addEventListener('input', (e) =&gt; {
if (shouldClearOtherOnEdit === 'buy') {
buyInput.value = '';
shouldClearOtherOnEdit = false;
}
calculate();
});
function stepValue(currentInput, otherInput, dir) {
let p = parseFloat(currentInput.value);
if (isNaN(p)) {
let otherP = parseFloat(otherInput.value);
if (!isNaN(otherP)) {
p = otherP;
} else {
p = 0;
}
}
let tick = getTickSize(p);
if (dir === -1 &amp;&amp; (p === 10 || p === 50 || p === 100 || p === 500 || p === 1000)) {
tick = getTickSize(p - 0.001);
}
let m = 10000;
let pInt = Math.round(p * m);
let tickInt = Math.round(tick * m);
let nextP;
if (pInt % tickInt !== 0) {
if (dir === 1) {
nextP = (Math.floor(pInt / tickInt) * tickInt + tickInt) / m;
} else {
nextP = (Math.floor(pInt / tickInt) * tickInt) / m;
}
} else {
nextP = Math.round((p * m) + (dir * tickInt)) / m;
}
if (nextP &lt; 0) nextP = 0;
shouldClearOtherOnEdit = false;
currentInput.value = nextP;
calculate();
}
function handleArrowKeys(e, currentInput, otherInput) {
if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
e.preventDefault();
let dir = e.key === 'ArrowUp' ? 1 : -1;
stepValue(currentInput, otherInput, dir);
}
}
function handleWheel(e, currentInput, otherInput) {
if (document.activeElement === currentInput) {
e.preventDefault();
let dir = e.deltaY &lt; 0 ? 1 : -1;
stepValue(currentInput, otherInput, dir);
}
}
buyInput.addEventListener('keydown', (e) =&gt; handleArrowKeys(e, buyInput, sellInput));
sellInput.addEventListener('keydown', (e) =&gt; handleArrowKeys(e, sellInput, buyInput));
buyInput.addEventListener('wheel', (e) =&gt; handleWheel(e, buyInput, sellInput), { passive: false });
sellInput.addEventListener('wheel', (e) =&gt; handleWheel(e, sellInput, buyInput), { passive: false });
dayTradeCheck.addEventListener('change', (e) =&gt; {
if (e.target.checked) {
taxInput.value = '0.15';
} else {
taxInput.value = '0.3';
}
calculate();
});
taxInput.addEventListener('input', (e) =&gt; {
if (e.target.value === '0.15') {
dayTradeCheck.checked = true;
} else {
dayTradeCheck.checked = false;
}
});
document.querySelectorAll('#stock-calculator-wrapper input').forEach(input =&gt; {
if (input.id !== 'calc-buyPrice' &amp;&amp; input.id !== 'calc-sellPrice' &amp;&amp; input.id !== 'calc-dayTrade') {
input.addEventListener('input', calculate);
}
});
function getTickSize(p) {
if (p &lt; 10) return 0.01;
if (p &lt; 50) return 0.05;
if (p &lt; 100) return 0.1;
if (p &lt; 500) return 0.5;
if (p &lt; 1000) return 1;
return 5;
}
function getNextPrice(p, dir) {
let tick = getTickSize(p);
if (dir === -1 &amp;&amp; (p === 10 || p === 50 || p === 100 || p === 500 || p === 1000)) {
tick = getTickSize(p - 0.001);
}
return Math.round((p + dir * tick) * 1000) / 1000;
}
function getCost(p, shares, discount, minFee) {
const feeRate = 0.001425 * (discount / 10);
const fee = Math.max(minFee, Math.round(p * shares * feeRate));
return p * shares + fee;
}
function getRevenue(p, shares, discount, minFee, taxRate) {
const feeRate = 0.001425 * (discount / 10);
const fee = Math.max(minFee, Math.round(p * shares * feeRate));
const tax = Math.round(p * shares * taxRate);
return p * shares - fee - tax;
}
function getMinSharesForFee(p, discount, minFee) {
const feeRate = 0.001425 * (discount / 10);
let shares = Math.floor((minFee - 0.5) / (p * feeRate));
if (shares &lt; 1) shares = 1;
while (Math.round(p * shares * feeRate) &lt; minFee) {
shares++;
}
return shares;
}
function formatMoney(num) {
const val = Math.round(num);
if (val &gt; 0) return `&lt;span class="profit"&gt;+${val.toLocaleString()} 元&lt;/span&gt;`;
if (val &lt; 0) return `&lt;span class="loss"&gt;${val.toLocaleString()} 元&lt;/span&gt;`;
return `&lt;span&gt;0 元&lt;/span&gt;`;
}
function calculate() {
const buyPrice = parseFloat(document.getElementById('calc-buyPrice').value) || 0;
const sellPrice = parseFloat(document.getElementById('calc-sellPrice').value) || 0;
let lots = parseFloat(document.getElementById('calc-lots').value);
if (!lots || lots &lt;= 0) lots = 1;
const discount = parseFloat(document.getElementById('calc-discount').value) || 2.3;
const minFeeOdd = parseFloat(document.getElementById('calc-minFeeOdd').value) || 20;
const minFeeRound = parseFloat(document.getElementById('calc-minFeeRound').value) || 20;
const taxRate = (parseFloat(document.getElementById('calc-tax').value) || 0) / 100;
const shares = lots * 1000;
let html = "";
const buyBadge = document.getElementById('calc-buyFeeBadge');
const sellBadge = document.getElementById('calc-sellFeeBadge');
if (buyPrice &gt; 0) {
let sMid = getMinSharesForFee(buyPrice, discount, minFeeOdd);
buyBadge.innerText = `零股低消: ${sMid} 股`;
buyBadge.style.display = 'inline-block';
} else {
buyBadge.style.display = 'none';
}
if (sellPrice &gt; 0) {
let sMid = getMinSharesForFee(sellPrice, discount, minFeeOdd);
sellBadge.innerText = `零股低消: ${sMid} 股`;
sellBadge.style.display = 'inline-block';
} else {
sellBadge.style.display = 'none';
}
if (buyPrice === 0 &amp;&amp; sellPrice === 0) {
document.getElementById('calc-result').innerHTML = `&lt;div class="empty-state"&gt;請輸入「買進價」或「賣出價」開始試算...&lt;/div&gt;`;
return;
}
if (buyPrice &gt; 0 &amp;&amp; sellPrice &gt; 0) {
let costRound = getCost(buyPrice, shares, discount, minFeeRound);
let revOdd = getRevenue(sellPrice, shares, discount, minFeeOdd, taxRate);
let profit1 = revOdd - costRound;
let costOdd = getCost(buyPrice, shares, discount, minFeeOdd);
let revRound = getRevenue(sellPrice, shares, discount, minFeeRound, taxRate);
let profit2 = revRound - costOdd;
html += `
&lt;div class="result-box" style="border-left-color: #10b981;"&gt;
&lt;h3&gt;⚖️ 預期損益試算 (買 ${buyPrice} / 賣 ${sellPrice})&lt;/h3&gt;
&lt;p class="note"&gt;已扣除所有手續費與證交稅&lt;/p&gt;
&lt;table&gt;
&lt;tr&gt;&lt;th&gt;操作方式&lt;/th&gt;&lt;th&gt;買進總成本&lt;/th&gt;&lt;th&gt;賣出總收入&lt;/th&gt;&lt;th&gt;實際淨損益&lt;/th&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;買整股 / 賣零股&lt;/td&gt;&lt;td&gt;${Math.round(costRound).toLocaleString()}&lt;/td&gt;&lt;td&gt;${Math.round(revOdd).toLocaleString()}&lt;/td&gt;&lt;td&gt;${formatMoney(profit1)}&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;買零股 / 賣整股&lt;/td&gt;&lt;td&gt;${Math.round(costOdd).toLocaleString()}&lt;/td&gt;&lt;td&gt;${Math.round(revRound).toLocaleString()}&lt;/td&gt;&lt;td&gt;${formatMoney(profit2)}&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
`;
}
else if (buyPrice &gt; 0) {
let cost1A = getCost(buyPrice, shares, discount, minFeeRound);
let sellP1A = buyPrice;
let rev1A = 0;
while (true) {
sellP1A = getNextPrice(sellP1A, 1);
rev1A = getRevenue(sellP1A, shares, discount, minFeeOdd, taxRate);
if (rev1A &gt; cost1A || sellP1A &gt; buyPrice * 2) break;
}
let minShares1A = getMinSharesForFee(sellP1A, discount, minFeeOdd);
let cost1B = getCost(buyPrice, shares, discount, minFeeOdd);
let sellP1B = buyPrice;
let rev1B = 0;
while (true) {
sellP1B = getNextPrice(sellP1B, 1);
rev1B = getRevenue(sellP1B, shares, discount, minFeeRound, taxRate);
if (rev1B &gt; cost1B || sellP1B &gt; buyPrice * 2) break;
}
html += `
&lt;div class="result-box"&gt;
&lt;h3&gt;🟢 尋找最低獲利賣價 (成本 ${buyPrice} 元)&lt;/h3&gt;
&lt;table&gt;
&lt;tr&gt;&lt;th&gt;操作方式&lt;/th&gt;&lt;th&gt;最低獲利賣價&lt;/th&gt;&lt;th&gt;預期淨利&lt;/th&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;買整股 / 賣零股&lt;/td&gt;&lt;td class="highlight"&gt;${sellP1A} &lt;span class="fee-badge table-badge"&gt;低消: ${minShares1A} 股&lt;/span&gt;&lt;/td&gt;&lt;td&gt;${formatMoney(rev1A - cost1A)}&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;買零股 / 賣整股&lt;/td&gt;&lt;td class="highlight"&gt;${sellP1B}&lt;/td&gt;&lt;td&gt;${formatMoney(rev1B - cost1B)}&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
`;
}
else if (sellPrice &gt; 0) {
let rev2C = getRevenue(sellPrice, shares, discount, minFeeRound, taxRate);
let buyP2C = sellPrice;
let cost2C = 0;
while (true) {
buyP2C = getNextPrice(buyP2C, -1);
cost2C = getCost(buyP2C, shares, discount, minFeeOdd);
if (rev2C &gt; cost2C || buyP2C &lt;= 0.01) break;
}
let minShares2C = getMinSharesForFee(buyP2C, discount, minFeeOdd);
let rev2D = getRevenue(sellPrice, shares, discount, minFeeOdd, taxRate);
let buyP2D = sellPrice;
let cost2D = 0;
while (true) {
buyP2D = getNextPrice(buyP2D, -1);
cost2D = getCost(buyP2D, shares, discount, minFeeRound);
if (rev2D &gt; cost2D || buyP2D &lt;= 0.01) break;
}
html += `
&lt;div class="result-box" style="border-left-color: #ef4444;"&gt;
&lt;h3&gt;🔴 尋找最高買回成本 (收入 ${sellPrice} 元)&lt;/h3&gt;
&lt;table&gt;
&lt;tr&gt;&lt;th&gt;操作方式&lt;/th&gt;&lt;th&gt;最高允許買價&lt;/th&gt;&lt;th&gt;預期淨利&lt;/th&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;賣整股 / 買零股&lt;/td&gt;&lt;td class="highlight"&gt;${buyP2C} &lt;span class="fee-badge table-badge"&gt;低消: ${minShares2C} 股&lt;/span&gt;&lt;/td&gt;&lt;td&gt;${formatMoney(rev2C - cost2C)}&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;賣零股 / 買整股&lt;/td&gt;&lt;td class="highlight"&gt;${buyP2D}&lt;/td&gt;&lt;td&gt;${formatMoney(rev2D - cost2D)}&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
`;
}
document.getElementById('calc-result').innerHTML = html;
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', calculate);
} else {
setTimeout(calculate, 100);
}
&lt;/script&gt;
&lt;/div&gt;</description></item></channel></rss>