diff options
author | elioat <elioat@tilde.institute> | 2025-02-23 22:06:47 -0500 |
---|---|---|
committer | elioat <elioat@tilde.institute> | 2025-02-23 22:06:47 -0500 |
commit | 649883cae43e78c0a84f23b4bfa5622a95ae85a9 (patch) | |
tree | 279021b885450473de335bfb76e1ff428d08f267 | |
parent | 3bed0d3550d620217d29626cb0b4354e087f94f2 (diff) | |
download | tour-649883cae43e78c0a84f23b4bfa5622a95ae85a9.tar.gz |
*
-rw-r--r-- | js/sentiment/bookmarklet-minified.js | 1 | ||||
-rw-r--r-- | js/sentiment/bookmarklet.js | 8 | ||||
-rw-r--r-- | js/sentiment/sentiment/PressStart2P-Regular.ttf | 0 | ||||
-rw-r--r-- | js/sentiment/sentiment/index.html | 250 | ||||
-rw-r--r-- | js/sentiment/sentiment/sentiment.browser.js | 220 |
5 files changed, 479 insertions, 0 deletions
diff --git a/js/sentiment/bookmarklet-minified.js b/js/sentiment/bookmarklet-minified.js new file mode 100644 index 0000000..4671638 --- /dev/null +++ b/js/sentiment/bookmarklet-minified.js @@ -0,0 +1 @@ +javascript:void function(){let e=document.createElement("script");e.src="https://eli.li/_assets/bin/sentiment.browser.js",e.onload=function(){analyzePage()},document.head.appendChild(e)}(); \ No newline at end of file diff --git a/js/sentiment/bookmarklet.js b/js/sentiment/bookmarklet.js new file mode 100644 index 0000000..9209754 --- /dev/null +++ b/js/sentiment/bookmarklet.js @@ -0,0 +1,8 @@ +javascript:(function(){ + const script = document.createElement('script'); + script.src = 'https://eli.li/_assets/bin/sentiment.browser.js'; + script.onload = function() { + analyzePage(); + }; + document.head.appendChild(script); +})(); \ No newline at end of file diff --git a/js/sentiment/sentiment/PressStart2P-Regular.ttf b/js/sentiment/sentiment/PressStart2P-Regular.ttf new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/js/sentiment/sentiment/PressStart2P-Regular.ttf diff --git a/js/sentiment/sentiment/index.html b/js/sentiment/sentiment/index.html new file mode 100644 index 0000000..5c6569f --- /dev/null +++ b/js/sentiment/sentiment/index.html @@ -0,0 +1,250 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Sentiment Analyzer</title> + <link href="./PressStart2P-Regular.ttf" rel="stylesheet"> + <style> + @font-face { + font-family: 'Press Start 2P'; + src: url('./PressStart2P-Regular.ttf') format('truetype'); + font-weight: normal; + font-style: normal; + } + :root { + --gba-dark: #081820; + --gba-mid: #346856; + --gba-light: #88c070; + --gba-pale: #e0f8d0; + } + + body { + font-family: 'Press Start 2P', monospace; + line-height: 1.6; + max-width: 800px; + margin: 0 auto; + padding: 20px; + background-color: var(--gba-dark); + color: var(--gba-pale); + font-size: 12px; + } + + /* Retro window styling */ + .window { + border: 4px solid var(--gba-pale); + border-radius: 0; + padding: 20px; + margin: 20px 0; + position: relative; + background: var(--gba-dark); + } + + .window::before { + content: ''; + position: absolute; + top: -8px; + left: -8px; + right: -8px; + bottom: -8px; + border: 2px solid var(--gba-dark); + z-index: -1; + } + + h1 { + color: var(--gba-light); + text-align: center; + margin: 40px 0; + text-transform: uppercase; + letter-spacing: 2px; + text-shadow: + 2px 2px 0 var(--gba-dark), + 4px 4px 0 var(--gba-mid); + } + + h2 { + color: var(--gba-light); + font-size: 14px; + margin-top: 30px; + border-bottom: 4px solid var(--gba-mid); + padding-bottom: 10px; + } + + .bookmarklet { + display: inline-block; + padding: 15px 30px; + background: var(--gba-light); + color: var(--gba-dark); + text-decoration: none; + margin: 20px 0; + cursor: move; + border: 4px solid var(--gba-pale); + box-shadow: + 4px 4px 0 var(--gba-mid), + 8px 8px 0 var(--gba-dark); + transition: all 0.1s ease; + } + + .bookmarklet:hover { + transform: translate(2px, 2px); + box-shadow: + 2px 2px 0 var(--gba-mid), + 6px 6px 0 var(--gba-dark); + } + + .installation { + border: 4px solid var(--gba-light); + padding: 20px; + margin: 20px 0; + background: var(--gba-mid); + } + + code { + background: var(--gba-dark); + padding: 4px 8px; + border: 2px solid var(--gba-light); + color: var(--gba-pale); + } + + .warning { + border: 4px solid var(--gba-light); + border-style: dashed; + padding: 20px; + margin: 20px 0; + animation: blink 2s infinite; + } + + @keyframes blink { + 0% { border-color: var(--gba-light); } + 50% { border-color: var(--gba-mid); } + 100% { border-color: var(--gba-light); } + } + + ul, ol { + padding-left: 20px; + } + + li { + margin: 10px 0; + position: relative; + } + + li::before { + content: '►'; + position: absolute; + left: -20px; + color: var(--gba-light); + } + + footer { + margin-top: 40px; + padding-top: 20px; + border-top: 4px solid var(--gba-mid); + color: var(--gba-light); + text-align: center; + font-size: 10px; + } + + a { + color: var(--gba-light); + text-decoration: none; + } + + a:hover { + color: var(--gba-pale); + text-decoration: underline; + } + + /* Pixel art decorations */ + .pixel-corner { + position: fixed; + width: 32px; + height: 32px; + background: var(--gba-light); + clip-path: polygon(0 0, 100% 0, 0 100%); + } + + .top-left { top: 0; left: 0; } + .top-right { top: 0; right: 0; transform: rotate(90deg); } + .bottom-left { bottom: 0; left: 0; transform: rotate(-90deg); } + .bottom-right { bottom: 0; right: 0; transform: rotate(180deg); } + + </style> +</head> +<body> + <!-- Pixel art corners --> + <div class="pixel-corner top-left"></div> + <div class="pixel-corner top-right"></div> + <div class="pixel-corner bottom-left"></div> + <div class="pixel-corner bottom-right"></div> + + <h1>Sentiment Scanner</h1> + + <div class="window"> + <p> + This bookmarklet analyzes the emotional tone of any webpage. + </p> + </div> + + <div class="installation"> + <h2>Installation guide</h2> + <p><strong>Drag this power-up to your inventory:</strong></p> + <a class="bookmarklet" href="javascript:void function(){let e=document.createElement('script');e.src='https://smallandnearlysilent.com/sentiment/sentiment.browser.js',e.onload=function(){analyzePage()},document.head.appendChild(e)}();"> + Sentiment + </a> + + <p><strong>Installation steps:</strong></p> + <ol> + <li>Open your bookmarks (<code>ctrl/cmd + shift + b</code>)</li> + <li>Drag the bookmarklet to your bookmarks bar</li> + <li>Cook</li> + </ol> + </div> + + <h2>How to use it</h2> + <ol> + <li>Navigate to target webpage</li> + <li>Click the bookmarklet in your bookmarks bar</li> + <li>It'll analyze the page and display the results in a popup</li> + </ol> + + <div class="window"> + <h3>Results include:</h3> + <ul> + <li>Emotional alignment</li> + <li>Power level</li> + <li>Sentiment score</li> + <li>Word statistics</li> + <li>Positive/negative ratio</li> + </ul> + </div> + + <div class="warning"> + <h3>Important notice</h3> + <ul> + <li>Optimal targets: articles & blog posts</li> + <li>English text only</li> + <li>Some sites may have shields up</li> + <li>No data collection - stealth mode</li> + </ul> + </div> + + <h2>Security Considerations</h2> + <p> + Everything runs locally. No data is transmitted anywhere. + </p> + + <footer> + <p> + <a href="https://eli.li/_assets/bin/sentiment.browser.js" target="_blank">VIEW THE SCRIPT</a> + </p> + </footer> + + <script> + document.querySelector('.bookmarklet').addEventListener('click', function(e) { + e.preventDefault(); + alert('COMMAND INVALID!\nDRAG TO INVENTORY (BOOKMARKS) INSTEAD!'); + }); + </script> +</body> +</html> \ No newline at end of file diff --git a/js/sentiment/sentiment/sentiment.browser.js b/js/sentiment/sentiment/sentiment.browser.js new file mode 100644 index 0000000..e12f72f --- /dev/null +++ b/js/sentiment/sentiment/sentiment.browser.js @@ -0,0 +1,220 @@ +/** + * Browser-friendly Web Sentiment Analyzer + * Simplified version optimized for bookmarklet use + */ + +const createWebSentimentAnalyzer = (config = {}) => { + // Simplified default config with core sentiment words + const defaultConfig = { + positiveWords: new Set([ + 'love', 'joy', 'happy', 'excited', 'peaceful', 'wonderful', 'fantastic', + 'excellent', 'outstanding', 'superb', 'brilliant', 'helpful', 'great', + 'efficient', 'effective', 'reliable', 'innovative', 'productive', + 'friendly', 'supportive', 'kind', 'generous', 'caring' + ]), + + negativeWords: new Set([ + 'hate', 'angry', 'sad', 'upset', 'frustrated', 'disappointed', 'anxious', + 'poor', 'bad', 'terrible', 'horrible', 'awful', 'dreadful', 'inferior', + 'inefficient', 'ineffective', 'unreliable', 'problematic', 'failing', + 'hostile', 'unfriendly', 'unhelpful', 'rude', 'mean' + ]), + + intensifiers: new Map([ + ['extremely', 2.0], + ['very', 1.5], + ['really', 1.5], + ['quite', 1.25] + ]), + + negators: new Set([ + 'not', 'no', 'never', 'none', + "n't", 'cannot', "won't", "wouldn't" + ]) + }; + + const finalConfig = { ...defaultConfig, ...config }; + + const analyzeText = (text) => { + if (!text || typeof text !== 'string') { + return { + score: 0, + summary: { positive: 0, negative: 0, neutral: 0 }, + sentiment: 'Neutral', + intensity: 'None', + wordCount: 0 + }; + } + + const words = text.toLowerCase() + .replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g, '') + .split(/\s+/); + + let score = 0; + let multiplier = 1; + let positiveCount = 0; + let negativeCount = 0; + let intensifierCount = 0; + + for (let i = 0; i < words.length; i++) { + const word = words[i]; + + if (finalConfig.intensifiers.has(word)) { + multiplier = finalConfig.intensifiers.get(word); + intensifierCount++; + continue; + } + + if (finalConfig.negators.has(word)) { + multiplier *= -1; + continue; + } + + if (finalConfig.positiveWords.has(word)) { + score += 1 * multiplier; + positiveCount++; + } else if (finalConfig.negativeWords.has(word)) { + score += -1 * multiplier; + negativeCount++; + } + + multiplier = 1; + } + + const getIntensity = (score, intensifierCount) => { + const magnitude = Math.abs(score); + if (magnitude > 10 || intensifierCount > 5) return 'Very Strong'; + if (magnitude > 7 || intensifierCount > 3) return 'Strong'; + if (magnitude > 4 || intensifierCount > 1) return 'Moderate'; + if (magnitude > 0) return 'Mild'; + return 'Neutral'; + }; + + const getSentiment = (score) => { + if (score > 5) return 'Very Positive'; + if (score > 0) return 'Positive'; + if (score < -5) return 'Very Negative'; + if (score < 0) return 'Negative'; + return 'Neutral'; + }; + + return { + score, + summary: { + positive: positiveCount, + negative: negativeCount, + neutral: words.length - positiveCount - negativeCount, + total: words.length + }, + sentiment: getSentiment(score), + intensity: getIntensity(score, intensifierCount), + wordCount: words.length + }; + }; + + const extractPageContent = () => { + // Priority content selectors + const contentSelectors = [ + 'article', + 'main', + '.content', + '.post-content', + 'article p', + '.content p', + 'p' + ]; + + let content = ''; + for (const selector of contentSelectors) { + const elements = document.querySelectorAll(selector); + if (elements.length) { + elements.forEach(el => { + if (!el.closest('nav') && !el.closest('header') && !el.closest('footer')) { + content += el.textContent + '\n\n'; + } + }); + if (content.trim().length > 0) break; + } + } + + // Fallback to body text if no content found + if (!content) { + content = document.body.textContent || ''; + } + + return content + .replace(/\s+/g, ' ') + .replace(/\n\s*\n/g, '\n\n') + .trim(); + }; + + const createResultsOverlay = (analysis) => { + const overlay = document.createElement('div'); + overlay.style.cssText = ` + position: fixed; + top: 20px; + right: 20px; + max-width: 400px; + background: white; + border: 2px solid #ccc; + border-radius: 8px; + padding: 20px; + box-shadow: 0 4px 12px rgba(0,0,0,0.15); + z-index: 999999; + font-family: Arial, sans-serif; + font-size: 14px; + line-height: 1.4; + `; + + const closeButton = document.createElement('button'); + closeButton.textContent = '×'; + closeButton.style.cssText = ` + position: absolute; + top: 10px; + right: 10px; + border: none; + background: none; + font-size: 20px; + cursor: pointer; + color: #666; + `; + closeButton.onclick = () => overlay.remove(); + + const content = document.createElement('div'); + content.innerHTML = ` + <h2 style="margin: 0 0 15px 0; color: #333;">Sentiment Analysis</h2> + <p style="margin: 0 0 10px 0;"><strong>Overall Sentiment:</strong> ${analysis.sentiment}</p> + <p style="margin: 0 0 10px 0;"><strong>Intensity:</strong> ${analysis.intensity}</p> + <p style="margin: 0 0 10px 0;"><strong>Score:</strong> ${analysis.score.toFixed(2)}</p> + <hr style="margin: 15px 0; border: none; border-top: 1px solid #eee;"> + <p style="margin: 0 0 10px 0;"><strong>Word Count:</strong> ${analysis.wordCount}</p> + <p style="margin: 0 0 5px 0;"><strong>Breakdown:</strong></p> + <ul style="margin: 0; padding-left: 20px;"> + <li>Positive Words: ${analysis.summary.positive}</li> + <li>Negative Words: ${analysis.summary.negative}</li> + <li>Neutral Words: ${analysis.summary.neutral}</li> + </ul> + `; + + overlay.appendChild(closeButton); + overlay.appendChild(content); + document.body.appendChild(overlay); + }; + + return { + analyzeText, + extractPageContent, + createResultsOverlay + }; +}; + +// Bookmarklet-friendly function +const analyzePage = () => { + const analyzer = createWebSentimentAnalyzer(); + const content = analyzer.extractPageContent(); + const analysis = analyzer.analyzeText(content); + analyzer.createResultsOverlay(analysis); +}; + +// For testing in browser console +// analyzePage(); \ No newline at end of file |