diff --git a/frontend/src/index.css b/frontend/src/index.css index 66ef541..a3f33f4 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -392,8 +392,17 @@ button:disabled { opacity: 0.45; cursor: default; transform: none; box-shadow: n .turn-buttons button { font-size: 0.78rem; padding: 4px 12px; color: var(--text-dim); } .turn-buttons button:hover:not(:disabled) { color: var(--text); } -.input-bar { display: flex; gap: 8px; align-items: stretch; } +.input-bar { display: flex; gap: 8px; align-items: flex-end; } .input-bar input { flex: 1; } +/* Auto-growing action box: starts at one line, grows to ~4, then scrolls. */ +.input-bar textarea { + flex: 1; + resize: none; + min-height: 0; + max-height: calc(6em + 20px); /* ~4 lines (1.5em each) + padding/border */ + overflow-y: auto; + line-height: 1.5; +} .mode-select { display: flex; } .mode-select button { diff --git a/frontend/src/pages/Play.jsx b/frontend/src/pages/Play.jsx index 7bdac83..64c3b0a 100644 --- a/frontend/src/pages/Play.jsx +++ b/frontend/src/pages/Play.jsx @@ -612,6 +612,16 @@ export default function Play() { const storyEndRef = useRef(null) const abortRef = useRef(null) const pinnedRef = useRef(true) // autoscroll only while the reader is at the bottom + const inputRef = useRef(null) + + // Grow the action box with its content (CSS caps it at ~4 lines, then + // scrolls); shrinks back after send() clears the text. + useEffect(() => { + const el = inputRef.current + if (!el) return + el.style.height = 'auto' + el.style.height = `${el.scrollHeight}px` + }, [input]) useEffect(() => { api.getAdventure(id) @@ -863,8 +873,9 @@ export default function Play() { ))} - setInput(e.target.value)} - onKeyDown={(e) => { if (e.key === 'Enter' && !busy) send() }} + // Enter sends; Shift+Enter inserts a newline. + onKeyDown={(e) => { + if (e.key === 'Enter' && !e.shiftKey && !busy) { e.preventDefault(); send() } + }} /> {busy ? (