human (Dec 19, 2024, 12:09 PM)
artifact that gives me a large textarea for writing in which constantly saves what I have entered to localStorage (restoring when I reload the page) and displays a word counter
assistant (Dec 19, 2024, 12:10 PM)
I'll create a simple but effective writing interface with auto-save and word count functionality.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Writing interface</title>
<style>
* {
box-sizing: border-box;
}
body {
font-family: Helvetica, Arial, sans-serif;
margin: 0;
padding: 20px;
background: #f5f5f5;
}
.container {
max-width: 800px;
margin: 0 auto;
}
h1 {
color: #333;
margin-bottom: 20px;
}
.writing-area {
width: 100%;
min-height: 400px;
padding: 20px;
margin-bottom: 15px;
border: 1px solid #ccc;
border-radius: 4px;
background: white;
font-size: 16px;
line-height: 1.5;
resize: vertical;
}
.stats {
color: #666;
font-size: 14px;
}
.save-status {
color: #888;
font-style: italic;
margin-left: 15px;
}
</style>
</head>
<body>
<div class="container">
<h1>Writing space</h1>
<textarea class="writing-area" placeholder="Start writing here..."></textarea>
<div class="stats">
Words: <span id="wordCount">0</span>
<span class="save-status" id="saveStatus"></span>
</div>
</div>
<script type="module">
const STORAGE_KEY = 'writing-content'
const textarea = document.querySelector('.writing-area')
const wordCountElement = document.getElementById('wordCount')
const saveStatus = document.getElementById('saveStatus')
let saveTimeout
function countWords(text) {
return text.trim() ? text.trim().split(/\s+/).length : 0
}
function updateWordCount() {
const count = countWords(textarea.value)
wordCountElement.textContent = count
}
function showSavedStatus() {
saveStatus.textContent = 'Saved'
setTimeout(() => {
saveStatus.textContent = ''
}, 2000)
}
function saveToLocalStorage() {
localStorage.setItem(STORAGE_KEY, textarea.value)
showSavedStatus()
}
function debouncedSave() {
clearTimeout(saveTimeout)
saveStatus.textContent = 'Saving...'
saveTimeout = setTimeout(saveToLocalStorage, 1000)
}
// Load saved content
const savedContent = localStorage.getItem(STORAGE_KEY)
if (savedContent) {
textarea.value = savedContent
updateWordCount()
}
// Event listeners
textarea.addEventListener('input', () => {
updateWordCount()
debouncedSave()
})
textarea.addEventListener('paste', () => {
setTimeout(updateWordCount, 0)
})
</script>
</body>
</html>
I've created a clean and simple writing interface with the following features:
- A large textarea that automatically saves to localStorage after 1 second of inactivity
- Word counter that updates in real-time
- Save status indicator that shows when content is being saved or has been saved
- Content restoration on page reload
- Mobile-friendly design with proper font sizing
- Clean, minimal styling with good contrast
The interface uses vanilla JavaScript with no dependencies and follows the requested styling conventions. Let me know if you'd like any adjustments to the layout or functionality!
human (Dec 19, 2024, 12:17 PM)
Upgrade it to support multiple textareas. Have a button which, when clicked, adds another textarea with its own counter. All of the textareas should be persisted. Have a remove button next to each one too which removes it (and discards the saved text). The remove button should become a "Clear" button if there is only one textarea left, which clears the memory but does not remove the textarea
assistant (Dec 19, 2024, 12:17 PM)
I'll upgrade the interface to support multiple textareas with persistence and removal functionality.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Multi-textarea writing interface</title>
<style>
* {
box-sizing: border-box;
}
body {
font-family: Helvetica, Arial, sans-serif;
margin: 0;
padding: 20px;
background: #f5f5f5;
}
.container {
max-width: 800px;
margin: 0 auto;
}
h1 {
color: #333;
margin-bottom: 20px;
}
.writing-section {
margin-bottom: 30px;
background: white;
padding: 20px;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.writing-area {
width: 100%;
min-height: 200px;
padding: 20px;
margin-bottom: 15px;
border: 1px solid #ccc;
border-radius: 4px;
background: white;
font-size: 16px;
line-height: 1.5;
resize: vertical;
}
.controls {
display: flex;
justify-content: space-between;
align-items: center;
}
.stats {
color: #666;
font-size: 14px;
}
.save-status {
color: #888;
font-style: italic;
margin-left: 15px;
}
button {
padding: 8px 16px;
border: none;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
transition: background-color 0.2s;
}
.remove-btn {
background-color: #ff4444;
color: white;
}
.remove-btn:hover {
background-color: #ff6666;
}
.add-btn {
background-color: #4CAF50;
color: white;
margin-top: 20px;
}
.add-btn:hover {
background-color: #45a049;
}
.clear-btn {
background-color: #ff9800;
color: white;
}
.clear-btn:hover {
background-color: #ffa726;
}
</style>
</head>
<body>
<div class="container">
<h1>Writing space</h1>
<div id="writing-container"></div>
<button class="add-btn" id="addSection">Add new section</button>
</div>
<script type="module">
const STORAGE_KEY = 'writing-sections'
const container = document.getElementById('writing-container')
const addButton = document.getElementById('addSection')
let saveTimeouts = new Map()
let sections = []
function generateId() {
return Date.now().toString(36) + Math.random().toString(36).substr(2)
}
function createSection(id, content = '') {
const section = document.createElement('div')
section.className = 'writing-section'
section.dataset.id = id
section.innerHTML = `
<textarea class="writing-area" placeholder="Start writing here...">${content}</textarea>
<div class="controls">
<div class="stats">
Words: <span class="word-count">0</span>
<span class="save-status"></span>
</div>
<button class="remove-btn">Remove</button>
</div>
`
const textarea = section.querySelector('textarea')
const wordCount = section.querySelector('.word-count')
const saveStatus = section.querySelector('.save-status')
const removeBtn = section.querySelector('.remove-btn')
updateWordCount(textarea, wordCount)
textarea.addEventListener('input', () => {
updateWordCount(textarea, wordCount)
debouncedSave(id, textarea, saveStatus)
})
removeBtn.addEventListener('click', () => {
if (container.children.length === 1) {
clearSection(id, textarea, saveStatus)
removeBtn.textContent = 'Clear'
removeBtn.className = 'clear-btn'
} else {
removeSection(id, section)
}
})
return section
}
function countWords(text) {
return text.trim() ? text.trim().split(/\s+/).length : 0
}
function updateWordCount(textarea, wordCountElement) {
const count = countWords(textarea.value)
wordCountElement.textContent = count
}
function showSavedStatus(saveStatus) {
saveStatus.textContent = 'Saved'
setTimeout(() => {
saveStatus.textContent = ''
}, 2000)
}
function saveToLocalStorage() {
const sectionsData = Array.from(container.children).map(section => ({
id: section.dataset.id,
content: section.querySelector('textarea').value
}))
localStorage.setItem(STORAGE_KEY, JSON.stringify(sectionsData))
}
function debouncedSave(id, textarea, saveStatus) {
if (saveTimeouts.has(id)) {
clearTimeout(saveTimeouts.get(id))
}
saveStatus.textContent = 'Saving...'
saveTimeouts.set(id, setTimeout(() => {
saveTimeouts.delete(id)
saveToLocalStorage()
showSavedStatus(saveStatus)
}, 1000))
}
function removeSection(id, section) {
if (saveTimeouts.has(id)) {
clearTimeout(saveTimeouts.get(id))
saveTimeouts.delete(id)
}
section.remove()
saveToLocalStorage()
}
function clearSection(id, textarea, saveStatus) {
textarea.value = ''
updateWordCount(textarea, saveStatus.previousElementSibling)
saveToLocalStorage()
}
// Load saved content
const savedContent = localStorage.getItem(STORAGE_KEY)
if (savedContent) {
const savedSections = JSON.parse(savedContent)
savedSections.forEach(section => {
const newSection = createSection(section.id, section.content)
container.appendChild(newSection)
})
} else {
// Create initial section if no saved content
const newSection = createSection(generateId())
container.appendChild(newSection)
}
// Add new section button handler
addButton.addEventListener('click', () => {
const newSection = createSection(generateId())
container.appendChild(newSection)
newSection.querySelector('textarea').focus()
// Update the first/only section's remove button if needed
if (container.children.length === 2) {
const firstSection = container.children[0]
const firstRemoveBtn = firstSection.querySelector('button')
firstRemoveBtn.textContent = 'Remove'
firstRemoveBtn.className = 'remove-btn'
}
})
</script>
</body>
</html>
I've upgraded the interface with the following changes:
- Support for multiple writing sections, each with its own:
- Textarea
- Word counter
- Save status indicator
- Remove/Clear button
- New "Add new section" button at the bottom
- Persistent storage of all sections using localStorage
- Automatic conversion of Remove → Clear button when only one section remains
- Each section has a unique ID for proper state management
- Improved visual design with section cards and clearer button states
The interface maintains all the original functionality while adding the requested multi-section support. Let me know if you'd like any adjustments to the layout or behavior!
human (Dec 19, 2024, 12:18 PM)
Ensure that when the page first loads if there is only one textarea the button says Clear and not remove - also have that button be disabled if there is no text in the box
assistant (Dec 19, 2024, 12:19 PM)
I'll fix the initial button state and add the disable functionality.
Find this:
textarea.addEventListener('input', () => {
updateWordCount(textarea, wordCount)
debouncedSave(id, textarea, saveStatus)
})
Replace with this:
textarea.addEventListener('input', () => {
updateWordCount(textarea, wordCount)
debouncedSave(id, textarea, saveStatus)
if (container.children.length === 1) {
removeBtn.disabled = textarea.value.trim().length === 0
}
})
Find this:
.clear-btn:hover {
background-color: #ffa726;
}
Replace with this:
.clear-btn:hover {
background-color: #ffa726;
}
.clear-btn:disabled {
background-color: #ccc;
cursor: not-allowed;
}
Find this:
function createSection(id, content = '') {
Replace with this:
function createSection(id, content = '', isFirstLoad = false) {
Find this:
updateWordCount(textarea, wordCount)
Replace with this:
updateWordCount(textarea, wordCount)
// Set initial button state
if (isFirstLoad && container.children.length === 0) {
removeBtn.textContent = 'Clear'
removeBtn.className = 'clear-btn'
removeBtn.disabled = content.trim().length === 0
}
Find this:
const newSection = createSection(section.id, section.content)
container.appendChild(newSection)
Replace with this:
const newSection = createSection(section.id, section.content, true)
container.appendChild(newSection)
Find this:
const newSection = createSection(generateId())
container.appendChild(newSection)
Replace with this:
const newSection = createSection(generateId(), '', true)
container.appendChild(newSection)
I've made the following changes:
- Added an
isFirstLoad
parameter to thecreateSection
function to properly handle initial button state - Set the button to "Clear" when there's only one textarea on initial load
- Added disabled state styling for the Clear button
- Added logic to disable the Clear button when the textarea is empty
- The button updates its disabled state in real-time as the user types
Now when you first load the page with a single textarea, you'll see a "Clear" button that's disabled until you start typing. The button state will update automatically as you type or clear the content.
human (Dec 19, 2024, 12:19 PM)
The Clear button should toggle to disabled any time that first textarea is empty, including if the button has been clicked and emptied it
assistant (Dec 19, 2024, 12:19 PM)
I'll update the logic to handle the Clear button's disabled state in all cases where the textarea becomes empty.
Find this:
function clearSection(id, textarea, saveStatus) {
textarea.value = ''
updateWordCount(textarea, saveStatus.previousElementSibling)
saveToLocalStorage()
}
Replace with this:
function clearSection(id, textarea, saveStatus) {
textarea.value = ''
updateWordCount(textarea, saveStatus.previousElementSibling)
saveToLocalStorage()
// Update clear button state if this is the only section
if (container.children.length === 1) {
const clearBtn = textarea.parentElement.querySelector('button')
clearBtn.disabled = true
}
}
I've updated the clearSection
function to also handle the button's disabled state after clearing. Now the Clear button will become disabled in all cases where the textarea becomes empty:
- When the page first loads with an empty textarea
- When the user manually deletes all content
- When the Clear button is clicked
- When there's only one section and its content becomes empty through any means
The button will remain disabled until the user starts typing new content in the textarea.
human (Dec 19, 2024, 12:21 PM)
Let's simplify this a lot: you CAN have 0 textareas, if you do the Add button is still there - so don't bother with the clear button, each one should have a "remove" button instead
Rewrite it all from scratch
assistant (Dec 19, 2024, 12:21 PM)
I'll create a simpler version where textareas can be freely added and removed.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Multi-textarea writing interface</title>
<style>
* {
box-sizing: border-box;
}
body {
font-family: Helvetica, Arial, sans-serif;
margin: 0;
padding: 20px;
background: #f5f5f5;
}
.container {
max-width: 800px;
margin: 0 auto;
}
h1 {
color: #333;
margin-bottom: 20px;
}
.writing-section {
margin-bottom: 30px;
background: white;
padding: 20px;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.writing-area {
width: 100%;
min-height: 200px;
padding: 20px;
margin-bottom: 15px;
border: 1px solid #ccc;
border-radius: 4px;
background: white;
font-size: 16px;
line-height: 1.5;
resize: vertical;
}
.controls {
display: flex;
justify-content: space-between;
align-items: center;
}
.stats {
color: #666;
font-size: 14px;
}
.save-status {
color: #888;
font-style: italic;
margin-left: 15px;
}
.add-btn {
background-color: #4CAF50;
color: white;
padding: 8px 16px;
border: none;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
transition: background-color 0.2s;
}
.add-btn:hover {
background-color: #45a049;
}
.remove-btn {
background-color: #ff4444;
color: white;
padding: 8px 16px;
border: none;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
transition: background-color 0.2s;
}
.remove-btn:hover {
background-color: #ff6666;
}
</style>
</head>
<body>
<div class="container">
<h1>Writing space</h1>
<div id="writing-container"></div>
<button class="add-btn" id="addSection">Add new section</button>
</div>
<script type="module">
const STORAGE_KEY = 'writing-sections'
const container = document.getElementById('writing-container')
const addButton = document.getElementById('addSection')
let saveTimeouts = new Map()
function generateId() {
return Date.now().toString(36) + Math.random().toString(36).substr(2)
}
function createSection(id, content = '') {
const section = document.createElement('div')
section.className = 'writing-section'
section.dataset.id = id
section.innerHTML = `
<textarea class="writing-area" placeholder="Start writing here...">${content}</textarea>
<div class="controls">
<div class="stats">
Words: <span class="word-count">0</span>
<span class="save-status"></span>
</div>
<button class="remove-btn">Remove</button>
</div>
`
const textarea = section.querySelector('textarea')
const wordCount = section.querySelector('.word-count')
const saveStatus = section.querySelector('.save-status')
const removeBtn = section.querySelector('.remove-btn')
updateWordCount(textarea, wordCount)
textarea.addEventListener('input', () => {
updateWordCount(textarea, wordCount)
debouncedSave()
})
removeBtn.addEventListener('click', () => {
section.remove()
debouncedSave()
})
return section
}
function countWords(text) {
return text.trim() ? text.trim().split(/\s+/).length : 0
}
function updateWordCount(textarea, wordCountElement) {
const count = countWords(textarea.value)
wordCountElement.textContent = count
}
function saveToLocalStorage() {
const sectionsData = Array.from(container.children).map(section => ({
id: section.dataset.id,
content: section.querySelector('textarea').value
}))
localStorage.setItem(STORAGE_KEY, JSON.stringify(sectionsData))
// Update save status for all sections
document.querySelectorAll('.save-status').forEach(status => {
status.textContent = 'Saved'
setTimeout(() => {
status.textContent = ''
}, 2000)
})
}
function debouncedSave() {
// Clear any existing save timeout
if (saveTimeouts.has('save')) {
clearTimeout(saveTimeouts.get('save'))
}
// Show 'Saving...' status
document.querySelectorAll('.save-status').forEach(status => {
status.textContent = 'Saving...'
})
// Set new save timeout
saveTimeouts.set('save', setTimeout(() => {
saveTimeouts.delete('save')
saveToLocalStorage()
}, 1000))
}
// Load saved content
const savedContent = localStorage.getItem(STORAGE_KEY)
if (savedContent) {
const savedSections = JSON.parse(savedContent)
savedSections.forEach(section => {
const newSection = createSection(section.id, section.content)
container.appendChild(newSection)
})
}
// Add new section button handler
addButton.addEventListener('click', () => {
const newSection = createSection(generateId())
container.appendChild(newSection)
newSection.querySelector('textarea').focus()
})
</script>
</body>
</html>
I've created a simplified version with the following changes:
- Removed the Clear button concept entirely
- Each section has a simple Remove button that deletes it
- You can now have 0 textareas - the Add button remains at the bottom
- Simplified the save status handling to work across all sections
- Maintained all core functionality:
- Word counting
- Auto-saving
- Local storage persistence
- Unique IDs for each section
The interface is now more straightforward and intuitive - you can freely add and remove sections as needed. Let me know if you'd like any adjustments to the layout or functionality!