sam

I want a prototype of a “text selection” widget.

  1. Focus on mobile experience.
  2. Render 2, 3 paragraphs
  3. When text is selected, at the bottom of the screen show a floating panel with various options like (quote, copy quote, share)
claude-3.5-sonnet Web Artifact Creator

I’ll help you create a mobile-friendly text selection widget. I’ll build this with a focus on touch devices and add a floating action panel that appears when text is selected.

View Source

HTML


<div class="container">
  <h1>Article Title</h1>
  
  <p>The art of programming is the skill of controlling complexity. Good programming is not about being clever and obscure — it's about being clear and maintainable. Clean code reads like well-written prose, telling a story through carefully chosen names and logical structure.</p>

  <p>Software engineering is not just about writing code that works — it's about writing code that can be understood, modified, and maintained by others. The true measure of code quality lies not in its cleverness or brevity, but in how well it communicates its purpose and intent to future readers.</p>

  <p>The best programs are those that handle the user's needs while being invisible — they do their job so naturally that the user doesn't even think about the technology behind it. This is the essence of good software design: it disappears into the background while solving real problems.</p>

  <div id="selection-toolbar" class="selection-toolbar hidden">
    <button id="quote-btn" class="toolbar-btn">
      <svg viewBox="0 0 24 24" width="24" height="24">
        <path fill="currentColor" d="M6,17h3l2-4V7H5v6h3L6,17z M14,17h3l2-4V7h-6v6h3L14,17z"/>
      </svg>
      Quote
    </button>
    <button id="copy-btn" class="toolbar-btn">
      <svg viewBox="0 0 24 24" width="24" height="24">
        <path fill="currentColor" d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/>
      </svg>
      Copy
    </button>
    <button id="share-btn" class="toolbar-btn">
      <svg viewBox="0 0 24 24" width="24" height="24">
        <path fill="currentColor" d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92s2.92-1.31 2.92-2.92c0-1.61-1.31-2.92-2.92-2.92zM18 4c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zM6 13c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm12 7.02c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1z"/>
      </svg>
      Share
    </button>
  </div>
</div>

CSS


* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
  line-height: 1.6;
  padding: 20px;
  max-width: 800px;
  margin: 0 auto;
  -webkit-tap-highlight-color: rgba(0,0,0,0);
}

h1 {
  font-size: 24px;
  margin-bottom: 20px;
  color: #333;
}

p {
  margin-bottom: 16px;
  font-size: 16px;
  color: #444;
}

.selection-toolbar {
  position: fixed;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
  background: #fff;
  border-radius: 12px;
  box-shadow: 0 4px 12px rgba(0,0,0,0.15);
  padding: 8px 16px;
  display: flex;
  gap: 16px;
  transition: all 0.3s ease;
  z-index: 1000;
}

.selection-toolbar.hidden {
  transform: translate(-50%, 150%);
  opacity: 0;
}

.toolbar-btn {
  display: flex;
  align-items: center;
  gap: 6px;
  background: none;
  border: none;
  color: #333;
  padding: 8px 12px;
  border-radius: 8px;
  cursor: pointer;
  font-size: 14px;
  transition: background-color 0.2s ease;
}

.toolbar-btn:active {
  background-color: #f0f0f0;
}

.toolbar-btn svg {
  width: 20px;
  height: 20px;
}

::selection {
  background: rgba(46, 170, 220, 0.2);
}

JavaScript


const toolbar = document.getElementById('selection-toolbar');
const copyBtn = document.getElementById('copy-btn');
const quoteBtn = document.getElementById('quote-btn');
const shareBtn = document.getElementById('share-btn');

// Handle text selection
document.addEventListener('selectionchange', () => {
  const selection = window.getSelection();
  const selectedText = selection.toString().trim();
  
  if (selectedText.length > 0) {
    toolbar.classList.remove('hidden');
  } else {
    toolbar.classList.add('hidden');
  }
});

// Copy button handler
copyBtn.addEventListener('click', () => {
  const selection = window.getSelection();
  const selectedText = selection.toString().trim();
  
  if (selectedText) {
    navigator.clipboard.writeText(selectedText).then(() => {
      // Visual feedback
      copyBtn.style.backgroundColor = '#e6f7ff';
      setTimeout(() => {
        copyBtn.style.backgroundColor = '';
      }, 200);
    });
  }
});

// Quote button handler
quoteBtn.addEventListener('click', () => {
  const selection = window.getSelection();
  const selectedText = selection.toString().trim();
  
  if (selectedText) {
    const quote = `"${selectedText}"`;
    navigator.clipboard.writeText(quote).then(() => {
      quoteBtn.style.backgroundColor = '#e6f7ff';
      setTimeout(() => {
        quoteBtn.style.backgroundColor = '';
      }, 200);
    });
  }
});

// Share button handler
shareBtn.addEventListener('click', async () => {
  const selection = window.getSelection();
  const selectedText = selection.toString().trim();
  
  if (selectedText && navigator.share) {
    try {
      await navigator.share({
        text: selectedText
      });
    } catch (err) {
      console.log('Share failed:', err);
    }
  }
  
  shareBtn.style.backgroundColor = '#e6f7ff';
  setTimeout(() => {
    shareBtn.style.backgroundColor = '';
  }, 200);
});

// Hide toolbar when clicking outside
document.addEventListener('click', (e) => {
  if (!toolbar.contains(e.target)) {
    window.getSelection().removeAllRanges();
    toolbar.classList.add('hidden');
  }
});

Preview