// showcase.jsx — interactive tool showcase with category filter and detail expand const { useState: useStateSC, useEffect: useEffectSC } = React; function ToolShowcase() { const [cat, setCat] = useStateSC("All"); const [active, setActive] = useStateSC(null); const tools = cat === "All" ? TOOLS : TOOLS.filter(t => t.cat === cat); return (
04 / WORK

これまで作った、 "ちょっと面倒" を畳むツールたち。

業務用も生活用も、規模感の小さなものから運用に耐える業務システムまで。
ジャンルで絞れます。気になるカードをクリックすると詳細が開きます。

{/* Category tabs */}
{CATS.map(c => { const sel = cat === c; return ( ); })}
{/* Grid */}
{tools.map((t, i) => ( setActive(active===t.id?null:t.id)} delay={i}/> ))}
{/* Active detail modal */} {active && (() => { const t = TOOLS.find(x => x.id === active); if (!t) return null; const accent = ACC[t.accent]; return setActive(null)}/>; })()}
); } // Modal overlay for tool detail function ToolModal({ t, accent, onClose }) { useEffectSC(() => { const onKey = (e) => { if (e.key === "Escape") onClose(); }; document.addEventListener("keydown", onKey); const prevOverflow = document.body.style.overflow; document.body.style.overflow = "hidden"; return () => { document.removeEventListener("keydown", onKey); document.body.style.overflow = prevOverflow; }; }, [onClose]); return (
e.stopPropagation()} style={{ width: "min(1040px, 100%)", maxHeight: "min(82vh, 720px)", background: "var(--surface)", border: "1px solid var(--line-2)", borderRadius: 22, boxShadow: "0 30px 80px rgba(11,14,19,0.18), 0 0 0 1px rgba(11,14,19,0.02)", position: "relative", overflow: "hidden", display: "flex", flexDirection: "column", animation: "tmRise .28s cubic-bezier(.2,.7,.2,1) both" }}>
{t.cat.toUpperCase()} {t.en.toUpperCase()}

{t.name}

{t.blurb}

    {t.bullets.map((b, i) => (
  • {b}
  • ))}
); } function ToolCard({ t, active, onClick, delay }) { const accent = ACC[t.accent]; return ( ); } // Per-tool stylized preview (no fake screenshots — abstracted UI moments) function ToolPreview({ t, accent }) { // a different mini-mock per tool id, falling back to a generic const mono = {fontFamily:"var(--mono)",fontSize:11.5,color:"var(--muted)",letterSpacing:"0.06em"}; switch (t.id) { case "shipping": return (
SHIPPING SLIP / 2026.05.14
{[["市場A 出荷","142件"],["市場B 出荷","87件"],["返品処理","3件"]].map(([k,v],i)=>( {k} {v} ))}
✓ 232 件 / 印刷キュー送信完了
); case "excel-dict": case "img-to-list": return (
{t.en.toUpperCase()}
raw.xlsx
output.xlsx
· 辞書ルール: 17 件
· 処理レコード: 3,412 行
· 経過: 1.8 s
); case "streak": return (
STREAK · MAY
27days
{Array.from({length:28}).map((_,i)=>( ))}
{["🥇","🔥","⭐"].map((e,i)=>( {e} ))}
); case "newtab": return (
NEW TAB / WORK
{["Drive","Sheets","Gmail","Slack","Notion","Cal","Stripe","GA","Figma"].map((l,i)=>(
{l}
))}
); case "sns-viewer": return (
FEED · FILTERED
{[1,2,3].map(i=>(
@user_{i}2{i}m
{["業務改善のヒント。", "新しいツールを試した結果。", "今日のオペレーション進捗。"][i-1]}
))}
); case "course-site": case "members-msg": return (
{t.en.toUpperCase()}
{(t.id==="course-site" ? [["Chapter 01 · イントロ","✓"],["Chapter 02 · 基礎編","✓"],["Chapter 03 · 応用編","45%"],["Chapter 04 · 実践編","—"]] : [["田中 さん","2件の未読"],["山田 さん","既読"],["全体配信","12人着"]] ).map(([k,v],i)=>(
{k} {v}
))}
); case "house-task": return (
HOUSEHOLD · THIS WEEK
{[["皿洗い","パートナー A"],["ごみ出し","パートナー B"],["買い物","パートナー A"],["子供のお迎え","パートナー B"]].map(([k,v],i)=>(
{k} {v}
))}
); case "split": return (
SPLIT · MAY
¥ 12,840
A → B (50:50 折半 / 立替分差額)
A 58%B 42%
); case "pdf-edit": case "pdf-compress": return (
{t.id==="pdf-edit"?"PDF EDITOR":"PDF COMPRESS"}
{t.id==="pdf-compress" ? ( <>
12.4MB 1.1MB
SAVED 91%
) : (
{Array.from({length:8}).map((_,i)=>(
p.{i+1}
))}
)}
); default: return (
{t.en.toUpperCase()}
業務ヒアリングをもとに、必要十分な構成で実装した小〜中規模ツール。
); } } Object.assign(window, { ToolShowcase });