HirModel/human-translation-accessibility-stack
0
1<!DOCTYPE html>2<html lang="en">3<head>4 <meta charset="UTF-8">5 <meta name="viewport" content="width=device-width, initial-scale=1.0">6 <title>Threshold ยท Communication Bridge for Nonspeakers</title>7 <style>8 :root {9 --bg: #0b0c0d;10 --panel: #151312;11 --panel2: #1f1b18;12 --ink: #f3ede2;13 --muted: #cbbda9;14 --soft: #9e8f7a;15 --copper: #c77748;16 --terracotta: #b75d3a;17 --gold: #d7a85b;18 --green: #73956f;19 --line: rgba(215,168,91,.36);20 --danger: #d26a5c;21 --safe: #7dbb8e;22 --shadow: rgba(0,0,0,.45);23 }24 25 * { box-sizing: border-box; margin: 0; padding: 0; }26 27 body {28 margin: 0;29 background: var(--bg);30 color: var(--ink);31 font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;32 line-height: 1.6;33 touch-action: manipulation;34 user-select: none;35 -webkit-user-select: none;36 overflow-x: hidden;37 }38 39 .container {40 max-width: 100%;41 padding: 0;42 }43 44 /* HEADER */45 .header {46 background: linear-gradient(180deg, var(--panel) 0%, var(--bg) 100%);47 border-bottom: 2px solid var(--copper);48 padding: 20px;49 text-align: center;50 }51 52 .header h1 {53 font-size: clamp(24px, 5vw, 36px);54 color: var(--gold);55 margin-bottom: 8px;56 font-weight: 600;57 }58 59 .header p {60 font-size: clamp(12px, 3vw, 14px);61 color: var(--muted);62 max-width: 600px;63 margin: 0 auto;64 }65 66 /* MODE SELECTOR */67 .mode-select {68 display: flex;69 gap: 10px;70 padding: 15px;71 background: var(--panel);72 border-bottom: 1px solid var(--line);73 justify-content: center;74 flex-wrap: wrap;75 }76 77 .mode-btn {78 background: rgba(215,168,91,.12);79 border: 2px solid rgba(215,168,91,.3);80 color: var(--ink);81 padding: 12px 20px;82 border-radius: 12px;83 font-size: 15px;84 font-weight: 600;85 cursor: pointer;86 transition: all 0.2s;87 min-width: 140px;88 }89 90 .mode-btn:hover {91 background: rgba(215,168,91,.2);92 border-color: var(--gold);93 transform: translateY(-2px);94 }95 96 .mode-btn.active {97 background: var(--copper);98 border-color: var(--copper);99 color: var(--bg);100 }101 102 /* MAIN INTERFACE */103 .main-interface {104 min-height: calc(100vh - 180px);105 padding: 20px;106 }107 108 .interface-panel {109 display: none;110 }111 112 .interface-panel.active {113 display: block;114 }115 116 /* FEELING GRID */117 .feeling-grid {118 display: grid;119 grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));120 gap: 15px;121 margin-top: 20px;122 }123 124 .feeling-card {125 background: linear-gradient(145deg, rgba(255,255,255,.04), rgba(255,255,255,.01));126 border: 2px solid var(--line);127 border-radius: 16px;128 padding: 30px 20px;129 text-align: center;130 cursor: pointer;131 transition: all 0.3s;132 position: relative;133 overflow: hidden;134 }135 136 .feeling-card::before {137 content: '';138 position: absolute;139 top: 0;140 left: 0;141 right: 0;142 bottom: 0;143 background: radial-gradient(circle at center, var(--card-color), transparent 70%);144 opacity: 0;145 transition: opacity 0.3s;146 }147 148 .feeling-card:hover::before,149 .feeling-card.selected::before {150 opacity: 0.15;151 }152 153 .feeling-card:hover {154 transform: translateY(-4px);155 border-color: var(--card-color);156 box-shadow: 0 8px 20px rgba(0,0,0,.3);157 }158 159 .feeling-card.selected {160 border-color: var(--card-color);161 border-width: 3px;162 background: linear-gradient(145deg, rgba(255,255,255,.08), rgba(255,255,255,.02));163 }164 165 .feeling-icon {166 font-size: 48px;167 margin-bottom: 10px;168 position: relative;169 z-index: 1;170 }171 172 .feeling-label {173 font-size: 16px;174 font-weight: 600;175 color: var(--ink);176 position: relative;177 z-index: 1;178 }179 180 .feeling-sublabel {181 font-size: 12px;182 color: var(--muted);183 margin-top: 5px;184 position: relative;185 z-index: 1;186 }187 188 /* INTENSITY SLIDER */189 .intensity-section {190 margin-top: 30px;191 padding: 25px;192 background: var(--panel);193 border-radius: 16px;194 border: 1px solid var(--line);195 }196 197 .intensity-label {198 font-size: 18px;199 color: var(--gold);200 margin-bottom: 15px;201 text-align: center;202 font-weight: 600;203 }204 205 .slider-container {206 position: relative;207 padding: 20px 0;208 }209 210 .intensity-slider {211 width: 100%;212 height: 60px;213 -webkit-appearance: none;214 appearance: none;215 background: linear-gradient(90deg, 216 rgba(115,149,111,.3) 0%, 217 rgba(215,168,91,.3) 50%, 218 rgba(210,106,92,.3) 100%);219 border-radius: 30px;220 outline: none;221 cursor: pointer;222 }223 224 .intensity-slider::-webkit-slider-thumb {225 -webkit-appearance: none;226 appearance: none;227 width: 70px;228 height: 70px;229 background: radial-gradient(circle, var(--gold), var(--copper));230 border: 4px solid var(--ink);231 border-radius: 50%;232 cursor: grab;233 box-shadow: 0 4px 12px rgba(0,0,0,.4);234 }235 236 .intensity-slider::-webkit-slider-thumb:active {237 cursor: grabbing;238 transform: scale(1.1);239 }240 241 .intensity-value {242 text-align: center;243 font-size: 24px;244 color: var(--ink);245 margin-top: 15px;246 font-weight: 600;247 }248 249 /* BODY MAP */250 .body-map {251 display: grid;252 grid-template-columns: repeat(3, 1fr);253 gap: 10px;254 max-width: 500px;255 margin: 20px auto;256 }257 258 .body-zone {259 background: var(--panel);260 border: 2px solid var(--line);261 padding: 20px 10px;262 text-align: center;263 border-radius: 12px;264 cursor: pointer;265 transition: all 0.2s;266 font-size: 14px;267 font-weight: 600;268 }269 270 .body-zone:hover {271 background: rgba(215,168,91,.15);272 border-color: var(--gold);273 transform: scale(1.05);274 }275 276 .body-zone.active {277 background: var(--copper);278 border-color: var(--copper);279 color: var(--bg);280 }281 282 /* TEMPERATURE SELECTOR */283 .temp-selector {284 display: grid;285 grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));286 gap: 15px;287 margin-top: 20px;288 }289 290 .temp-card {291 background: var(--panel);292 border: 2px solid var(--line);293 padding: 25px 15px;294 border-radius: 12px;295 text-align: center;296 cursor: pointer;297 transition: all 0.2s;298 }299 300 .temp-card:hover {301 transform: translateY(-3px);302 }303 304 .temp-card.active {305 border-color: var(--card-color);306 background: linear-gradient(145deg, 307 rgba(var(--card-color-rgb), 0.15), 308 rgba(var(--card-color-rgb), 0.05));309 }310 311 .temp-icon {312 font-size: 40px;313 margin-bottom: 8px;314 }315 316 /* OUTPUT PANEL */317 .output-panel {318 position: fixed;319 bottom: 0;320 left: 0;321 right: 0;322 background: linear-gradient(180deg, transparent, var(--panel) 20%, var(--panel));323 border-top: 2px solid var(--copper);324 padding: 20px;325 transform: translateY(100%);326 transition: transform 0.3s;327 max-height: 40vh;328 overflow-y: auto;329 }330 331 .output-panel.visible {332 transform: translateY(0);333 }334 335 .output-text {336 font-size: 18px;337 color: var(--ink);338 background: var(--bg);339 padding: 20px;340 border-radius: 12px;341 border: 1px solid var(--line);342 margin-bottom: 15px;343 line-height: 1.6;344 }345 346 .output-controls {347 display: flex;348 gap: 10px;349 flex-wrap: wrap;350 justify-content: center;351 }352 353 .output-btn {354 background: var(--copper);355 border: none;356 color: var(--ink);357 padding: 12px 24px;358 border-radius: 8px;359 font-size: 16px;360 font-weight: 600;361 cursor: pointer;362 transition: all 0.2s;363 }364 365 .output-btn:hover {366 background: var(--terracotta);367 transform: translateY(-2px);368 }369 370 .output-btn.secondary {371 background: rgba(215,168,91,.2);372 border: 1px solid var(--gold);373 }374 375 .provenance {376 font-size: 11px;377 color: var(--soft);378 margin-top: 10px;379 padding: 10px;380 background: rgba(0,0,0,.2);381 border-radius: 6px;382 border-left: 3px solid var(--gold);383 }384 385 /* SECTION HEADERS */386 .section-header {387 font-size: 20px;388 color: var(--gold);389 margin: 30px 0 15px;390 padding-bottom: 10px;391 border-bottom: 1px solid var(--line);392 font-weight: 600;393 }394 395 /* URGENCY INDICATOR */396 .urgency-indicator {397 display: flex;398 gap: 10px;399 justify-content: center;400 margin: 20px 0;401 }402 403 .urgency-btn {404 flex: 1;405 max-width: 200px;406 padding: 20px;407 background: var(--panel);408 border: 2px solid var(--line);409 border-radius: 12px;410 cursor: pointer;411 transition: all 0.2s;412 text-align: center;413 }414 415 .urgency-btn:hover {416 transform: scale(1.05);417 }418 419 .urgency-btn.active {420 border-width: 3px;421 }422 423 .urgency-btn.low.active {424 border-color: var(--green);425 background: rgba(115,149,111,.15);426 }427 428 .urgency-btn.medium.active {429 border-color: var(--gold);430 background: rgba(215,168,91,.15);431 }432 433 .urgency-btn.high.active {434 border-color: var(--danger);435 background: rgba(210,106,92,.15);436 }437 438 .urgency-icon {439 font-size: 32px;440 margin-bottom: 8px;441 }442 </style>443</head>444<body>445 <div class="container">446 <div class="header">447 <h1>Threshold</h1>448 <p>Communication bridge for nonspeakers ยท Internal โ External ยท HIR-bounded translation</p>449 </div>450 451 <div class="mode-select">452 <button class="mode-btn active" data-mode="feeling">How I Feel</button>453 <button class="mode-btn" data-mode="body">Where I Feel It</button>454 <button class="mode-btn" data-mode="need">What I Need</button>455 <button class="mode-btn" data-mode="custom">My Words</button>456 </div>457 458 <div class="main-interface">459 <!-- FEELING MODE -->460 <div class="interface-panel active" id="feeling-panel">461 <div class="section-header">Choose what matches</div>462 <div class="feeling-grid">463 <div class="feeling-card" data-feeling="safe" style="--card-color: #7dbb8e">464 <div class="feeling-icon">๐ฟ</div>465 <div class="feeling-label">Safe</div>466 <div class="feeling-sublabel">settled, calm</div>467 </div>468 <div class="feeling-card" data-feeling="urgent" style="--card-color: #d26a5c">469 <div class="feeling-icon">โก</div>470 <div class="feeling-label">Urgent</div>471 <div class="feeling-sublabel">needs now</div>472 </div>473 <div class="feeling-card" data-feeling="pressure" style="--card-color: #c77748">474 <div class="feeling-icon">๐ฅ</div>475 <div class="feeling-label">Pressure</div>476 <div class="feeling-sublabel">building, tight</div>477 </div>478 <div class="feeling-card" data-feeling="tired" style="--card-color: #9e8f7a">479 <div class="feeling-icon">๐</div>480 <div class="feeling-label">Tired</div>481 <div class="feeling-sublabel">heavy, need rest</div>482 </div>483 <div class="feeling-card" data-feeling="excited" style="--card-color: #d7a85b">484 <div class="feeling-icon">โจ</div>485 <div class="feeling-label">Excited</div>486 <div class="feeling-sublabel">energy, want</div>487 </div>488 <div class="feeling-card" data-feeling="uncomfortable" style="--card-color: #b75d3a">489 <div class="feeling-icon">โ ๏ธ</div>490 <div class="feeling-label">Uncomfortable</div>491 <div class="feeling-sublabel">something wrong</div>492 </div>493 <div class="feeling-card" data-feeling="confused" style="--card-color: #6b7a88">494 <div class="feeling-icon">โ</div>495 <div class="feeling-label">Confused</div>496 <div class="feeling-sublabel">unclear, lost</div>497 </div>498 <div class="feeling-card" data-feeling="happy" style="--card-color: #73956f">499 <div class="feeling-icon">๐</div>500 <div class="feeling-label">Happy</div>501 <div class="feeling-sublabel">good, light</div>502 </div>503 </div>504 505 <div class="intensity-section">506 <div class="intensity-label">How much?</div>507 <div class="slider-container">508 <input type="range" min="1" max="10" value="5" class="intensity-slider" id="feeling-intensity">509 <div class="intensity-value"><span id="intensity-display">5</span> / 10</div>510 </div>511 </div>512 513 <div class="section-header">How urgent?</div>514 <div class="urgency-indicator">515 <div class="urgency-btn low" data-urgency="low">516 <div class="urgency-icon">๐</div>517 <div>Can wait</div>518 </div>519 <div class="urgency-btn medium" data-urgency="medium">520 <div class="urgency-icon">โฐ</div>521 <div>Soon</div>522 </div>523 <div class="urgency-btn high" data-urgency="high">524 <div class="urgency-icon">๐จ</div>525 <div>Right now</div>526 </div>527 </div>528 </div>529 530 <!-- BODY MODE -->531 <div class="interface-panel" id="body-panel">532 <div class="section-header">Where do you feel it?</div>533 <div class="body-map">534 <div class="body-zone" data-zone="head">Head</div>535 <div class="body-zone" data-zone="neck">Neck</div>536 <div class="body-zone" data-zone="shoulders">Shoulders</div>537 <div class="body-zone" data-zone="chest">Chest</div>538 <div class="body-zone" data-zone="stomach">Stomach</div>539 <div class="body-zone" data-zone="back">Back</div>540 <div class="body-zone" data-zone="arms">Arms</div>541 <div class="body-zone" data-zone="hands">Hands</div>542 <div class="body-zone" data-zone="legs">Legs</div>543 </div>544 545 <div class="section-header">What does it feel like?</div>546 <div class="temp-selector">547 <div class="temp-card" data-temp="hot" style="--card-color: #d26a5c; --card-color-rgb: 210,106,92">548 <div class="temp-icon">๐ฅ</div>549 <div>Hot</div>550 </div>551 <div class="temp-card" data-temp="cold" style="--card-color: #7dbb8e; --card-color-rgb: 125,187,142">552 <div class="temp-icon">โ๏ธ</div>553 <div>Cold</div>554 </div>555 <div class="temp-card" data-temp="tight" style="--card-color: #c77748; --card-color-rgb: 199,119,72">556 <div class="temp-icon">๐</div>557 <div>Tight</div>558 </div>559 <div class="temp-card" data-temp="heavy" style="--card-color: #9e8f7a; --card-color-rgb: 158,143,122">560 <div class="temp-icon">โฌ๏ธ</div>561 <div>Heavy</div>562 </div>563 <div class="temp-card" data-temp="sharp" style="--card-color: #b75d3a; --card-color-rgb: 183,93,58">564 <div class="temp-icon">๐</div>565 <div>Sharp</div>566 </div>567 <div class="temp-card" data-temp="numb" style="--card-color: #6b7a88; --card-color-rgb: 107,122,136">568 <div class="temp-icon">โ</div>569 <div>Numb</div>570 </div>571 </div>572 573 <div class="intensity-section">574 <div class="intensity-label">How strong?</div>575 <div class="slider-container">576 <input type="range" min="1" max="10" value="5" class="intensity-slider" id="body-intensity">577 <div class="intensity-value"><span id="body-intensity-display">5</span> / 10</div>578 </div>579 </div>580 </div>581 582 <!-- NEED MODE -->583 <div class="interface-panel" id="need-panel">584 <div class="section-header">What would help?</div>585 <div class="feeling-grid">586 <div class="feeling-card" data-need="quiet" style="--card-color: #73956f">587 <div class="feeling-icon">๐</div>588 <div class="feeling-label">Quiet</div>589 <div class="feeling-sublabel">less noise</div>590 </div>591 <div class="feeling-card" data-need="space" style="--card-color: #7dbb8e">592 <div class="feeling-icon">๐</div>593 <div class="feeling-label">Space</div>594 <div class="feeling-sublabel">room to be</div>595 </div>596 <div class="feeling-card" data-need="water" style="--card-color: #6b7a88">597 <div class="feeling-icon">๐ง</div>598 <div class="feeling-label">Water</div>599 <div class="feeling-sublabel">thirsty</div>600 </div>601 <div class="feeling-card" data-need="food" style="--card-color: #d7a85b">602 <div class="feeling-icon">๐</div>603 <div class="feeling-label">Food</div>604 <div class="feeling-sublabel">hungry</div>605 </div>606 <div class="feeling-card" data-need="rest" style="--card-color: #9e8f7a">607 <div class="feeling-icon">๐</div>608 <div class="feeling-label">Rest</div>609 <div class="feeling-sublabel">need to lie down</div>610 </div>611 <div class="feeling-card" data-need="bathroom" style="--card-color: #c77748">612 <div class="feeling-icon">๐ป</div>613 <div class="feeling-label">Bathroom</div>614 <div class="feeling-sublabel">need to go</div>615 </div>616 <div class="feeling-card" data-need="help" style="--card-color: #d26a5c">617 <div class="feeling-icon">๐</div>618 <div class="feeling-label">Help</div>619 <div class="feeling-sublabel">something's wrong</div>620 </div>621 <div class="feeling-card" data-need="connection" style="--card-color: #b75d3a">622 <div class="feeling-icon">๐ค</div>623 <div class="feeling-label">Connection</div>624 <div class="feeling-sublabel">want presence</div>625 </div>626 </div>627 </div>628 629 <!-- CUSTOM MODE -->630 <div class="interface-panel" id="custom-panel">631 <div class="section-header">Build your own message</div>632 <div style="padding: 20px;">633 <p style="color: var(--muted); margin-bottom: 20px; text-align: center;">634 This mode lets you create personal meanings that the system will remember for you.635 Tap any feeling/body/need card from other modes, then add your own words here.636 </p>637 <div style="background: var(--panel); padding: 20px; border-radius: 12px; border: 1px solid var(--line);">638 <div style="color: var(--gold); margin-bottom: 10px; font-weight: 600;">My message:</div>639 <textarea id="custom-message" 640 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;"641 placeholder="Type or dictate what you want to say..."></textarea>642 </div>643 </div>644 </div>645 </div>646 647 <!-- OUTPUT PANEL -->648 <div class="output-panel" id="output">649 <div class="output-text" id="output-text"></div>650 <div class="provenance" id="provenance"></div>651 <div class="output-controls">652 <button class="output-btn" onclick="speakMessage()">๐ Speak</button>653 <button class="output-btn" onclick="copyMessage()">๐ Copy</button>654 <button class="output-btn secondary" onclick="saveMapping()">๐พ Remember This</button>655 <button class="output-btn secondary" onclick="closeOutput()">โ Close</button>656 </div>657 </div>658 </div>659 660 <script>661 // STATE662 const state = {663 mode: 'feeling',664 feeling: null,665 feelingIntensity: 5,666 urgency: null,667 bodyZone: null,668 bodyQuality: null,669 bodyIntensity: 5,670 need: null,671 customMessage: '',672 timestamp: null,673 mappings: JSON.parse(localStorage.getItem('threshold_mappings') || '{}')674 };675 676 // MODE SWITCHING677 document.querySelectorAll('.mode-btn').forEach(btn => {678 btn.addEventListener('click', () => {679 const mode = btn.dataset.mode;680 document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active'));681 btn.classList.add('active');682 document.querySelectorAll('.interface-panel').forEach(p => p.classList.remove('active'));683 document.getElementById(mode + '-panel').classList.add('active');684 state.mode = mode;685 });686 });687 688 // FEELING SELECTION689 document.querySelectorAll('.feeling-card').forEach(card => {690 card.addEventListener('click', () => {691 const panel = card.closest('.interface-panel');692 panel.querySelectorAll('.feeling-card').forEach(c => c.classList.remove('selected'));693 card.classList.add('selected');694 695 if (state.mode === 'feeling') {696 state.feeling = card.dataset.feeling;697 } else if (state.mode === 'need') {698 state.need = card.dataset.need;699 }700 701 generateOutput();702 });703 });704 705 // INTENSITY SLIDERS706 document.getElementById('feeling-intensity').addEventListener('input', (e) => {707 state.feelingIntensity = parseInt(e.target.value);708 document.getElementById('intensity-display').textContent = state.feelingIntensity;709 if (state.feeling) generateOutput();710 });711 712 document.getElementById('body-intensity').addEventListener('input', (e) => {713 state.bodyIntensity = parseInt(e.target.value);714 document.getElementById('body-intensity-display').textContent = state.bodyIntensity;715 if (state.bodyZone) generateOutput();716 });717 718 // URGENCY719 document.querySelectorAll('.urgency-btn').forEach(btn => {720 btn.addEventListener('click', () => {721 document.querySelectorAll('.urgency-btn').forEach(b => b.classList.remove('active'));722 btn.classList.add('active');723 state.urgency = btn.dataset.urgency;724 if (state.feeling) generateOutput();725 });726 });727 728 // BODY MAP729 document.querySelectorAll('.body-zone').forEach(zone => {730 zone.addEventListener('click', () => {731 document.querySelectorAll('.body-zone').forEach(z => z.classList.remove('active'));732 zone.classList.add('active');733 state.bodyZone = zone.dataset.zone;734 generateOutput();735 });736 });737 738 // TEMP/QUALITY739 document.querySelectorAll('.temp-card').forEach(card => {740 card.addEventListener('click', () => {741 document.querySelectorAll('.temp-card').forEach(c => c.classList.remove('active'));742 card.classList.add('active');743 state.bodyQuality = card.dataset.temp;744 if (state.bodyZone) generateOutput();745 });746 });747 748 // CUSTOM MESSAGE749 document.getElementById('custom-message').addEventListener('input', (e) => {750 state.customMessage = e.target.value;751 if (state.customMessage.trim()) generateOutput();752 });753 754 // GENERATE OUTPUT755 function generateOutput() {756 state.timestamp = new Date();757 let message = '';758 let provenance = '';759 760 if (state.mode === 'feeling' && state.feeling) {761 const intensityWord = state.feelingIntensity <= 3 ? 'a little' :762 state.feelingIntensity <= 6 ? 'moderately' :763 state.feelingIntensity <= 8 ? 'very' : 'extremely';764 const urgencyPhrase = state.urgency === 'low' ? 'when you can' :765 state.urgency === 'medium' ? 'soon' :766 state.urgency === 'high' ? 'right now' : '';767 768 message = `I feel ${intensityWord} ${state.feeling}`;769 if (urgencyPhrase) message += ` and I need attention ${urgencyPhrase}`;770 message += '.';771 772 provenance = `Mode: How I Feel | Feeling: ${state.feeling} | Intensity: ${state.feelingIntensity}/10`;773 if (state.urgency) provenance += ` | Urgency: ${state.urgency}`;774 }775 else if (state.mode === 'body' && state.bodyZone) {776 const qualityPhrase = state.bodyQuality ? ` and it feels ${state.bodyQuality}` : '';777 const intensityWord = state.bodyIntensity <= 3 ? 'mild' :778 state.bodyIntensity <= 6 ? 'noticeable' :779 state.bodyIntensity <= 8 ? 'strong' : 'intense';780 781 message = `I feel something in my ${state.bodyZone}${qualityPhrase}. The sensation is ${intensityWord}.`;782 783 provenance = `Mode: Where I Feel It | Zone: ${state.bodyZone}`;784 if (state.bodyQuality) provenance += ` | Quality: ${state.bodyQuality}`;785 provenance += ` | Intensity: ${state.bodyIntensity}/10`;786 }787 else if (state.mode === 'need' && state.need) {788 const needMessages = {789 quiet: 'I need it to be quieter.',790 space: 'I need some space.',791 water: 'I need water.',792 food: 'I need food.',793 rest: 'I need to rest.',794 bathroom: 'I need to use the bathroom.',795 help: 'I need help. Something is wrong.',796 connection: 'I need someone with me.'797 };798 799 message = needMessages[state.need] || `I need ${state.need}.`;800 provenance = `Mode: What I Need | Need: ${state.need}`;801 }802 else if (state.mode === 'custom' && state.customMessage.trim()) {803 message = state.customMessage.trim();804 provenance = `Mode: My Words | Custom message entered by user`;805 }806 807 if (message) {808 document.getElementById('output-text').textContent = message;809 document.getElementById('provenance').innerHTML = 810 `<strong>Translation details:</strong> ${provenance}<br>` +811 `<strong>Timestamp:</strong> ${state.timestamp.toLocaleString()}<br>` +812 `<strong>Source:</strong> User-defined meaning, not imposed interpretation<br>` +813 `<strong>Certainty:</strong> Direct user signal, high fidelity`;814 document.getElementById('output').classList.add('visible');815 }816 }817 818 // OUTPUT CONTROLS819 function speakMessage() {820 const text = document.getElementById('output-text').textContent;821 if ('speechSynthesis' in window) {822 const utterance = new SpeechSynthesisUtterance(text);823 utterance.rate = 0.9;824 utterance.pitch = 1;825 speechSynthesis.speak(utterance);826 } else {827 alert('Speech synthesis not supported in this browser');828 }829 }830 831 function copyMessage() {832 const text = document.getElementById('output-text').textContent;833 navigator.clipboard.writeText(text).then(() => {834 alert('Message copied to clipboard');835 });836 }837 838 function saveMapping() {839 const key = `${state.mode}_${state.feeling || state.need || state.bodyZone}_${state.timestamp.getTime()}`;840 const mapping = {841 mode: state.mode,842 message: document.getElementById('output-text').textContent,843 state: {...state},844 timestamp: state.timestamp.toISOString()845 };846 847 state.mappings[key] = mapping;848 localStorage.setItem('threshold_mappings', JSON.stringify(state.mappings));849 alert('This translation has been saved to your personal vocabulary');850 }851 852 function closeOutput() {853 document.getElementById('output').classList.remove('visible');854 }855 </script>856</body>857</html>