From 4304bcdc9645b11421cc259105c0ebcd874e3d67 Mon Sep 17 00:00:00 2001 From: posimai Date: Sat, 21 Mar 2026 11:23:56 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=E3=82=AB=E3=82=B9=E3=82=BF=E3=83=A0?= =?UTF-8?q?=E3=82=A2=E3=83=97=E3=83=AA=E7=99=BB=E9=8C=B2=E6=A9=9F=E8=83=BD?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0=20=E2=80=94=20=E3=82=A2=E3=82=A4=E3=82=B3?= =?UTF-8?q?=E3=83=B3=E3=83=94=E3=83=83=E3=82=AB=E3=83=BC=E3=83=BB=E3=82=A4?= =?UTF-8?q?=E3=83=8B=E3=82=B7=E3=83=A3=E3=83=AB=E3=83=90=E3=83=83=E3=82=B8?= =?UTF-8?q?=E3=83=BB=E5=89=8A=E9=99=A4=E3=83=9C=E3=82=BF=E3=83=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + index.html | 541 +++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 501 insertions(+), 41 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e985853 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.vercel diff --git a/index.html b/index.html index 14c1ccd..980a939 100644 --- a/index.html +++ b/index.html @@ -158,13 +158,20 @@ display: flex; align-items: center; justify-content: space-between; - padding: 4px 16px 0; - min-height: 32px; + padding: 8px 16px 0; + min-height: 36px; + gap: 8px; + } + .edit-bar-left { + display: flex; + align-items: center; + gap: 8px; + flex: 1; } .edit-bar-hint { font-size: 11px; color: var(--text3); } .edit-toggle-btn { font-size: 12px; - padding: 4px 12px; + padding: 5px 12px; border-radius: 20px; border: 1px solid var(--border); background: transparent; @@ -172,6 +179,7 @@ font-family: inherit; cursor: pointer; transition: all 0.12s; + flex-shrink: 0; } .edit-toggle-btn.active { border-color: var(--accent); @@ -179,6 +187,25 @@ background: var(--surface); } + /* ── Add app button ── */ + .add-app-btn { + display: none; + align-items: center; + gap: 5px; + padding: 5px 11px 5px 8px; + border-radius: 20px; + border: 1px dashed var(--accent); + background: transparent; + color: var(--accent); + font-size: 12px; + font-family: inherit; + cursor: pointer; + transition: background 0.12s; + flex-shrink: 0; + } + .add-app-btn.visible { display: flex; } + .add-app-btn:active { background: var(--surface); } + /* ── App sections ── */ .app-section { padding: 0 16px 16px; } .section-label { @@ -237,6 +264,19 @@ white-space: nowrap; } + /* ── Text initial badge ── */ + .app-initial { + width: 26px; + height: 26px; + border-radius: 6px; + display: flex; + align-items: center; + justify-content: center; + font-size: 13px; + font-weight: 700; + flex-shrink: 0; + } + .check-badge { display: none; position: absolute; @@ -251,6 +291,29 @@ } .app-item.edit-mode.on .check-badge { display: flex; } + /* ── Custom app delete button ── */ + .custom-del-btn { + display: none; + position: absolute; + top: 4px; + left: 4px; + width: 16px; + height: 16px; + background: #EF4444; + border-radius: 50%; + align-items: center; + justify-content: center; + border: none; + cursor: pointer; + z-index: 2; + padding: 0; + line-height: 1; + color: #fff; + font-size: 10px; + font-weight: 700; + } + .app-item.edit-mode .custom-del-btn { display: flex; } + /* ── Empty state ── */ .empty-state { text-align: center; @@ -353,6 +416,176 @@ cursor: pointer; } + /* ── Add app modal ── */ + .add-modal-backdrop { + display: none; + position: fixed; + inset: 0; + background: rgba(0,0,0,.55); + z-index: 200; + backdrop-filter: blur(2px); + } + .add-modal-backdrop.open { display: block; } + .add-modal { + position: fixed; + bottom: 0; + left: 0; + right: 0; + background: var(--surface); + border-radius: 16px 16px 0 0; + padding-bottom: max(24px, env(safe-area-inset-bottom)); + z-index: 201; + max-height: 88vh; + overflow-y: auto; + transform: translateY(100%); + transition: transform 0.25s cubic-bezier(0.2,0.9,0.2,1); + } + .add-modal.open { transform: translateY(0); } + .add-modal-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 16px 12px; + border-bottom: 1px solid var(--border); + position: sticky; + top: 0; + background: var(--surface); + z-index: 1; + } + .add-modal-title { + font-size: 15px; + font-weight: 600; + color: var(--text); + } + .add-modal-body { + padding: 16px; + display: flex; + flex-direction: column; + gap: 16px; + } + .form-field-label { + font-size: 12px; + color: var(--text2); + font-weight: 500; + margin-bottom: 6px; + } + .form-text-input { + width: 100%; + box-sizing: border-box; + background: var(--bg); + border: 1px solid var(--border); + border-radius: 8px; + color: var(--text); + font-size: 14px; + padding: 9px 12px; + outline: none; + font-family: inherit; + transition: border-color 0.15s; + } + .form-text-input:focus { border-color: var(--accent); } + .form-select { + width: 100%; + box-sizing: border-box; + background: var(--bg); + border: 1px solid var(--border); + border-radius: 8px; + color: var(--text); + font-size: 14px; + padding: 9px 12px; + outline: none; + font-family: inherit; + cursor: pointer; + -webkit-appearance: none; + appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%239CA3AF' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 12px center; + padding-right: 32px; + transition: border-color 0.15s; + } + .form-select:focus { border-color: var(--accent); } + + .icon-picker-label { + font-size: 12px; + color: var(--text2); + font-weight: 500; + margin-bottom: 8px; + } + .icon-picker-grid { + display: grid; + grid-template-columns: repeat(7, 1fr); + gap: 5px; + } + .icon-pick-btn { + display: flex; + align-items: center; + justify-content: center; + padding: 8px 4px; + border: 1px solid var(--border); + border-radius: 8px; + background: var(--bg); + color: var(--text3); + cursor: pointer; + transition: all 0.12s; + aspect-ratio: 1; + } + .icon-pick-btn.selected { + border-color: var(--accent); + background: var(--surface2); + color: var(--accent); + } + .icon-pick-btn:active { background: var(--surface2); } + .icon-initial-pick { + display: flex; + align-items: center; + gap: 8px; + padding: 9px 12px; + border: 1px solid var(--border); + border-radius: 8px; + background: var(--bg); + color: var(--text2); + cursor: pointer; + font-size: 12px; + font-family: inherit; + transition: all 0.12s; + margin-top: 6px; + width: 100%; + box-sizing: border-box; + } + .icon-initial-pick.selected { + border-color: var(--accent); + background: var(--surface2); + color: var(--accent); + } + .initial-preview { + width: 22px; + height: 22px; + border-radius: 5px; + background: var(--surface2); + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + font-weight: 700; + flex-shrink: 0; + } + .form-submit-btn { + width: 100%; + padding: 12px; + background: var(--accent); + color: #0D0D0D; + border: none; + border-radius: 8px; + font-family: inherit; + font-size: 14px; + font-weight: 600; + cursor: pointer; + } + .form-submit-btn:disabled { + opacity: 0.38; + cursor: not-allowed; + } + /* ── Safe area ── */ main { padding-bottom: max(16px, env(safe-area-inset-bottom)); } @@ -432,6 +665,61 @@ + +
+ +
@@ -465,7 +753,13 @@
- +
+ + +
@@ -482,6 +776,7 @@ // ============================================================ const ENABLED_KEY = 'posimai-veil-enabled'; +const CUSTOM_KEY = 'posimai-veil-custom'; const API_KEY_KEY = 'posimai_api_key'; const COLOR_MODE_KEY = 'posimai-veil-color-mode'; const API_BASE = 'https://posimai-lab.tail72e846.ts.net/brain/api'; @@ -489,29 +784,41 @@ const API_BASE = 'https://posimai-lab.tail72e846.ts.net/brain/api'; // ── カテゴリカラー(ダーク / ライト) ────────────────────── const CAT_COLORS = { dark: { - posimai: '#6EE7B7', // Teal(ブランドカラー) - sns: '#60A5FA', // Blue - media: '#F472B6', // Pink - news: '#FB923C', // Orange - tools: '#94A3B8', // Slate - nav: '#4ADE80', // Green - shop: '#FBBF24', // Amber + posimai: '#6EE7B7', + sns: '#60A5FA', + media: '#F472B6', + news: '#FB923C', + tools: '#94A3B8', + nav: '#4ADE80', + shop: '#FBBF24', }, light: { - posimai: '#059669', // Emerald-600 - sns: '#2563EB', // Blue-600 - media: '#DB2777', // Pink-600 - news: '#EA580C', // Orange-600 - tools: '#475569', // Slate-600 - nav: '#16A34A', // Green-600 - shop: '#D97706', // Amber-600 + posimai: '#059669', + sns: '#2563EB', + media: '#DB2777', + news: '#EA580C', + tools: '#475569', + nav: '#16A34A', + shop: '#D97706', }, }; +// ── アイコンピッカー候補リスト ────────────────────────────── +const ICON_PICKER_ICONS = [ + 'message-circle','message-square','send','at-sign','phone','mail','users', + 'play','music','film','tv','radio','headphones','mic','airplay', + 'camera','image','aperture','eye','star','heart','bookmark', + 'map-pin','map','navigation','compass','globe', + 'shopping-cart','shopping-bag','tag','credit-card','wallet', + 'newspaper','rss','trending-up','bar-chart-2', + 'calendar','clock','bell','search','share', + 'settings','lock','key','shield','zap', + 'briefcase','book','coffee','home','layers', + 'monitor','smartphone','cpu','hard-drive','file-text', + 'calculator','activity','layout-dashboard','check-circle', +]; + // ── アプリDB ──────────────────────────────────────────────── -// android : Androidパッケージ名(_camera/_phone/_settings は特殊intent) -// ios : iOS URLスキーム -// web : HTTPSフォールバック const APP_DB = [ // SNS・メッセージ { id:'line', label:'LINE', icon:'message-circle', cat:'sns', @@ -647,12 +954,27 @@ const CAT_LABELS = { const CAT_ORDER = ['posimai','sns','media','news','tools','nav','shop']; +// ── カスタムアプリ ────────────────────────────────────────── +function loadCustomApps() { + try { + const raw = localStorage.getItem(CUSTOM_KEY); + if (raw) return JSON.parse(raw); + } catch {} + return []; +} + +function saveCustomApps() { + localStorage.setItem(CUSTOM_KEY, JSON.stringify(customApps)); +} + +let customApps = loadCustomApps(); + // ── 状態 ──────────────────────────────────────────────────── let enabledIds = loadEnabled(); let activeCat = 'all'; let searchQ = ''; let editMode = false; -let colorMode = localStorage.getItem(COLOR_MODE_KEY) || 'accent'; // 'accent' | 'colorful' +let colorMode = localStorage.getItem(COLOR_MODE_KEY) || 'accent'; let apiKey = localStorage.getItem(API_KEY_KEY) || ''; function getTheme() { @@ -669,13 +991,20 @@ function loadEnabled() { const raw = localStorage.getItem(ENABLED_KEY); if (raw) return new Set(JSON.parse(raw)); } catch {} - return new Set(APP_DB.map(a => a.id)); // デフォルト: 全ON + // デフォルト: 全 ON(組み込み + カスタム) + const base = new Set(APP_DB.map(a => a.id)); + customApps.forEach(a => base.add(a.id)); + return base; } function saveEnabled() { localStorage.setItem(ENABLED_KEY, JSON.stringify([...enabledIds])); } +function allApps() { + return [...APP_DB, ...customApps]; +} + // ── アプリ起動 ────────────────────────────────────────────── function launchApp(app) { const ua = navigator.userAgent.toLowerCase(); @@ -714,6 +1043,16 @@ function toggleApp(id) { renderApps(); } +// ── カスタムアプリ削除 ────────────────────────────────────── +function deleteCustomApp(id) { + customApps = customApps.filter(a => a.id !== id); + saveCustomApps(); + enabledIds.delete(id); + saveEnabled(); + renderApps(); + showToast('削除しました'); +} + // ── カテゴリタブ描画 ──────────────────────────────────────── function renderCatTabs() { const wrap = document.getElementById('catScroll'); @@ -734,15 +1073,18 @@ function renderCatTabs() { // ── アプリグリッド描画 ────────────────────────────────────── function renderApps() { - const container = document.getElementById('appSections'); - const hint = document.getElementById('editBarHint'); - const editBtn = document.getElementById('editToggleBtn'); + const container = document.getElementById('appSections'); + const hint = document.getElementById('editBarHint'); + const editBtn = document.getElementById('editToggleBtn'); + const addBtn = document.getElementById('addAppBtn'); editBtn.classList.toggle('active', editMode); hint.textContent = editMode ? 'タップで表示/非表示を切り替え' : ''; + addBtn.classList.toggle('visible', editMode); - const q = searchQ.toLowerCase(); - const filtered = APP_DB.filter(app => { + const q = searchQ.toLowerCase(); + const source = allApps(); + const filtered = source.filter(app => { if (activeCat !== 'all' && app.cat !== activeCat) return false; if (q && !app.label.toLowerCase().includes(q) && !app.id.includes(q)) return false; return true; @@ -768,34 +1110,40 @@ function renderApps() { const orderedCats = CAT_ORDER.filter(c => groups[c]); container.innerHTML = orderedCats.map(cat => { - const apps = groups[cat]; - const color = getCatColor(cat); + const apps = groups[cat]; + const color = getCatColor(cat); const isColorful = colorMode === 'colorful'; - // カラフルモード時: カードに極薄の色背景+ボーダー - const cardColorStyle = isColorful - ? `--item-color:${color};` - : ''; - return `
${apps.map(app => { - const on = enabledIds.has(app.id); + const on = enabledIds.has(app.id); const hidden = !editMode && !on ? ' hidden' : ''; const editCls = editMode ? ` edit-mode ${on ? 'on' : 'off'}` : ''; const bgStyle = isColorful ? `background:${color}14;border-color:${color}38;` : ''; + const isCustom = !!app.custom; + const isInitial = app.icon === '_initial'; + + const iconHTML = isInitial + ? `
${app.label.charAt(0).toUpperCase()}
` + : ``; + + const delBtn = isCustom + ? `` + : ''; + return `
- + style="${bgStyle}"> + ${delBtn} + ${iconHTML} ${app.label}