framojis / index.html
guidouil's picture
↘️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️↙️ ➡️ ⬅️ ➡️ It's not aligned ⬅️ ➡️ ⬅️ ↗️⬆️⬆️⬆️⬆️⬆️⬆️⬆️⬆️⬆️⬆️⬆️⬆️⬆️⬆️⬆️⬆️⬆️⬆️↖️ - Initial Deployment
2456d3f verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Framemoji - Create Emoji Frames Around Text</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.emoji-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(40px, 1fr));
gap: 8px;
}
#preview {
font-family: 'Segoe UI Emoji', 'Apple Color Emoji', 'Noto Color Emoji', sans-serif;
line-height: 1.2;
white-space: pre;
overflow-x: auto;
}
@media (max-width: 640px) {
.emoji-grid {
grid-template-columns: repeat(auto-fill, minmax(32px, 1fr));
}
}
/* For the canvas export */
#exportCanvas {
display: none;
}
</style>
</head>
<body class="bg-gray-100 min-h-screen">
<div class="container mx-auto px-4 py-8">
<header class="text-center mb-8">
<h1 class="text-4xl font-bold text-indigo-700 mb-2">Framemoji</h1>
<p class="text-gray-600">Create beautiful emoji frames around your text</p>
</header>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<!-- Controls Section -->
<div class="bg-white rounded-xl shadow-md p-6">
<h2 class="text-2xl font-semibold mb-4 text-gray-800">Customize Your Frame</h2>
<!-- Text Input -->
<div class="mb-6">
<label for="textInput" class="block text-sm font-medium text-gray-700 mb-2">Center Text (Supports multiple lines)</label>
<textarea id="textInput" rows="4" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500" placeholder="Enter your text here..."></textarea>
</div>
<!-- Frame Width -->
<div class="mb-6">
<label for="frameWidth" class="block text-sm font-medium text-gray-700 mb-2">Frame Width (in emojis)</label>
<input type="range" id="frameWidth" min="1" max="10" value="2" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
<div class="flex justify-between text-xs text-gray-500 mt-1">
<span>1</span>
<span>2</span>
<span>3</span>
<span>4</span>
<span>5</span>
<span>6</span>
<span>7</span>
<span>8</span>
<span>9</span>
<span>10</span>
</div>
</div>
<!-- Emoji Selection -->
<div class="mb-6">
<label class="block text-sm font-medium text-gray-700 mb-2">Select Frame Emoji</label>
<div class="emoji-grid mb-4">
<!-- Common emojis -->
<button class="emoji-btn text-2xl p-2 rounded hover:bg-gray-100" data-emoji="⭐"></button>
<button class="emoji-btn text-2xl p-2 rounded hover:bg-gray-100" data-emoji="✨"></button>
<button class="emoji-btn text-2xl p-2 rounded hover:bg-gray-100" data-emoji="❤️">❤️</button>
<button class="emoji-btn text-2xl p-2 rounded hover:bg-gray-100" data-emoji="🔥">🔥</button>
<button class="emoji-btn text-2xl p-2 rounded hover:bg-gray-100" data-emoji="🌸">🌸</button>
<button class="emoji-btn text-2xl p-2 rounded hover:bg-gray-100" data-emoji="🍕">🍕</button>
<button class="emoji-btn text-2xl p-2 rounded hover:bg-gray-100" data-emoji="🎉">🎉</button>
<button class="emoji-btn text-2xl p-2 rounded hover:bg-gray-100" data-emoji="🌈">🌈</button>
<button class="emoji-btn text-2xl p-2 rounded hover:bg-gray-100" data-emoji="🦄">🦄</button>
<button class="emoji-btn text-2xl p-2 rounded hover:bg-gray-100" data-emoji="🍄">🍄</button>
<button class="emoji-btn text-2xl p-2 rounded hover:bg-gray-100" data-emoji="🪐">🪐</button>
<button class="emoji-btn text-2xl p-2 rounded hover:bg-gray-100" data-emoji="💎">💎</button>
<button class="emoji-btn text-2xl p-2 rounded hover:bg-gray-100" data-emoji="🍀">🍀</button>
<button class="emoji-btn text-2xl p-2 rounded hover:bg-gray-100" data-emoji="🦋">🦋</button>
<button class="emoji-btn text-2xl p-2 rounded hover:bg-gray-100" data-emoji="🧿">🧿</button>
<button class="emoji-btn text-2xl p-2 rounded hover:bg-gray-100" data-emoji="🕶️">🕶️</button>
</div>
<!-- Arrow option -->
<div class="flex items-center mb-2">
<input type="checkbox" id="arrowMode" class="mr-2 h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded">
<label for="arrowMode" class="text-sm font-medium text-gray-700">Use arrow frame (pointing inward)</label>
</div>
<!-- Blank line borders option -->
<div class="flex items-center mb-4">
<input type="checkbox" id="blankBorders" class="mr-2 h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded">
<label for="blankBorders" class="text-sm font-medium text-gray-700">Add blank line borders</label>
</div>
<!-- Custom Emoji Input -->
<div>
<label for="customEmoji" class="block text-sm font-medium text-gray-700 mb-2">Or enter custom emoji:</label>
<div class="flex">
<input type="text" id="customEmoji" maxlength="5" class="flex-1 px-3 py-2 border border-gray-300 rounded-l-md focus:outline-none focus:ring-2 focus:ring-indigo-500" placeholder="Paste any emoji">
<button id="useCustomEmoji" class="px-4 py-2 bg-indigo-600 text-white rounded-r-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500">Use</button>
</div>
</div>
</div>
<!-- Action Buttons -->
<div class="flex flex-wrap gap-3">
<button id="copyText" class="px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 flex items-center">
<i class="fas fa-copy mr-2"></i> Copy as Text
</button>
<button id="exportPNG" class="px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 flex items-center">
<i class="fas fa-image mr-2"></i> Export as PNG
</button>
<button id="clearAll" class="px-4 py-2 bg-gray-600 text-white rounded-md hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-500 flex items-center">
<i class="fas fa-trash-alt mr-2"></i> Clear
</button>
</div>
</div>
<!-- Preview Section -->
<div class="bg-white rounded-xl shadow-md p-6">
<div class="flex justify-between items-center mb-4">
<h2 class="text-2xl font-semibold text-gray-800">Preview</h2>
<div class="text-sm text-gray-500" id="charCount">0 characters</div>
</div>
<div class="bg-gray-50 p-4 rounded-lg border border-gray-200 min-h-64 flex items-center justify-center">
<div id="preview" class="text-center"></div>
</div>
<div class="mt-4 text-sm text-gray-500">
<p>Tip: The frame will automatically adjust based on your text length.</p>
</div>
</div>
</div>
</div>
<!-- Hidden canvas for PNG export -->
<canvas id="exportCanvas"></canvas>
<script>
document.addEventListener('DOMContentLoaded', function() {
// DOM elements
const textInput = document.getElementById('textInput');
const frameWidth = document.getElementById('frameWidth');
const preview = document.getElementById('preview');
const emojiBtns = document.querySelectorAll('.emoji-btn');
const customEmojiInput = document.getElementById('customEmoji');
const useCustomEmojiBtn = document.getElementById('useCustomEmoji');
const copyTextBtn = document.getElementById('copyText');
const exportPNGBtn = document.getElementById('exportPNG');
const clearAllBtn = document.getElementById('clearAll');
const arrowMode = document.getElementById('arrowMode');
const charCount = document.getElementById('charCount');
const exportCanvas = document.getElementById('exportCanvas');
const ctx = exportCanvas.getContext('2d');
// State
let selectedEmoji = '⭐';
let isArrowMode = false;
let hasBlankBorders = false;
// Arrow emojis for corners and sides
const cornerEmojis = {
topLeft: '↘️',
topRight: '↙️',
bottomLeft: '↗️',
bottomRight: '↖️',
top: '⬇️',
right: '⬅️',
bottom: '⬆️',
left: '➡️'
};
// Initialize
updatePreview();
// Event listeners
textInput.addEventListener('input', updatePreview);
frameWidth.addEventListener('input', updatePreview);
arrowMode.addEventListener('change', function() {
isArrowMode = this.checked;
updatePreview();
});
document.getElementById('blankBorders').addEventListener('change', function() {
hasBlankBorders = this.checked;
updatePreview();
});
emojiBtns.forEach(btn => {
btn.addEventListener('click', function() {
selectedEmoji = this.getAttribute('data-emoji');
arrowMode.checked = false;
isArrowMode = false;
updatePreview();
// Highlight selected button
emojiBtns.forEach(b => b.classList.remove('bg-indigo-100', 'border-indigo-300'));
this.classList.add('bg-indigo-100', 'border', 'border-indigo-300');
});
});
useCustomEmojiBtn.addEventListener('click', function() {
if (customEmojiInput.value.trim()) {
selectedEmoji = customEmojiInput.value.trim();
arrowMode.checked = false;
isArrowMode = false;
updatePreview();
// Highlight custom emoji button
emojiBtns.forEach(b => b.classList.remove('bg-indigo-100', 'border-indigo-300'));
}
});
copyTextBtn.addEventListener('click', function() {
const textToCopy = preview.textContent;
navigator.clipboard.writeText(textToCopy).then(() => {
// Show success feedback
const originalText = this.innerHTML;
this.innerHTML = '<i class="fas fa-check mr-2"></i> Copied!';
this.classList.remove('bg-indigo-600');
this.classList.add('bg-green-600');
setTimeout(() => {
this.innerHTML = originalText;
this.classList.remove('bg-green-600');
this.classList.add('bg-indigo-600');
}, 2000);
});
});
exportPNGBtn.addEventListener('click', exportAsPNG);
clearAllBtn.addEventListener('click', clearAll);
// Functions
function updatePreview() {
const text = textInput.value;
const width = parseInt(frameWidth.value);
// Split text into lines
const lines = text.split('\n');
if (lines.length === 1 && lines[0] === '') {
preview.textContent = 'Your framed text will appear here';
charCount.textContent = '0 characters';
return;
}
// Find the longest line for frame width
let maxLineLength = 0;
lines.forEach(line => {
if (line.length > maxLineLength) maxLineLength = line.length;
});
// Calculate total width (text + 2 * frame width)
const totalWidth = maxLineLength + 2 * width;
// Build the frame
let framedText = '';
// Top frame
if (hasBlankBorders) {
framedText += ' '.repeat(totalWidth) + '\n';
}
for (let i = 0; i < width; i++) {
framedText += buildFrameLine(totalWidth, i, 'top') + '\n';
}
// Middle section with text
lines.forEach(line => {
// Left frame
for (let i = 0; i < width; i++) {
framedText += getFrameEmoji(i, 'left');
}
// Center text with padding
const paddingLeft = Math.floor((maxLineLength - line.length) / 2);
const paddingRight = maxLineLength - line.length - paddingLeft;
framedText += ' '.repeat(paddingLeft) + line + ' '.repeat(paddingRight);
// Right frame
for (let i = 0; i < width; i++) {
framedText += getFrameEmoji(width - i - 1, 'right');
}
framedText += '\n';
});
// Bottom frame
for (let i = 0; i < width; i++) {
framedText += buildFrameLine(totalWidth, width - i - 1, 'bottom') + '\n';
}
if (hasBlankBorders) {
framedText += ' '.repeat(totalWidth) + '\n';
}
preview.textContent = framedText;
charCount.textContent = `${framedText.length} characters`;
}
function buildFrameLine(width, position, side) {
let line = '';
const textWidth = width;
if (isArrowMode) {
// Handle corners
if (position === 0) {
line += side === 'top' ? cornerEmojis.topLeft : cornerEmojis.bottomLeft;
for (let i = 1; i < textWidth - 1; i++) {
line += side === 'top' ? cornerEmojis.top : cornerEmojis.bottom;
}
line += side === 'top' ? cornerEmojis.topRight : cornerEmojis.bottomRight;
} else {
for (let i = 0; i < textWidth; i++) {
line += selectedEmoji;
}
}
} else {
for (let i = 0; i < textWidth; i++) {
line += selectedEmoji;
}
}
return line;
}
function getFrameEmoji(position, side) {
if (isArrowMode) {
if (side === 'left') return cornerEmojis.left;
if (side === 'right') return cornerEmojis.right;
}
return selectedEmoji;
}
function exportAsPNG() {
const text = preview.textContent;
if (!text || text === 'Your framed text will appear here') {
alert('Please create some content first!');
return;
}
// Calculate canvas size
const lines = text.split('\n');
const lineHeight = 30;
const fontSize = 20;
const padding = 20;
// Find the longest line
let maxLineLength = 0;
lines.forEach(line => {
if (line.length > maxLineLength) maxLineLength = line.length;
});
// Set canvas dimensions
exportCanvas.width = maxLineLength * fontSize * 0.6 + padding * 2;
exportCanvas.height = lines.length * lineHeight + padding * 2;
// Clear canvas
ctx.clearRect(0, 0, exportCanvas.width, exportCanvas.height);
// Set background
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, exportCanvas.width, exportCanvas.height);
// Set text style
ctx.font = `${fontSize}px 'Segoe UI Emoji', 'Apple Color Emoji', 'Noto Color Emoji', sans-serif`;
ctx.fillStyle = '#000000';
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
// Draw each line
lines.forEach((line, index) => {
ctx.fillText(line, padding, padding + index * lineHeight);
});
// Create download link
const link = document.createElement('a');
link.download = 'framemoji-export.png';
link.href = exportCanvas.toDataURL('image/png');
link.click();
}
function clearAll() {
textInput.value = '';
frameWidth.value = 2;
selectedEmoji = '⭐';
customEmojiInput.value = '';
arrowMode.checked = false;
isArrowMode = false;
emojiBtns.forEach(b => b.classList.remove('bg-indigo-100', 'border-indigo-300'));
updatePreview();
}
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=guidouil/framojis" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>