| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Threshold · Communication Bridge for Nonspeakers</title> |
| <style> |
| :root { |
| --bg: #0b0c0d; |
| --panel: #151312; |
| --panel2: #1f1b18; |
| --ink: #f3ede2; |
| --muted: #cbbda9; |
| --soft: #9e8f7a; |
| --copper: #c77748; |
| --terracotta: #b75d3a; |
| --gold: #d7a85b; |
| --green: #73956f; |
| --line: rgba(215,168,91,.36); |
| --danger: #d26a5c; |
| --safe: #7dbb8e; |
| --shadow: rgba(0,0,0,.45); |
| } |
| |
| * { box-sizing: border-box; margin: 0; padding: 0; } |
| |
| body { |
| margin: 0; |
| background: var(--bg); |
| color: var(--ink); |
| font-family: ui-sans-serif, system-ui, -apple-system, sans-serif; |
| line-height: 1.6; |
| touch-action: manipulation; |
| user-select: none; |
| -webkit-user-select: none; |
| overflow-x: hidden; |
| } |
| |
| .container { |
| max-width: 100%; |
| padding: 0; |
| } |
| |
| |
| .header { |
| background: linear-gradient(180deg, var(--panel) 0%, var(--bg) 100%); |
| border-bottom: 2px solid var(--copper); |
| padding: 20px; |
| text-align: center; |
| } |
| |
| .header h1 { |
| font-size: clamp(24px, 5vw, 36px); |
| color: var(--gold); |
| margin-bottom: 8px; |
| font-weight: 600; |
| } |
| |
| .header p { |
| font-size: clamp(12px, 3vw, 14px); |
| color: var(--muted); |
| max-width: 600px; |
| margin: 0 auto; |
| } |
| |
| |
| .mode-select { |
| display: flex; |
| gap: 10px; |
| padding: 15px; |
| background: var(--panel); |
| border-bottom: 1px solid var(--line); |
| justify-content: center; |
| flex-wrap: wrap; |
| } |
| |
| .mode-btn { |
| background: rgba(215,168,91,.12); |
| border: 2px solid rgba(215,168,91,.3); |
| color: var(--ink); |
| padding: 12px 20px; |
| border-radius: 12px; |
| font-size: 15px; |
| font-weight: 600; |
| cursor: pointer; |
| transition: all 0.2s; |
| min-width: 140px; |
| } |
| |
| .mode-btn:hover { |
| background: rgba(215,168,91,.2); |
| border-color: var(--gold); |
| transform: translateY(-2px); |
| } |
| |
| .mode-btn.active { |
| background: var(--copper); |
| border-color: var(--copper); |
| color: var(--bg); |
| } |
| |
| |
| .main-interface { |
| min-height: calc(100vh - 180px); |
| padding: 20px; |
| } |
| |
| .interface-panel { |
| display: none; |
| } |
| |
| .interface-panel.active { |
| display: block; |
| } |
| |
| |
| .feeling-grid { |
| display: grid; |
| grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); |
| gap: 15px; |
| margin-top: 20px; |
| } |
| |
| .feeling-card { |
| background: linear-gradient(145deg, rgba(255,255,255,.04), rgba(255,255,255,.01)); |
| border: 2px solid var(--line); |
| border-radius: 16px; |
| padding: 30px 20px; |
| text-align: center; |
| cursor: pointer; |
| transition: all 0.3s; |
| position: relative; |
| overflow: hidden; |
| } |
| |
| .feeling-card::before { |
| content: ''; |
| position: absolute; |
| top: 0; |
| left: 0; |
| right: 0; |
| bottom: 0; |
| background: radial-gradient(circle at center, var(--card-color), transparent 70%); |
| opacity: 0; |
| transition: opacity 0.3s; |
| } |
| |
| .feeling-card:hover::before, |
| .feeling-card.selected::before { |
| opacity: 0.15; |
| } |
| |
| .feeling-card:hover { |
| transform: translateY(-4px); |
| border-color: var(--card-color); |
| box-shadow: 0 8px 20px rgba(0,0,0,.3); |
| } |
| |
| .feeling-card.selected { |
| border-color: var(--card-color); |
| border-width: 3px; |
| background: linear-gradient(145deg, rgba(255,255,255,.08), rgba(255,255,255,.02)); |
| } |
| |
| .feeling-icon { |
| font-size: 48px; |
| margin-bottom: 10px; |
| position: relative; |
| z-index: 1; |
| } |
| |
| .feeling-label { |
| font-size: 16px; |
| font-weight: 600; |
| color: var(--ink); |
| position: relative; |
| z-index: 1; |
| } |
| |
| .feeling-sublabel { |
| font-size: 12px; |
| color: var(--muted); |
| margin-top: 5px; |
| position: relative; |
| z-index: 1; |
| } |
| |
| |
| .intensity-section { |
| margin-top: 30px; |
| padding: 25px; |
| background: var(--panel); |
| border-radius: 16px; |
| border: 1px solid var(--line); |
| } |
| |
| .intensity-label { |
| font-size: 18px; |
| color: var(--gold); |
| margin-bottom: 15px; |
| text-align: center; |
| font-weight: 600; |
| } |
| |
| .slider-container { |
| position: relative; |
| padding: 20px 0; |
| } |
| |
| .intensity-slider { |
| width: 100%; |
| height: 60px; |
| -webkit-appearance: none; |
| appearance: none; |
| background: linear-gradient(90deg, |
| rgba(115,149,111,.3) 0%, |
| rgba(215,168,91,.3) 50%, |
| rgba(210,106,92,.3) 100%); |
| border-radius: 30px; |
| outline: none; |
| cursor: pointer; |
| } |
| |
| .intensity-slider::-webkit-slider-thumb { |
| -webkit-appearance: none; |
| appearance: none; |
| width: 70px; |
| height: 70px; |
| background: radial-gradient(circle, var(--gold), var(--copper)); |
| border: 4px solid var(--ink); |
| border-radius: 50%; |
| cursor: grab; |
| box-shadow: 0 4px 12px rgba(0,0,0,.4); |
| } |
| |
| .intensity-slider::-webkit-slider-thumb:active { |
| cursor: grabbing; |
| transform: scale(1.1); |
| } |
| |
| .intensity-value { |
| text-align: center; |
| font-size: 24px; |
| color: var(--ink); |
| margin-top: 15px; |
| font-weight: 600; |
| } |
| |
| |
| .body-map { |
| display: grid; |
| grid-template-columns: repeat(3, 1fr); |
| gap: 10px; |
| max-width: 500px; |
| margin: 20px auto; |
| } |
| |
| .body-zone { |
| background: var(--panel); |
| border: 2px solid var(--line); |
| padding: 20px 10px; |
| text-align: center; |
| border-radius: 12px; |
| cursor: pointer; |
| transition: all 0.2s; |
| font-size: 14px; |
| font-weight: 600; |
| } |
| |
| .body-zone:hover { |
| background: rgba(215,168,91,.15); |
| border-color: var(--gold); |
| transform: scale(1.05); |
| } |
| |
| .body-zone.active { |
| background: var(--copper); |
| border-color: var(--copper); |
| color: var(--bg); |
| } |
| |
| |
| .temp-selector { |
| display: grid; |
| grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); |
| gap: 15px; |
| margin-top: 20px; |
| } |
| |
| .temp-card { |
| background: var(--panel); |
| border: 2px solid var(--line); |
| padding: 25px 15px; |
| border-radius: 12px; |
| text-align: center; |
| cursor: pointer; |
| transition: all 0.2s; |
| } |
| |
| .temp-card:hover { |
| transform: translateY(-3px); |
| } |
| |
| .temp-card.active { |
| border-color: var(--card-color); |
| background: linear-gradient(145deg, |
| rgba(var(--card-color-rgb), 0.15), |
| rgba(var(--card-color-rgb), 0.05)); |
| } |
| |
| .temp-icon { |
| font-size: 40px; |
| margin-bottom: 8px; |
| } |
| |
| |
| .output-panel { |
| position: fixed; |
| bottom: 0; |
| left: 0; |
| right: 0; |
| background: linear-gradient(180deg, transparent, var(--panel) 20%, var(--panel)); |
| border-top: 2px solid var(--copper); |
| padding: 20px; |
| transform: translateY(100%); |
| transition: transform 0.3s; |
| max-height: 40vh; |
| overflow-y: auto; |
| } |
| |
| .output-panel.visible { |
| transform: translateY(0); |
| } |
| |
| .output-text { |
| font-size: 18px; |
| color: var(--ink); |
| background: var(--bg); |
| padding: 20px; |
| border-radius: 12px; |
| border: 1px solid var(--line); |
| margin-bottom: 15px; |
| line-height: 1.6; |
| } |
| |
| .output-controls { |
| display: flex; |
| gap: 10px; |
| flex-wrap: wrap; |
| justify-content: center; |
| } |
| |
| .output-btn { |
| background: var(--copper); |
| border: none; |
| color: var(--ink); |
| padding: 12px 24px; |
| border-radius: 8px; |
| font-size: 16px; |
| font-weight: 600; |
| cursor: pointer; |
| transition: all 0.2s; |
| } |
| |
| .output-btn:hover { |
| background: var(--terracotta); |
| transform: translateY(-2px); |
| } |
| |
| .output-btn.secondary { |
| background: rgba(215,168,91,.2); |
| border: 1px solid var(--gold); |
| } |
| |
| .provenance { |
| font-size: 11px; |
| color: var(--soft); |
| margin-top: 10px; |
| padding: 10px; |
| background: rgba(0,0,0,.2); |
| border-radius: 6px; |
| border-left: 3px solid var(--gold); |
| } |
| |
| |
| .section-header { |
| font-size: 20px; |
| color: var(--gold); |
| margin: 30px 0 15px; |
| padding-bottom: 10px; |
| border-bottom: 1px solid var(--line); |
| font-weight: 600; |
| } |
| |
| |
| .urgency-indicator { |
| display: flex; |
| gap: 10px; |
| justify-content: center; |
| margin: 20px 0; |
| } |
| |
| .urgency-btn { |
| flex: 1; |
| max-width: 200px; |
| padding: 20px; |
| background: var(--panel); |
| border: 2px solid var(--line); |
| border-radius: 12px; |
| cursor: pointer; |
| transition: all 0.2s; |
| text-align: center; |
| } |
| |
| .urgency-btn:hover { |
| transform: scale(1.05); |
| } |
| |
| .urgency-btn.active { |
| border-width: 3px; |
| } |
| |
| .urgency-btn.low.active { |
| border-color: var(--green); |
| background: rgba(115,149,111,.15); |
| } |
| |
| .urgency-btn.medium.active { |
| border-color: var(--gold); |
| background: rgba(215,168,91,.15); |
| } |
| |
| .urgency-btn.high.active { |
| border-color: var(--danger); |
| background: rgba(210,106,92,.15); |
| } |
| |
| .urgency-icon { |
| font-size: 32px; |
| margin-bottom: 8px; |
| } |
| </style> |
| </head> |
| <body> |
| <div class="container"> |
| <div class="header"> |
| <h1>Threshold</h1> |
| <p>Communication bridge for nonspeakers · Internal → External · HIR-bounded translation</p> |
| </div> |
| |
| <div class="mode-select"> |
| <button class="mode-btn active" data-mode="feeling">How I Feel</button> |
| <button class="mode-btn" data-mode="body">Where I Feel It</button> |
| <button class="mode-btn" data-mode="need">What I Need</button> |
| <button class="mode-btn" data-mode="custom">My Words</button> |
| </div> |
| |
| <div class="main-interface"> |
| |
| <div class="interface-panel active" id="feeling-panel"> |
| <div class="section-header">Choose what matches</div> |
| <div class="feeling-grid"> |
| <div class="feeling-card" data-feeling="safe" style="--card-color: #7dbb8e"> |
| <div class="feeling-icon">🌿</div> |
| <div class="feeling-label">Safe</div> |
| <div class="feeling-sublabel">settled, calm</div> |
| </div> |
| <div class="feeling-card" data-feeling="urgent" style="--card-color: #d26a5c"> |
| <div class="feeling-icon">⚡</div> |
| <div class="feeling-label">Urgent</div> |
| <div class="feeling-sublabel">needs now</div> |
| </div> |
| <div class="feeling-card" data-feeling="pressure" style="--card-color: #c77748"> |
| <div class="feeling-icon">🔥</div> |
| <div class="feeling-label">Pressure</div> |
| <div class="feeling-sublabel">building, tight</div> |
| </div> |
| <div class="feeling-card" data-feeling="tired" style="--card-color: #9e8f7a"> |
| <div class="feeling-icon">🌙</div> |
| <div class="feeling-label">Tired</div> |
| <div class="feeling-sublabel">heavy, need rest</div> |
| </div> |
| <div class="feeling-card" data-feeling="excited" style="--card-color: #d7a85b"> |
| <div class="feeling-icon">✨</div> |
| <div class="feeling-label">Excited</div> |
| <div class="feeling-sublabel">energy, want</div> |
| </div> |
| <div class="feeling-card" data-feeling="uncomfortable" style="--card-color: #b75d3a"> |
| <div class="feeling-icon">⚠️</div> |
| <div class="feeling-label">Uncomfortable</div> |
| <div class="feeling-sublabel">something wrong</div> |
| </div> |
| <div class="feeling-card" data-feeling="confused" style="--card-color: #6b7a88"> |
| <div class="feeling-icon">❓</div> |
| <div class="feeling-label">Confused</div> |
| <div class="feeling-sublabel">unclear, lost</div> |
| </div> |
| <div class="feeling-card" data-feeling="happy" style="--card-color: #73956f"> |
| <div class="feeling-icon">😊</div> |
| <div class="feeling-label">Happy</div> |
| <div class="feeling-sublabel">good, light</div> |
| </div> |
| </div> |
| |
| <div class="intensity-section"> |
| <div class="intensity-label">How much?</div> |
| <div class="slider-container"> |
| <input type="range" min="1" max="10" value="5" class="intensity-slider" id="feeling-intensity"> |
| <div class="intensity-value"><span id="intensity-display">5</span> / 10</div> |
| </div> |
| </div> |
| |
| <div class="section-header">How urgent?</div> |
| <div class="urgency-indicator"> |
| <div class="urgency-btn low" data-urgency="low"> |
| <div class="urgency-icon">🕐</div> |
| <div>Can wait</div> |
| </div> |
| <div class="urgency-btn medium" data-urgency="medium"> |
| <div class="urgency-icon">⏰</div> |
| <div>Soon</div> |
| </div> |
| <div class="urgency-btn high" data-urgency="high"> |
| <div class="urgency-icon">🚨</div> |
| <div>Right now</div> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div class="interface-panel" id="body-panel"> |
| <div class="section-header">Where do you feel it?</div> |
| <div class="body-map"> |
| <div class="body-zone" data-zone="head">Head</div> |
| <div class="body-zone" data-zone="neck">Neck</div> |
| <div class="body-zone" data-zone="shoulders">Shoulders</div> |
| <div class="body-zone" data-zone="chest">Chest</div> |
| <div class="body-zone" data-zone="stomach">Stomach</div> |
| <div class="body-zone" data-zone="back">Back</div> |
| <div class="body-zone" data-zone="arms">Arms</div> |
| <div class="body-zone" data-zone="hands">Hands</div> |
| <div class="body-zone" data-zone="legs">Legs</div> |
| </div> |
| |
| <div class="section-header">What does it feel like?</div> |
| <div class="temp-selector"> |
| <div class="temp-card" data-temp="hot" style="--card-color: #d26a5c; --card-color-rgb: 210,106,92"> |
| <div class="temp-icon">🔥</div> |
| <div>Hot</div> |
| </div> |
| <div class="temp-card" data-temp="cold" style="--card-color: #7dbb8e; --card-color-rgb: 125,187,142"> |
| <div class="temp-icon">❄️</div> |
| <div>Cold</div> |
| </div> |
| <div class="temp-card" data-temp="tight" style="--card-color: #c77748; --card-color-rgb: 199,119,72"> |
| <div class="temp-icon">🔒</div> |
| <div>Tight</div> |
| </div> |
| <div class="temp-card" data-temp="heavy" style="--card-color: #9e8f7a; --card-color-rgb: 158,143,122"> |
| <div class="temp-icon">⬇️</div> |
| <div>Heavy</div> |
| </div> |
| <div class="temp-card" data-temp="sharp" style="--card-color: #b75d3a; --card-color-rgb: 183,93,58"> |
| <div class="temp-icon">📍</div> |
| <div>Sharp</div> |
| </div> |
| <div class="temp-card" data-temp="numb" style="--card-color: #6b7a88; --card-color-rgb: 107,122,136"> |
| <div class="temp-icon">➖</div> |
| <div>Numb</div> |
| </div> |
| </div> |
| |
| <div class="intensity-section"> |
| <div class="intensity-label">How strong?</div> |
| <div class="slider-container"> |
| <input type="range" min="1" max="10" value="5" class="intensity-slider" id="body-intensity"> |
| <div class="intensity-value"><span id="body-intensity-display">5</span> / 10</div> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div class="interface-panel" id="need-panel"> |
| <div class="section-header">What would help?</div> |
| <div class="feeling-grid"> |
| <div class="feeling-card" data-need="quiet" style="--card-color: #73956f"> |
| <div class="feeling-icon">🔇</div> |
| <div class="feeling-label">Quiet</div> |
| <div class="feeling-sublabel">less noise</div> |
| </div> |
| <div class="feeling-card" data-need="space" style="--card-color: #7dbb8e"> |
| <div class="feeling-icon">🌌</div> |
| <div class="feeling-label">Space</div> |
| <div class="feeling-sublabel">room to be</div> |
| </div> |
| <div class="feeling-card" data-need="water" style="--card-color: #6b7a88"> |
| <div class="feeling-icon">💧</div> |
| <div class="feeling-label">Water</div> |
| <div class="feeling-sublabel">thirsty</div> |
| </div> |
| <div class="feeling-card" data-need="food" style="--card-color: #d7a85b"> |
| <div class="feeling-icon">🍎</div> |
| <div class="feeling-label">Food</div> |
| <div class="feeling-sublabel">hungry</div> |
| </div> |
| <div class="feeling-card" data-need="rest" style="--card-color: #9e8f7a"> |
| <div class="feeling-icon">🛌</div> |
| <div class="feeling-label">Rest</div> |
| <div class="feeling-sublabel">need to lie down</div> |
| </div> |
| <div class="feeling-card" data-need="bathroom" style="--card-color: #c77748"> |
| <div class="feeling-icon">🚻</div> |
| <div class="feeling-label">Bathroom</div> |
| <div class="feeling-sublabel">need to go</div> |
| </div> |
| <div class="feeling-card" data-need="help" style="--card-color: #d26a5c"> |
| <div class="feeling-icon">🆘</div> |
| <div class="feeling-label">Help</div> |
| <div class="feeling-sublabel">something's wrong</div> |
| </div> |
| <div class="feeling-card" data-need="connection" style="--card-color: #b75d3a"> |
| <div class="feeling-icon">🤝</div> |
| <div class="feeling-label">Connection</div> |
| <div class="feeling-sublabel">want presence</div> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div class="interface-panel" id="custom-panel"> |
| <div class="section-header">Build your own message</div> |
| <div style="padding: 20px;"> |
| <p style="color: var(--muted); margin-bottom: 20px; text-align: center;"> |
| This mode lets you create personal meanings that the system will remember for you. |
| Tap any feeling/body/need card from other modes, then add your own words here. |
| </p> |
| <div style="background: var(--panel); padding: 20px; border-radius: 12px; border: 1px solid var(--line);"> |
| <div style="color: var(--gold); margin-bottom: 10px; font-weight: 600;">My message:</div> |
| <textarea id="custom-message" |
| style="width: 100%; min-height: 120px; background: var(--bg); color: var(--ink); border: 1px solid var(--line); border-radius: 8px; padding: 15px; font-size: 16px; font-family: inherit; resize: vertical;" |
| placeholder="Type or dictate what you want to say..."></textarea> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div class="output-panel" id="output"> |
| <div class="output-text" id="output-text"></div> |
| <div class="provenance" id="provenance"></div> |
| <div class="output-controls"> |
| <button class="output-btn" onclick="speakMessage()">🔊 Speak</button> |
| <button class="output-btn" onclick="copyMessage()">📋 Copy</button> |
| <button class="output-btn secondary" onclick="saveMapping()">💾 Remember This</button> |
| <button class="output-btn secondary" onclick="closeOutput()">✕ Close</button> |
| </div> |
| </div> |
| </div> |
| |
| <script> |
| |
| const state = { |
| mode: 'feeling', |
| feeling: null, |
| feelingIntensity: 5, |
| urgency: null, |
| bodyZone: null, |
| bodyQuality: null, |
| bodyIntensity: 5, |
| need: null, |
| customMessage: '', |
| timestamp: null, |
| mappings: JSON.parse(localStorage.getItem('threshold_mappings') || '{}') |
| }; |
| |
| |
| document.querySelectorAll('.mode-btn').forEach(btn => { |
| btn.addEventListener('click', () => { |
| const mode = btn.dataset.mode; |
| document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active')); |
| btn.classList.add('active'); |
| document.querySelectorAll('.interface-panel').forEach(p => p.classList.remove('active')); |
| document.getElementById(mode + '-panel').classList.add('active'); |
| state.mode = mode; |
| }); |
| }); |
| |
| |
| document.querySelectorAll('.feeling-card').forEach(card => { |
| card.addEventListener('click', () => { |
| const panel = card.closest('.interface-panel'); |
| panel.querySelectorAll('.feeling-card').forEach(c => c.classList.remove('selected')); |
| card.classList.add('selected'); |
| |
| if (state.mode === 'feeling') { |
| state.feeling = card.dataset.feeling; |
| } else if (state.mode === 'need') { |
| state.need = card.dataset.need; |
| } |
| |
| generateOutput(); |
| }); |
| }); |
| |
| |
| document.getElementById('feeling-intensity').addEventListener('input', (e) => { |
| state.feelingIntensity = parseInt(e.target.value); |
| document.getElementById('intensity-display').textContent = state.feelingIntensity; |
| if (state.feeling) generateOutput(); |
| }); |
| |
| document.getElementById('body-intensity').addEventListener('input', (e) => { |
| state.bodyIntensity = parseInt(e.target.value); |
| document.getElementById('body-intensity-display').textContent = state.bodyIntensity; |
| if (state.bodyZone) generateOutput(); |
| }); |
| |
| |
| document.querySelectorAll('.urgency-btn').forEach(btn => { |
| btn.addEventListener('click', () => { |
| document.querySelectorAll('.urgency-btn').forEach(b => b.classList.remove('active')); |
| btn.classList.add('active'); |
| state.urgency = btn.dataset.urgency; |
| if (state.feeling) generateOutput(); |
| }); |
| }); |
| |
| |
| document.querySelectorAll('.body-zone').forEach(zone => { |
| zone.addEventListener('click', () => { |
| document.querySelectorAll('.body-zone').forEach(z => z.classList.remove('active')); |
| zone.classList.add('active'); |
| state.bodyZone = zone.dataset.zone; |
| generateOutput(); |
| }); |
| }); |
| |
| |
| document.querySelectorAll('.temp-card').forEach(card => { |
| card.addEventListener('click', () => { |
| document.querySelectorAll('.temp-card').forEach(c => c.classList.remove('active')); |
| card.classList.add('active'); |
| state.bodyQuality = card.dataset.temp; |
| if (state.bodyZone) generateOutput(); |
| }); |
| }); |
| |
| |
| document.getElementById('custom-message').addEventListener('input', (e) => { |
| state.customMessage = e.target.value; |
| if (state.customMessage.trim()) generateOutput(); |
| }); |
| |
| |
| function generateOutput() { |
| state.timestamp = new Date(); |
| let message = ''; |
| let provenance = ''; |
| |
| if (state.mode === 'feeling' && state.feeling) { |
| const intensityWord = state.feelingIntensity <= 3 ? 'a little' : |
| state.feelingIntensity <= 6 ? 'moderately' : |
| state.feelingIntensity <= 8 ? 'very' : 'extremely'; |
| const urgencyPhrase = state.urgency === 'low' ? 'when you can' : |
| state.urgency === 'medium' ? 'soon' : |
| state.urgency === 'high' ? 'right now' : ''; |
| |
| message = `I feel ${intensityWord} ${state.feeling}`; |
| if (urgencyPhrase) message += ` and I need attention ${urgencyPhrase}`; |
| message += '.'; |
| |
| provenance = `Mode: How I Feel | Feeling: ${state.feeling} | Intensity: ${state.feelingIntensity}/10`; |
| if (state.urgency) provenance += ` | Urgency: ${state.urgency}`; |
| } |
| else if (state.mode === 'body' && state.bodyZone) { |
| const qualityPhrase = state.bodyQuality ? ` and it feels ${state.bodyQuality}` : ''; |
| const intensityWord = state.bodyIntensity <= 3 ? 'mild' : |
| state.bodyIntensity <= 6 ? 'noticeable' : |
| state.bodyIntensity <= 8 ? 'strong' : 'intense'; |
| |
| message = `I feel something in my ${state.bodyZone}${qualityPhrase}. The sensation is ${intensityWord}.`; |
| |
| provenance = `Mode: Where I Feel It | Zone: ${state.bodyZone}`; |
| if (state.bodyQuality) provenance += ` | Quality: ${state.bodyQuality}`; |
| provenance += ` | Intensity: ${state.bodyIntensity}/10`; |
| } |
| else if (state.mode === 'need' && state.need) { |
| const needMessages = { |
| quiet: 'I need it to be quieter.', |
| space: 'I need some space.', |
| water: 'I need water.', |
| food: 'I need food.', |
| rest: 'I need to rest.', |
| bathroom: 'I need to use the bathroom.', |
| help: 'I need help. Something is wrong.', |
| connection: 'I need someone with me.' |
| }; |
| |
| message = needMessages[state.need] || `I need ${state.need}.`; |
| provenance = `Mode: What I Need | Need: ${state.need}`; |
| } |
| else if (state.mode === 'custom' && state.customMessage.trim()) { |
| message = state.customMessage.trim(); |
| provenance = `Mode: My Words | Custom message entered by user`; |
| } |
| |
| if (message) { |
| document.getElementById('output-text').textContent = message; |
| document.getElementById('provenance').innerHTML = |
| `<strong>Translation details:</strong> ${provenance}<br>` + |
| `<strong>Timestamp:</strong> ${state.timestamp.toLocaleString()}<br>` + |
| `<strong>Source:</strong> User-defined meaning, not imposed interpretation<br>` + |
| `<strong>Certainty:</strong> Direct user signal, high fidelity`; |
| document.getElementById('output').classList.add('visible'); |
| } |
| } |
| |
| |
| function speakMessage() { |
| const text = document.getElementById('output-text').textContent; |
| if ('speechSynthesis' in window) { |
| const utterance = new SpeechSynthesisUtterance(text); |
| utterance.rate = 0.9; |
| utterance.pitch = 1; |
| speechSynthesis.speak(utterance); |
| } else { |
| alert('Speech synthesis not supported in this browser'); |
| } |
| } |
| |
| function copyMessage() { |
| const text = document.getElementById('output-text').textContent; |
| navigator.clipboard.writeText(text).then(() => { |
| alert('Message copied to clipboard'); |
| }); |
| } |
| |
| function saveMapping() { |
| const key = `${state.mode}_${state.feeling || state.need || state.bodyZone}_${state.timestamp.getTime()}`; |
| const mapping = { |
| mode: state.mode, |
| message: document.getElementById('output-text').textContent, |
| state: {...state}, |
| timestamp: state.timestamp.toISOString() |
| }; |
| |
| state.mappings[key] = mapping; |
| localStorage.setItem('threshold_mappings', JSON.stringify(state.mappings)); |
| alert('This translation has been saved to your personal vocabulary'); |
| } |
| |
| function closeOutput() { |
| document.getElementById('output').classList.remove('visible'); |
| } |
| </script> |
| </body> |
| </html> |