<?xml version="1.0" encoding="UTF-8" standalone="no"?><rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" version="2.0">

<channel>
	<title>計算0123456789</title>
	<atom:link href="https://0123456789.tw/feed/" rel="self" type="application/rss+xml"/>
	<link>https://0123456789.tw</link>
	<description></description>
	<lastBuildDate>Wed, 02 Sep 2026 04:28:48 +0000</lastBuildDate>
	<language>zh-TW</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1</generator>
	<itunes:explicit>no</itunes:explicit><itunes:image href="http://en.gravatar.com/userimage/7521775/c9d0938851e6c18316c4dfa3dd2270b7.png"/><itunes:keywords>計算0123456789</itunes:keywords><itunes:summary>計算0123456789</itunes:summary><itunes:subtitle>0123456789.tw</itunes:subtitle><itunes:owner><itunes:email>t0918013013@gmail.com</itunes:email></itunes:owner><item>
		<title>定時開啟網址工具(搶票倒數.待辦提醒)</title>
		<link>https://0123456789.tw/urlopen/</link>
		
		
		<pubDate>Wed, 02 Sep 2026 04:27:06 +0000</pubDate>
				<category><![CDATA[計算blog文]]></category>
		<guid isPermaLink="false">https://0123456789.tw/?p=2239</guid>

					<description><![CDATA[前往使用：https://0123456789.tw/urlopentimer 使用這個定時開啟網址的工具可以 [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">前往使用：<a href="https://0123456789.tw/urlopentimer">https://0123456789.tw/urlopentimer</a></p>



<figure class="wp-block-image size-large"><img decoding="async" src="https://imgpoi.com/i/QRZEB5.png" alt=""/></figure>



<p class="wp-block-paragraph">使用這個定時開啟網址的工具可以搭配這一個快速複製貼上的工具，達到開啟網頁之後就快速把資訊複製貼上讓搶票更快速</p>



<figure class="wp-block-embed is-type-wp-embed is-provider-0123456789 wp-block-embed-0123456789"><div class="wp-block-embed__wrapper">
<blockquote class="wp-embedded-content" data-secret="0lWcvnbEBt"><a href="https://0123456789.tw/ticket/">快速複製貼上(搶票.搶商品.報名表單)</a></blockquote><iframe class="wp-embedded-content" sandbox="allow-scripts" security="restricted"  title="快速複製貼上(搶票.搶商品.報名表單) — 計算0123456789" src="https://0123456789.tw/ticket/embed/#?secret=aM2Fk1glJs#?secret=0lWcvnbEBt" data-secret="0lWcvnbEBt" width="500" height="282" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
</div></figure>



<figure class="wp-block-image size-large"><a href="https://0123456789.tw/urlopentimer/"><img decoding="async" src="https://imgpoi.com/i/QRZBJ9.png" alt=""/></a></figure>



<p class="wp-block-paragraph">前往使用：<a href="https://0123456789.tw/urlopentimer">https://0123456789.tw/urlopentimer</a></p>



<p class="wp-block-paragraph">提醒：多數瀏覽器會封鎖非使用者點擊觸發的分頁彈出。若時間到後沒有自動開啟新分頁，該列會顯示「點我開啟」按鈕，請手動點擊。<br>若想測試自動彈出功能，可在被封鎖後，於瀏覽器網址列右側找到彈出視窗封鎖圖示，點選「一律允許此網站顯示彈出式視窗」，之後同一頁籤內時間到即可自動開啟新分頁，不需再手動點擊。<br>設定內容會自動儲存在瀏覽器中，重新整理頁面不會遺失。</p>



<h2 class="wp-block-heading">常見問題</h2>



<p class="wp-block-paragraph">定時開啟網址工具會不會自動彈出新分頁？多數瀏覽器預設會封鎖非使用者點擊觸發的分頁彈出。若時間到後沒有自動開啟，畫面會顯示「點我開啟」按鈕，手動點擊即可；也可以在瀏覽器網址列允許此網站顯示彈出視窗，之後時間到即可自動開啟。設定的網址和時間會不會遺失？所有設定會自動儲存在瀏覽器的本機儲存空間（localStorage），重新整理頁面不會遺失，但清除瀏覽器資料或更換裝置、瀏覽器就需要重新設定。可以同時設定幾個網址排程？可以無限新增多列，每一列可設定不同的網址和開啟時間，並提供即時倒數計時，適合同時管理多個定時提醒。</p>
]]></content:encoded>
					
		
		
			<dc:creator>t0918013013@gmail.com (admin)</dc:creator></item>
		<item>
		<title>台灣蔬菜水果產季月曆-依「有生產的月份」整理｜不是「全年都買得到」</title>
		<link>https://0123456789.tw/wawamonth/</link>
		
		
		<pubDate>Wed, 26 Aug 2026 04:47:39 +0000</pubDate>
				<category><![CDATA[線上表格類]]></category>
		<category><![CDATA[日曆]]></category>
		<category><![CDATA[月份]]></category>
		<category><![CDATA[統計]]></category>
		<category><![CDATA[蔬菜]]></category>
		<guid isPermaLink="false">https://0123456789.tw/?p=2230</guid>

					<description><![CDATA[台灣蔬菜／水果產季月曆｜1～12月 台灣蔬菜／水果產季月曆 依「台灣有生產的月份」整理｜不是「全年都買得到」  [&#8230;]]]></description>
										<content:encoded><![CDATA[
<!doctype html>
<html lang="zh-Hant">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>台灣蔬菜／水果產季月曆｜1～12月</title>
<style>
*{box-sizing:border-box}body{margin:0;background:#f6f5f2;color:#424242;font-family:-apple-system,BlinkMacSystemFont,"Noto Sans TC","Microsoft JhengHei",sans-serif}
header{background:#fff;border-bottom:1px solid #ddd;text-align:center;padding:30px 16px 24px}h1{margin:0 0 7px;font-size:28px}header p{margin:0;color:#777}
.wrap{max-width:1200px;margin:auto;padding:20px}.toolbar{display:flex;gap:9px;flex-wrap:wrap;margin-bottom:10px}
button,input{font:inherit}button{border:1px solid #ccc;background:#fff;border-radius:10px;padding:9px 13px;cursor:pointer}
button.active{background:#A80000;color:#fff;border-color:#A80000}input{flex:1;min-width:220px;padding:10px 13px;border:1px solid #ccc;border-radius:10px;background:#fff}
.typebar{display:flex;gap:9px;margin-bottom:14px}
.months{display:grid;grid-template-columns:repeat(12,1fr);gap:6px;margin-bottom:18px}.months button{padding:8px 2px}
.card{background:#fff;border:1px solid #ddd;border-radius:14px;overflow:hidden}.card h2{margin:0;padding:15px 16px;border-bottom:1px solid #eee;font-size:20px}
.cat{padding:13px 16px}.cat-title{font-weight:700;margin-bottom:8px}.tags{display:flex;flex-wrap:wrap;gap:7px}.tag{background:#f0efec;border-radius:999px;padding:6px 10px;font-size:14px}
.grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}.item{background:#fff;border:1px solid #ddd;border-radius:12px;padding:14px}.item h3{margin:0 0 4px}.item small{color:#777}
.bar{display:grid;grid-template-columns:repeat(12,1fr);gap:3px;margin-top:10px}.dot{height:12px;border-radius:3px;background:#e8e8e8}.dot.on{background:#A80000}.dot.now{outline:2px solid #333;outline-offset:1px}
.month-labels{display:grid;grid-template-columns:repeat(12,1fr);font-size:10px;color:#999;text-align:center;margin-top:4px}
.note{margin-top:18px;background:#fff;border:1px solid #ddd;border-radius:10px;padding:14px;font-size:13px;line-height:1.75;color:#666}
.import-block{margin-top:16px;background:#fff;border:1px dashed #bbb;border-radius:14px;padding:15px 16px}
.import-block h2{margin:0 0 10px;font-size:17px;color:#424242}
.import-list{display:flex;flex-direction:column;gap:8px}
.import-row{display:flex;flex-wrap:wrap;align-items:baseline;gap:8px;padding:9px 11px;background:#f7f6f3;border-radius:9px}
.import-row .iname{font-weight:700;color:#424242}
.import-row .ibadge{background:#958E8A;color:#fff;font-size:11px;padding:3px 8px;border-radius:999px}
.import-row .ireason{color:#777;font-size:13px}
.import-item{background:#fff;border:1px solid #ddd;border-radius:12px;padding:14px;opacity:.85}
.import-item h3{margin:0 0 4px;display:flex;align-items:center;gap:7px}
.import-item .ibadge{background:#958E8A;color:#fff;font-size:11px;padding:3px 8px;border-radius:999px;font-weight:400}
@media(max-width:800px){.months{grid-template-columns:repeat(6,1fr)}.grid{grid-template-columns:repeat(2,1fr)}}
@media(max-width:500px){h1{font-size:23px}.wrap{padding:12px}.grid{grid-template-columns:1fr}}
</style>
</head>
<body>
<header><h1>台灣蔬菜／水果產季月曆</h1><p>依「台灣有生產的月份」整理｜不是「全年都買得到」</p></header>
<div class="wrap">

<div class="typebar">
<button class="active" data-type="veg">蔬菜</button>
<button data-type="fruit">水果</button>
</div>

<div class="toolbar">
<button class="active" data-view="month">依月份</button><button data-view="item">依名稱</button>
<input id="search" placeholder="搜尋：小白菜、高麗菜、玉米、菠菜…">
</div>
<div id="monthBtns" class="months"></div><div id="content"></div>

<div class="note">
<strong>本頁定義：</strong>「產季」指台灣有生產、可作為當季食材參考的月份；不等於其他月份完全沒有進口品或設施栽培。
<br><strong>資料整理原則：</strong>蔬菜部分你提供的 9～12 月清單作為主要基礎，1～8 月依台灣公開產季資料與季節性農作資訊補足；水果部分依農糧署公開產期分布資料與一般市場慣用產季彙整。不同品種、產地、海拔及氣候會造成產期差異，因此本表適合「吃當季」的生活型查詢，而非精確農產統計。
<br><strong>特別注意：</strong>例如小白菜在本表不是全年，而是依你的需求標示為 <b>9～12月</b>；大白菜則為 <b>11～12月</b>。菇類雖可全年供應，但若追求「田間當季」，也不應一律視為全年自然產季。
<br><strong>水果「台灣無生產」標注：</strong>凡本表列為無產季（例如藍莓、櫻桃、奇異果、榴槤、山竹等），代表台灣氣候不適合商業化栽培或僅有極少量試種，市售商品幾乎全為進口，因此不提供月份分布，僅列出並標注原因供參考。
</div>
</div>
<script>
// ---------- 蔬菜資料 ----------
const rawVeg=[["葉菜類", "A菜", "1-5,9-12"], ["葉菜類", "地瓜葉", "5-10"], ["葉菜類", "小白菜", "9-12"], ["葉菜類", "娃娃菜", "10-12"], ["葉菜類", "大白菜", "11-12"], ["葉菜類", "山茼蒿", "10-12"], ["葉菜類", "油菜", "1-5,9-12"], ["葉菜類", "福山萵苣", "9-12"], ["葉菜類", "空心菜", "5-10"], ["葉菜類", "芥蘭菜", "9-12"], ["葉菜類", "茴香菜", "10-12"], ["葉菜類", "莧菜", "5-10"], ["葉菜類", "蚵白菜", "1-5,9-12"], ["葉菜類", "青江菜", "1-5,9-12"], ["葉菜類", "高麗菜", "1-5,8-12"], ["葉菜類", "龍鬚菜", "5-10"], ["葉菜類", "美生菜", "10-4"], ["葉菜類", "茼蒿", "11-3"], ["葉菜類", "菠菜", "11-3"], ["葉菜類", "A菜心", "11-3"], ["根莖類", "地瓜", "1-12"], ["根莖類", "山藥", "9-12"], ["根莖類", "水蓮", "9-12"], ["根莖類", "烏殼綠竹筍", "5-10"], ["根莖類", "綠竹筍", "5-10"], ["根莖類", "芋頭", "9-12"], ["根莖類", "茭白筍", "5-10"], ["根莖類", "菱角", "8-12"], ["根莖類", "蓮藕", "7-12"], ["根莖類", "蘆筍", "3-11"], ["根莖類", "麻竹筍", "4-9"], ["根莖類", "A菜心", "11-3"], ["根莖類", "大頭菜", "11-3"], ["根莖類", "大心菜心", "11-3"], ["根莖類", "抱子芥菜", "11-3"], ["根莖類", "白蘿蔔", "11-4"], ["根莖類", "紅蘿蔔", "10-4"], ["根莖類", "荸薺", "9-12"], ["根莖類", "豆薯", "10-3"], ["根莖類", "馬鈴薯", "1-5"], ["瓜果類", "南瓜", "9-12"], ["瓜果類", "大黃瓜", "9-12"], ["瓜果類", "小黃瓜", "9-12"], ["瓜果類", "櫛瓜", "9-12"], ["瓜果類", "玉米", "7-12"], ["瓜果類", "玉米筍", "1-4,9-12"], ["瓜果類", "秋葵", "6-10"], ["瓜果類", "苦瓜", "5-12"], ["瓜果類", "青苦瓜", "5-12"], ["瓜果類", "茄子", "5-12"], ["瓜果類", "蒲瓜", "4-10"], ["瓜果類", "青木瓜", "1-12"], ["瓜果類", "番茄", "10-6"], ["瓜果類", "青花菜", "10-4"], ["瓜果類", "青花筍", "11-4"], ["瓜果類", "甜椒", "10-6"], ["瓜果類", "青椒", "9-12"], ["豆類", "四季豆", "9-4"], ["豆類", "菜豆", "9-4"], ["豆類", "毛豆", "6-12"], ["豆類", "皇帝豆", "11-2"], ["豆類", "甜豆", "11-3"], ["豆類", "荷蘭豆", "11-3"], ["豆類", "豆芽菜", "1-12"], ["豆類", "豌豆", "11-3"], ["豆類", "醜豆", "9-10"], ["辛香類", "九層塔", "5-10"], ["辛香類", "巴西里", "10-3"], ["辛香類", "芹菜", "10-6"], ["辛香類", "芹菜管", "10-3"], ["辛香類", "薑", "9-12"], ["辛香類", "青蒜", "11-3"], ["辛香類", "青蔥", "1-12"], ["辛香類", "青龍辣椒", "9-12"], ["辛香類", "辣椒", "10-6"], ["辛香類", "韭菜", "1-12"], ["辛香類", "韭菜花", "1-12"], ["辛香類", "韭菜黃", "1-12"], ["辛香類", "香菜", "10-3"], ["辛香類", "西洋芹", "10-3"], ["菇類", "乾香菇", "1-12"], ["菇類", "杏鮑菇", "1-12"], ["菇類", "生香菇", "1-12"], ["菇類", "白木耳", "1-12"], ["菇類", "秀珍菇", "1-12"], ["菇類", "金滑菇", "1-12"], ["菇類", "金針菇", "1-12"], ["菇類", "雪白菇", "1-12"], ["菇類", "鴻喜菇", "1-12"], ["菇類", "黑木耳", "5-10"], ["菇類", "洋菇", "10-5"]];

// ---------- 水果資料（有台灣產季）----------
const rawFruit=[
["熱帶／副熱帶","香蕉","1-12"],
["熱帶／副熱帶","鳳梨","1-12"],
["熱帶／副熱帶","木瓜","1-12"],
["熱帶／副熱帶","芭樂","1-12"],
["熱帶／副熱帶","蓮霧","11-4"],
["熱帶／副熱帶","火龍果","5-11"],
["熱帶／副熱帶","西瓜","4-9"],
["熱帶／副熱帶","香瓜","3-9"],
["熱帶／副熱帶","芒果","4-8"],
["熱帶／副熱帶","百香果","3-11"],
["熱帶／副熱帶","釋迦","7-2"],
["熱帶／副熱帶","鳳梨釋迦","11-4"],
["熱帶／副熱帶","酪梨","7-11"],
["核果仁果類","荔枝","5-7"],
["核果仁果類","龍眼","7-9"],
["核果仁果類","枇杷","2-4"],
["核果仁果類","梅子","3-5"],
["核果仁果類","李子","3-6"],
["核果仁果類","桃子／水蜜桃","4-9"],
["核果仁果類","水梨","6-9"],
["核果仁果類","葡萄","6-8,12-2"],
["核果仁果類","楊桃","8-2"],
["柑橘類","椪柑","11-1"],
["柑橘類","桶柑","12-3"],
["柑橘類","茂谷柑","12-3"],
["柑橘類","柳丁","10-2"],
["柑橘類","金柑／金橘","11-3"],
["柑橘類","檸檬","1-12"],
["柑橘類","文旦柚","8-10"],
["柑橘類","白柚","9-11"],
["漿果與其他","草莓","11-4"],
["漿果與其他","柿子","9-12"],
["漿果與其他","甜柿","10-12"]
];

// ---------- 台灣無生產（以進口為主）水果 ----------
const importFruit=[
["藍莓","台灣僅極少量高冷地試種，市售幾乎全為美國、智利等地進口"],
["蘋果","僅福壽山等高冷地小量栽培，市售以美國、日本、紐西蘭進口為主"],
["櫻桃","氣候不適合商業栽培，市售為美國、日本、智利進口"],
["奇異果","氣候限制，商業產量極少，市售以紐西蘭進口為主"],
["榴槤","熱帶果樹在台灣氣候下無法商業結果，全為泰國、馬來西亞進口"],
["山竹","不耐台灣氣候與環境，全為泰國、印尼等地進口"],
["蔓越莓","溫帶漿果，台灣未商業栽培，多為美國、加拿大進口（常見為果乾）"],
["覆盆莓","台灣未商業化栽培，市售多為進口或冷凍品"]
];

function expand(s){let out=[];s.split(",").forEach(p=>{let [a,b]=p.split("-").map(Number);if(a<=b){for(let i=a;i<=b;i++)out.push(i)}else{for(let i=a;i<=12;i++)out.push(i);for(let i=1;i<=b;i++)out.push(i)}});return [...new Set(out)]}

const DATA={
 veg:{ list: rawVeg.map(x=>[...x,expand(x[2])]), imports:[], label:"蔬菜", ph:"搜尋：小白菜、高麗菜、玉米、菠菜…" },
 fruit:{ list: rawFruit.map(x=>[...x,expand(x[2])]), imports: importFruit, label:"水果", ph:"搜尋：芒果、草莓、釋迦、檸檬…" }
};

let type="veg", mode="month", selected=new Date().getMonth()+1;

function cats(){ return [...new Set(DATA[type].list.map(x=>x[0]))]; }

function renderMonths(){monthBtns.innerHTML=Array.from({length:12},(_,i)=>`<button class="${selected===i+1?'active':''}" onclick="selectMonth(${i+1})">${i+1}月</button>`).join("")}
function selectMonth(m){selected=m;renderMonths();render()}

function importBlock(){
 const im=DATA[type].imports;
 if(!im.length) return "";
 if(mode==="month"){
  return `<div class="import-block"><h2>台灣不產，市售以進口為主</h2><div class="import-list">${im.map(x=>`<div class="import-row"><span class="iname">${x[0]}</span><span class="ibadge">無台灣產季</span><span class="ireason">${x[1]}</span></div>`).join("")}</div></div>`;
 }
 return "";
}

function render(){
 const q=search.value.trim().toLowerCase(),c=document.getElementById("content");
 const D=DATA[type].list, im=DATA[type].imports;
 if(mode==="month"){
  let h=`<div class="card"><h2>${selected}月｜台灣當季${DATA[type].label}</h2>`;
  cats().forEach(cat=>{let a=D.filter(x=>x[0]===cat&&x[3].includes(selected)&&(!q||x[1].toLowerCase().includes(q)));if(a.length)h+=`<div class="cat"><div class="cat-title">${cat}</div><div class="tags">${a.map(x=>`<span class="tag">${x[1]}</span>`).join("")}</div></div>`});
  c.innerHTML=h+"</div>"+importBlock();
 }else{
  let a=D.filter(x=>!q||x[1].toLowerCase().includes(q));
  let itemsHtml=a.map(x=>`<div class="item"><h3>${x[1]}</h3><small>${x[0]}｜產季：${x[2]}月</small><div class="bar">${Array.from({length:12},(_,i)=>`<span class="dot ${x[3].includes(i+1)?'on':''} ${selected===i+1?'now':''}"></span>`).join("")}</div><div class="month-labels">${Array.from({length:12},(_,i)=>`<span>${i+1}</span>`).join("")}</div></div>`).join("");
  let impFiltered=im.filter(x=>!q||x[0].toLowerCase().includes(q));
  let impHtml=impFiltered.map(x=>`<div class="import-item"><h3>${x[0]}<span class="ibadge">無台灣產季</span></h3><small>${x[1]}</small></div>`).join("");
  c.innerHTML=`<div class="grid">${itemsHtml}${impHtml}</div>`;
 }
}

document.querySelectorAll("[data-view]").forEach(b=>b.onclick=()=>{mode=b.dataset.view;document.querySelectorAll("[data-view]").forEach(x=>x.classList.remove("active"));b.classList.add("active");render()});
document.querySelectorAll("[data-type]").forEach(b=>b.onclick=()=>{type=b.dataset.type;document.querySelectorAll("[data-type]").forEach(x=>x.classList.remove("active"));b.classList.add("active");search.placeholder=DATA[type].ph;search.value="";render()});

search.oninput=render;renderMonths();render();
</script>
</body></html>
]]></content:encoded>
					
		
		
			<dc:creator>t0918013013@gmail.com (admin)</dc:creator></item>
		<item>
		<title>電商分潤網址清洗器</title>
		<link>https://0123456789.tw/shopeeurl/</link>
		
		
		<pubDate>Thu, 20 Aug 2026 04:32:23 +0000</pubDate>
				<category><![CDATA[線上工具類]]></category>
		<category><![CDATA[分潤]]></category>
		<category><![CDATA[短網址]]></category>
		<category><![CDATA[蝦皮]]></category>
		<category><![CDATA[解析]]></category>
		<category><![CDATA[還原網址]]></category>
		<guid isPermaLink="false">https://0123456789.tw/?p=2226</guid>

					<description><![CDATA[點此前往使用：https://0123456789.tw/shopeeurl/ 貼上分享連結，例如 threa [&#8230;]]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-full"><a href="https://0123456789.tw/shopeeurl/"><img fetchpriority="high" decoding="async" width="581" height="555" src="https://0123456789.tw/wp-content/uploads/2026/08/iShot_2026-08-20_12.25.35.jpg" alt="" class="wp-image-2227" srcset="https://0123456789.tw/wp-content/uploads/2026/08/iShot_2026-08-20_12.25.35.jpg 581w, https://0123456789.tw/wp-content/uploads/2026/08/iShot_2026-08-20_12.25.35-300x287.jpg 300w" sizes="(max-width: 581px) 100vw, 581px" /></a></figure>



<p class="wp-block-paragraph"><a href="https://0123456789.tw/shopeeurl/">點此前往使用：https://0123456789.tw/shopeeurl/</a></p>



<p class="wp-block-paragraph">貼上分享連結，例如 threads.com / facebook 轉址、蝦皮短網址、帶有 fbclid、utm_source 等追蹤碼的網址， 自動展開所有跳轉並移除追蹤碼、推薦碼，取得最乾淨的原始網址。</p>
]]></content:encoded>
					
		
		
			<dc:creator>t0918013013@gmail.com (admin)</dc:creator></item>
		<item>
		<title>台灣交通號誌互動判讀器</title>
		<link>https://0123456789.tw/traffic-signal/</link>
		
		
		<pubDate>Wed, 19 Aug 2026 04:10:46 +0000</pubDate>
				<category><![CDATA[本站內嵌計算機]]></category>
		<category><![CDATA[交通號誌]]></category>
		<category><![CDATA[判讀]]></category>
		<category><![CDATA[燈號]]></category>
		<guid isPermaLink="false">https://0123456789.tw/?p=2214</guid>

					<description><![CDATA[台灣交通號誌互動判讀器 台灣交通號誌互動判讀器 自己點亮燈號，立即判斷「左轉、直行、右轉」能不能走 ① 點選號 [&#8230;]]]></description>
										<content:encoded><![CDATA[
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>台灣交通號誌互動判讀器</title>
<style>
*{box-sizing:border-box}
:root{
  --bg:#f1f1f1;--panel:#f1f1f1;--panel2:#f1f1f1;--line:#3a404c;
  --text:#666;--muted:#aeb4bf;--green:#20d15a;--yellow:#ffd21c;
  --red:#ff3348;--blue:#4da3ff;--ok:#24d36b
}
body{margin:0;background:#f1f1f1;color:var(--text);
font-family:-apple-system,BlinkMacSystemFont,"Noto Sans TC","PingFang TC","Microsoft JhengHei",sans-serif}
.wrap{max-width:1050px;margin:auto;padding:24px 16px 45px}
h1{text-align:center;font-size:clamp(26px,5vw,42px);margin:8px 0 6px}
.sub{text-align:center;color:var(--muted);margin:0 0 24px}
.grid{display:grid;grid-template-columns:330px 1fr;gap:18px}
.card{background:rgba(24,27,34,.96);border:1px solid var(--line);border-radius:18px;padding:18px}
h2{font-size:20px;margin:0 0 14px}
.traffic{background:#08090b;border-radius:28px;padding:20px 28px;width:170px;margin:5px auto 16px}
.lamp{width:82px;height:82px;border-radius:50%;margin:12px auto;background:#2a2d33;border:5px solid #17191e;
box-shadow:inset 0 0 15px #000;cursor:pointer;transition:.18s}
.lamp.red.on{background:var(--red);box-shadow:0 0 28px rgba(255,51,72,.65)}
.lamp.yellow.on{background:var(--yellow);box-shadow:0 0 28px rgba(255,210,28,.65)}
.lamp.green.on{background:var(--green);box-shadow:0 0 28px rgba(32,209,90,.65)}
.arrowbox{display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px;margin-top:12px}
.arrow{background:#272b34;border:1px solid #454b57;border-radius:10px;padding:11px 4px;text-align:center;cursor:pointer;font-size:24px}
.arrow.active{border-color:var(--green);background:#12361f;color:var(--green)}
.small{font-size:12px;color:var(--muted);text-align:center}
.btns{display:grid;grid-template-columns:repeat(2,1fr);gap:8px}
.preset{background:#252a33;border:1px solid #454b57;color:#fff;padding:11px 8px;border-radius:10px;cursor:pointer;font-size:13px;line-height:1.4}
.preset:hover{background:#303641}
.result{min-height:280px}
.status{display:flex;align-items:center;gap:12px;border-radius:14px;padding:15px;margin-bottom:16px;background:#22262f}
.status-dot{width:17px;height:17px;border-radius:50%;background:#777;flex:none}
.status-dot.ok{background:var(--green);box-shadow:0 0 14px rgba(32,209,90,.6)}
.status-dot.warn{background:var(--yellow);box-shadow:0 0 14px rgba(255,210,28,.5)}
.status-dot.stop{background:var(--red);box-shadow:0 0 14px rgba(255,51,72,.5)}
.status strong{font-size:21px}
.directions{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}
.dir{padding:18px 8px;text-align:center;border-radius:14px;background:#242832;border:1px solid #353b46}
.dir .icon{font-size:34px;display:block;margin-bottom:5px}
.dir.yes{background:#11331e;border-color:#1ca94e;color:#69ed91}
.dir.no{color:#ff6878;background:#2d1b20;border-color:#63313a}
.dir.caution{color:#ffe36a;background:#332e15;border-color:#6b5d19}
.note{margin-top:15px;padding:13px;border-radius:12px;background:#12151a;color:#c9ced7;line-height:1.7}
.rules{margin-top:18px}
.rules button{width:100%;text-align:left}
.tag{display:inline-block;padding:4px 8px;border-radius:99px;font-size:12px;background:#303641;color:#cbd2dd;margin-right:6px}
.special{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-top:12px}
.special .preset{min-height:62px}
footer{text-align:center;color:#747b87;font-size:12px;margin-top:22px;line-height:1.7}
@media(max-width:760px){.grid{grid-template-columns:1fr}.special{grid-template-columns:1fr}.traffic{width:150px;padding:15px 22px}.lamp{width:72px;height:72px}}
</style>
</head>
<body>
<div class="wrap">
<h1>台灣交通號誌互動判讀器</h1>
<p class="sub">自己點亮燈號，立即判斷「左轉、直行、右轉」能不能走</p>

<div class="grid">
<section class="card">
<h2>① 點選號誌</h2>
<div class="traffic">
  <div id="red" class="lamp red" title="點擊切換紅燈"></div>
  <div id="yellow" class="lamp yellow" title="點擊切換黃燈"></div>
  <div id="green" class="lamp green" title="點擊切換綠燈"></div>
</div>
<div class="small">圓形燈只能單選；箭頭可「同時」點亮多個</div>

<div class="arrowbox">
  <div class="arrow" data-arrow="left">←</div>
  <div class="arrow" data-arrow="straight">↑</div>
  <div class="arrow" data-arrow="right">→</div>
</div>

<h2 style="margin-top:20px">快速情境</h2>
<div class="btns">
  <button class="preset" data-preset="green"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f7e2.png" alt="🟢" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 圓形綠燈</button>
  <button class="preset" data-preset="green_left"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f7e2.png" alt="🟢" class="wp-smiley" style="height: 1em; max-height: 1em;" />＋<img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f7e2.png" alt="🟢" class="wp-smiley" style="height: 1em; max-height: 1em;" />← 綠燈＋左箭頭</button>
  <button class="preset" data-preset="straight_only"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f7e2.png" alt="🟢" class="wp-smiley" style="height: 1em; max-height: 1em;" />↑ 僅直行箭頭</button>
  <button class="preset" data-preset="straight_right"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f7e2.png" alt="🟢" class="wp-smiley" style="height: 1em; max-height: 1em;" />↑→ 直行＋右箭頭</button>
  <button class="preset" data-preset="red_left"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f534.png" alt="🔴" class="wp-smiley" style="height: 1em; max-height: 1em;" />＋<img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f7e2.png" alt="🟢" class="wp-smiley" style="height: 1em; max-height: 1em;" />← 紅燈＋左箭頭</button>
  <button class="preset" data-preset="red_right"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f534.png" alt="🔴" class="wp-smiley" style="height: 1em; max-height: 1em;" />＋<img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f7e2.png" alt="🟢" class="wp-smiley" style="height: 1em; max-height: 1em;" />→ 紅燈＋右箭頭</button>
  <button class="preset" data-preset="red"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f534.png" alt="🔴" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 圓形紅燈</button>
  <button class="preset" data-preset="yellow"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f7e1.png" alt="🟡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 圓形黃燈</button>
  <button class="preset" data-preset="flashgreen"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f7e2.png" alt="🟢" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 閃光圓形綠燈</button>
  <button class="preset" data-preset="flashyellow"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f7e1.png" alt="🟡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 閃光黃燈</button>
  <button class="preset" data-preset="flashred"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f534.png" alt="🔴" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 閃光紅燈</button>
  <button class="preset" data-preset="flashleft"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f7e2.png" alt="🟢" class="wp-smiley" style="height: 1em; max-height: 1em;" />← 閃光左箭頭</button>
  <button class="preset" data-preset="off">清除</button>
</div>
</section>

<section class="card result">
<h2>② 判讀結果</h2>
<div id="status" class="status">
  <span id="dot" class="status-dot"></span>
  <div><strong id="title">請點選燈號</strong><div id="desc">選擇左側號誌後，這裡會顯示行駛規則。</div></div>
</div>

<div class="directions">
  <div id="leftResult" class="dir"><span class="icon">↰</span><b>左轉</b><div>—</div></div>
  <div id="straightResult" class="dir"><span class="icon">↑</span><b>直行</b><div>—</div></div>
  <div id="rightResult" class="dir"><span class="icon">↱</span><b>右轉</b><div>—</div></div>
</div>
<div id="note" class="note">提示：圓形綠燈准許直行或左、右轉；箭頭綠燈只可依箭頭方向行駛（可同時亮不只一個箭頭）。</div>

<div class="rules">
<h2 style="margin-top:22px">③ 特殊號誌補充</h2>
<div class="special">
<button class="preset" data-special="pedgreen"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f6b6.png" alt="🚶" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 綠色行人</button>
<button class="preset" data-special="pedred"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f6d1.png" alt="🛑" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 紅色行人</button>
<button class="preset" data-special="pedflash"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f6b6-200d-2642-fe0f.png" alt="🚶‍♂️" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 閃光行人</button>
<button class="preset" data-special="lanegreen"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b07.png" alt="⬇" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 綠色車道箭頭</button>
<button class="preset" data-special="lanered"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 紅色 X</button>
<button class="preset" data-special="rail"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f686.png" alt="🚆" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 鐵路平交道</button>
</div>
</div>
</section>
</div>

<footer>
本工具為交通號誌學習工具。實際行車仍應以現場號誌、標誌、標線、交通指揮及相關法規為準。
</footer>
</div>

<script>
(function(){
'use strict';

// state.color：圓形燈（單選：red / yellow / green / null）
// state.arrows：箭頭燈，可同時多選 {left,straight,right}
var state={color:null,arrows:{left:false,straight:false,right:false},flash:false,special:null};

var presets={
 green:{color:'green'},
 green_left:{color:'green',arrows:['left']},
 straight_only:{color:null,arrows:['straight']},
 straight_right:{color:null,arrows:['straight','right']},
 red_left:{color:'red',arrows:['left']},
 red_right:{color:'red',arrows:['right']},
 red:{color:'red'},
 yellow:{color:'yellow'},
 flashgreen:{color:'green',flash:true},
 flashyellow:{color:'yellow',flash:true},
 flashred:{color:'red',flash:true},
 flashleft:{color:null,arrows:['left'],flash:true},
 off:{}
};

function $(id){return document.getElementById(id)}

function setState(o){
 var arrowsObj={left:false,straight:false,right:false};
 (o.arrows||[]).forEach(function(a){arrowsObj[a]=true});
 state={color:o.color||null,arrows:arrowsObj,flash:!!o.flash,special:null};
 render();
}

function toggleArrow(name){
 var na={left:state.arrows.left,straight:state.arrows.straight,right:state.arrows.right};
 na[name]=!na[name];
 state={color:state.color,arrows:na,flash:state.flash,special:null};
 render();
}

function toggleColor(c){
 state={color:(state.color===c?null:c),arrows:state.arrows,flash:false,special:null};
 render();
}

function activeArrowList(){
 var list=[];
 ['left','straight','right'].forEach(function(a){if(state.arrows[a])list.push(a)});
 return list;
}

function result(el,cls,text){
 el.className='dir '+cls;
 el.querySelector('div').textContent=text;
}

var LABEL={left:'左轉',straight:'直行',right:'右轉'};

function render(){
 ['red','yellow','green'].forEach(function(c){
   $(c).classList.toggle('on',state.color===c);
 });
 document.querySelectorAll('.arrow').forEach(function(a){
   var name=a.getAttribute('data-arrow');
   a.classList.toggle('active',!!state.arrows[name]);
 });

 var title=$('title'),desc=$('desc'),dot=$('dot'),note=$('note');
 dot.className='status-dot';
 var L='—',S='—',R='—',lc='',sc='',rc='';
 var arrows=activeArrowList();
 var hasArrows=arrows.length>0;

 function setDir(name,ok,cautionOnly){
  var mark = ok ? (cautionOnly?'&#x26a0; 儘量不再'+LABEL[name]:'✓ 可以') : '✕ 禁止';
  var cls = ok ? (cautionOnly?'caution':'yes') : 'no';
  if(name==='left'){L=mark;lc=cls}
  if(name==='straight'){S=mark;sc=cls}
  if(name==='right'){R=mark;rc=cls}
 }

 if(!state.color && !hasArrows && !state.special){
   title.textContent='請點選燈號'; desc.textContent='選擇左側號誌後，這裡會顯示行駛規則。';
 } else if(state.special){
   renderSpecial(); return;

 // ===== 閃光號誌 =====
 } else if(state.flash && state.color==='red'){
   title.textContent='閃光紅燈';
   desc.textContent='先停車，再確認安全後通過。';
   dot.className='status-dot stop';
   L=S=R='&#x26d4; 停車';lc=sc=rc='no';
   note.textContent='必須先停止於路口前，確認安全後才可通過。';
 } else if(state.flash && state.color==='yellow'){
   title.textContent='閃光黃燈';
   desc.textContent='減速、注意，確認安全後小心通過。';
   dot.className='status-dot warn';
   L=S=R='&#x26a0;';lc=sc=rc='caution';
   note.textContent='閃光黃燈不是「看到就加速」，應減速並注意其他車輛、行人。';
 } else if(state.flash && hasArrows){
   title.textContent='閃光箭頭綠燈（可能不只一個）';
   desc.textContent='表示該方向綠燈時段即將結束；已進入路口者可依規定續行。';
   dot.className='status-dot warn';
   ['left','straight','right'].forEach(function(name){
     setDir(name, arrows.indexOf(name)>-1, true);
   });
   note.textContent='閃光箭頭綠燈表示該方向綠燈時段即將結束，尚未進入路口者應儘量不要再進入；未亮箭頭的方向不可通行。';
 } else if(state.flash && state.color==='green'){
   title.textContent='閃光圓形綠燈';
   desc.textContent='綠燈時段即將結束，尚未進入路口者應儘量不要再進入。';
   dot.className='status-dot warn';
   L=S=R='&#x26a0;';lc=sc=rc='caution';
   note.textContent='已進入路口者可繼續通行；尚未進入路口者應儘量不要再進入。';

 // ===== 圓形綠燈：不論箭頭是否同時亮，三個方向皆可行駛 =====
 } else if(state.color==='green'){
   title.textContent = hasArrows ? '圓形綠燈＋方向箭頭' : '圓形綠燈';
   desc.textContent='圓形綠燈准許直行或左、右轉；同時亮的箭頭僅為加強指示，不會限縮方向。';
   dot.className='status-dot ok';
   L='✓ 可以';S='✓ 可以';R='✓ 可以';lc=sc=rc='yes';
   note.textContent='仍須注意轉彎車讓直行車、行人，以及現場的標誌、標線與其他交通管制。';

 // ===== 沒有圓形綠燈，但有一個以上綠色箭頭亮著（可與紅燈同時、也可單獨亮） =====
 } else if(hasArrows){
   if(state.color==='red'){
     title.textContent='紅燈＋方向箭頭（可能不只一個）';
     desc.textContent='圓形紅燈亮著，但可依已亮起的箭頭方向行駛。';
   } else if(state.color==='yellow'){
     title.textContent='黃燈＋方向箭頭';
     desc.textContent='圓形黃燈原則上應停止，但已亮起的箭頭方向仍可通行，應減速注意。';
   } else {
     title.textContent='方向箭頭號誌（可能不只一個）';
     desc.textContent='沒有圓形燈號時，僅能依已亮起的箭頭方向行駛。';
   }
   dot.className='status-dot ok';
   ['left','straight','right'].forEach(function(name){
     setDir(name, arrows.indexOf(name)>-1, false);
   });
   note.textContent='箭頭綠燈是方向性指示，可能同時亮不只一個；未亮箭頭的方向即使圓形燈是紅／黃燈或未亮，也一律禁止通行。';

 // ===== 只有圓形燈，沒有任何箭頭 =====
 } else if(state.color==='yellow'){
   title.textContent='圓形黃燈';
   desc.textContent='紅燈即將出現，原則上應停止。';
   dot.className='status-dot warn';
   L=S=R='&#x26a0; 原則停止';lc=sc=rc='caution';
   note.textContent='已超越停止線或已無法安全停車者，應依規定繼續通過，不應為了等紅燈而急煞。';
 } else if(state.color==='red'){
   title.textContent='圓形紅燈';
   desc.textContent='禁止通行，不得超越停止線進入路口。';
   dot.className='status-dot stop';
   L=S=R='✕ 禁止';lc=sc=rc='no';
   note.textContent='紅燈狀態下不能因為「路上沒車」就自行通行；除非同時有合法允許該方向的號誌指示。';
 }
 result($('leftResult'),lc,L); result($('straightResult'),sc,S); result($('rightResult'),rc,R);
}

function renderSpecial(){
 var title=$('title'),desc=$('desc'),dot=$('dot'),note=$('note');
 var sp=state.special;
 if(sp==='pedgreen'){title.textContent='綠色行人號誌';desc.textContent='行人可依行人號誌穿越道路。';dot.className='status-dot ok';note.textContent='車輛駕駛仍應依車輛號誌及相關規定，注意行人穿越。'}
 if(sp==='pedred'){title.textContent='紅色行人號誌';desc.textContent='行人禁止進入穿越道。';dot.className='status-dot stop';note.textContent='車輛仍依車輛號誌行駛，並注意行人。'}
 if(sp==='pedflash'){title.textContent='閃光行人號誌';desc.textContent='表示行人綠燈時段即將結束，應儘速通過。';dot.className='status-dot warn';note.textContent='不要在閃光階段突然進入穿越道。'}
 if(sp==='lanegreen'){title.textContent='綠色車道箭頭';desc.textContent='該車道依箭頭指示方向行駛。';dot.className='status-dot ok';note.textContent='車道管制號誌與一般紅綠燈的判讀方式不同，須依車道上方號誌行駛。'}
 if(sp==='lanered'){title.textContent='紅色 X 車道號誌';desc.textContent='該車道禁止通行。';dot.className='status-dot stop';note.textContent='應依現場其他車道號誌改道，不可駛入紅色 X 所管制的車道。'}
 if(sp==='rail'){title.textContent='鐵路平交道號誌';desc.textContent='看到紅燈閃爍、警鈴或遮斷設備動作時，應停止。';dot.className='status-dot stop';note.textContent='平交道安全優先，禁止搶越或穿越已啟動的遮斷設備。'}
 result($('leftResult'),'','—');result($('straightResult'),'','—');result($('rightResult'),'','—');
}

document.querySelectorAll('.lamp').forEach(function(el){
 el.addEventListener('click',function(){ toggleColor(el.id); });
});
document.querySelectorAll('.arrow').forEach(function(el){
 el.addEventListener('click',function(){ toggleArrow(el.getAttribute('data-arrow')); });
});
document.querySelectorAll('[data-preset]').forEach(function(el){
 el.addEventListener('click',function(){setState(presets[el.getAttribute('data-preset')])});
});
document.querySelectorAll('[data-special]').forEach(function(el){
 el.addEventListener('click',function(){
   state={color:null,arrows:{left:false,straight:false,right:false},flash:false,special:el.getAttribute('data-special')};
   ['red','yellow','green'].forEach(function(c){$(c).classList.remove('on')});
   document.querySelectorAll('.arrow').forEach(function(a){a.classList.remove('active')});
   renderSpecial();
 });
});
render();
})();
</script>
</body>
</html>



<figure class="wp-block-image size-large"><img decoding="async" src="https://imgpoi.com/i/TB0PO2.png" alt=""/></figure>



<figure class="wp-block-image size-large"><img decoding="async" src="https://imgpoi.com/i/TB04KM.jpg" alt=""/></figure>
]]></content:encoded>
					
		
		
			<dc:creator>t0918013013@gmail.com (admin)</dc:creator></item>
		<item>
		<title>「攝影計算」曝光轉盤-曝光計算器</title>
		<link>https://0123456789.tw/ev/</link>
		
		
		<pubDate>Sat, 15 Aug 2026 05:32:33 +0000</pubDate>
				<category><![CDATA[本站內嵌計算機]]></category>
		<category><![CDATA[攝影]]></category>
		<guid isPermaLink="false">https://0123456789.tw/?p=2201</guid>

					<description><![CDATA[曝光計算器 &#8211; EV / 光圈 / 快門 即時換算 ⚡ 曝光計算器 原理與圓形曝光轉盤相同：EV  [&#8230;]]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-full"><img decoding="async" width="568" height="568" src="https://0123456789.tw/wp-content/uploads/2026/08/iShot_2026-08-15_13.39.08.jpg" alt="" class="wp-image-2205" srcset="https://0123456789.tw/wp-content/uploads/2026/08/iShot_2026-08-15_13.39.08.jpg 568w, https://0123456789.tw/wp-content/uploads/2026/08/iShot_2026-08-15_13.39.08-300x300.jpg 300w, https://0123456789.tw/wp-content/uploads/2026/08/iShot_2026-08-15_13.39.08-150x150.jpg 150w" sizes="(max-width: 568px) 100vw, 568px" /></figure>



<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>曝光計算器 &#8211; EV / 光圈 / 快門 即時換算</title>
<style>
  .expocalc-wrap * { box-sizing: border-box; }
  .expocalc-wrap {
    max-width: 680px;
    margin: 0 auto;
    font-family: -apple-system, "PingFang TC", "Microsoft JhengHei", sans-serif;
    color: #2c2c2c;
    padding: 16px;
    background: #f5f5f5;
  }
  .expocalc-title {
    font-size: 22px;
    font-weight: 700;
    color: #A80000;
    margin: 0 0 4px;
  }
  .expocalc-sub {
    font-size: 13px;
    color: #958E8A;
    margin: 0 0 18px;
  }
  .expocalc-card {
    background: #ffffff;
    border: 1px solid #d0d0d0;
    border-radius: 10px;
    padding: 16px;
    margin-bottom: 16px;
  }
  .expocalc-card h2 {
    font-size: 16px;
    margin: 0 0 12px;
    color: #424242;
    border-left: 4px solid #A80000;
    padding-left: 8px;
  }
  .expocalc-row {
    display: flex;
    gap: 10px;
    flex-wrap: wrap;
    margin-bottom: 10px;
    align-items: center;
  }
  .expocalc-field {
    flex: 1;
    min-width: 130px;
  }
  .expocalc-field label {
    display: block;
    font-size: 12px;
    color: #958E8A;
    margin-bottom: 4px;
  }
  .expocalc-field input,
  .expocalc-field select {
    width: 100%;
    padding: 9px 10px;
    border: 1px solid #d0d0d0;
    border-radius: 6px;
    font-size: 15px;
    color: #2c2c2c;
    background: #fff;
  }
  .expocalc-btn {
    background: #A80000;
    color: #fff;
    border: none;
    padding: 10px 18px;
    border-radius: 6px;
    font-size: 15px;
    font-weight: 600;
    cursor: pointer;
    width: 100%;
  }
  .expocalc-btn:active { opacity: 0.85; }
  .expocalc-result {
    margin-top: 12px;
    background: #EEEEEE;
    border-radius: 8px;
    padding: 12px 14px;
    font-size: 14px;
    line-height: 1.6;
  }
  .expocalc-result strong { color: #A80000; font-size: 18px; }
  .expocalc-table-wrap { overflow-x: auto; }
  table.expocalc-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
    margin-top: 10px;
  }
  table.expocalc-table th, table.expocalc-table td {
    border: 1px solid #d0d0d0;
    padding: 7px 6px;
    text-align: center;
  }
  table.expocalc-table th {
    background: #424242;
    color: #fff;
    font-weight: 500;
  }
  table.expocalc-table tr:nth-child(even) td { background: #f5f5f5; }
  .expocalc-zonebar {
    display: flex;
    border-radius: 6px;
    overflow: hidden;
    margin: 10px 0 4px;
    border: 1px solid #d0d0d0;
  }
  .expocalc-zonebar span {
    flex: 1;
    text-align: center;
    font-size: 11px;
    padding: 6px 0;
    color: #fff;
    cursor: pointer;
    user-select: none;
  }
  .expocalc-zonebar span.active {
    outline: 2px solid #A80000;
    outline-offset: -2px;
    font-weight: 700;
  }
  .expocalc-note {
    font-size: 12px;
    color: #958E8A;
    margin-top: 8px;
    line-height: 1.6;
  }
  .expocalc-hint {
    font-size: 12px;
    color: #A80000;
    margin-top: 4px;
  }
</style>
</head>
<body>

<div class="expocalc-wrap" id="expocalcRoot">
  <div class="expocalc-title"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a1.png" alt="⚡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 曝光計算器</div>
  <div class="expocalc-sub">原理與圓形曝光轉盤相同：EV = log₂(光圈² ÷ 快門秒數)，只是把「對齊刻度」換成「直接輸入」</div>

  <!-- 區塊一：EV -> 光圈/快門對照表，並支援 Zone System 偏移 -->
  <div class="expocalc-card">
    <h2>從點測光表 EV 值查完整光圈／快門對照表</h2>
    <div class="expocalc-row">
      <div class="expocalc-field">
        <label>測光表讀數 EV</label>
        <input type="number" id="expocalcEvInput" step="0.1" placeholder="例如 10">
      </div>
      <div class="expocalc-field">
        <label>ISO（僅供記錄，需與測光表一致）</label>
        <input type="number" id="expocalcIsoInput" step="1" placeholder="例如 100">
      </div>
    </div>

    <label style="display:block;font-size:12px;color:#958E8A;margin-bottom:4px;">
      Zone System 影調偏移（預設 V = 中間灰，不調整）
    </label>
    <div class="expocalc-zonebar" id="expocalcZonebar"></div>
    <div class="expocalc-hint" id="expocalcZoneDesc"></div>

    <div class="expocalc-row" style="margin-top:12px;">
      <button class="expocalc-btn" data-action="calc-table">計算並產生對照表</button>
    </div>

    <div class="expocalc-result" id="expocalcTableResult" style="display:none;"></div>
  </div>

  <!-- 區塊二：已知光圈+快門，反推 EV -->
  <div class="expocalc-card">
    <h2>已知光圈 + 快門，反推 EV 值</h2>
    <div class="expocalc-row">
      <div class="expocalc-field">
        <label>光圈 f/</label>
        <select id="expocalcApertureSelect"></select>
      </div>
      <div class="expocalc-field">
        <label>快門</label>
        <select id="expocalcShutterSelect"></select>
      </div>
    </div>
    <button class="expocalc-btn" data-action="calc-ev">計算 EV</button>
    <div class="expocalc-result" id="expocalcEvResult" style="display:none;"></div>
  </div>

  <div class="expocalc-note">
    說明：EV（曝光值）只由光圈與快門的組合決定，測光表在讀取當下已經把 ISO 換算進 EV 裡，所以查表時不需要再套一次 ISO；ISO 欄位只是方便您核對相機與測光表設定是否一致。若把某個高光點測光後想放在 Zone VIII（比 Zone V 亮 3 級），代表需要比表列數值再增加 3 級曝光量，本工具已自動處理這個偏移計算。
  </div>
</div>

<script>
(function () {
  'use strict';

  var APERTURES = [1, 1.4, 2, 2.8, 4, 5.6, 8, 11, 16, 22, 32];
  var SHUTTERS = [
    { label: '1/4000', sec: 1/4000 }, { label: '1/2000', sec: 1/2000 },
    { label: '1/1000', sec: 1/1000 }, { label: '1/500', sec: 1/500 },
    { label: '1/250', sec: 1/250 }, { label: '1/125', sec: 1/125 },
    { label: '1/60', sec: 1/60 }, { label: '1/30', sec: 1/30 },
    { label: '1/15', sec: 1/15 }, { label: '1/8', sec: 1/8 },
    { label: '1/4', sec: 1/4 }, { label: '1/2', sec: 1/2 },
    { label: '1"', sec: 1 }, { label: '2"', sec: 2 },
    { label: '4"', sec: 4 }, { label: '8"', sec: 8 },
    { label: '15"', sec: 15 }, { label: '30"', sec: 30 }
  ];

  var ZONES = [
    { v: 0, label: '0', desc: '全黑，無任何細節' },
    { v: 1, label: 'I', desc: '接近全黑，僅隱約可見紋理' },
    { v: 2, label: 'II', desc: '最暗但仍保有紋理的暗部' },
    { v: 3, label: 'III', desc: '暗部細節清楚可辨' },
    { v: 4, label: 'IV', desc: '中暗色調（如深色皮膚、陰影）' },
    { v: 5, label: 'V', desc: '中間灰 18%（測光表預設基準）' },
    { v: 6, label: 'VI', desc: '中亮色調（如一般膚色）' },
    { v: 7, label: 'VII', desc: '亮部，紋理仍清楚' },
    { v: 8, label: 'VIII', desc: '最亮但仍保有紋理的亮部' },
    { v: 9, label: 'IX', desc: '接近全白，僅隱約可見紋理' },
    { v: 10, label: 'X', desc: '全白，無任何細節（純白反光）' }
  ];

  var STORAGE_KEY = 'expocalc_last_state';
  var selectedZone = 5;

  function zoneColor(v) {
    var pct = Math.round((v / 10) * 200); // 0~200 gray value
    var g = Math.min(230, Math.max(15, pct));
    return 'rgb(' + g + ',' + g + ',' + g + ')';
  }

  function formatShutterFromSeconds(t) {
    if (t <= 0 || !isFinite(t)) return '--';
    if (t >= 1) {
      var whole = t >= 10 ? Math.round(t) : Math.round(t * 10) / 10;
      return whole + '"';
    }
    var denom = Math.round(1 / t);
    return '1/' + denom;
  }

  function calcEV(aperture, shutterSec) {
    return Math.log2((aperture * aperture) / shutterSec);
  }

  function buildZonebar() {
    var bar = document.getElementById('expocalcZonebar');
    var html = '';
    for (var i = 0; i < ZONES.length; i++) {
      var z = ZONES[i];
      var cls = (z.v === selectedZone) ? 'active' : '';
      html += '<span class="' + cls + '" style="background:' + zoneColor(z.v) + ';color:' + (z.v <= 4 ? '#fff' : '#2c2c2c') + '" data-action="pick-zone" data-zone="' + z.v + '">' + z.label + '</span>';
    }
    bar.innerHTML = html;
    updateZoneDesc();
  }

  function updateZoneDesc() {
    var z = ZONES[selectedZone];
    var offset = z.v - 5;
    var offsetText = offset === 0 ? '不需偏移（等同直接使用測光表讀數）' :
      (offset > 0 ? '需增加 ' + offset + ' 級曝光（EV 需 -' + offset + '）' : '需減少 ' + Math.abs(offset) + ' 級曝光（EV 需 +' + Math.abs(offset) + '）');
    document.getElementById('expocalcZoneDesc').textContent =
      'Zone ' + z.label + '：' + z.desc + '　｜　' + offsetText;
  }

  function buildSelectOptions() {
    var apSel = document.getElementById('expocalcApertureSelect');
    var shSel = document.getElementById('expocalcShutterSelect');
    var apHtml = '', shHtml = '';
    var i;
    for (i = 0; i < APERTURES.length; i++) {
      apHtml += '<option value="' + APERTURES[i] + '"' + (APERTURES[i] === 5.6 ? ' selected' : '') + '>f/' + APERTURES[i] + '</option>';
    }
    for (i = 0; i < SHUTTERS.length; i++) {
      shHtml += '<option value="' + SHUTTERS[i].sec + '"' + (SHUTTERS[i].label === '1/125' ? ' selected' : '') + '>' + SHUTTERS[i].label + '</option>';
    }
    apSel.innerHTML = apHtml;
    shSel.innerHTML = shHtml;
  }

  function renderTable(adjustedEV, meteredEV, zoneLabel, offset) {
    var rows = '';
    for (var i = 0; i < APERTURES.length; i++) {
      var n = APERTURES[i];
      var t = (n * n) / Math.pow(2, adjustedEV);
      rows += '<tr><td>f/' + n + '</td><td>' + formatShutterFromSeconds(t) + '</td></tr>';
    }

    var offsetLine = offset === 0
      ? ''
      : '（已套用 Zone ' + zoneLabel + ' 偏移 ' + (offset > 0 ? '-' : '+') + Math.abs(offset) + ' 級）';

    var html =
      '<div>測光表讀數 EV = <strong>' + meteredEV.toFixed(1) + '</strong>　→　實際計算用 EV = <strong>' + adjustedEV.toFixed(1) + '</strong> ' + offsetLine + '</div>' +
      '<div class="expocalc-table-wrap"><table class="expocalc-table"><thead><tr><th>光圈 f/</th><th>對應快門</th></tr></thead><tbody>' +
      rows + '</tbody></table></div>';

    var resultBox = document.getElementById('expocalcTableResult');
    resultBox.innerHTML = html;
    resultBox.style.display = 'block';
  }

  function saveState(ev, iso) {
    try {
      localStorage.setItem(STORAGE_KEY, JSON.stringify({ ev: ev, iso: iso, zone: selectedZone }));
    } catch (e) { /* localStorage unavailable, ignore */ }
  }

  function loadState() {
    try {
      var raw = localStorage.getItem(STORAGE_KEY);
      if (!raw) return;
      var state = JSON.parse(raw);
      if (state.ev !== undefined) document.getElementById('expocalcEvInput').value = state.ev;
      if (state.iso !== undefined) document.getElementById('expocalcIsoInput').value = state.iso;
      if (state.zone !== undefined) selectedZone = state.zone;
    } catch (e) { /* ignore malformed data */ }
  }

  function handleCalcTable() {
    var evInput = document.getElementById('expocalcEvInput');
    var isoInput = document.getElementById('expocalcIsoInput');
    var meteredEV = parseFloat(evInput.value);

    if (isNaN(meteredEV)) {
      evInput.focus();
      var resultBox = document.getElementById('expocalcTableResult');
      resultBox.innerHTML = '<span style="color:#A80000;">請先輸入測光表讀數 EV 值</span>';
      resultBox.style.display = 'block';
      return;
    }

    var offset = selectedZone - 5;
    var adjustedEV = meteredEV - offset;
    var zoneLabel = ZONES[selectedZone].label;

    renderTable(adjustedEV, meteredEV, zoneLabel, offset);
    saveState(meteredEV, isoInput.value);
  }

  function handleCalcEV() {
    var aperture = parseFloat(document.getElementById('expocalcApertureSelect').value);
    var shutterSec = parseFloat(document.getElementById('expocalcShutterSelect').value);
    var ev = calcEV(aperture, shutterSec);

    var resultBox = document.getElementById('expocalcEvResult');
    resultBox.innerHTML =
      '光圈 f/' + aperture + '　+　快門 ' + formatShutterFromSeconds(shutterSec) +
      '　→　EV = <strong>' + ev.toFixed(2) + '</strong>';
    resultBox.style.display = 'block';
  }

  function handleClick(e) {
    var el = e.target.closest('[data-action]');
    if (!el) return;
    var action = el.getAttribute('data-action');

    if (action === 'calc-table') {
      handleCalcTable();
    } else if (action === 'calc-ev') {
      handleCalcEV();
    } else if (action === 'pick-zone') {
      selectedZone = parseInt(el.getAttribute('data-zone'), 10);
      buildZonebar();
    }
  }

  function init() {
    buildSelectOptions();
    loadState();
    buildZonebar();
    document.getElementById('expocalcRoot').addEventListener('click', handleClick);
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init);
  } else {
    init();
  }
})();
</script>

</body>
</html>
]]></content:encoded>
					
		
		
			<dc:creator>t0918013013@gmail.com (admin)</dc:creator></item>
		<item>
		<title>打七折最高折200，需要買多少剛好最高計算機</title>
		<link>https://0123456789.tw/7-200/</link>
		
		
		<pubDate>Fri, 14 Aug 2026 07:52:53 +0000</pubDate>
				<category><![CDATA[計算blog文]]></category>
		<guid isPermaLink="false">https://0123456789.tw/?p=2196</guid>

					<description><![CDATA[最高折價優惠計算機｜折扣上限甜蜜點試算 最高折價優惠計算機 輸入折扣與封頂金額，算出最划算的消費金額 打幾折  [&#8230;]]]></description>
										<content:encoded><![CDATA[
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>最高折價優惠計算機｜折扣上限甜蜜點試算</title>
<style>
  * { box-sizing: border-box; margin: 0; padding: 0; }
  body {
    font-family: -apple-system, BlinkMacSystemFont, "Microsoft JhengHei", "PingFang TC", sans-serif;
    background: #f5f5f5;
    color: #2c2c2c;
    padding: 20px;
    line-height: 1.6;
  }
  .dcc-wrap {
    max-width: 480px;
    margin: 0 auto;
  }
  .dcc-header {
    text-align: center;
    margin-bottom: 20px;
  }
  .dcc-header h1 {
    font-size: 22px;
    color: #2c2c2c;
  }
  .dcc-header p {
    font-size: 13px;
    color: #958E8A;
    margin-top: 4px;
  }
  .dcc-card {
    background: #EEEEEE;
    border-radius: 12px;
    padding: 20px;
    margin-bottom: 16px;
  }
  .dcc-field {
    margin-bottom: 14px;
  }
  .dcc-field:last-child { margin-bottom: 0; }
  .dcc-label {
    display: block;
    font-size: 13px;
    color: #424242;
    margin-bottom: 6px;
    font-weight: bold;
  }
  .dcc-input-row {
    display: flex;
    align-items: center;
    gap: 8px;
  }
  .dcc-input {
    flex: 1;
    padding: 10px 12px;
    border: 1px solid #d0d0d0;
    border-radius: 8px;
    font-size: 16px;
    background: #fff;
    color: #2c2c2c;
  }
  .dcc-input:focus {
    outline: none;
    border-color: #A80000;
  }
  .dcc-unit {
    font-size: 14px;
    color: #958E8A;
    white-space: nowrap;
  }
  .dcc-result {
    background: #2c2c2c;
    border-radius: 12px;
    padding: 20px;
    text-align: center;
    color: #EEEEEE;
    margin-bottom: 16px;
  }
  .dcc-result-label {
    font-size: 13px;
    color: #d0d0d0;
    margin-bottom: 6px;
  }
  .dcc-result-value {
    font-size: 32px;
    font-weight: bold;
    color: #EEEEEE;
  }
  .dcc-result-sub {
    font-size: 12px;
    color: #958E8A;
    margin-top: 8px;
  }
  .dcc-check-title {
    font-size: 14px;
    font-weight: bold;
    color: #424242;
    margin-bottom: 10px;
  }
  .dcc-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
  }
  .dcc-table th, .dcc-table td {
    padding: 8px 6px;
    text-align: center;
    border-bottom: 1px solid #d0d0d0;
  }
  .dcc-table th {
    color: #958E8A;
    font-weight: normal;
    font-size: 12px;
  }
  .dcc-table td {
    color: #2c2c2c;
  }
  .dcc-table .dcc-best {
    color: #A80000;
    font-weight: bold;
  }
  .dcc-error {
    color: #A80000;
    font-size: 13px;
    text-align: center;
    padding: 8px 0;
  }
  .dcc-note {
    font-size: 12px;
    color: #958E8A;
    text-align: center;
    margin-top: 10px;
  }
</style>
</head>
<body>
<div class="dcc-wrap">
  <div class="dcc-header">
    <h1>最高折價優惠計算機</h1>
    <p>輸入折扣與封頂金額，算出最划算的消費金額</p>
  </div>

  <div class="dcc-card">
    <div class="dcc-field">
      <label class="dcc-label">打幾折</label>
      <div class="dcc-input-row">
        <input type="number" class="dcc-input" id="dcc-percent" placeholder="例如：7" step="0.1" min="0.1" max="9.9">
        <span class="dcc-unit">折（付原價的幾成）</span>
      </div>
    </div>
    <div class="dcc-field">
      <label class="dcc-label">最高折抵金額</label>
      <div class="dcc-input-row">
        <input type="number" class="dcc-input" id="dcc-cap" placeholder="例如：200" min="1">
        <span class="dcc-unit">元</span>
      </div>
    </div>
  </div>

  <div id="dcc-result-area"></div>

  <div class="dcc-card" id="dcc-check-card" style="display:none;">
    <div class="dcc-check-title">花費金額對照表</div>
    <table class="dcc-table" id="dcc-table"></table>
  </div>

  <div class="dcc-note">甜蜜點＝最高折抵金額 ÷ 折扣比例，超過此金額折扣不再增加</div>
</div>

<script>
(function () {
  "use strict";

  var percentInput = document.getElementById("dcc-percent");
  var capInput = document.getElementById("dcc-cap");
  var resultArea = document.getElementById("dcc-result-area");
  var checkCard = document.getElementById("dcc-check-card");
  var tableEl = document.getElementById("dcc-table");

  var STORAGE_KEY = "dcc_last_input";

  function loadSaved() {
    try {
      var raw = localStorage.getItem(STORAGE_KEY);
      if (!raw) return;
      var data = JSON.parse(raw);
      if (data.percent) percentInput.value = data.percent;
      if (data.cap) capInput.value = data.cap;
    } catch (e) {}
  }

  function saveInput() {
    try {
      localStorage.setItem(STORAGE_KEY, JSON.stringify({
        percent: percentInput.value,
        cap: capInput.value
      }));
    } catch (e) {}
  }

  function formatNumber(n) {
    return Math.round(n).toLocaleString("zh-Hant-TW");
  }

  function calc() {
    var percent = parseFloat(percentInput.value);
    var cap = parseFloat(capInput.value);

    if (!percent || !cap || percent <= 0 || percent >= 10 || cap <= 0) {
      resultArea.innerHTML = '<div class="dcc-card"><div class="dcc-error">請輸入正確的折數（0.1～9.9）與折抵上限金額</div></div>';
      checkCard.style.display = "none";
      return;
    }

    var discountRate = (10 - percent) / 10; // 折扣比例，例如打7折 = 折30%
    var breakeven = cap / discountRate;

    resultArea.innerHTML =
      '<div class="dcc-result">' +
        '<div class="dcc-result-label">花到這個金額最划算（折扣剛好封頂）</div>' +
        '<div class="dcc-result-value">約 ' + formatNumber(breakeven) + ' 元</div>' +
        '<div class="dcc-result-sub">折扣比例：' + (discountRate * 100).toFixed(1) + '%　｜　封頂折抵：' + formatNumber(cap) + ' 元</div>' +
      '</div>';

    renderTable(breakeven, discountRate, cap);
    checkCard.style.display = "block";
    saveInput();
  }

  function renderTable(breakeven, discountRate, cap) {
    var amounts = [];
    var base = breakeven;
    var steps = [-0.4, -0.2, 0, 0.2, 0.5];
    for (var i = 0; i < steps.length; i++) {
      var amt = Math.max(0, base * (1 + steps[i]));
      amounts.push(amt);
    }

    var html = '<tr><th>花費金額</th><th>折扣金額</th><th>實付金額</th></tr>';
    for (var j = 0; j < amounts.length; j++) {
      var spend = amounts[j];
      var discount = Math.min(spend * discountRate, cap);
      var actualPay = spend - discount;
      var isBest = Math.abs(spend - breakeven) < 0.01 || j === 2;
      var rowClass = isBest ? ' class="dcc-best"' : '';
      html += '<tr>' +
        '<td' + rowClass + '>' + formatNumber(spend) + '</td>' +
        '<td' + rowClass + '>' + formatNumber(discount) + '</td>' +
        '<td' + rowClass + '>' + formatNumber(actualPay) + '</td>' +
        '</tr>';
    }
    tableEl.innerHTML = html;
  }

  percentInput.addEventListener("input", calc);
  capInput.addEventListener("input", calc);

  loadSaved();
  calc();
})();
</script>
</body>
</html>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="407" height="896" src="https://0123456789.tw/wp-content/uploads/2026/08/iShot_2026-08-14_15.48.39.jpg" alt="" class="wp-image-2199" srcset="https://0123456789.tw/wp-content/uploads/2026/08/iShot_2026-08-14_15.48.39.jpg 407w, https://0123456789.tw/wp-content/uploads/2026/08/iShot_2026-08-14_15.48.39-136x300.jpg 136w" sizes="auto, (max-width: 407px) 100vw, 407px" /></figure>
]]></content:encoded>
					
		
		
			<dc:creator>t0918013013@gmail.com (admin)</dc:creator></item>
		<item>
		<title>可開始訂票日期計算器（台鐵高鐵.自訂搶票）</title>
		<link>https://0123456789.tw/ticketday/</link>
		
		
		<pubDate>Wed, 12 Aug 2026 05:55:29 +0000</pubDate>
				<category><![CDATA[本站內嵌計算機]]></category>
		<category><![CDATA[台鐵]]></category>
		<category><![CDATA[月曆]]></category>
		<category><![CDATA[訂票]]></category>
		<category><![CDATA[計算]]></category>
		<guid isPermaLink="false">https://0123456789.tw/?p=2193</guid>

					<description><![CDATA[台鐵訂票日期計算器 輸入您想搭乘的日期，立即算出台鐵對號列車的「開賣日期與時間」，並在月曆上標示乘車日與可開始 [&#8230;]]]></description>
										<content:encoded><![CDATA[
<!-- 台鐵訂票日期計算器 | 可直接貼入 WordPress 自訂 HTML 區塊 -->
<div class="tccal-container">
  <div class="tccal-card">
    <h2 class="tccal-title">台鐵訂票日期計算器</h2>
    <p class="tccal-desc">輸入您想搭乘的日期，立即算出台鐵對號列車的「開賣日期與時間」，並在月曆上標示乘車日與可開始訂票日。</p>

    <div class="tccal-input-wrapper">
      <label class="tccal-label" for="">乘車日期</label>
      <input type="date" class="tccal-input" data-role="dateInput" />

      <div class="tccal-options">
        <label class="tccal-checklabel">
          <input type="checkbox" data-role="puyumaCheck" />
          普悠瑪號桌型座位車廂
        </label>
        <label class="tccal-checklabel">
          <input type="checkbox" data-role="localCheck" />
          非對號列車（區間車等）
        </label>
      </div>

      <button type="button" class="tccal-btn" data-action="calc">計算開賣日期</button>
    </div>

    <div class="tccal-result" data-role="result" hidden></div>

    <div class="tccal-calendar-wrapper" data-role="calendarWrapper" hidden>
      <div class="tccal-cal-nav">
        <button type="button" class="tccal-navbtn" data-action="nav" data-dir="-1" aria-label="上個月">‹</button>
        <span class="tccal-monthlabel" data-role="monthLabel"></span>
        <button type="button" class="tccal-navbtn" data-action="nav" data-dir="1" aria-label="下個月">›</button>
      </div>
      <div class="tccal-cal-jump">
        <button type="button" class="tccal-jumpbtn" data-action="jump-opening">跳至開賣月</button>
        <button type="button" class="tccal-jumpbtn" data-action="jump-travel">跳至乘車月</button>
        <button type="button" class="tccal-jumpbtn" data-action="jump-today">回到今天</button>
      </div>

      <div class="tccal-weekrow">
        <span>日</span><span>一</span><span>二</span><span>三</span><span>四</span><span>五</span><span>六</span>
      </div>
      <div class="tccal-grid" data-role="grid"></div>

      <div class="tccal-legend">
        <span class="tccal-legend-item"><i class="tccal-swatch tccal-swatch-opening"></i>可開始訂票日</span>
        <span class="tccal-legend-item"><i class="tccal-swatch tccal-swatch-travel"></i>乘車日</span>
        <span class="tccal-legend-item"><i class="tccal-swatch tccal-swatch-today"></i>今天</span>
        <span class="tccal-legend-item tccal-legend-puyuma" data-role="legendPuyuma" hidden><i class="tccal-swatch tccal-swatch-puyuma"></i>普悠瑪桌型座位限購放寬日</span>
      </div>
    </div>

    <div class="tccal-notes">
      <h3 class="tccal-notes-title">訂票規則說明</h3>
      <ul class="tccal-notes-list">
        <li><strong>一般規則：</strong>對號列車開放乘車前 28 天（4 週）起預訂，最晚可訂至開車前 30 分鐘以上。</li>
        <li><strong>週五加開：</strong>每逢週五，會一併多開放兩天，即最多可訂到「四週後的週五、週六、週日」三天車票，讓週六、週日的車票提前一起開賣。</li>
        <li><strong>開放時間：</strong>網路 / App /語音訂票 / 超商，於開放當日 <strong>凌晨 00:00</strong> 開賣；車站售票窗口則於 <strong>上午 09:00</strong> 同步開賣。</li>
        <li><strong>連續假期 / 春節疏運：</strong>疏運期間開賣日由台鐵另行公告，不適用上述 28 天一般規則，請以台鐵官網公告為準。</li>
        <li><strong>普悠瑪桌型座位：</strong>乘車日前 7 天以上，每次限購 4 張或 8 張；乘車日前 7 天以內（含當日）開放單張（1～9 張）零星訂購。</li>
        <li><strong>非對號列車（區間車等）：</strong>不開放事前預訂，請直接至車站購票或以票卡刷卡進出站。</li>
      </ul>
    </div>
  </div>
</div>

<style>
.tccal-container{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang TC","Microsoft JhengHei",sans-serif;color:#424242;font-size:1.05rem;line-height:1.7;box-sizing:border-box}
.tccal-container *,.tccal-container *:before,.tccal-container *:after{box-sizing:border-box}
.tccal-card{background:#ffffff;border:1px solid #d0d0d0;border-radius:12px;padding:32px;max-width:720px;margin:0 auto}
.tccal-title{font-size:2rem;font-weight:700;color:#2c2c2c;margin:0 0 8px}
.tccal-desc{color:#958E8A;margin:0 0 24px;font-size:1.05rem}

.tccal-input-wrapper{max-width:50%;margin:0 auto 24px;text-align:left}
@media (max-width:640px){.tccal-input-wrapper{max-width:100%}}

.tccal-label{display:block;font-weight:600;color:#2c2c2c;margin-bottom:6px;font-size:.95rem}
.tccal-input{width:100%;padding:10px 12px;border:1px solid #d0d0d0;border-radius:8px;font-size:1.05rem;color:#2c2c2c;background:#f5f5f5}
.tccal-input:focus{outline:2px solid #A80000;outline-offset:1px;background:#fff}

.tccal-options{margin:14px 0;display:flex;flex-direction:column;gap:8px}
.tccal-checklabel{display:flex;align-items:center;gap:8px;font-size:.92rem;color:#424242;cursor:pointer}
.tccal-checklabel input{width:16px;height:16px;accent-color:#A80000;cursor:pointer}

.tccal-btn{display:block;width:100%;margin-top:16px;padding:12px 20px;background:#A80000;color:#EEEEEE;border:none;border-radius:8px;font-size:1.05rem;font-weight:600;cursor:pointer;transition:background .15s ease}
.tccal-btn:hover{background:#8a0000}
.tccal-btn:active{background:#700000}

.tccal-result{max-width:640px;margin:0 auto 24px;background:#f5f5f5;border:1px solid #d0d0d0;border-radius:10px;padding:20px}
.tccal-result-headline{font-size:1.2rem;font-weight:700;color:#2c2c2c;margin:0 0 8px}
.tccal-result-headline.tccal-status-open{color:#A80000}
.tccal-result-row{margin:4px 0;font-size:.98rem;color:#424242}
.tccal-result-row strong{color:#2c2c2c}
.tccal-result-warn{margin-top:10px;padding:10px 12px;background:#EEEEEE;border-left:3px solid #A80000;border-radius:4px;font-size:.9rem;color:#424242}

.tccal-calendar-wrapper{max-width:640px;margin:0 auto 28px}
.tccal-cal-nav{display:flex;align-items:center;justify-content:center;gap:16px;margin-bottom:10px}
.tccal-navbtn{background:#EEEEEE;border:1px solid #d0d0d0;color:#2c2c2c;width:34px;height:34px;border-radius:50%;font-size:1.1rem;cursor:pointer;line-height:1}
.tccal-navbtn:hover{background:#d0d0d0}
.tccal-monthlabel{font-weight:700;font-size:1.1rem;color:#2c2c2c;min-width:130px;text-align:center}

.tccal-cal-jump{display:flex;justify-content:center;gap:8px;margin-bottom:14px;flex-wrap:wrap}
.tccal-jumpbtn{background:#fff;border:1px solid #d0d0d0;color:#424242;padding:5px 12px;border-radius:16px;font-size:.82rem;cursor:pointer}
.tccal-jumpbtn:hover{border-color:#A80000;color:#A80000}

.tccal-weekrow{display:grid;grid-template-columns:repeat(7,1fr);text-align:center;font-size:.85rem;color:#958E8A;margin-bottom:4px}
.tccal-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:4px}

.tccal-day{position:relative;aspect-ratio:1/1;border:1px solid #EEEEEE;border-radius:8px;display:flex;flex-direction:column;align-items:center;justify-content:center;font-size:.85rem;color:#424242;background:#fff;gap:2px}
.tccal-day.is-other-month{color:#d0d0d0;background:#f9f9f9}
.tccal-day .tccal-daynum{font-size:.95rem}
.tccal-day.is-today{border:2px solid #424242}
.tccal-day.is-opening{background:#A80000;color:#fff;border-color:#A80000}
.tccal-day.is-opening .tccal-daynum{color:#fff;font-weight:700}
.tccal-day.is-travel{background:#EEEEEE;border:2px solid #A80000}
.tccal-day.is-travel .tccal-daynum{color:#A80000;font-weight:700}
.tccal-day.is-travel.is-opening{background:linear-gradient(135deg,#A80000 50%,#EEEEEE 50%)}
.tccal-day-tag{font-size:.62rem;letter-spacing:.02em}
.tccal-day.is-opening .tccal-day-tag{color:#EEEEEE}
.tccal-day.is-travel .tccal-day-tag{color:#A80000}
.tccal-puyuma-dot{position:absolute;bottom:4px;right:4px;width:6px;height:6px;border-radius:50%;background:#958E8A}

.tccal-legend{display:flex;flex-wrap:wrap;gap:14px;justify-content:center;margin-top:16px;font-size:.82rem;color:#424242}
.tccal-legend-item{display:flex;align-items:center;gap:6px}
.tccal-swatch{display:inline-block;width:12px;height:12px;border-radius:3px}
.tccal-swatch-opening{background:#A80000}
.tccal-swatch-travel{background:#EEEEEE;border:2px solid #A80000}
.tccal-swatch-today{background:#fff;border:2px solid #424242}
.tccal-swatch-puyuma{background:#958E8A;border-radius:50%}

.tccal-notes{max-width:640px;margin:0 auto;border-top:1px solid #d0d0d0;padding-top:20px}
.tccal-notes-title{font-size:1.1rem;color:#2c2c2c;margin:0 0 10px}
.tccal-notes-list{margin:0;padding-left:20px;font-size:.92rem;color:#424242}
.tccal-notes-list li{margin-bottom:8px}
</style>

<script>
(function(){
  'use strict';

  var WEEKDAY_NAMES = ['日','一','二','三','四','五','六'];
  var MS_DAY = 86400000;

  function pad(n){ return (n < 10 ? '0' : '') + n; }

  function dateKey(d){ return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()); }

  function stripTime(d){ return new Date(d.getFullYear(), d.getMonth(), d.getDate()); }

  function addDays(d, n){
    var r = new Date(d.getTime());
    r.setDate(r.getDate() + n);
    return r;
  }

  function parseInputDate(str){
    // str is "YYYY-MM-DD" from <input type="date">
    var parts = str.split('-');
    if(parts.length !== 3){ return null; }
    var y = parseInt(parts[0], 10), m = parseInt(parts[1], 10), d = parseInt(parts[2], 10);
    if(isNaN(y) || isNaN(m) || isNaN(d)){ return null; }
    return new Date(y, m - 1, d);
  }

  function formatDate(d){
    return d.getFullYear() + '年' + (d.getMonth() + 1) + '月' + d.getDate() + '日（' + WEEKDAY_NAMES[d.getDay()] + '）';
  }

  // 核心規則：
  // 一般開賣日 O = 乘車日 - 28 天。
  // 因每逢週五加開週末兩天，若 O 落在週六，實際開賣日提前 1 天（週五）；
  // 若 O 落在週日，實際開賣日提前 2 天（週五）。
  function computeOpening(travelDate){
    var rawO = addDays(travelDate, -28);
    var wd = rawO.getDay();
    var opening = rawO;
    var bundled = false;
    if(wd === 6){ opening = addDays(rawO, -1); bundled = true; }
    else if(wd === 0){ opening = addDays(rawO, -2); bundled = true; }
    return { opening: opening, rawOpening: rawO, bundled: bundled };
  }

  function init(container){
    var dateInput = container.querySelector('[data-role="dateInput"]');
    var puyumaCheck = container.querySelector('[data-role="puyumaCheck"]');
    var localCheck = container.querySelector('[data-role="localCheck"]');
    var resultEl = container.querySelector('[data-role="result"]');
    var calWrapper = container.querySelector('[data-role="calendarWrapper"]');
    var gridEl = container.querySelector('[data-role="grid"]');
    var monthLabelEl = container.querySelector('[data-role="monthLabel"]');
    var legendPuyuma = container.querySelector('[data-role="legendPuyuma"]');

    var today = stripTime(new Date());

    var state = {
      travelDate: null,
      opening: null,
      puyumaCutoff: null,
      viewYear: today.getFullYear(),
      viewMonth: today.getMonth()
    };

    // 還原上次輸入
    try {
      var saved = localStorage.getItem('tccal_last_date');
      if(saved){ dateInput.value = saved; }
    } catch(e){ /* localStorage 不可用時略過 */ }

    function setView(d){
      state.viewYear = d.getFullYear();
      state.viewMonth = d.getMonth();
      renderGrid();
    }

    function renderGrid(){
      var y = state.viewYear, m = state.viewMonth;
      monthLabelEl.textContent = y + ' 年 ' + (m + 1) + ' 月';

      var firstOfMonth = new Date(y, m, 1);
      var startOffset = firstOfMonth.getDay(); // 0=Sun
      var gridStart = addDays(firstOfMonth, -startOffset);

      var openingKey = state.opening ? dateKey(state.opening) : null;
      var travelKey = state.travelDate ? dateKey(state.travelDate) : null;
      var todayKey = dateKey(today);
      var puyumaKey = state.puyumaCutoff ? dateKey(state.puyumaCutoff) : null;

      var html = '';
      var cursor = gridStart;
      for(var i = 0; i < 42; i++){
        var isOtherMonth = cursor.getMonth() !== m;
        var key = dateKey(cursor);
        var classes = ['tccal-day'];
        var tags = '';
        if(isOtherMonth){ classes.push('is-other-month'); }
        if(key === todayKey){ classes.push('is-today'); }
        if(key === openingKey){ classes.push('is-opening'); tags += '<span class="tccal-day-tag">開賣</span>'; }
        if(key === travelKey){ classes.push('is-travel'); tags += '<span class="tccal-day-tag">乘車</span>'; }
        var puyumaDot = (key === puyumaKey) ? '<span class="tccal-puyuma-dot" title="普悠瑪桌型座位限購放寬"></span>' : '';

        html += '<div class="' + classes.join(' ') + '" data-date="' + key + '">' +
                  '<span class="tccal-daynum">' + cursor.getDate() + '</span>' +
                  tags + puyumaDot +
                '</div>';
        cursor = addDays(cursor, 1);
      }
      gridEl.innerHTML = html;
    }

    function renderResult(){
      var localMode = localCheck.checked;
      var puyumaMode = puyumaCheck.checked;

      if(localMode){
        resultEl.hidden = false;
        calWrapper.hidden = true;
        resultEl.innerHTML =
          '<p class="tccal-result-headline">非對號列車不開放預售</p>' +
          '<p class="tccal-result-row">區間車等非對號列車沒有事先訂票制度，請直接至車站購票或使用票卡（如悠遊卡、一卡通）刷卡進出站搭乘。</p>';
        return;
      }

      if(!state.travelDate){ return; }

      var travelDate = state.travelDate;
      var isPast = travelDate.getTime() < today.getTime();

      var html = '';

      if(isPast){
        html += '<p class="tccal-result-headline">此日期已經過去</p>' +
                '<p class="tccal-result-row">請重新選擇今天（' + formatDate(today) + '）以後的乘車日期。</p>';
        resultEl.innerHTML = html;
        resultEl.hidden = false;
        calWrapper.hidden = true;
        return;
      }

      var calc = computeOpening(travelDate);
      state.opening = calc.opening;

      var diffDays = Math.round((calc.opening.getTime() - today.getTime()) / MS_DAY);
      var isOpenNow = diffDays <= 0;

      html += '<p class="tccal-result-headline' + (isOpenNow ? ' tccal-status-open' : '') + '">' +
                (isOpenNow ? '&#x2705; 這天的車票已經開放訂購' : '&#x23f3; 尚未開賣，還要等 ' + diffDays + ' 天') +
              '</p>';
      html += '<p class="tccal-result-row"><strong>乘車日期：</strong>' + formatDate(travelDate) + '</p>';
      html += '<p class="tccal-result-row"><strong>可開始訂票日：</strong>' + formatDate(calc.opening) + '</p>';
      html += '<p class="tccal-result-row"><strong>開賣時間：</strong>網路 / App / 語音 / 超商 於當日 00:00 起；車站窗口於當日 09:00 起。</p>';

      if(calc.bundled){
        html += '<p class="tccal-result-row">因乘車日往前推 28 天恰好落在週' + WEEKDAY_NAMES[calc.rawOpening.getDay()] + '，依台鐵「週五加開」規則，該週票券已隨週五一併開賣，故開賣日提前為 ' + formatDate(calc.opening) + '。</p>';
      }

      if(travelDate.getTime() === today.getTime()){
        html += '<div class="tccal-result-warn">今天正是乘車日，仍需於開車前 30 分鐘以上完成購票。</div>';
      }

      if(puyumaMode){
        var cutoff = addDays(travelDate, -7);
        state.puyumaCutoff = cutoff;
        legendPuyuma.hidden = false;
        html += '<div class="tccal-result-warn">普悠瑪號桌型座位：' + formatDate(cutoff) + '（乘車前 7 天）以前，每次限購 4 張或 8 張；' + formatDate(cutoff) + ' 起（含乘車當日）開放單張 1～9 張零星訂購。</div>';
      } else {
        state.puyumaCutoff = null;
        legendPuyuma.hidden = true;
      }

      html += '<div class="tccal-result-warn">若乘車日落在國定連續假期或春節疏運期間，開賣日將由台鐵另行公告，不適用上述 28 天一般規則，請以台鐵官網公告為準。</div>';

      resultEl.innerHTML = html;
      resultEl.hidden = false;
      calWrapper.hidden = false;

      setView(calc.opening);
    }

    function handleCalc(){
      var val = dateInput.value;
      if(!val){
        resultEl.hidden = false;
        calWrapper.hidden = true;
        resultEl.innerHTML = '<p class="tccal-result-headline">請先選擇乘車日期</p>';
        return;
      }
      var d = parseInputDate(val);
      if(!d){ return; }
      state.travelDate = stripTime(d);

      try { localStorage.setItem('tccal_last_date', val); } catch(e){ /* 略過 */ }

      renderResult();
    }

    container.addEventListener('click', function(evt){
      var target = evt.target.closest ? evt.target.closest('[data-action]') : null;
      if(!target){ return; }
      var action = target.getAttribute('data-action');

      if(action === 'calc'){
        handleCalc();
      } else if(action === 'nav'){
        var dir = parseInt(target.getAttribute('data-dir'), 10);
        var newDate = new Date(state.viewYear, state.viewMonth + dir, 1);
        setView(newDate);
      } else if(action === 'jump-opening'){
        if(state.opening){ setView(state.opening); }
      } else if(action === 'jump-travel'){
        if(state.travelDate){ setView(state.travelDate); }
      } else if(action === 'jump-today'){
        setView(today);
      }
    });

    localCheck.addEventListener('change', function(){
      if(localCheck.checked){ puyumaCheck.checked = false; }
      if(state.travelDate || localCheck.checked){ renderResult(); }
    });
    puyumaCheck.addEventListener('change', function(){
      if(puyumaCheck.checked){ localCheck.checked = false; }
      if(state.travelDate){ renderResult(); }
    });

    renderGrid();
  }

  function ready(){
    var containers = document.querySelectorAll('.tccal-container');
    for(var i = 0; i < containers.length; i++){
      init(containers[i]);
    }
  }

  if(document.readyState === 'loading'){
    document.addEventListener('DOMContentLoaded', ready);
  } else {
    ready();
  }
})();
</script>
]]></content:encoded>
					
		
		
			<dc:creator>t0918013013@gmail.com (admin)</dc:creator></item>
		<item>
		<title>一句話卡片生成器</title>
		<link>https://0123456789.tw/quotes/</link>
		
		
		<pubDate>Wed, 05 Aug 2026 07:37:59 +0000</pubDate>
				<category><![CDATA[線上工具類]]></category>
		<category><![CDATA[app]]></category>
		<category><![CDATA[工具]]></category>
		<category><![CDATA[改圖]]></category>
		<category><![CDATA[製圖]]></category>
		<guid isPermaLink="false">https://0123456789.tw/?p=2187</guid>

					<description/>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img decoding="async" src="https://imgpoi.com/i/OWVJYB.png" alt=""/></figure>
]]></content:encoded>
					
		
		
			<dc:creator>t0918013013@gmail.com (admin)</dc:creator></item>
		<item>
		<title>股票多空對沖策略計算機</title>
		<link>https://0123456789.tw/locki/</link>
		
		
		<pubDate>Wed, 05 Aug 2026 05:09:43 +0000</pubDate>
				<category><![CDATA[計算blog文]]></category>
		<category><![CDATA[對沖]]></category>
		<category><![CDATA[技巧]]></category>
		<category><![CDATA[股票]]></category>
		<guid isPermaLink="false">https://0123456789.tw/?p=2182</guid>

					<description><![CDATA[情境：3000 放空一張，多空單一起持有到 1800 結算；或 2500 先賣掉多單、留空單，等 1800 回 [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">情境：3000 放空一張，多空單一起持有到 1800 結算；<br>或 2500 先賣掉多單、留空單，等 1800 回補空單並買回多單</p>



<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>多空對沖策略計算機</title>
<style>
  .lyfx-wrap { max-width: 900px; margin: 0 auto; padding: 24px 16px 60px; font-family: -apple-system, "Noto Sans TC", "Microsoft JhengHei", sans-serif; color: #424242; }
  .lyfx-wrap * { box-sizing: border-box; }
  .lyfx-title { font-size: 2rem; font-weight: 700; color: #A80000; text-align: center; margin: 0 0 6px; }
  .lyfx-sub { font-size: 1.05rem; color: #958E8A; text-align: center; margin: 0 0 28px; line-height: 1.6; }

  .lyfx-card { background: #EEEEEE; border-radius: 12px; padding: 20px 20px 8px; margin-bottom: 20px; }
  .lyfx-card-title { font-size: 1.3rem; font-weight: 700; color: #A80000; margin: 0 0 16px; padding-bottom: 8px; border-bottom: 2px solid #d0d0d0; }

  .lyfx-input-row { display: flex; flex-wrap: wrap; gap: 0; }
  .lyfx-input-wrap { width: 50%; padding: 0 10px 16px 0; }
  .lyfx-input-wrap label { display: block; font-size: 1.05rem; margin-bottom: 6px; color: #424242; }
  .lyfx-input-wrap input { width: 100%; padding: 10px 12px; font-size: 1.05rem; border: 1px solid #d0d0d0; border-radius: 8px; background: #f5f5f5; color: #2c2c2c; }
  .lyfx-input-wrap input:focus { outline: none; border-color: #A80000; }
  .lyfx-hint { font-size: 0.85rem; color: #958E8A; margin-top: 4px; }

  .lyfx-btn { display: block; width: 100%; padding: 14px; font-size: 1.1rem; font-weight: 700; color: #fff; background: #A80000; border: none; border-radius: 8px; cursor: pointer; margin: 8px 0 24px; }
  .lyfx-btn:hover { background: #8a0000; }

  .lyfx-result-grid { display: flex; flex-wrap: wrap; gap: 16px; margin-bottom: 20px; }
  .lyfx-result-card { flex: 1 1 260px; background: #fff; border: 1px solid #d0d0d0; border-radius: 10px; padding: 16px 18px; }
  .lyfx-result-card h3 { font-size: 1.15rem; color: #A80000; margin: 0 0 10px; }
  .lyfx-line { display: flex; justify-content: space-between; font-size: 1.05rem; padding: 6px 0; border-bottom: 1px dashed #EEEEEE; }
  .lyfx-line span:first-child { color: #958E8A; }
  .lyfx-line .lyfx-pos { color: #A80000; font-weight: 700; }
  .lyfx-line .lyfx-neg { color: #2c7d32; font-weight: 700; }
  .lyfx-total { display: flex; justify-content: space-between; font-size: 1.2rem; font-weight: 700; padding-top: 10px; margin-top: 6px; border-top: 2px solid #d0d0d0; }

  .lyfx-note { background: #fff; border-left: 4px solid #A80000; border-radius: 6px; padding: 14px 16px; font-size: 1.0rem; line-height: 1.8; color: #424242; margin-bottom: 20px; }
  .lyfx-note b { color: #A80000; }

  @media (max-width: 640px) {
    .lyfx-input-wrap { width: 100%; padding-right: 0; }
  }
</style>
</head>
<body>
<div class="lyfx-wrap">
  <div class="lyfx-title">多空對沖策略計算機</div>
  <div class="lyfx-sub">情境：3000 放空一張，多空單一起持有到 1800 結算；<br>或 2500 先賣掉多單、留空單，等 1800 回補空單並買回多單</div>

  <div class="lyfx-card">
    <div class="lyfx-card-title">基本資料</div>
    <div class="lyfx-input-row">
      <div class="lyfx-input-wrap">
        <label for="lyfx-long-shares">原有多單股數</label>
        <input type="number" id="lyfx-long-shares" data-field="longShares" value="1000" min="0" step="1">
      </div>
      <div class="lyfx-input-wrap">
        <label for="lyfx-long-cost">多單原始成本（元/股）</label>
        <input type="number" id="lyfx-long-cost" data-field="longCost" value="2000" min="0" step="0.01">
      </div>
      <div class="lyfx-input-wrap">
        <label for="lyfx-short-shares">放空股數</label>
        <input type="number" id="lyfx-short-shares" data-field="shortShares" value="1000" min="0" step="1">
      </div>
      <div class="lyfx-input-wrap">
        <label for="lyfx-short-entry">空單進場價（元/股）</label>
        <input type="number" id="lyfx-short-entry" data-field="shortEntry" value="3000" min="0" step="0.01">
      </div>
      <div class="lyfx-input-wrap">
        <label for="lyfx-sell-price">策略二：賣出多單價位（元/股）</label>
        <input type="number" id="lyfx-sell-price" data-field="sellPrice" value="2500" min="0" step="0.01">
      </div>
      <div class="lyfx-input-wrap">
        <label for="lyfx-cover-price">回補空單 / 買回多單價位（元/股）</label>
        <input type="number" id="lyfx-cover-price" data-field="coverPrice" value="1800" min="0" step="0.01">
      </div>
      <div class="lyfx-input-wrap">
        <label for="lyfx-buyback-shares">策略二：買回多單股數</label>
        <input type="number" id="lyfx-buyback-shares" data-field="buybackShares" value="1000" min="0" step="1">
        <div class="lyfx-hint">預設等於原多單股數，可自行調整</div>
      </div>
    </div>
  </div>

  <div class="lyfx-result-grid">
    <div class="lyfx-result-card">
      <h3>策略一：多空單一起撐到 1800</h3>
      <div class="lyfx-line"><span>多單損益</span><span id="lyfx-a-long">&#8211;</span></div>
      <div class="lyfx-line"><span>空單損益</span><span id="lyfx-a-short">&#8211;</span></div>
      <div class="lyfx-total"><span>合計損益（出場、空手）</span><span id="lyfx-a-total">&#8211;</span></div>
    </div>

    <div class="lyfx-result-card">
      <h3>策略二：2500 賣多單，1800 回補＋買回</h3>
      <div class="lyfx-line"><span>多單於 2500 賣出損益</span><span id="lyfx-b-sell">&#8211;</span></div>
      <div class="lyfx-line"><span>空單於 1800 回補損益</span><span id="lyfx-b-cover">&#8211;</span></div>
      <div class="lyfx-total"><span>已實現損益合計</span><span id="lyfx-b-realized">&#8211;</span></div>
      <div class="lyfx-line" style="margin-top:10px;"><span>買回多單花費</span><span id="lyfx-b-buyback-cost">&#8211;</span></div>
      <div class="lyfx-line"><span>買回後持有</span><span id="lyfx-b-position">&#8211;</span></div>
    </div>
  </div>

  <div class="lyfx-note" id="lyfx-explain">說明將在此顯示。</div>
</div>

<script>
(function () {
  "use strict";

  var STORAGE_KEY = "lyfx_hedge_calc_v1";
  var fields = ["longShares", "longCost", "shortShares", "shortEntry", "sellPrice", "coverPrice", "buybackShares"];

  function fmtMoney(n) {
    var sign = n < 0 ? "-" : "";
    var abs = Math.abs(n);
    var s = abs.toFixed(0).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
    return sign + "$" + s;
  }

  function getValues() {
    var v = {};
    for (var i = 0; i < fields.length; i++) {
      var el = document.querySelector('[data-field="' + fields[i] + '"]');
      v[fields[i]] = parseFloat(el.value) || 0;
    }
    return v;
  }

  function saveToStorage(v) {
    try {
      localStorage.setItem(STORAGE_KEY, JSON.stringify(v));
    } catch (e) {}
  }

  function loadFromStorage() {
    try {
      var raw = localStorage.getItem(STORAGE_KEY);
      if (!raw) return;
      var v = JSON.parse(raw);
      for (var i = 0; i < fields.length; i++) {
        if (v[fields[i]] !== undefined) {
          var el = document.querySelector('[data-field="' + fields[i] + '"]');
          el.value = v[fields[i]];
        }
      }
    } catch (e) {}
  }

  function setLine(id, value) {
    var el = document.getElementById(id);
    el.textContent = fmtMoney(value);
    el.className = value > 0 ? "lyfx-pos" : (value < 0 ? "lyfx-neg" : "");
  }

  function calculate() {
    var v = getValues();
    saveToStorage(v);

    // 策略一：多空單同時撐到 coverPrice 結算
    var aLong = (v.coverPrice - v.longCost) * v.longShares;
    var aShort = (v.shortEntry - v.coverPrice) * v.shortShares;
    var aTotal = aLong + aShort;

    setLine("lyfx-a-long", aLong);
    setLine("lyfx-a-short", aShort);
    setLine("lyfx-a-total", aTotal);

    // 策略二：2500 賣多單，1800 回補空單，1800 買回多單
    var bSell = (v.sellPrice - v.longCost) * v.longShares;
    var bCover = (v.shortEntry - v.coverPrice) * v.shortShares;
    var bRealized = bSell + bCover;
    var bBuybackCost = v.buybackShares * v.coverPrice;

    setLine("lyfx-b-sell", bSell);
    setLine("lyfx-b-cover", bCover);
    setLine("lyfx-b-realized", bRealized);

    var buybackCostEl = document.getElementById("lyfx-b-buyback-cost");
    buybackCostEl.textContent = fmtMoney(bBuybackCost);
    buybackCostEl.className = "";

    var posEl = document.getElementById("lyfx-b-position");
    posEl.textContent = v.buybackShares + " 股 ＠ " + v.coverPrice + " 元／股（新成本）";
    posEl.className = "";

    // 說明文字
    var explain = document.getElementById("lyfx-explain");
    var diffShares = v.buybackShares - v.longShares;
    var shareNote = "";
    if (diffShares === 0) {
      shareNote = "買回股數與原本相同，等於用空單獲利把多單成本從 " + v.longCost + " 降到 " + v.coverPrice + "，並多拿回 " + fmtMoney(bRealized) + " 現金。";
    } else if (diffShares > 0) {
      shareNote = "買回股數比原本多 " + diffShares + " 股，等於用空單獲利加碼；持股會比原本更多。";
    } else {
      shareNote = "買回股數比原本少 " + Math.abs(diffShares) + " 股，等於部分收回現金、部分續留股票部位。";
    }

    var better = "";
    if (aTotal > bRealized) {
      better = "以「最終空手」的角度比較，策略一結算損益（" + fmtMoney(aTotal) + "）高於策略二已實現損益（" + fmtMoney(bRealized) + "）。但策略二結束後手上還留有 " + v.buybackShares + " 股（成本 " + v.coverPrice + "），若股價之後反彈，策略二仍保有續抱獲利的空間，這是策略一（空手出場）沒有的。";
    } else if (aTotal < bRealized) {
      better = "策略二已實現損益（" + fmtMoney(bRealized) + "）已經高於策略一空手結算損益（" + fmtMoney(aTotal) + "），而且策略二結束後手上還留有 " + v.buybackShares + " 股可續抱，等於兩頭都佔優勢。";
    } else {
      better = "兩種策略的損益數字相同，差別在於策略二結束後手上仍持有 " + v.buybackShares + " 股（策略一則是空手）。";
    }

    explain.innerHTML = "<b>策略一</b>：多單與空單都撐到 " + v.coverPrice + " 才一起結算，出場後空手，已實現損益 " + fmtMoney(aTotal) + "。<br><b>策略二</b>：先在 " + v.sellPrice + " 賣掉多單、留著空單，等跌到 " + v.coverPrice + " 回補空單，同時用 " + v.coverPrice + " 買回多單。" + shareNote + "<br><b>比較</b>：" + better;
  }

  function init() {
    loadFromStorage();
    var inputs = document.querySelectorAll(".lyfx-wrap input");
    for (var i = 0; i < inputs.length; i++) {
      inputs[i].addEventListener("input", calculate);
    }
    calculate();
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", init);
  } else {
    init();
  }
})();
</script>
</body>
</html>



<p class="wp-block-paragraph">優點主要圍繞在「降低方向性風險」和「靈活鎖利」這兩個核心概念：</p>



<p class="wp-block-paragraph"><strong>1. 鎖定區間利潤，不用猜對確切時間</strong><br>不管股價是緩跌、急跌還是先震盪後跌，只要最終跌到 1800，策略一的多空合計損益是固定的（多單虧的、空單賺的會互相抵銷一部分），不用精準抓進出場時機。</p>



<p class="wp-block-paragraph"><strong>2. 降低單邊持股的心理壓力</strong><br>手上同時有多空單，股價下跌時空單在賺錢，可以緩解「眼睜睜看多單套牢」的焦慮，比較不容易因為恐慌而亂砍單。</p>



<p class="wp-block-paragraph"><strong>3. 策略二等於用波動幫自己攤平成本</strong><br>2500 先出清多單、留空單，等 1800 回補再買回同樣股數的多單，等於把多單成本從原始成本硬生生降到 1800，而且中間還多賺了一筆已實現獲利，比「死抱多單等反彈」更能把下跌行情轉換成實質好處。</p>



<p class="wp-block-paragraph"><strong>4. 保留反彈的參與權</strong><br>比起單純停損出場、從此空手，策略二買回多單後手上還是有部位，如果之後股價反彈，還是吃得到漲幅，不像純停損那樣完全出局。</p>



<p class="wp-block-paragraph"><strong>5. 資金運用比「砍了再等機會」更主動</strong><br>與其多單套牢乾等、或認賠殺出後在旁邊觀望，用空單去對沖，等於讓下跌行情本身變成獲利來源，而不是純粹的損失。</p>



<p class="wp-block-paragraph"><strong>6. 可以分批應對不同劇本</strong><br>兩種做法（一起撐到底 vs. 2500 先減碼）給你彈性：如果盤中訊號顯示股價可能提前反彈，你可以選擇 2500 先落袋一部分，而不是死板地等到 1800。</p>



<p class="wp-block-paragraph">不過這些優點都建立在「股價確實往你預期的方向走」這個前提上，一旦劇本不成立，前面提到的空單無上限虧損、融券強制回補、保證金追繳等缺點就會蓋過這些好處。整體來說，這類對沖比較適合對標的走勢有一定把握、且能承受空單風險的操作者，而不是穩賺不賠的萬用招式。</p>



<p class="wp-block-paragraph">這種多空對沖策略乍看之下像是「鎖住風險」，但實際上有幾個常被忽略的缺點：</p>



<p class="wp-block-paragraph"><strong>1. 空單風險是不對稱的</strong> 多單最大虧損就是成本歸零，但空單理論上虧損沒有上限。如果股價不跌反漲，空單那邊的虧損可能遠遠超過多單能賺的錢，整個「對沖」在極端行情下反而變成放大虧損。</p>



<p class="wp-block-paragraph"><strong>2. 融券強制回補的風險</strong> 放空通常要透過融券，若碰到公司辦理現金增資、股東會、或券源被大戶回收，可能會被券商強制回補，時間點不是你能控制的，未必能撐到你設定的 1800。</p>



<p class="wp-block-paragraph"><strong>3. 保證金追繳（Margin Call）</strong> 空單要繳保證金，股價一旦往上噴，保證金不夠會被追繳，甚至在到你預期的目標價之前就被強制平倉，策略還沒走完就先出場。</p>



<p class="wp-block-paragraph"><strong>4. 借券費、利息、稅費會侵蝕獲利</strong> 融券的借券費是浮動的，股價越熱門、券越難借，費率可能飆高；加上證交稅、手續費是多空兩邊各收一次，這些成本會慢慢吃掉你算出來的「理論損益」。</p>



<p class="wp-block-paragraph"><strong>5. 資金效率差</strong> 多空同時卡住，等於兩筆保證金/資金都被鎖住不能動用，機會成本不小。</p>



<p class="wp-block-paragraph"><strong>6. 策略二的風險更集中</strong> 2500 先賣掉多單、只留空單，這時候你的曝險就從「對沖」變成「單純做空」。如果股價在 2500 之後不跌反彈，你已經失去多單的上漲空間，只剩空單在虧損，反而風險更集中。</p>



<p class="wp-block-paragraph"><strong>7. 除權息影響</strong> 放空期間如果遇到除息，你要補繳股利給出借人（融券逆效果），這也是隱藏成本。</p>



<p class="wp-block-paragraph"><strong>8. 執行滑價</strong> 真正下單時，2500、1800 這種整數價位常常一堆人卡單，實際成交價可能跟你設定的有落差，尤其量不大的股票更明顯。</p>



<p class="wp-block-paragraph">簡單說，這個策略的核心風險是：<strong>你在賭股價會照劇本跌到 1800</strong>，一旦劇本走偏（不跌、急漲、或中途被強制回補），對沖的保護作用會大打折扣，甚至讓虧損比單純做多或單純做空更難控制。</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
		
		
			<dc:creator>t0918013013@gmail.com (admin)</dc:creator></item>
		<item>
		<title>ODDCAM 非主流相機</title>
		<link>https://0123456789.tw/oddcam-app/</link>
		
		
		<pubDate>Mon, 27 Jul 2026 09:13:24 +0000</pubDate>
				<category><![CDATA[APP推薦]]></category>
		<guid isPermaLink="false">https://0123456789.tw/?p=2166</guid>

					<description><![CDATA[ODDCAM 免費下載 NEW · 氣場相機 AURA CAMERA 你的氣場，今天是什麼顏色？ ODDCAM [&#8230;]]]></description>
										<content:encoded><![CDATA[
<script data-wp-block-html="js">
// GENERATED from dc-runtime/src/*.ts — do not edit. Rebuild with `cd dc-runtime && bun run build`.
"use strict";
(() => {
  var __defProp = Object.defineProperty;
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);

  // src/react.ts
  function getReact() {
    const R = window.React;
    if (!R) throw new Error("dc-runtime: window.React is not available yet");
    return R;
  }
  function getReactDOM() {
    const RD = window.ReactDOM;
    if (!RD) throw new Error("dc-runtime: window.ReactDOM is not available yet");
    return RD;
  }
  var h = ((...args) => getReact().createElement(
    ...args
  ));

  // src/parse.ts
  function parseDcDocument(doc) {
    const dc = doc.querySelector("x-dc");
    if (!dc) return null;
    const scriptEl = doc.querySelector("script[data-dc-script]");
    const { props, preview } = parseDataProps(
      scriptEl?.getAttribute("data-props") ?? null
    );
    return {
      template: dc.innerHTML,
      js: scriptEl ? scriptEl.textContent || "" : "",
      props,
      preview
    };
  }
  function parseDcText(src) {
    const openMatch = /<x-dc(?:\s[^>]*)?>/.exec(src);
    if (!openMatch) return null;
    const close = src.lastIndexOf("</x-dc>");
    if (close === -1 || close < openMatch.index) return null;
    const template = src.slice(openMatch.index + openMatch[0].length, close);
    const doc = new DOMParser().parseFromString(src, "text/html");
    const scriptEl = doc.querySelector("script[data-dc-script]");
    const { props, preview } = parseDataProps(
      scriptEl?.getAttribute("data-props") ?? null
    );
    return {
      template,
      js: scriptEl ? scriptEl.textContent || "" : "",
      props,
      preview
    };
  }
  function parseDataProps(raw) {
    if (!raw) return { props: null, preview: null };
    let parsed;
    try {
      parsed = JSON.parse(raw);
    } catch {
      return { props: null, preview: null };
    }
    if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
      return { props: null, preview: null };
    }
    const obj = parsed;
    const preview = obj.$preview &#038;&#038; typeof obj.$preview === "object" ? obj.$preview : null;
    const rest = {};
    for (const k of Object.keys(obj)) {
      if (k[0] !== "$") rest[k] = obj[k];
    }
    return { props: Object.keys(rest).length ? rest : null, preview };
  }
  function dcNameFromPath(pathname) {
    let p = pathname || "";
    try {
      p = decodeURIComponent(p);
    } catch {
    }
    const base = p.split("/").pop() || "Root";
    return base.replace(/\.dc\.html$/, "").replace(/\.html?$/, "") || "Root";
  }

  // src/boot.ts
  var BASE_CSS = `
    .sc-placeholder{background:color-mix(in srgb,currentColor 8%,transparent);
      border:1px solid color-mix(in srgb,currentColor 50%,transparent);
      border-radius:2px;box-sizing:border-box;overflow:hidden}
    @keyframes sc-shine{0%{background-position:100% 50%}100%{background-position:0% 50%}}
    html.sc-dc-streaming .sc-placeholder,
    html.sc-dc-streaming .sc-interp.sc-missing{position:relative;
      background:color-mix(in srgb,currentColor 5%,transparent);
      border-color:transparent}
    html.sc-dc-streaming .sc-placeholder::before,
    html.sc-dc-streaming .sc-interp.sc-missing::before{content:'';
      position:absolute;inset:0;pointer-events:none;
      background:linear-gradient(90deg,rgba(217,119,87,0) 25%,rgba(247,225,211,.95) 37%,rgba(217,119,87,0) 63%);
      background-size:400% 100%;animation:sc-shine 1.4s ease infinite}
    html.sc-dc-streaming .sc-placeholder:nth-child(n+9 of .sc-placeholder)::before,
    html.sc-dc-streaming .sc-interp.sc-missing:nth-child(n+9 of .sc-interp.sc-missing)::before{animation:none;
      background:color-mix(in srgb,currentColor 8%,transparent)}
    .sc-placeholder-error{padding:4px 8px;font:11px/1.4 ui-monospace,monospace;
      color:color-mix(in srgb,currentColor 70%,transparent);word-break:break-word}
    .sc-interp.sc-missing{display:inline-block;width:2em;height:1em;overflow:hidden;
      vertical-align:text-bottom;background:rgba(255,255,255,.3);border:1px solid rgba(0,0,0,.5);
      border-radius:2px;box-sizing:border-box;color:transparent;
      user-select:none}
    .sc-interp.sc-unresolved{font-family:ui-monospace,monospace;font-size:.85em;
      color:color-mix(in srgb,currentColor 50%,transparent);
      background:color-mix(in srgb,currentColor 10%,transparent);border-radius:3px;
      padding:0 3px}
    .sc-host.sc-has-error{position:relative}
    .sc-logic-error{position:absolute;top:8px;left:8px;z-index:2147483647;max-width:60ch;
      padding:6px 10px;background:#b00020;color:#fff;font:12px/1.4 ui-monospace,monospace;
      border-radius:4px;white-space:pre-wrap;pointer-events:none}
    /* Mirrors PRINT_BASELINE_CSS in apps/web deck-stage-export.ts \u2014 keep both
       in sync until dc-runtime regains a build step. */
    @media print {
      @page { margin: 0.5cm; }
      figure, table { break-inside: avoid; }
      #dc-root, #dc-root > .sc-host { height: auto; }
      *, *::before, *::after {
        print-color-adjust: exact; -webkit-print-color-adjust: exact;
        backdrop-filter: none !important; -webkit-backdrop-filter: none !important;
        animation-delay: -99s !important; animation-duration: .001s !important;
        animation-iteration-count: 1 !important; animation-fill-mode: both !important;
        animation-play-state: running !important; transition-duration: 0s !important;
      }
    }
  `;
  var FULL_PAGE_CSS = "html,body{height:100%;margin:0}#dc-root,#dc-root>.sc-host{height:100%}";
  function rootNameForDocument(doc, loc) {
    let bootPath = loc.pathname || "";
    if (!/\.dc\.html?$/i.test(safeDecode(bootPath))) {
      try {
        bootPath = new URL(doc.baseURI || "/").pathname;
      } catch {
      }
    }
    return dcNameFromPath(bootPath);
  }
  function safeDecode(s) {
    try {
      return decodeURIComponent(s);
    } catch {
      return s;
    }
  }
  function boot(runtime, doc = document) {
    const parsed = parseDcDocument(doc);
    if (!parsed) return null;
    const React = getReact();
    const rootName = rootNameForDocument(doc, location);
    runtime.markFetched(rootName);
    runtime.setRootName(rootName);
    runtime.adoptParsed(rootName, parsed);
    if (!window.__resources) {
      fetch(location.href).then((res) => res.ok ? res.text() : "").then((t) => {
        const raw = t ? parseDcText(t) : null;
        if (raw?.template) runtime.updateHtml(rootName, raw.template);
      }).catch(() => {
      });
    }
    const dc = doc.querySelector("x-dc");
    const hostEl = doc.createElement("div");
    hostEl.id = "dc-root";
    dc.replaceWith(hostEl);
    if (!parsed.preview) {
      const s = doc.createElement("style");
      s.textContent = FULL_PAGE_CSS;
      doc.head.appendChild(s);
    }
    const Root = runtime.getDC(rootName);
    const entry = runtime.registry.get(rootName);
    function StandaloneRoot() {
      const [, setTick] = React.useState(0);
      React.useEffect(() => {
        const sub = () => setTick((n) => n + 1);
        entry.subs.add(sub);
        return () => {
          entry.subs.delete(sub);
        };
      }, []);
      const defaults = React.useMemo(() => {
        const d = {};
        for (const k in entry.propsMeta || {}) {
          const v = entry.propsMeta?.[k]?.default;
          if (v !== void 0) d[k] = v;
        }
        return d;
      }, [entry.propsMeta]);
      return h(Root, { ...defaults, ...entry.propOverrides || {} });
    }
    const ReactDOM = getReactDOM();
    if (ReactDOM.createRoot)
      ReactDOM.createRoot(hostEl).render(h(StandaloneRoot));
    else ReactDOM.render(h(StandaloneRoot), hostEl);
    return rootName;
  }

  // src/expr.ts
  var IDENT_RE = /^[A-Za-z_$][A-Za-z0-9_$]*/;
  var NUMBER_RE = /^-?\d+(\.\d+)?$/;
  function resolve(vals, src) {
    const expr = String(src).trim();
    if (!expr) return void 0;
    if (expr[0] === "(" && expr[expr.length - 1] === ")" && parensWrapWhole(expr)) {
      return resolve(vals, expr.slice(1, -1));
    }
    const eq = findTopLevelEquality(expr);
    if (eq) {
      const lv = resolve(vals, expr.slice(0, eq.index));
      const rv = resolve(vals, expr.slice(eq.index + eq.op.length));
      switch (eq.op) {
        case "===":
          return lv === rv;
        case "!==":
          return lv !== rv;
        case "==":
          return lv == rv;
        default:
          return lv != rv;
      }
    }
    if (expr[0] === "!") return !resolve(vals, expr.slice(1));
    if (expr === "true") return true;
    if (expr === "false") return false;
    if (expr === "null") return null;
    if (expr === "undefined") return void 0;
    if (NUMBER_RE.test(expr)) return Number(expr);
    if (expr.length >= 2 && (expr[0] === '"' || expr[0] === "'") && expr[expr.length - 1] === expr[0]) {
      return expr.slice(1, -1);
    }
    return resolvePath(vals, expr);
  }
  function parensWrapWhole(expr) {
    let depth = 0;
    for (let i = 0; i < expr.length - 1; i++) {
      if (expr[i] === "(") depth++;
      else if (expr[i] === ")") {
        depth--;
        if (depth === 0) return false;
      }
    }
    return true;
  }
  function findTopLevelEquality(expr) {
    let depth = 0;
    for (let i = 0; i < expr.length; i++) {
      const c = expr[i];
      if (c === "[" || c === "(") depth++;
      else if (c === "]" || c === ")") depth--;
      else if (depth === 0 &#038;&#038; (c === "=" || c === "!") &#038;&#038; expr[i + 1] === "=") {
        if (i > 0 && (expr[i - 1] === "=" || expr[i - 1] === "!")) continue;
        if (!expr.slice(0, i).trim()) continue;
        const op = expr[i + 2] === "=" ? c + "==" : c + "=";
        return { index: i, op };
      }
    }
    return null;
  }
  function resolvePath(vals, expr) {
    const head = expr.match(IDENT_RE);
    if (!head) return void 0;
    let cur = vals == null ? void 0 : vals[head[0]];
    let i = head[0].length;
    while (i < expr.length) {
      if (expr[i] === ".") {
        const m = expr.slice(i + 1).match(IDENT_RE) || expr.slice(i + 1).match(/^\d+/);
        if (!m) return void 0;
        cur = cur == null ? void 0 : cur[m[0]];
        i += 1 + m[0].length;
      } else if (expr[i] === "[") {
        let depth = 1;
        let j = i + 1;
        while (j < expr.length &#038;&#038; depth > 0) {
          if (expr[j] === "[") depth++;
          else if (expr[j] === "]") {
            depth--;
            if (depth === 0) break;
          }
          j++;
        }
        if (depth !== 0) return void 0;
        const key = resolve(vals, expr.slice(i + 1, j));
        cur = cur == null ? void 0 : cur[key];
        i = j + 1;
      } else {
        return void 0;
      }
    }
    return cur;
  }

  // src/encode.ts
  var CAMEL_ATTR = "sc-camel-";
  var INLINE_TEXT_TAGS = new Set(
    "a abbr b bdi bdo br cite code del dfn em i ins kbd mark q s samp small span strike strong sub sup u var wbr".split(
      " "
    )
  );
  var RAW_WRAP = {
    select: "sc-raw-select",
    table: "sc-raw-table",
    tbody: "sc-raw-tbody",
    thead: "sc-raw-thead",
    tfoot: "sc-raw-tfoot",
    tr: "sc-raw-tr",
    td: "sc-raw-td",
    th: "sc-raw-th",
    caption: "sc-raw-caption"
  };
  var RAW_UNWRAP = Object.fromEntries(
    Object.entries(RAW_WRAP).map(([k, v]) => [v, k])
  );
  var EVENT_MAP = {
    onclick: "onClick",
    onchange: "onChange",
    oninput: "onInput",
    onsubmit: "onSubmit",
    onkeydown: "onKeyDown",
    onkeyup: "onKeyUp",
    onkeypress: "onKeyPress",
    onmousedown: "onMouseDown",
    onmouseup: "onMouseUp",
    onmouseenter: "onMouseEnter",
    onmouseleave: "onMouseLeave",
    onfocus: "onFocus",
    onblur: "onBlur",
    ondoubleclick: "onDoubleClick",
    oncontextmenu: "onContextMenu",
    onmousemove: "onMouseMove",
    onmouseover: "onMouseOver",
    onmouseout: "onMouseOut",
    onpointerdown: "onPointerDown",
    onpointerup: "onPointerUp",
    onpointermove: "onPointerMove",
    onpointerenter: "onPointerEnter",
    onpointerleave: "onPointerLeave",
    onpointercancel: "onPointerCancel",
    onpointerover: "onPointerOver",
    onpointerout: "onPointerOut",
    ongotpointercapture: "onGotPointerCapture",
    onlostpointercapture: "onLostPointerCapture",
    ontouchstart: "onTouchStart",
    ontouchend: "onTouchEnd",
    ontouchmove: "onTouchMove",
    ontouchcancel: "onTouchCancel",
    ondragstart: "onDragStart",
    ondragend: "onDragEnd",
    ondragenter: "onDragEnter",
    ondragleave: "onDragLeave",
    ondragover: "onDragOver",
    onanimationstart: "onAnimationStart",
    onanimationend: "onAnimationEnd",
    onanimationiteration: "onAnimationIteration",
    ontransitionend: "onTransitionEnd"
  };
  var ATTRS = `(?:[^>"']|"[^"]*"|'[^']*')*`;
  var IMPORT_SELF_CLOSE_RE = new RegExp(
    "<(x-import|dc-import)(" + ATTRS + ")/>",
    "gi"
  );
  var CAMEL_ATTR_RE = /(\s)([a-z]+[A-Z][A-Za-z0-9]*)(\s*=)/g;
  function encodeCamelAttrs(html) {
    return html.replace(
      CAMEL_ATTR_RE,
      (_, sp, name, eq) => sp + CAMEL_ATTR + name.replace(/[A-Z]/g, (c) => "-" + c.toLowerCase()) + eq
    );
  }
  function encodeCase(html) {
    html = html.replace(
      IMPORT_SELF_CLOSE_RE,
      (_, t, a) => "<" + t + a + "></" + t + ">"
    );
    html = html.replace(/<helmet(\s|>)/gi, "<sc-helmet$1");
    html = html.replace(/<\/helmet\s*>/gi, "</sc-helmet>");
    html = encodeCamelAttrs(html);
    for (const [real, alias] of Object.entries(RAW_WRAP)) {
      html = html.replace(
        new RegExp("(</?)" + real + "(?=[\\s>])", "gi"),
        "$1" + alias
      );
    }
    return html;
  }
  function kebabToCamel(s) {
    return s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
  }
  function cssToObj(css) {
    const o = {};
    for (const decl of css.split(";")) {
      const i = decl.indexOf(":");
      if (i < 0) continue;
      const prop = decl.slice(0, i).trim();
      o[prop.startsWith("--") ? prop : kebabToCamel(prop)] = decl.slice(i + 1).trim();
    }
    return o;
  }
  function compileAttr(raw) {
    const whole = raw.match(/^\s*\{\{([\s\S]+?)\}\}\s*$/);
    if (whole) {
      const path = whole[1];
      return (vals) => resolve(vals, path);
    }
    if (raw.includes("{{")) {
      const parts = raw.split(/\{\{([\s\S]+?)\}\}/g);
      return (vals) => parts.map((s, i) => i & 1 ? resolve(vals, s) ?? "" : s).join("");
    }
    return () => raw;
  }

  // src/compile.ts
  function collectProps(node, kind, host) {
    const propGetters = [];
    const pseudoClasses = [];
    let hintSize = null;
    for (const { name, value } of [...node.attributes]) {
      if (name === "sc-name" || name === "data-dc-tpl") continue;
      let key = name;
      if (key.startsWith(CAMEL_ATTR))
        key = kebabToCamel(key.slice(CAMEL_ATTR.length));
      if (key === "hint-size") {
        hintSize = value;
        continue;
      }
      if (key.startsWith("style-")) {
        pseudoClasses.push(host.pseudoClass(key.slice(6), value));
        continue;
      }
      if (kind !== "dom") {
        if (key.includes("-") && !(kind === "x-import" && (key.startsWith("aria-") || key.startsWith("data-"))))
          key = kebabToCamel(key);
      } else {
        if (key === "class") key = "className";
        else if (key === "for") key = "htmlFor";
        else if (key.startsWith("on"))
          key = EVENT_MAP[key] || "on" + key[2].toUpperCase() + key.slice(3);
      }
      propGetters.push([key, compileAttr(value)]);
    }
    return { propGetters, pseudoClasses, hintSize };
  }
  var HOST_STYLE_PROPS = /* @__PURE__ */ new Set([
    "position",
    "left",
    "right",
    "top",
    "bottom",
    "inset",
    "width",
    "height",
    "z-index",
    "transform"
  ]);
  function hostPositionStyle(style) {
    const all = typeof style === "string" ? cssToObj(style) : style != null && typeof style === "object" ? style : null;
    if (!all) return void 0;
    const out = {};
    for (const [k, v] of Object.entries(all)) {
      const kebab = k.replace(/[A-Z]/g, (c) => "-" + c.toLowerCase());
      if (HOST_STYLE_PROPS.has(kebab)) out[k] = v;
    }
    return Object.keys(out).length ? out : void 0;
  }
  function compileTemplate(html, host) {
    const tpl = document.createElement("template");
    //! nosemgrep: direct-inner-html-assignment
    tpl.innerHTML = encodeCase(html);
    let tplN = 0;
    (function stamp(node) {
      if (node.nodeType === Node.ELEMENT_NODE) {
        node.setAttribute("data-dc-tpl", String(tplN++));
      }
      for (const c of node.childNodes) stamp(c);
    })(tpl.content);
    const builders = walkChildren(tpl.content, host);
    const render = ((vals, ctx) => builders.map((b, i) => b(vals || {}, ctx, i)));
    render.__annotated = tpl.innerHTML;
    return render;
  }
  function walkChildren(node, host) {
    return [...node.childNodes].map((c) => walk(c, host)).filter((b) => b != null);
  }
  var SLIDE_ID_VALUE_RE = /^[0-9a-f]{8}$/;
  var DECK_CONTROL_FLOW_RE = /^(sc-if|sc-for|sc-else|dc-import|x-import)$/;
  var DECK_AUX_RE = /^(template|script|style|sc-helmet|helmet)$/;
  function isDeckMountTag(el) {
    if (el.localName === "deck-stage") return true;
    return el.localName === "x-import" && (el.getAttribute("component-from-global-scope") || "") === "deck-stage";
  }
  function walkDeckChildren(el, host) {
    const pairs = [...el.childNodes].map((c) => ({ c, b: walk(c, host) })).filter((p) => p.b !== null);
    const kids = pairs.map((p) => p.b);
    const seen = /* @__PURE__ */ new Set();
    const wsSeen = /* @__PURE__ */ new Map();
    const keys = [];
    const nextSlideId = new Array(pairs.length);
    {
      let upcoming = null;
      for (let j = pairs.length - 1; j >= 0; j--) {
        const n = pairs[j].c;
        if (n.nodeType === Node.ELEMENT_NODE) {
          const t = n.localName;
          upcoming = !DECK_AUX_RE.test(t) && !DECK_CONTROL_FLOW_RE.test(t) ? n.getAttribute("data-om-slide-id") : null;
        }
        nextSlideId[j] = upcoming;
      }
    }
    for (let j = 0; j < pairs.length; j++) {
      const { c } = pairs[j];
      if (c.nodeType === Node.TEXT_NODE) {
        if ((c.nodeValue ?? "").trim() === "") {
          const base = nextSlideId[j] ? "omid-ws:" + nextSlideId[j] : "omid-ws:aux";
          const n = wsSeen.get(base) ?? 0;
          wsSeen.set(base, n + 1);
          keys.push(n === 0 ? base : base + ":" + n);
          continue;
        }
        return { kids, keys: null };
      }
      if (c.nodeType !== Node.ELEMENT_NODE) {
        keys.push(j);
        continue;
      }
      const child = c;
      const tag = child.localName;
      if (DECK_AUX_RE.test(tag)) {
        keys.push(j);
        continue;
      }
      if (DECK_CONTROL_FLOW_RE.test(tag)) return { kids, keys: null };
      const v = child.getAttribute("data-om-slide-id");
      if (!v || !SLIDE_ID_VALUE_RE.test(v) || seen.has(v)) {
        return { kids, keys: null };
      }
      seen.add(v);
      keys.push("omid:" + v);
    }
    return { kids, keys };
  }
  function renderDeckKids(kids, kidKeys, vals, ctx) {
    return kids.map((b, j) => {
      const k = kidKeys ? kidKeys[j] : j;
      const out = b(vals, ctx, k);
      return kidKeys != null && typeof out === "string" ? h(getReact().Fragment, { key: k }, out) : out;
    });
  }
  function walk(node, host) {
    if (node.nodeType === Node.TEXT_NODE) return walkText(node);
    if (node.nodeType !== Node.ELEMENT_NODE) return null;
    const el = node;
    const tag = el.tagName.toLowerCase();
    if (tag === "sc-for") return walkFor(el, host);
    if (tag === "sc-if") return walkIf(el, host);
    if (tag === "x-import") return walkXImport(el, host);
    if (tag === "sc-helmet") return host.helmet(el);
    if (tag === "dc-import") return walkComponent(el, host);
    return walkElement(el, host);
  }
  var warnedHoles = /* @__PURE__ */ new Set();
  function warnUnresolved(ctx, what) {
    const key = (ctx?.__name || "?") + "\0" + what;
    if (warnedHoles.has(key)) return;
    warnedHoles.add(key);
    console.warn("[dc-runtime] " + (ctx?.__name || "template") + ": " + what);
  }
  function walkText(node) {
    const txt = node.nodeValue ?? "";
    if (!txt.includes("{{")) {
      if (!txt.trim() && !txt.includes(" ")) return null;
      return () => txt;
    }
    const parts = txt.split(/\{\{([\s\S]+?)\}\}/g);
    return (vals, ctx, key) => h(
      getReact().Fragment,
      { key },
      ...parts.map((p, i) => {
        if (!(i & 1)) return p;
        const v = resolve(vals, p);
        if (v === void 0) {
          if (!ctx?.__streamingNow) {
            if (document.body?.hasAttribute("data-dc-editor-on")) {
              return h(
                "span",
                { key: i, className: "sc-interp sc-unresolved" },
                "{{ " + p.trim() + " }}"
              );
            }
            warnUnresolved(
              ctx,
              "{{ " + p.trim() + " }} never resolved \u2014 rendered as empty"
            );
            return null;
          }
          return h(
            "span",
            { key: i, className: "sc-interp sc-missing" },
            p.trim()
          );
        }
        if (getReact().isValidElement(v) || Array.isArray(v)) {
          return h(getReact().Fragment, { key: i }, v);
        }
        if (v === null || typeof v === "boolean") return null;
        return h("span", { key: i, className: "sc-interp" }, String(v));
      })
    );
  }
  function walkFor(el, host) {
    const listGet = compileAttr(el.getAttribute("list") || "");
    const asName = el.getAttribute("as") || "item";
    const hintN = parseInt(el.getAttribute("hint-placeholder-count") || "0", 10);
    const kids = walkChildren(el, host);
    const listSrc = el.getAttribute("list") || "";
    return (vals, ctx, key) => {
      let list = listGet(vals);
      if (!Array.isArray(list)) {
        if (!ctx?.__streamingNow) {
          if (list !== void 0 && list !== null) {
            warnUnresolved(
              ctx,
              'sc-for list="' + listSrc + '" is not an array (' + typeof list + ")"
            );
          }
          list = [];
        } else {
          list = hintN > 0 ? Array(hintN).fill(void 0) : [];
        }
      }
      return h(
        getReact().Fragment,
        { key },
        list.map((item, i) => {
          const sub = { ...vals, [asName]: item, $index: i };
          return h(
            getReact().Fragment,
            { key: i },
            kids.map((b, j) => b(sub, ctx, j))
          );
        })
      );
    };
  }
  function walkIf(el, host) {
    const valGet = compileAttr(el.getAttribute("value") || "");
    const hintRaw = el.getAttribute("hint-placeholder-val");
    const hintGet = hintRaw != null ? compileAttr(hintRaw) : null;
    const kids = walkChildren(el, host);
    return (vals, ctx, key) => {
      let v = valGet(vals);
      if (v === void 0 && hintGet && ctx?.__streamingNow) v = hintGet(vals);
      return v ? h(
        getReact().Fragment,
        { key },
        kids.map((b, j) => b(vals, ctx, j))
      ) : null;
    };
  }
  function walkComponent(el, host) {
    const name = el.getAttribute("name") || el.getAttribute("component") || "";
    el.removeAttribute("name");
    el.removeAttribute("component");
    const tplId = el.getAttribute("data-dc-tpl");
    const styleRaw = el.getAttribute("style");
    el.removeAttribute("style");
    const styleGet = styleRaw != null ? compileAttr(styleRaw) : null;
    const { propGetters, hintSize } = collectProps(el, "dc-import", host);
    const kids = walkChildren(el, host);
    return (vals, ctx, key) => {
      const props = {
        key,
        __hintSize: hintSize,
        __tplId: tplId,
        __hostStyle: styleGet ? hostPositionStyle(styleGet(vals)) : void 0
      };
      for (const [k, g] of propGetters) {
        const v = g(vals);
        if (k === "dcProps") {
          if (v && typeof v === "object") Object.assign(props, v);
          continue;
        }
        props[k] = v;
      }
      if (kids.length) props.children = kids.map((b, j) => b(vals, ctx, j));
      return h(host.component(name), props);
    };
  }
  function walkXImport(el, host) {
    const globalNameGet = compileAttr(
      el.getAttribute("component-from-global-scope") || ""
    );
    const exportNameGet = compileAttr(
      el.getAttribute("component") || el.getAttribute("name") || ""
    );
    const fromRaw = el.getAttribute("from") || (el.getAttribute("component-from-global-scope") ? "" : el.getAttribute("src") || el.getAttribute("import") || "");
    const urls = fromRaw.trim() ? fromRaw.trim().split(/\s+/) : [];
    const url = urls.length ? urls[urls.length - 1] : "";
    const kindOf = (u) => /\.(jsx|tsx)(\?|#|$)/i.test(u) ? "jsx" : "js";
    const tplId = el.getAttribute("data-dc-tpl");
    const styleRaw = el.getAttribute("style");
    el.removeAttribute("style");
    const styleGet = styleRaw != null ? compileAttr(styleRaw) : null;
    const wrap = tplId != null || styleGet != null;
    const { propGetters, hintSize } = collectProps(el, "x-import", host);
    const hasContent = el.children.length > 0 || !!(el.textContent || "").trim();
    const deckKeyed = hasContent && isDeckMountTag(el) ? walkDeckChildren(el, host) : null;
    const kids = deckKeyed ? deckKeyed.kids : hasContent ? walkChildren(el, host) : [];
    const kidKeys = deckKeyed?.keys ?? null;
    const urlBindable = fromRaw.includes("{{");
    if (urls.length && !urlBindable) {
      let prev;
      for (const u of urls) prev = host.loadExternal(kindOf(u), u, prev);
    }
    const evalName = (g, vals) => {
      const v = g(vals);
      const s = v == null ? "" : String(v);
      return s.includes("{{") ? "" : s;
    };
    return (vals, ctx, key) => {
      const globalName = evalName(globalNameGet, vals);
      const name = globalName || evalName(exportNameGet, vals);
      const C = !name || urlBindable ? null : globalName ? host.resolveExternalGlobal(url, globalName) : host.resolveExternal(url, name);
      const hostStyle = styleGet ? hostPositionStyle(styleGet(vals)) : void 0;
      const wrapper = wrap ? {
        key,
        className: "sc-host-x",
        "data-dc-tpl": tplId,
        style: hostStyle || { display: "contents" }
      } : null;
      if (!C) {
        const error = urlBindable ? "x-import `from` cannot contain {{ \u2026 }} \u2014 module URLs are resolved at parse time; use a literal URL" : host.resolveExternalError(url, name);
        const ph = host.placeholder({
          key: wrapper ? void 0 : key,
          name,
          hintSize,
          error
        });
        return wrapper ? h("div", wrapper, ph) : ph;
      }
      const props = wrapper ? {} : { key };
      let unresolvedHole = false;
      for (const [k, g] of propGetters) {
        if (k === "component" || k === "componentFromGlobalScope" || k === "from") {
          continue;
        }
        const v = g(vals);
        if (v === void 0) unresolvedHole = true;
        if (k === "dcProps") {
          if (v && typeof v === "object") Object.assign(props, v);
          continue;
        }
        props[k] = v;
      }
      if (unresolvedHole && ctx?.__htmlStreamingNow) {
        const ph = host.placeholder({
          key: wrapper ? void 0 : key,
          name,
          hintSize,
          error: null
        });
        return wrapper ? h("div", wrapper, ph) : ph;
      }
      if (kids.length) {
        props.children = renderDeckKids(kids, kidKeys, vals, ctx);
      }
      return wrapper ? h("div", wrapper, h(C, props)) : h(C, props);
    };
  }
  function contentKey(el) {
    const clone = el.cloneNode(true);
    for (const d of clone.querySelectorAll("*")) {
      while (d.attributes.length) d.removeAttribute(d.attributes[0].name);
    }
    const s = clone.innerHTML;
    let h2 = 5381;
    for (let i = 0; i < s.length; i++) h2 = (h2 << 5) + h2 + s.charCodeAt(i) | 0;
    return s.length + "." + (h2 >>> 0).toString(36);
  }
  var NEVER_CONTENT_KEYED = new Set(
    "script style textarea option title select canvas iframe video audio".split(
      " "
    )
  );
  var NOT_INLINE_SELECTOR = ":not(" + [...INLINE_TEXT_TAGS].join(",") + ")";
  function walkElement(el, host) {
    const realTag = RAW_UNWRAP[el.localName] || el.localName;
    const tplId = el.getAttribute("data-dc-tpl");
    const inlineOnly = el.childNodes.length > 0 && !NEVER_CONTENT_KEYED.has(realTag) && el.querySelector(NOT_INLINE_SELECTOR) === null;
    const keySuffix = inlineOnly ? "|" + contentKey(el) : "";
    const { propGetters, pseudoClasses } = collectProps(el, "dom", host);
    const deckKeyed = isDeckMountTag(el) ? walkDeckChildren(el, host) : null;
    const kids = deckKeyed ? deckKeyed.kids : walkChildren(el, host);
    const kidKeys = deckKeyed?.keys ?? null;
    return (vals, ctx, key) => {
      const props = {
        key: key + keySuffix,
        "data-dc-tpl": tplId
      };
      for (const [k, g] of propGetters) {
        let v = g(vals);
        if (k === "style" && typeof v === "string") v = cssToObj(v);
        if ((k === "value" || k === "checked") && v === void 0) {
          v = k === "checked" ? false : "";
        }
        props[k] = v;
      }
      if (pseudoClasses.length) {
        props.className = [props.className, ...pseudoClasses].filter(Boolean).join(" ");
      }
      return h(realTag, props, ...renderDeckKids(kids, kidKeys, vals, ctx));
    };
  }

  // src/logic.ts
  var StreamableLogic = class {
    constructor(props) {
      __publicField(this, "props");
      __publicField(this, "state", {});
      /** Back-pointer to the wrapper component, installed after construction. */
      __publicField(this, "__host");
      this.props = props || {};
    }
    setState(update, cb) {
      this.__host && this.__host.__setLogicState(update, cb);
    }
    forceUpdate() {
      this.__host && this.__host.forceUpdate();
    }
    componentDidMount() {
    }
    componentDidUpdate(_prevProps) {
    }
    componentWillUnmount() {
    }
    /** The flat object the template renders against (merged over props). */
    renderVals() {
      return {};
    }
  };
  function evalDcLogic(src) {
    //! nosemgrep: eval-and-function-constructor
    const fn = new Function(
      "DCLogic",
      "StreamableLogic",
      "React",
      src + '\n;return (typeof Component!=="undefined"&&Component)||undefined;'
    );
    return fn(StreamableLogic, StreamableLogic, getReact());
  }

  // src/component.ts
  function shallowEqual(a, b) {
    if (!b) return false;
    const ak = Object.keys(a).filter((k) => k !== "children");
    const bk = Object.keys(b).filter((k) => k !== "children");
    if (ak.length !== bk.length) return false;
    for (const k of ak) if (a[k] !== b[k]) return false;
    return true;
  }
  function Placeholder({
    name,
    hintSize,
    streaming,
    error
  }) {
    const [w, hgt] = (hintSize || "100%,60px").split(",");
    return h(
      "div",
      {
        className: "sc-placeholder" + (streaming ? " sc-streaming" : ""),
        style: { width: w.trim(), height: hgt && hgt.trim() },
        title: name
      },
      error ? h(
        "div",
        { className: "sc-placeholder-error" },
        (name ? name + ": " : "") + error
      ) : null
    );
  }
  function hintToMin(hint) {
    if (!hint) return void 0;
    const [w, hgt] = hint.split(",");
    return { minWidth: w.trim(), minHeight: hgt && hgt.trim() };
  }
  function createComponentFactory(registry, ensureFetched) {
    const React = getReact();
    const AncestorContext = React.createContext([]);
    class StreamableComponent extends React.Component {
      constructor(props) {
        super(props);
        __publicField(this, "__name");
        __publicField(this, "__sub");
        __publicField(this, "__needsDidMount", false);
        /** Snapshot of the registry's streaming flags taken at render time —
         *  builders read it off the RenderCtx (this) to pick placeholder vs
         *  render-nothing for unresolved values. */
        __publicField(this, "__streamingNow", false);
        __publicField(this, "__htmlStreamingNow", false);
        /** When a construct throws, remember the (class, registry.ver, props)
         *  triple so render-time reconcile doesn't re-attempt it on every parent
         *  re-render. A registry bump (new class, template, external module
         *  resolving via bumpAll) changes `ver` and breaks the memo so an
         *  env-dependent constructor can self-heal. */
        __publicField(this, "__failedLogic", null);
        __publicField(this, "__failedUserProps", null);
        __publicField(this, "__failedVer", -1);
        /** Per-instance constructor error — kept here (not on the registry entry)
         *  so one instance's successful construct can't hide a sibling's failure,
         *  and a construct can never wipe an eval error `updateJs` recorded on
         *  `r.logicError`. */
        __publicField(this, "__ctorError", null);
        __publicField(this, "logic");
        this.__name = props.__name;
        this.state = { __v: 0, __err: null };
        this.__sub = () => {
          if (this.state.__err) this.setState({ __err: null });
          this.forceUpdate();
        };
        this.__makeLogic(registry.get(this.__name).Logic, null);
        ensureFetched(this.__name);
      }
      /** Error-boundary hook: a render crash anywhere in this DC's subtree
       *  (its own template, an x-import'd component, a child DC without its
       *  own deeper boundary) lands here instead of unmounting the page. */
      static getDerivedStateFromError(e) {
        return { __err: e instanceof Error && e.message ? e.message : String(e) };
      }
      componentDidCatch(e, info) {
        console.error(
          "[dc-runtime] render error in <" + this.__name + ">:",
          e,
          info?.componentStack || ""
        );
      }
      /** Instantiate the logic class (or the no-op base) and adopt `prevState`
       *  over its initial state — used both at mount and on hot-swap. */
      __makeLogic(Logic, prevState) {
        const L = Logic || StreamableLogic;
        try {
          this.logic = new L(this.__userProps());
          this.__failedLogic = null;
          this.__failedUserProps = null;
          this.__ctorError = null;
        } catch (e) {
          console.error(e);
          this.__failedLogic = Logic;
          this.__failedUserProps = this.__userProps();
          this.__failedVer = registry.get(this.__name).ver;
          this.__ctorError = this.__name + ": " + (e instanceof Error && e.message ? e.message : String(e));
          this.logic = new StreamableLogic(
            this.__userProps()
          );
        }
        this.logic.__host = this;
        if (prevState)
          this.logic.state = { ...this.logic.state || {}, ...prevState };
      }
      /** The props the author's logic + template see — internal __-prefixed
       *  wiring stripped. */
      __userProps() {
        const { __name, __hintSize, __tplId, __hostStyle, ...rest } = this.props;
        return rest;
      }
      __setLogicState(update, cb) {
        const prev = this.logic.state;
        const patch = typeof update === "function" ? update(prev) : update;
        this.logic.state = { ...prev, ...patch };
        this.setState((s) => ({ __v: s.__v + 1 }), cb);
      }
      /** Swap the logic instance when the registry's Logic class changed
       *  (streaming completion, hot reload). State carries over; didMount
       *  re-fires after the swap commits so refs exist. */
      __reconcileLogic() {
        const r = registry.get(this.__name);
        const Next = r.Logic;
        const Cur = this.logic.constructor;
        if (Next === Cur || !Next && Cur === StreamableLogic || Next === this.__failedLogic && r.ver === this.__failedVer && shallowEqual(this.__userProps(), this.__failedUserProps)) {
          return;
        }
        if (!this.__needsDidMount) {
          try {
            this.logic.componentWillUnmount();
          } catch (e) {
            console.error(e);
          }
        }
        this.__makeLogic(Next, this.logic.state);
        this.__needsDidMount = true;
      }
      componentDidMount() {
        registry.get(this.__name).subs.add(this.__sub);
        try {
          this.logic.componentDidMount();
        } catch (e) {
          console.error(e);
        }
      }
      componentDidUpdate(prevProps) {
        this.logic.props = this.__userProps();
        if (this.__needsDidMount) {
          if (this.state.__err || !registry.get(this.__name).tpl) return;
          this.__needsDidMount = false;
          try {
            this.logic.componentDidMount();
          } catch (e) {
            console.error(e);
          }
        } else {
          try {
            this.logic.componentDidUpdate(prevProps);
          } catch (e) {
            console.error(e);
          }
        }
      }
      componentWillUnmount() {
        registry.get(this.__name).subs.delete(this.__sub);
        if (!this.__needsDidMount) {
          try {
            this.logic.componentWillUnmount();
          } catch (e) {
            console.error(e);
          }
        }
      }
      render() {
        const r = registry.get(this.__name);
        const cls = "sc-host" + (r.htmlStreaming ? " sc-streaming-html" : "") + (r.jsStreaming ? " sc-streaming-js" : "");
        const hintStyle = r.htmlStreaming ? hintToMin(this.props.__hintSize) : void 0;
        const hostStyle = this.props.__hostStyle || hintStyle ? { ...hintStyle || {}, ...this.props.__hostStyle || {} } : void 0;
        const hostBase = {
          className: cls,
          style: hostStyle,
          "data-sc-name": this.__name,
          "data-dc-tpl": this.props.__tplId
        };
        const chain = Array.isArray(this.context) ? this.context : [];
        if (chain.includes(this.__name)) {
          const cycle = [
            ...chain.slice(chain.indexOf(this.__name)),
            this.__name
          ].join(" \u2192 ");
          return h(
            "div",
            { ...hostBase, className: cls + " sc-has-error" },
            h(Placeholder, {
              name: this.__name,
              hintSize: this.props.__hintSize,
              error: "circular import: " + cycle
            })
          );
        }
        if (this.state.__err) {
          return h(
            "div",
            { ...hostBase, className: cls + " sc-has-error" },
            h(
              "div",
              { className: "sc-logic-error", "data-omelette-chrome": "" },
              this.__name + ": " + this.state.__err
            ),
            h(Placeholder, {
              name: this.__name,
              hintSize: this.props.__hintSize,
              error: this.state.__err
            })
          );
        }
        this.__reconcileLogic();
        if (!r.tpl) {
          return h(
            "div",
            hostBase,
            h(Placeholder, { name: this.__name, hintSize: this.props.__hintSize })
          );
        }
        const userProps = this.__userProps();
        this.logic.props = userProps;
        let vals = userProps;
        let renderErr = r.logicError || this.__ctorError;
        try {
          vals = { ...userProps, ...this.logic.renderVals() || {} };
        } catch (e) {
          console.error(e);
          renderErr = this.__name + ".renderVals(): " + (e instanceof Error && e.message ? e.message : String(e));
        }
        this.__streamingNow = !!(r.htmlStreaming || r.jsStreaming);
        this.__htmlStreamingNow = !!r.htmlStreaming;
        return h(
          "div",
          { ...hostBase, className: cls + (renderErr ? " sc-has-error" : "") },
          renderErr && h(
            "div",
            { className: "sc-logic-error", "data-omelette-chrome": "" },
            renderErr
          ),
          h(
            AncestorContext.Provider,
            { value: [...chain, this.__name] },
            r.tpl(vals, this)
          )
        );
      }
    }
    __publicField(StreamableComponent, "contextType", AncestorContext);
    const named = /* @__PURE__ */ new Map();
    function getDC(name) {
      const hit = named.get(name);
      if (hit) return hit;
      function Dispatcher(p) {
        const [, setTick] = React.useState(0);
        React.useEffect(() => {
          const sub = () => setTick((n) => n + 1);
          registry.get(name).subs.add(sub);
          return () => {
            registry.get(name).subs.delete(sub);
          };
        }, []);
        ensureFetched(name);
        return h(StreamableComponent, { ...p, __name: name });
      }
      Dispatcher.displayName = name;
      named.set(name, Dispatcher);
      return Dispatcher;
    }
    return {
      getDC,
      StreamableComponent
    };
  }

  // src/bundled.ts
  function bundledBlob(url) {
    const blobs = window.__resourceBlobs;
    const b = blobs ? blobs[url.split("#")[0]] : void 0;
    return b instanceof Blob ? b : null;
  }

  // src/cdn.ts
  var REACT_URL = "https://unpkg.com/react@18.3.1/umd/react.production.min.js";
  var REACT_SRI = "sha384-DGyLxAyjq0f9SPpVevD6IgztCFlnMF6oW/XQGmfe+IsZ8TqEiDrcHkMLKI6fiB/Z";
  var REACT_DOM_URL = "https://unpkg.com/react-dom@18.3.1/umd/react-dom.production.min.js";
  var REACT_DOM_SRI = "sha384-gTGxhz21lVGYNMcdJOyq01Edg0jhn/c22nsx0kyqP0TxaV5WVdsSH1fSDUf5YJj1";
  var BABEL_URL = "https://unpkg.com/@babel/standalone@7.29.0/babel.min.js";
  var BABEL_SRI = "sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y";
  function cdnScriptFor(url, sri) {
    const res = window.__resources;
    const v = res ? res[url] : void 0;
    return typeof v === "string" && v ? { src: v } : { src: url, integrity: sri };
  }

  // src/external.ts
  var isCustomElementName = (n) => !n.includes(".") && n.includes("-");
  function isRenderableType(g) {
    if (typeof g === "function") return !isElementClass(g);
    return typeof g === "object" && g !== null && typeof g.$$typeof === "symbol";
  }
  function resolveDottedPath(root, name) {
    let cur = root;
    for (const seg of name.split(".")) {
      if (cur == null) return void 0;
      cur = cur[seg];
    }
    return cur;
  }
  var GLOBAL_POLL_INTERVAL_MS = 50;
  var GLOBAL_POLL_TIMEOUT_MS = 3e4;
  function createExternalModules(onResolved) {
    const cache = /* @__PURE__ */ new Map();
    let babelLoading = null;
    const reportedMissing = /* @__PURE__ */ new Map();
    const polling = /* @__PURE__ */ new Set();
    function ensureBabel() {
      if (window.Babel) return Promise.resolve();
      if (babelLoading) return babelLoading;
      const babel = cdnScriptFor(BABEL_URL, BABEL_SRI);
      babelLoading = new Promise((res, rej) => {
        const s = document.createElement("script");
        s.src = babel.src;
        if (babel.integrity) {
          s.integrity = babel.integrity;
          s.crossOrigin = "anonymous";
        }
        s.onload = () => res();
        s.onerror = rej;
        document.head.appendChild(s);
      });
      return babelLoading;
    }
    const pending = /* @__PURE__ */ new Map();
    function load(kind, url, after) {
      const existing = pending.get(url);
      if (existing) return existing;
      cache.set(url, null);
      console.info("[dc-runtime] x-import: loading", url, "(" + kind + ")");
      const ready = Promise.all([
        kind === "jsx" ? ensureBabel() : Promise.resolve(),
        after ?? Promise.resolve()
      ]);
      const p = ready.then(() => {
        const pre = bundledBlob(url);
        if (pre) return pre.text();
        return fetch(url).then((r) => {
          if (!r.ok) throw new Error("HTTP " + r.status);
          return r.text();
        });
      }).then((src) => {
        const code = kind === "jsx" ? window.Babel.transform(src, {
          filename: url,
          presets: ["react", "typescript"]
        }).code : src;
        const module = { exports: {} };
        const before = new Set(Object.keys(window));
        //! nosemgrep: eval-and-function-constructor
        new Function("React", "module", "exports", "require", code)(
          getReact(),
          module,
          module.exports,
          () => ({})
        );
        const globals = {};
        for (const k of Object.keys(window)) {
          if (!before.has(k) && typeof window[k] === "function") {
            globals[k] = window[k];
          }
        }
        cache.set(url, { mod: module.exports, globals });
        console.info(
          "[dc-runtime] x-import: loaded",
          url,
          "\u2014 exports:",
          Object.keys(module.exports),
          "window globals:",
          Object.keys(globals)
        );
        onResolved();
      }).catch((e) => {
        cache.set(url, {
          mod: {},
          globals: {},
          error: "failed to load: " + (e instanceof Error && e.message ? e.message : String(e))
        });
        console.error(
          "[dc-runtime] x-import: FAILED to load",
          url,
          "(" + kind + ")",
          e
        );
        onResolved();
      });
      pending.set(url, p);
      return p;
    }
    function resolve2(url, name) {
      const entry = cache.get(url);
      if (!entry) return null;
      const { mod, globals } = entry;
      const C = mod && mod[name] || globals && globals[name] || typeof window !== "undefined" && window[name] || mod && mod.default;
      if (typeof C === "function") return C;
      const key = url + "\0" + name;
      if (!reportedMissing.has(key)) {
        reportedMissing.set(
          key,
          entry.error || 'no export named "' + name + '" (has: ' + Object.keys(mod).join(", ") + ")"
        );
        console.error(
          "[dc-runtime] x-import: module",
          url,
          "loaded but has no component named",
          JSON.stringify(name),
          "\u2014 available exports:",
          Object.keys(mod),
          "window globals:",
          Object.keys(globals),
          ". The module must `module.exports = {" + name + "}` or set `window." + name + "`."
        );
      }
      return null;
    }
    function waitForGlobal(name) {
      if (polling.has(name)) return;
      polling.add(name);
      const started = Date.now();
      const isCE = isCustomElementName(name);
      const tick = () => {
        const found = isCE ? customElements.get(name) : isRenderableType(resolveDottedPath(window, name));
        if (found) {
          polling.delete(name);
          onResolved();
          return;
        }
        if (Date.now() - started >= GLOBAL_POLL_TIMEOUT_MS) {
          console.warn(
            "[dc-runtime] x-import: global",
            JSON.stringify(name),
            "never appeared on window after " + GLOBAL_POLL_TIMEOUT_MS + "ms"
          );
          return;
        }
        setTimeout(tick, GLOBAL_POLL_INTERVAL_MS);
      };
      setTimeout(tick, GLOBAL_POLL_INTERVAL_MS);
    }
    function resolveGlobal(url, name) {
      const isCE = isCustomElementName(name);
      if (!url) {
        if (isCE) {
          if (customElements.get(name)) return name;
          waitForGlobal(name);
          return null;
        }
        const g2 = resolveDottedPath(window, name);
        if (isRenderableType(g2)) return g2;
        waitForGlobal(name);
        return null;
      }
      const entry = cache.get(url);
      if (!entry) return null;
      if (isCE && customElements.get(name)) return name;
      const g = entry.globals[name] ?? resolveDottedPath(window, name);
      if (isRenderableType(g)) return g;
      if (name.includes(".")) return null;
      const key = url + "\0global\0" + name;
      if (!reportedMissing.has(key)) {
        reportedMissing.set(key, null);
        if (isCE && !customElements.get(name)) {
          console.warn(
            "[dc-runtime] x-import:",
            url,
            "loaded but no custom element",
            JSON.stringify(name),
            "is registered and window." + name + " is not a function \u2014 rendering <" + name + "> as an unknown element."
          );
        }
      }
      return name;
    }
    function getError(url, name) {
      const entry = cache.get(url);
      if (entry?.error) return entry.error;
      return reportedMissing.get(url + "\0" + name) || null;
    }
    return { load, resolve: resolve2, resolveGlobal, getError };
  }
  function isElementClass(g) {
    try {
      return typeof g === "function" && typeof HTMLElement !== "undefined" && g.prototype instanceof HTMLElement;
    } catch {
      return false;
    }
  }

  // src/atomics.ts
  var ATOMIC_CSS = (
    // layout
    ".fx{display:flex}.col{display:flex;flex-direction:column}.grid{display:grid}.ac{align-items:center}.jc{justify-content:center}.jb{justify-content:space-between}.f1{flex:1}.noshrink{flex-shrink:0}.wrap{flex-wrap:wrap}.fw5{font-weight:500}.fw6{font-weight:600}.fw7{font-weight:700}.fw8{font-weight:800}.fs11{font-size:11px}.fs12{font-size:12px}.fs13{font-size:13px}.fs14{font-size:14px}.fs15{font-size:15px}.fs16{font-size:16px}.fs20{font-size:20px}.fs22{font-size:22px}.upper{text-transform:uppercase}.tc{text-align:center}.nowrap{white-space:nowrap}.gap8{gap:8px}.gap10{gap:10px}.gap12{gap:12px}.gap16{gap:16px}.gap24{gap:24px}.m0{margin:0}.mt8{margin-top:8px}.mt12{margin-top:12px}.mt16{margin-top:16px}.mb8{margin-bottom:8px}.mb12{margin-bottom:12px}.mb16{margin-bottom:16px}.posrel{position:relative}.posabs{position:absolute}.round{border-radius:50%}.ohide{overflow:hidden}.bbox{box-sizing:border-box}.pointer{cursor:pointer}.w100{width:100%}.b0{border:none}"
  );

  // src/helmet.ts
  var DESIGN_DOC_MODE_RE = /<meta\b[^>]*\bname\s*=\s*["']design_doc_mode["'][^>]*\b(?:content|value)\s*=\s*["'](\w+)["']/i;
  var CANVAS_BG_LIGHT = "#f0eee6";
  var CANVAS_BG_DARK = "#2e2c26";
  function createHelmetManager(doc, isStreaming) {
    const mounted = /* @__PURE__ */ new Set();
    const live = /* @__PURE__ */ new Map();
    let designDocMode = null;
    let canvasStyleEl = null;
    let appTheme = "light";
    try {
      const ds = doc.documentElement.dataset.theme;
      appTheme = ds === "dark" || ds === "light" ? ds : new URLSearchParams(doc.defaultView?.location.search ?? "").get(
        "theme"
      ) === "dark" ? "dark" : "light";
    } catch {
    }
    function applyCanvasBg() {
      if (!canvasStyleEl) return;
      const bg = appTheme === "dark" ? CANVAS_BG_DARK : CANVAS_BG_LIGHT;
      canvasStyleEl.textContent = `html,body{background:${bg}}#dc-root>.sc-host{position:relative}`;
    }
    function postDesignMode(mode) {
      if (window.parent === window) return;
      try {
        window.parent.postMessage({ type: "__dc_design_mode", mode }, "*");
      } catch {
      }
    }
    function setDesignDocMode(mode) {
      if (mode === designDocMode) return;
      designDocMode = mode;
      postDesignMode(mode);
      if (mode === "canvas") {
        doc.documentElement.setAttribute("data-dc-canvas", "");
        canvasStyleEl = doc.createElement("style");
        canvasStyleEl.setAttribute("data-dc-canvas", "");
        applyCanvasBg();
        doc.head.appendChild(canvasStyleEl);
      } else {
        doc.documentElement.removeAttribute("data-dc-canvas");
        canvasStyleEl?.remove();
        canvasStyleEl = null;
      }
    }
    window.addEventListener("message", (e) => {
      const type = e.data && e.data.type;
      if (type === "__dc_theme") {
        const t = e.data.theme;
        if (t === "light" || t === "dark") {
          appTheme = t;
          applyCanvasBg();
        }
        return;
      }
      if (!designDocMode || type !== "__dc_probe") return;
      postDesignMode(designDocMode);
    });
    function compile(node) {
      const raw = [...node.children];
      const helmetClosed = node.nextSibling != null || node.parentNode?.nextSibling != null;
      if (node.hasAttribute("data-dc-atomics") && !mounted.has("__dc-atomics")) {
        mounted.add("__dc-atomics");
        const el = doc.createElement("style");
        el.id = "__dc-atomics";
        el.textContent = ATOMIC_CSS;
        doc.head.appendChild(el);
      }
      return (_vals, ctx) => {
        const name = ctx && ctx.__name || "";
        const streaming = !!(name && isStreaming(name));
        for (let i = 0; i < raw.length; i++) {
          const child = raw[i];
          const tag = child.tagName;
          const mayBePartial = streaming &#038;&#038; !helmetClosed &#038;&#038; i === raw.length - 1;
          if (tag === "SCRIPT") {
            if (mayBePartial) continue;
            const key = "SCRIPT|" + (child.getAttribute("src") || child.textContent || "");
            if (mounted.has(key)) continue;
            mounted.add(key);
            const el = doc.createElement("script");
            for (const { name: an, value } of [...child.attributes])
              el.setAttribute(an, value);
            if (child.textContent) el.textContent = child.textContent;
            doc.head.appendChild(el);
          } else if (tag === "LINK" || tag === "META") {
            if (mayBePartial) continue;
            const key = tag + "|" + (child.getAttribute("href") || child.getAttribute("src") || child.outerHTML);
            if (mounted.has(key)) continue;
            mounted.add(key);
            if (tag === "LINK") {
              const rel = (child.getAttribute("rel") || "").toLowerCase().split(/\s+/);
              const href = (child.getAttribute("href") || "").trim();
              const res = window.__resources;
              const pre = res &#038;&#038; rel.includes("stylesheet") &#038;&#038; !rel.includes("alternate") ? res[href] : void 0;
              const blob = typeof pre === "string" &#038;&#038; pre ? bundledBlob(pre) : null;
              if (blob) {
                const el = doc.createElement("style");
                if (child.hasAttribute("disabled")) {
                  el.setAttribute("media", "not all");
                } else if (child.getAttribute("media")) {
                  el.setAttribute("media", child.getAttribute("media"));
                }
                if (child.getAttribute("title"))
                  el.setAttribute("title", child.getAttribute("title"));
                void blob.text().then((css) => {
                  el.textContent = css;
                });
                doc.head.appendChild(el);
                continue;
              }
            }
            doc.head.appendChild(child.cloneNode(true));
          } else {
            const key = name + "|" + i;
            let el = live.get(key);
            if (!el || el.tagName !== tag) {
              if (el) el.remove();
              el = doc.createElement(tag.toLowerCase());
              live.set(key, el);
              doc.head.appendChild(el);
            }
            for (const { name: an, value } of [...child.attributes]) {
              if (el.getAttribute(an) !== value) el.setAttribute(an, value);
            }
            if (el.textContent !== child.textContent)
              el.textContent = child.textContent;
          }
        }
        return null;
      };
    }
    return { compile, setDesignDocMode };
  }

  // src/pseudo.ts
  function scanUnquotedUrl(css, i) {
    if (css[i] !== "u" && css[i] !== "U" || css.slice(i, i + 4).toLowerCase() !== "url(" || /[a-z0-9_-]/i.test(css[i - 1] ?? "")) {
      return -1;
    }
    let j = i + 4;
    while (j < css.length &#038;&#038; /\s/.test(css[j])) j++;
    if (css[j] === '"' || css[j] === "'") return -1;
    while (j < css.length &#038;&#038; css[j] !== ")") {
      if (css[j] === "\\") j++;
      j++;
    }
    return j < css.length ? j + 1 : css.length;
  }
  function stripComments(css) {
    let out = "";
    let quote = "";
    for (let i = 0; i < css.length; i++) {
      const c = css[i];
      if (quote) {
        if (c === "\\") {
          out += c + (css[i + 1] ?? "");
          i++;
          continue;
        }
        if (c === quote) quote = "";
        out += c;
      } else if (c === "'" || c === '"') {
        quote = c;
        out += c;
      } else if (c === "/" &#038;&#038; css[i + 1] === "*") {
        const end = css.indexOf("*/", i + 2);
        i = end === -1 ? css.length : end + 1;
        out += " ";
      } else {
        const end = scanUnquotedUrl(css, i);
        if (end === -1) out += c;
        else {
          out += css.slice(i, end);
          i = end - 1;
        }
      }
    }
    return out;
  }
  function importantify(css) {
    css = stripComments(css);
    const decls = [];
    let start = 0;
    let depth = 0;
    let quote = "";
    for (let i = 0; i < css.length; i++) {
      const c = css[i];
      if (quote) {
        if (c === "\\") i++;
        else if (c === quote) quote = "";
      } else if (c === "'" || c === '"') quote = c;
      else if (c === "(") depth++;
      else if (c === ")") depth = Math.max(0, depth - 1);
      else if (c === ";" &#038;&#038; depth === 0) {
        decls.push(css.slice(start, i));
        start = i + 1;
      } else {
        const end = scanUnquotedUrl(css, i);
        if (end !== -1) i = end - 1;
      }
    }
    decls.push(css.slice(start));
    return decls.map((d) => d.trim()).filter(Boolean).map((d) => /!\s*important$/i.test(d) ? d : d + " !important").join(";");
  }
  function createPseudoSheet(doc) {
    let el = null;
    const cache = /* @__PURE__ */ new Map();
    let n = 0;
    return (pseudo, css) => {
      const k = pseudo + "|" + css;
      const hit = cache.get(k);
      if (hit) return hit;
      if (!el) {
        el = doc.createElement("style");
        doc.head.appendChild(el);
      }
      const cls = "scp" + (n++).toString(36);
      const isPseudoElement = pseudo === "before" || pseudo === "after";
      const sel = isPseudoElement ? "." + cls + "::" + pseudo : "." + cls + ":" + pseudo;
      el.sheet.insertRule(
        sel + "{" + (isPseudoElement ? css : importantify(css)) + "}",
        el.sheet.cssRules.length
      );
      cache.set(k, cls);
      return cls;
    };
  }

  // src/registry.ts
  function createRegistry() {
    const entries = /* @__PURE__ */ Object.create(null);
    function get(name) {
      return entries[name] || (entries[name] = {
        html: "",
        tpl: null,
        Logic: null,
        jsStreaming: false,
        htmlStreaming: false,
        ver: 0,
        subs: /* @__PURE__ */ new Set(),
        fetched: false
      });
    }
    function bump(name) {
      const r = get(name);
      r.ver++;
      for (const fn of r.subs) fn();
    }
    return {
      entries,
      get,
      bump,
      bumpAll() {
        for (const n in entries) bump(n);
      }
    };
  }

  // src/runtime.ts
  var COMPONENT_DIR = ".";
  function createRuntime(doc = document) {
    const registry = createRegistry();
    const pseudoClass = createPseudoSheet(doc);
    const helmet = createHelmetManager(
      doc,
      (name) => registry.get(name).htmlStreaming
    );
    const external = createExternalModules(() => registry.bumpAll());
    const factory = createComponentFactory(registry, ensureFetched);
    const host = {
      component: (name) => factory.getDC(name),
      placeholder: (props) => h(Placeholder, props),
      helmet: (node) => helmet.compile(node),
      loadExternal: (kind, url, after) => external.load(kind, url, after),
      resolveExternal: (url, name) => external.resolve(url, name),
      resolveExternalGlobal: (url, name) => external.resolveGlobal(url, name),
      resolveExternalError: (url, name) => external.getError(url, name),
      pseudoClass
    };
    function ensureFetched(name) {
      const r = registry.get(name);
      if (r.fetched) return;
      r.fetched = true;
      const url = COMPONENT_DIR + "/" + encodeURIComponent(name) + ".dc.html";
      const res = window.__resources;
      const pre = res ? res[url] : void 0;
      const target = typeof pre === "string" && pre ? pre : url;
      const blob = bundledBlob(target);
      (blob ? blob.text() : fetch(target).then((res2) => {
        if (!res2.ok) {
          console.error(
            '[dc-runtime] sibling fetch for "' + name + '" failed:',
            url,
            "returned",
            res2.status,
            "\u2014 the reference renders as an empty placeholder."
          );
          return "";
        }
        return res2.text();
      })).then((t) => {
        if (!t) return;
        const parsed = parseDcText(t);
        if (!parsed) {
          console.error(
            '[dc-runtime] sibling fetch for "' + name + '":',
            url,
            "has no <x-dc> block \u2014 not a Design Component."
          );
          return;
        }
        if (parsed.props) r.propsMeta = parsed.props;
        if (parsed.preview) r.preview = parsed.preview;
        if (parsed.template && !r.html) updateHtml(name, parsed.template);
        if (parsed.js && !r.Logic) updateJs(name, parsed.js);
      }).catch(
        (e) => console.error(
          '[dc-runtime] sibling fetch for "' + name + '" threw:',
          url,
          e
        )
      );
    }
    let rootName = null;
    function updateHtml(name, html) {
      const r = registry.get(name);
      r.html = html;
      if (name === rootName) {
        const mode = DESIGN_DOC_MODE_RE.exec(html)?.[1] ?? null;
        if (mode || !r.htmlStreaming) helmet.setDesignDocMode(mode);
      }
      try {
        r.tpl = compileTemplate(html, host);
      } catch (e) {
        console.error("[dc-runtime] template compile FAILED for", name, e);
      }
      registry.bump(name);
    }
    function updateJs(name, src) {
      const r = registry.get(name);
      const seq = r.jsSeq = (r.jsSeq || 0) + 1;
      try {
        const Cls = evalDcLogic(src);
        if (r.jsSeq !== seq) return;
        if (typeof Cls !== "function") {
          r.logicError = name + ".dc.html: <script data-dc-script> must define `class Component extends DCLogic`";
        } else {
          r.logicError = null;
          r.Logic = Cls;
        }
      } catch (e) {
        if (r.jsSeq !== seq) return;
        console.error(
          "[dc-runtime] logic class eval FAILED for",
          name,
          "\u2014 the template renders with props only.",
          e
        );
        r.logicError = name + ": " + (e instanceof Error && e.message ? e.message : String(e));
      }
      registry.bump(name);
    }
    function setStreaming(name, kind, on) {
      const r = registry.get(name);
      if (kind === "html") r.htmlStreaming = !!on;
      else r.jsStreaming = !!on;
      let any = false;
      for (const n in registry.entries) {
        const e = registry.entries[n];
        if (e && (e.htmlStreaming || e.jsStreaming)) {
          any = true;
          break;
        }
      }
      doc.documentElement.classList.toggle("sc-dc-streaming", any);
      registry.bump(name);
    }
    function dcUpdate(name, kind, content, streaming) {
      if (streaming) registry.get(name).fetched = true;
      if (kind === "html") {
        setStreaming(name, "html", !!streaming);
        updateHtml(name, content);
      } else if (kind === "js") {
        setStreaming(name, "js", !!streaming);
        if (!streaming) updateJs(name, content);
      } else if (kind === "props") {
        const { props, preview } = parseDataProps(content);
        const r = registry.get(name);
        r.propsMeta = props ?? void 0;
        r.preview = preview;
        registry.bump(name);
      }
    }
    function setProps(name, overrides) {
      registry.get(name).propOverrides = overrides && typeof overrides === "object" ? { ...overrides } : null;
      registry.bump(name);
    }
    function adoptParsed(name, parsed) {
      if (!parsed) return;
      const r = registry.get(name);
      if (parsed.props) r.propsMeta = parsed.props;
      if (parsed.preview) r.preview = parsed.preview;
      if (parsed.template) updateHtml(name, parsed.template);
      if (parsed.js) updateJs(name, parsed.js);
    }
    return {
      registry,
      getDC: factory.getDC,
      updateHtml,
      updateJs,
      dcUpdate,
      setProps,
      adoptParsed,
      setRootName: (name) => {
        rootName = name;
      },
      markFetched: (name) => {
        registry.get(name).fetched = true;
      },
      annotatedTemplate: (name) => {
        const r = registry.get(name);
        return r.tpl && r.tpl.__annotated || null;
      },
      templateSource: (name) => registry.get(name).html || null,
      StreamableLogic
    };
  }

  // src/stream-state.ts
  function createStreamTracker(staleMs = 6e4, now = Date.now) {
    const since = /* @__PURE__ */ new Map();
    const liveOne = (n) => {
      const t = since.get(n);
      if (t === void 0) return false;
      if (now() - t > staleMs) {
        since.delete(n);
        return false;
      }
      return true;
    };
    return {
      push(name, streaming, viewportKey) {
        if (viewportKey === "dc-model") return;
        if (streaming) since.set(name, now());
        else since.delete(name);
      },
      live(name) {
        if (name !== void 0) return liveOne(name);
        for (const n of [...since.keys()]) if (liveOne(n)) return true;
        return false;
      }
    };
  }

  // src/index.ts
  function hideRawTemplate() {
    const s = document.createElement("style");
    s.textContent = "x-dc{display:none!important}";
    document.head.appendChild(s);
  }
  function loadScript(src, integrity) {
    return new Promise((resolve2, reject) => {
      //! nosemgrep: create-script-element
      const s = document.createElement("script");
      s.src = src;
      if (integrity) {
        s.integrity = integrity;
        s.crossOrigin = "anonymous";
      }
      s.async = false;
      s.onload = () => resolve2();
      s.onerror = () => reject(new Error(`failed to load ${src}`));
      document.head.appendChild(s);
    });
  }
  function loadReactUmd() {
    const w = window;
    if (w.React && w.ReactDOM) return Promise.resolve();
    const react = cdnScriptFor(REACT_URL, REACT_SRI);
    const reactDom = cdnScriptFor(REACT_DOM_URL, REACT_DOM_SRI);
    return Promise.all([
      loadScript(react.src, react.integrity),
      loadScript(reactDom.src, reactDom.integrity)
    ]).then(() => void 0);
  }
  function init() {
    const runtime = createRuntime(document);
    let rootName = "Root";
    const baseCss = document.createElement("style");
    baseCss.textContent = BASE_CSS;
    document.head.prepend(baseCss);
    const notifyHost = () => {
      if (window.parent === window) return;
      const r = runtime.registry.entries[rootName];
      try {
        window.parent.postMessage(
          {
            type: "__dc_booted",
            rootName,
            propsMeta: r && r.propsMeta || null,
            preview: r && r.preview || null
          },
          "*"
        );
      } catch {
      }
    };
    const streams = createStreamTracker();
    const api = {
      __dcUpdate: (name, kind, content, streaming, viewportKey) => {
        streams.push(name, streaming, viewportKey);
        runtime.dcUpdate(name, kind, content, streaming);
        if (name === rootName && !streaming && kind === "props") notifyHost();
      },
      __dcStreaming: (name) => streams.live(name),
      __dcSetProps: (name, overrides) => runtime.setProps(name, overrides),
      /** Name of the component currently mounted as the page root — DC tools
       *  push their template-stream here when targeting "the open page". */
      __dcRootName: () => rootName,
      /** Editor bridge — the encoded, `data-dc-tpl`-annotated template source.
       *  The host editor parses this into its own template DOM so it can map a
       *  rendered node (carrying the same `data-dc-tpl`) back to the source
       *  node that emitted it. Returns the encoded form (`sc-camel-*` attrs,
       *  `<sc-raw-*>`/`<sc-helmet>` tags); the editor decodes on serialize. */
      __dcAnnotatedTemplate: (name) => runtime.annotatedTemplate(name),
      /** Editor bridge — the *original* (decoded) template source. */
      __dcTemplateSource: (name) => runtime.templateSource(name),
      __dcBoot: () => {
        rootName = boot(runtime, document) ?? rootName;
        notifyHost();
      },
      __dcRegistry: runtime.registry.entries,
      getDC: (name) => runtime.getDC(name),
      // `DCLogic` is the documented base class name; `StreamableLogic` is the
      // implementation alias kept for any project that already references it.
      DCLogic: runtime.StreamableLogic,
      StreamableLogic: runtime.StreamableLogic
    };
    Object.assign(window, api);
    window.__dcContentKeyed = true;
    if (document.readyState !== "loading") api.__dcBoot();
    else document.addEventListener("DOMContentLoaded", () => api.__dcBoot());
  }
  hideRawTemplate();
  loadReactUmd().then(init).catch((err) => {
    console.error("[dc] failed to load React or boot:", err);
    throw err;
  });
})();

</script>

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="" />
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;500;700;900&#038;family=Inter:wght@400;500;600;800&#038;display=swap" rel="stylesheet" />
<style>
  html, body { margin: 0; padding: 0; background: #f1f1f1; }
  body { font-family: "Noto Sans TC", "Inter", system-ui, sans-serif; color: #2A3138; -webkit-font-smoothing: antialiased; text-wrap: pretty; }
  a { color: #608075; text-decoration: none; }
  a:hover { color: #2A3138; }
  * { box-sizing: border-box; }
  input[type=range] { -webkit-appearance: none; appearance: none; background: transparent; }
  input[type=range]::-webkit-slider-runnable-track { height: 4px; border-radius: 2px; background: #cfd4d8; }
  input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; width: 22px; height: 22px; margin-top: -9px; border-radius: 50%; background: #F7C01B; border: 3px solid #fff; box-shadow: 0 2px 6px rgba(41,41,41,.25); cursor: grab; }
  summary::-webkit-details-marker { display: none; }
  @keyframes auraDrift { 0% { transform: rotate(0deg) scale(1.08); } 50% { transform: rotate(180deg) scale(1.2); } 100% { transform: rotate(360deg) scale(1.08); } }
  @media (prefers-reduced-motion: reduce) { * { animation: none !important; } }
</style>
</helmet>

<div style="background:#f1f1f1;overflow-x:hidden">

<header style="position:sticky;top:0;z-index:40;display:flex;align-items:center;justify-content:space-between;gap:16px;padding:14px clamp(18px,5vw,56px);background:rgba(241,241,241,.86);backdrop-filter:blur(14px);border-bottom:1px solid rgba(42,49,56,.08)">
  <div style="display:flex;align-items:center;gap:10px">
    <div style="width:26px;height:26px;border-radius:9px;background:conic-gradient(from 200deg,#FF2633,#FF851A,#FFE026,#26E659,#1AD9D9,#2659FF,#8C40FF,#FF40BF,#FF2633)"></div>
    <span style="font-family:Inter,sans-serif;font-weight:800;letter-spacing:.14em;font-size:15px">ODDCAM</span>
  </div>
  <a href="https://apps.apple.com/tw/app/oddcam/id6772143344" target="_blank" rel="noopener" style="display:inline-flex;align-items:center;padding:10px 20px;border-radius:999px;background:#F7C01B;color:#2A3138;font-weight:700;font-size:14px">免費下載</a>
</header>

<section style="position:relative;display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));align-items:center;gap:clamp(28px,5vw,64px);padding:clamp(48px,8vw,110px) clamp(18px,5vw,56px) clamp(56px,8vw,96px);max-width:1220px;margin:0 auto">
  <div style="position:relative;z-index:2">
    <div style="display:inline-flex;align-items:center;gap:8px;padding:6px 14px;border-radius:999px;background:#73A7BF;color:#fff;font-size:12px;font-weight:700;letter-spacing:.08em">NEW · 氣場相機 AURA CAMERA</div>
    <h1 style="margin:20px 0 0;font-size:clamp(38px,6.4vw,72px);line-height:1.04;font-weight:900;letter-spacing:-.02em">你的氣場，<br />今天是什麼顏色？</h1>
    <p style="margin:22px 0 0;max-width:30em;font-size:clamp(16px,1.4vw,19px);line-height:1.75;color:rgba(42,49,56,.78)">ODDCAM 氣場相機，把彩色光環即時疊在你身上——不必跑攝影棚，手機就能拍出底片感的氣場照。</p>
    <div style="display:flex;flex-wrap:wrap;align-items:center;gap:16px;margin-top:32px">
      <a href="https://apps.apple.com/tw/app/oddcam/id6772143344" target="_blank" rel="noopener" style="display:inline-flex;align-items:center;gap:10px;padding:17px 32px;border-radius:999px;background:#F7C01B;color:#2A3138;font-weight:700;font-size:17px;box-shadow:0 8px 20px rgba(247,192,27,.38)" style-hover="transform:translateY(-2px);box-shadow:0 12px 26px rgba(247,192,27,.5)">免費下載 ODDCAM</a>
      <a href="#modes" style="font-size:15px;font-weight:500;color:#608075;border-bottom:1px solid rgba(96,128,117,.4);padding-bottom:2px">看看能拍出什麼 ↓</a>
    </div>
    <p style="margin:22px 0 0;font-size:13px;color:rgba(42,49,56,.5);line-height:1.7">iOS 15+ · 每款相機免費試用 3 次 · Pro 一次買斷不訂閱</p>
  </div>
  <div style="position:relative;aspect-ratio:4/5;border-radius:24px;overflow:hidden;background:#e4e4e4;box-shadow:0 18px 50px rgba(41,41,41,.16)">
    <div style="position:absolute;inset:-30%;filter:blur(60px);opacity:.85;background:conic-gradient(from 30deg,#FF2633,#FF851A,#FFE026,#26E659,#1AD9D9,#2659FF,#8C40FF,#FF40BF,#FF2633);animation:auraDrift 26s linear infinite"></div>
    <img decoding="async" src="https://imgpoi.com/i/M4YPHB.jpg" alt="氣場相機實拍範例：彩色光環包圍人像" style="position:relative;z-index:2;width:100%;height:100%;object-fit:cover;mix-blend-mode:normal" />
  </div>
</section>

<section style="padding:clamp(44px,7vw,88px) clamp(18px,5vw,56px);max-width:1120px;margin:0 auto">
  <h2 style="margin:0 0 36px;font-size:clamp(26px,3.4vw,40px);font-weight:900;letter-spacing:-.01em">氣場照，不用再排隊等攝影棚</h2>
  <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:20px">
    <div style="padding:28px;border-radius:18px;background:#e6e6e6;color:rgba(42,49,56,.62)">
      <div style="font-size:13px;font-weight:700;letter-spacing:.1em;margin-bottom:20px">傳統氣場攝影</div>
      <div style="display:grid;gap:14px;font-size:15px;line-height:1.6">
        <div>要預約、要出門、要等沖印</div>
        <div>一張數百元，拍壞了也算</div>
        <div>顏色由機器決定，拍完才知道</div>
        <div>只有照片</div>
      </div>
    </div>
    <div style="padding:28px;border-radius:18px;background:#fff;box-shadow:0 3px 8px rgba(41,41,41,.08);border:2px solid #F7C01B">
      <div style="font-size:13px;font-weight:700;letter-spacing:.1em;margin-bottom:20px;color:#608075">ODDCAM 氣場相機</div>
      <div style="display:grid;gap:14px;font-size:15px;line-height:1.6;font-weight:500">
        <div>打開 App，即時預覽</div>
        <div>想拍幾張就拍幾張</div>
        <div>自己選顏色，或交給隨機</div>
        <div>照片、影片都能拍</div>
      </div>
    </div>
  </div>
  <p style="margin:28px 0 0;font-size:clamp(17px,2vw,22px);font-weight:700;color:#608075">同樣的夢幻色暈，主導權在你手上。</p>
</section>

<section style="padding:clamp(44px,7vw,88px) clamp(18px,5vw,56px);max-width:1120px;margin:0 auto">
  <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:clamp(28px,4vw,56px);align-items:center">
    <div style="position:relative;aspect-ratio:4/5;border-radius:18px;overflow:hidden;background:#111;box-shadow:0 3px 8px rgba(41,41,41,.12)">
      <img decoding="async" src="https://imgpoi.com/i/M4YPHB.jpg" alt="氣場相機互動示範" style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover" />
      <div style="position:absolute;inset:-20%;filter:blur(34px);opacity:.92;background:{{ auraBg }};-webkit-mask-image:{{ auraMask }};mask-image:{{ auraMask }}"></div>
      <div style="position:absolute;left:14px;bottom:14px;z-index:3;padding:6px 12px;border-radius:999px;background:rgba(0,0,0,.45);color:#fff;font-family:Inter,sans-serif;font-size:12px;font-weight:600;backdrop-filter:blur(6px)">氣場範圍 {{ rangeLabel }}</div>
    </div>
    <div>
      <div style="font-size:12px;font-weight:700;letter-spacing:.14em;color:#7093B4">互動示範 · 拖曳試試</div>
      <h3 style="margin:12px 0 0;font-size:clamp(24px,3vw,34px);font-weight:900;line-height:1.2">八種光暈色，最多四色同時混合</h3>
      <p style="margin:16px 0 0;font-size:16px;line-height:1.8;color:rgba(42,49,56,.78)">紅、橙、黃、綠、青、藍、紫、洋紅——挑最多四種顏色，光環會照你點選的順序混色。不知道挑什麼？切到隨機模式，每次進相機自動抽 2–3 色，拍到滿意為止。</p>
      <div style="display:flex;flex-wrap:wrap;gap:12px;margin-top:24px">
        <sc-for list="{{ swatches }}" as="sw" hint-placeholder-count="8">
          <button type="button" onClick="{{ sw.pick }}" aria-label="{{ sw.name }}" style="width:44px;height:44px;border-radius:50%;cursor:pointer;background:{{ sw.hex }};border:{{ sw.border }};box-shadow:0 2px 6px rgba(41,41,41,.16)" style-hover="transform:scale(1.08)"></button>
        </sc-for>
      </div>
      <div style="margin-top:32px;display:grid;gap:26px">
        <label style="display:block">
          <div style="display:flex;justify-content:space-between;font-size:14px;font-weight:700;margin-bottom:10px"><span>氣場範圍</span><span style="font-family:Inter,sans-serif;color:#608075">{{ rangeLabel }}</span></div>
          <input type="range" min="20" max="100" step="1" value="{{ range }}" onChange="{{ onRange }}" style="width:100%" />
          <div style="display:flex;justify-content:space-between;font-size:12px;color:rgba(42,49,56,.45);margin-top:6px"><span>20% 中央留白</span><span>100% 滿版</span></div>
        </label>
        <label style="display:block">
          <div style="display:flex;justify-content:space-between;font-size:14px;font-weight:700;margin-bottom:10px"><span>邊緣羽化</span><span style="font-family:Inter,sans-serif;color:#608075">{{ featherLabel }}</span></div>
          <input type="range" min="0" max="100" step="1" value="{{ feather }}" onChange="{{ onFeather }}" style="width:100%" />
          <div style="display:flex;justify-content:space-between;font-size:12px;color:rgba(42,49,56,.45);margin-top:6px"><span>邊界俐落</span><span>幾乎看不出交界</span></div>
        </label>
      </div>
    </div>
  </div>
</section>

<section style="padding:clamp(20px,4vw,44px) clamp(18px,5vw,56px) clamp(44px,7vw,80px);max-width:1120px;margin:0 auto">
  <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:clamp(28px,4vw,56px);align-items:center">
    <div>
      <h3 style="margin:0;font-size:clamp(22px,2.6vw,30px);font-weight:900;line-height:1.25">一條拉桿決定顏色離你多近，另一條讓交界消失</h3>
      <p style="margin:16px 0 0;font-size:16px;line-height:1.8;color:rgba(42,49,56,.78)">20% 時中央留一大片清晰區，人像清楚、光環在外圍；一路推到 100%，顏色淹沒整個畫面、只剩隱約輪廓透出來。羽化調低邊界俐落；調高，色暈化得幾乎看不出從哪裡開始——就像顏色真的從你身上散出來。</p>
      <p style="margin:18px 0 0;font-size:14px;color:rgba(42,49,56,.5);line-height:1.7">氣場強度另有三段可選：輕柔／標準／濃烈。</p>
    </div>
    <div style="border-radius:18px;overflow:hidden;background:#fff;box-shadow:0 3px 8px rgba(41,41,41,.08);padding:14px">
      <img decoding="async" src="https://imgpoi.com/i/M4Y1VG.png" alt="氣場相機設定畫面：選色器與拉桿" style="display:block;width:100%;border-radius:10px" />
    </div>
  </div>
</section>

<section style="padding:clamp(44px,7vw,80px) clamp(18px,5vw,56px);background:#608075">
  <div style="max-width:1120px;margin:0 auto;display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:18px">
    <div style="padding:24px;border-radius:18px;background:rgba(255,255,255,.1)">
      <div style="font-size:15px;font-weight:700;color:#F7C01B">即時預覽</div>
      <div style="margin-top:8px;font-size:14px;line-height:1.7;color:rgba(255,255,255,.85)">拍之前就看得到成品，不用事後修</div>
    </div>
    <div style="padding:24px;border-radius:18px;background:rgba(255,255,255,.1)">
      <div style="font-size:15px;font-weight:700;color:#F7C01B">重擲氣場</div>
      <div style="margin-top:8px;font-size:14px;line-height:1.7;color:rgba(255,255,255,.85)">一鍵換一組光環佈局，隨機模式連顏色一起重抽</div>
    </div>
    <div style="padding:24px;border-radius:18px;background:rgba(255,255,255,.1)">
      <div style="font-size:15px;font-weight:700;color:#F7C01B">照片與影片</div>
      <div style="margin-top:8px;font-size:14px;line-height:1.7;color:rgba(255,255,255,.85)">效果直接燒錄進 1080p／4K 影片</div>
    </div>
    <div style="padding:24px;border-radius:18px;background:rgba(255,255,255,.1)">
      <div style="font-size:15px;font-weight:700;color:#F7C01B">預設自拍</div>
      <div style="margin-top:8px;font-size:14px;line-height:1.7;color:rgba(255,255,255,.85)">為人像設計，前後鏡頭都能用</div>
    </div>
    <div style="padding:24px;border-radius:18px;background:rgba(255,255,255,.1)">
      <div style="font-size:15px;font-weight:700;color:#F7C01B">實體鍵操作</div>
      <div style="margin-top:8px;font-size:14px;line-height:1.7;color:rgba(255,255,255,.85)">音量下鍵拍照、音量上鍵重擲（iOS 17.2+）</div>
    </div>
    <div style="padding:24px;border-radius:18px;background:rgba(255,255,255,.1)">
      <div style="font-size:15px;font-weight:700;color:#F7C01B">本機處理</div>
      <div style="margin-top:8px;font-size:14px;line-height:1.7;color:rgba(255,255,255,.85)">影像不離開你的手機</div>
    </div>
  </div>
</section>

<section id="modes" style="padding:clamp(48px,7vw,96px) clamp(18px,5vw,56px);max-width:1220px;margin:0 auto">
  <h2 style="margin:0;font-size:clamp(26px,3.6vw,44px);font-weight:900;letter-spacing:-.01em">不只氣場，還有九種拍法在等你</h2>
  <p style="margin:14px 0 40px;font-size:17px;color:rgba(42,49,56,.62)">同一支 App，切換十種非主流相機。</p>
  <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:20px">
    <div style="border-radius:18px;background:#fff;box-shadow:0 3px 8px rgba(41,41,41,.08);overflow:hidden;border:2px solid #F7C01B">
      <div style="position:relative;aspect-ratio:4/5;background:#111;overflow:hidden">
        <img decoding="async" src="https://imgpoi.com/i/M4YY8M.png" alt="氣場相機首頁卡片" style="width:100%;height:100%;object-fit:cover" />
        <div style="position:absolute;top:12px;left:12px;padding:5px 11px;border-radius:999px;background:#F7C01B;font-size:11px;font-weight:800;letter-spacing:.08em">NEW</div>
      </div>
      <div style="padding:18px 20px 22px">
        <div style="font-size:17px;font-weight:800">氣場相機</div>
        <div style="margin-top:8px;font-size:14px;line-height:1.65;color:rgba(42,49,56,.68)">彩色光環即時包圍人像，配色、範圍、羽化都能調</div>
      </div>
    </div>
    <div style="border-radius:18px;background:#fff;box-shadow:0 3px 8px rgba(41,41,41,.08);overflow:hidden">
      <div style="aspect-ratio:4/5;background:#e9e9e9;overflow:hidden"><img decoding="async" src="https://imgpoi.com/i/M4NGDM.png" alt="放大鏡相機實拍" style="width:100%;height:100%;object-fit:cover" /></div>
      <div style="padding:18px 20px 22px"><div style="font-size:17px;font-weight:800">放大鏡相機</div><div style="margin-top:8px;font-size:14px;line-height:1.65;color:rgba(42,49,56,.68)">黃框內放大搭配完整原圖，一張圖看見兩種距離</div></div>
    </div>
    <div style="border-radius:18px;background:#fff;box-shadow:0 3px 8px rgba(41,41,41,.08);overflow:hidden">
      <div style="aspect-ratio:4/5;background:#e9e9e9;overflow:hidden"><img decoding="async" src="https://imgpoi.com/i/M4NE8D.png" alt="多點氣泡放大鏡相機實拍" style="width:100%;height:100%;object-fit:cover" /></div>
      <div style="padding:18px 20px 22px"><div style="font-size:17px;font-weight:800">多點氣泡放大鏡</div><div style="margin-top:8px;font-size:14px;line-height:1.65;color:rgba(42,49,56,.68)">最多六個圓形放大區，拖曳移動、捏合縮放</div></div>
    </div>
    <div style="border-radius:18px;background:#fff;box-shadow:0 3px 8px rgba(41,41,41,.08);overflow:hidden">
      <div style="aspect-ratio:4/5;background:#e9e9e9;overflow:hidden"><img decoding="async" src="https://imgpoi.com/i/M4ND9E.png" alt="抽色相機設定畫面" style="width:100%;height:100%;object-fit:cover" /></div>
      <div style="padding:18px 20px 22px"><div style="font-size:17px;font-weight:800">抽色相機</div><div style="margin-top:8px;font-size:14px;line-height:1.65;color:rgba(42,49,56,.68)">只留下你指定的顏色，其餘轉為黑白</div></div>
    </div>
    <div style="border-radius:18px;background:#fff;box-shadow:0 3px 8px rgba(41,41,41,.08);overflow:hidden">
      <div style="aspect-ratio:4/5;background:#e9e9e9;overflow:hidden"><img decoding="async" src="https://imgpoi.com/i/M4NVLV.png" alt="顛倒相機實拍" style="width:100%;height:100%;object-fit:cover" /></div>
      <div style="padding:18px 20px 22px"><div style="font-size:17px;font-weight:800">顛倒相機 Flip</div><div style="margin-top:8px;font-size:14px;line-height:1.65;color:rgba(42,49,56,.68)">兩側各套一種 LUT，可各自旋轉 90 度再合成</div></div>
    </div>
    <div style="border-radius:18px;background:#fff;box-shadow:0 3px 8px rgba(41,41,41,.08);overflow:hidden">
      <div style="aspect-ratio:4/5;background:#e9e9e9;overflow:hidden"><img decoding="async" src="https://imgpoi.com/i/M4N0QB.png" alt="分割相機實拍" style="width:100%;height:100%;object-fit:cover" /></div>
      <div style="padding:18px 20px 22px"><div style="font-size:17px;font-weight:800">分割相機</div><div style="margin-top:8px;font-size:14px;line-height:1.65;color:rgba(42,49,56,.68)">依序拍 2–8 格，合成一張分割作品</div></div>
    </div>
    <div style="border-radius:18px;background:#fff;box-shadow:0 3px 8px rgba(41,41,41,.08);overflow:hidden">
      <div style="aspect-ratio:4/5;background:#e9e9e9;overflow:hidden"><img decoding="async" src="https://imgpoi.com/i/M4NC1G.png" alt="地圖相機實拍" style="width:100%;height:100%;object-fit:cover" /></div>
      <div style="padding:18px 20px 22px"><div style="font-size:17px;font-weight:800">地圖相機</div><div style="margin-top:8px;font-size:14px;line-height:1.65;color:rgba(42,49,56,.68)">相機與地圖同框，記下經緯度、海拔與街道</div></div>
    </div>
    <div style="border-radius:18px;background:#fff;box-shadow:0 3px 8px rgba(41,41,41,.08);overflow:hidden">
      <div style="aspect-ratio:4/5;background:#e9e9e9;overflow:hidden"><img decoding="async" src="https://imgpoi.com/i/M4NBR2.png" alt="狹縫掃描相機實拍" style="width:100%;height:100%;object-fit:cover" /></div>
      <div style="padding:18px 20px 22px"><div style="font-size:17px;font-weight:800">狹縫掃描</div><div style="margin-top:8px;font-size:14px;line-height:1.65;color:rgba(42,49,56,.68)">固定狹縫逐條掃描，拍出時空拉伸的照片</div></div>
    </div>
    <div style="border-radius:18px;background:#fff;box-shadow:0 3px 8px rgba(41,41,41,.08);overflow:hidden">
      <div style="aspect-ratio:4/5;background:#e9e9e9;overflow:hidden"><img decoding="async" src="https://imgpoi.com/i/M4NQH9.png" alt="扭曲相機實拍" style="width:100%;height:100%;object-fit:cover" /></div>
      <div style="padding:18px 20px 22px"><div style="font-size:17px;font-weight:800">扭曲相機</div><div style="margin-top:8px;font-size:14px;line-height:1.65;color:rgba(42,49,56,.68)">正弦波幾何扭曲，強度、密度、水波都可調</div></div>
    </div>
    <div style="border-radius:18px;background:#fff;box-shadow:0 3px 8px rgba(41,41,41,.08);overflow:hidden">
      <div style="aspect-ratio:4/5;background:#e9e9e9;overflow:hidden"><img decoding="async" src="https://imgpoi.com/i/M4YY8M.png" alt="潛水相機首頁卡片" style="width:100%;height:100%;object-fit:cover;object-position:bottom" /></div>
      <div style="padding:18px 20px 22px"><div style="font-size:17px;font-weight:800">潛水相機</div><div style="margin-top:8px;font-size:14px;line-height:1.65;color:rgba(42,49,56,.68)">依水深還原水下流失的紅色，音量鍵水下也能操作（2／5／10／20 公尺）</div></div>
    </div>
  </div>
</section>

<section style="padding:clamp(44px,7vw,88px) clamp(18px,5vw,56px);max-width:1120px;margin:0 auto">
  <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:clamp(28px,4vw,56px);align-items:center">
    <div>
      <div style="font-size:12px;font-weight:700;letter-spacing:.14em;color:#7093B4">LUT 外觀濾鏡</div>
      <h3 style="margin:12px 0 0;font-size:clamp(24px,3vw,34px);font-weight:900;line-height:1.2">11 種原創 LUT，還能匯入你自己的</h3>
      <p style="margin:16px 0 0;font-size:16px;line-height:1.8;color:rgba(42,49,56,.78)">內建 11 種原創風格 LUT，一次套用到預覽、照片與影片。有自己慣用的調色？上傳 .cube 檔就能直接用。想拍原始色調，選「原始」即可關閉。</p>
    </div>
    <div style="border-radius:18px;overflow:hidden;background:#fff;box-shadow:0 3px 8px rgba(41,41,41,.08);padding:14px">
      <img decoding="async" src="https://imgpoi.com/i/M4YZV5.png" alt="扭曲相機設定頁面，含 LUT 選擇" style="display:block;width:100%;border-radius:10px" />
    </div>
  </div>
</section>

<section style="padding:clamp(44px,7vw,88px) clamp(18px,5vw,56px);max-width:1120px;margin:0 auto">
  <h2 style="margin:0 0 12px;text-align:center;font-size:clamp(26px,3.4vw,40px);font-weight:900">一次買斷，不是訂閱</h2>
  <p style="margin:0 auto 36px;max-width:44em;text-align:center;font-size:16px;line-height:1.8;color:rgba(42,49,56,.7)">每款相機都可以先免費進入 3 次（各相機分開計算），同一次進入內想拍多少張都可以。試過覺得對味，再用 ODDCAM Pro 一次解鎖全部模式、無限使用。</p>
  <div style="max-width:460px;margin:0 auto;padding:32px;border-radius:18px;background:#fff;border:2px solid #F7C01B;box-shadow:0 3px 8px rgba(41,41,41,.08)">
    <div style="display:flex;align-items:center;justify-content:space-between;gap:12px">
      <div style="font-size:22px;font-weight:900">ODDCAM Pro</div>
      <div style="padding:5px 12px;border-radius:999px;background:#73A7BF;color:#fff;font-size:12px;font-weight:700">一次買斷 · 非訂閱</div>
    </div>
    <div style="display:grid;gap:12px;margin:24px 0 28px;font-size:15px;line-height:1.6">
      <div>解鎖全部十種相機，無限次進入</div>
      <div>不綁月費、不自動續訂</div>
      <div>可跨裝置恢復購買</div>
    </div>
    <a href="https://apps.apple.com/tw/app/oddcam/id6772143344" target="_blank" rel="noopener" style="display:block;text-align:center;padding:16px;border-radius:999px;background:#F7C01B;color:#2A3138;font-weight:700;font-size:16px" style-hover="filter:brightness(1.05)">下載 App 查看價格</a>
  </div>
</section>

<section style="padding:clamp(44px,7vw,88px) clamp(18px,5vw,56px);background:#e7eaee">
  <div style="max-width:1120px;margin:0 auto">
    <h2 style="margin:0;font-size:clamp(24px,3.2vw,38px);font-weight:900">你的照片，不會離開你的手機</h2>
    <p style="margin:14px 0 32px;max-width:44em;font-size:16px;line-height:1.8;color:rgba(42,49,56,.72)">所有影像處理都在裝置本機完成，ODDCAM 不會把你的照片或影片上傳到任何雲端。</p>
    <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:16px">
      <div style="padding:20px;border-radius:18px;background:#fff"><div style="font-weight:800;font-size:15px">相機</div><div style="margin-top:6px;font-size:14px;color:rgba(42,49,56,.66)">各模式的即時預覽與拍攝</div></div>
      <div style="padding:20px;border-radius:18px;background:#fff"><div style="font-weight:800;font-size:15px">麥克風</div><div style="margin-top:6px;font-size:14px;color:rgba(42,49,56,.66)">錄影收音</div></div>
      <div style="padding:20px;border-radius:18px;background:#fff"><div style="font-weight:800;font-size:15px">相簿</div><div style="margin-top:6px;font-size:14px;color:rgba(42,49,56,.66)">儲存照片與影片</div></div>
      <div style="padding:20px;border-radius:18px;background:#fff"><div style="font-weight:800;font-size:15px">定位（使用時）</div><div style="margin-top:6px;font-size:14px;color:rgba(42,49,56,.66)">地圖相機顯示位置資訊</div></div>
    </div>
  </div>
</section>

<section style="padding:clamp(44px,7vw,88px) clamp(18px,5vw,56px);max-width:820px;margin:0 auto">
  <h2 style="margin:0 0 28px;font-size:clamp(24px,3.2vw,38px);font-weight:900">常見問題</h2>
  <div style="display:grid;gap:12px">
    <details style="padding:20px 22px;border-radius:18px;background:#fff;box-shadow:0 3px 8px rgba(41,41,41,.08)">
      <summary style="list-style:none;cursor:pointer;font-size:16px;font-weight:700">氣場相機拍出來的顏色，是真的量測我的氣場嗎？</summary>
      <p style="margin:14px 0 0;font-size:15px;line-height:1.8;color:rgba(42,49,56,.72)">不是。ODDCAM 的氣場相機是一種影像效果——由你選定（或隨機抽選）的顏色生成光環並疊在畫面上，用來拍出好看的氣場風格照片。它不做任何生理或能量量測。</p>
    </details>
    <details style="padding:20px 22px;border-radius:18px;background:#fff;box-shadow:0 3px 8px rgba(41,41,41,.08)">
      <summary style="list-style:none;cursor:pointer;font-size:16px;font-weight:700">需要網路連線嗎？</summary>
      <p style="margin:14px 0 0;font-size:15px;line-height:1.8;color:rgba(42,49,56,.72)">拍攝與處理完全在本機進行，不需要連線。</p>
    </details>
    <details style="padding:20px 22px;border-radius:18px;background:#fff;box-shadow:0 3px 8px rgba(41,41,41,.08)">
      <summary style="list-style:none;cursor:pointer;font-size:16px;font-weight:700">效果是拍完再套，還是拍的時候就有？</summary>
      <p style="margin:14px 0 0;font-size:15px;line-height:1.8;color:rgba(42,49,56,.72)">即時預覽就看得到，且效果會直接燒錄進照片與影片。</p>
    </details>
    <details style="padding:20px 22px;border-radius:18px;background:#fff;box-shadow:0 3px 8px rgba(41,41,41,.08)">
      <summary style="list-style:none;cursor:pointer;font-size:16px;font-weight:700">一定要用前鏡頭嗎？</summary>
      <p style="margin:14px 0 0;font-size:15px;line-height:1.8;color:rgba(42,49,56,.72)">氣場相機預設使用前鏡頭（為自拍人像設計），但前後鏡頭都可以切換使用。</p>
    </details>
    <details style="padding:20px 22px;border-radius:18px;background:#fff;box-shadow:0 3px 8px rgba(41,41,41,.08)">
      <summary style="list-style:none;cursor:pointer;font-size:16px;font-weight:700">Pro 是訂閱制嗎？</summary>
      <p style="margin:14px 0 0;font-size:15px;line-height:1.8;color:rgba(42,49,56,.72)">不是。ODDCAM Pro 是一次買斷的非消耗型購買，換手機可以恢復購買。</p>
    </details>
    <details style="padding:20px 22px;border-radius:18px;background:#fff;box-shadow:0 3px 8px rgba(41,41,41,.08)">
      <summary style="list-style:none;cursor:pointer;font-size:16px;font-weight:700">支援哪些機型？</summary>
      <p style="margin:14px 0 0;font-size:15px;line-height:1.8;color:rgba(42,49,56,.72)">iOS 15 以上的 iPhone。部分功能（如音量鍵拍攝）需要 iOS 17.2 以上。</p>
    </details>
  </div>
</section>

<footer style="position:relative;overflow:hidden;padding:clamp(60px,9vw,120px) clamp(18px,5vw,56px) clamp(90px,10vw,120px);background:#2A3138;color:#fff;text-align:center">
  <div style="position:absolute;inset:-40% -10%;filter:blur(90px);opacity:.5;background:conic-gradient(from 120deg,#FF2633,#FF851A,#FFE026,#26E659,#1AD9D9,#2659FF,#8C40FF,#FF40BF,#FF2633);animation:auraDrift 30s linear infinite"></div>
  <div style="position:relative;z-index:2;max-width:720px;margin:0 auto">
    <h2 style="margin:0;font-size:clamp(30px,5vw,58px);font-weight:900;line-height:1.1">今天的你，是什麼顏色？</h2>
    <a href="https://apps.apple.com/tw/app/oddcam/id6772143344" target="_blank" rel="noopener" style="display:inline-flex;margin-top:32px;padding:18px 38px;border-radius:999px;background:#F7C01B;color:#2A3138;font-weight:700;font-size:17px">免費下載 ODDCAM</a>
    <p style="margin:20px 0 0;font-size:13px;color:rgba(255,255,255,.6)">iOS 15+ · 每款相機免費試用 3 次</p>
    <div style="display:flex;flex-wrap:wrap;justify-content:center;gap:20px;margin-top:44px;font-size:14px">
      <a href="#" style="color:rgba(255,255,255,.7)">隱私政策</a>
      <a href="#" style="color:rgba(255,255,255,.7)">使用說明</a>
      <a href="#" style="color:rgba(255,255,255,.7)">聯絡我們</a>
      <a href="https://apps.apple.com/tw/app/oddcam/id6772143344" target="_blank" rel="noopener" style="color:rgba(255,255,255,.7)">App Store</a>
    </div>
  </div>
</footer>

</div>

</x-dc>
<script type="text/x-dc" data-dc-script data-props="{}">
const GLOW = [
  { hex: "#FF2633", name: "紅" }, { hex: "#FF851A", name: "橙" },
  { hex: "#FFE026", name: "黃" }, { hex: "#26E659", name: "綠" },
  { hex: "#1AD9D9", name: "青" }, { hex: "#2659FF", name: "藍" },
  { hex: "#8C40FF", name: "紫" }, { hex: "#FF40BF", name: "洋紅" }
];

class Component extends DCLogic {
  state = { range: 55, feather: 60, picked: ["#FF40BF", "#2659FF", "#FFE026"] };

  toggle(hex) {
    this.setState(s => {
      const has = s.picked.includes(hex);
      if (has) return s.picked.length > 1 ? { picked: s.picked.filter(c => c !== hex) } : null;
      return { picked: s.picked.length >= 4 ? [...s.picked.slice(1), hex] : [...s.picked, hex] };
    });
  }

  renderVals() {
    const { range, feather, picked } = this.state;
    const ring = picked.length === 1 ? [picked[0], picked[0]] : picked;
    const stops = [...ring, ring[0]].join(",");
    const clear = 72 - (range - 20) * 0.72;
    const soft = Math.max(2, clear + 3 + feather * 0.42);
    return {
      range, feather,
      rangeLabel: range + "%",
      featherLabel: feather + "%",
      auraBg: "conic-gradient(from 40deg," + stops + ")",
      auraMask: "radial-gradient(circle at 50% 44%, transparent " + clear.toFixed(1) + "%, #000 " + soft.toFixed(1) + "%)",
      onRange: e => this.setState({ range: Number(e.target.value) }),
      onFeather: e => this.setState({ feather: Number(e.target.value) }),
      swatches: GLOW.map(g => ({
        hex: g.hex, name: g.name,
        border: picked.includes(g.hex) ? "3px solid #2A3138" : "3px solid rgba(255,255,255,.9)",
        pick: () => this.toggle(g.hex)
      }))
    };
  }
}

</script>
</body>
</html>

]]></content:encoded>
					
		
		
			<dc:creator>t0918013013@gmail.com (admin)</dc:creator></item>
	</channel>
</rss>