human-translation-accessibility-stack / threshold_for_nonspeakers.html
HirModel's picture
Upload 6 files
5e0bc6d verified
Raw
History Blame Contribute Delete
28.1 kB
<!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 */
.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 SELECTOR */
.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 */
.main-interface {
min-height: calc(100vh - 180px);
padding: 20px;
}
.interface-panel {
display: none;
}
.interface-panel.active {
display: block;
}
/* FEELING GRID */
.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 SLIDER */
.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 */
.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);
}
/* TEMPERATURE SELECTOR */
.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 */
.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 HEADERS */
.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 */
.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">
<!-- FEELING MODE -->
<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>
<!-- BODY MODE -->
<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>
<!-- NEED MODE -->
<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>
<!-- CUSTOM MODE -->
<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>
<!-- OUTPUT PANEL -->
<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>
// STATE
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') || '{}')
};
// MODE SWITCHING
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;
});
});
// FEELING SELECTION
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();
});
});
// INTENSITY SLIDERS
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();
});
// URGENCY
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();
});
});
// BODY MAP
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();
});
});
// TEMP/QUALITY
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();
});
});
// CUSTOM MESSAGE
document.getElementById('custom-message').addEventListener('input', (e) => {
state.customMessage = e.target.value;
if (state.customMessage.trim()) generateOutput();
});
// GENERATE OUTPUT
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');
}
}
// OUTPUT CONTROLS
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>