text-flow / index.html
victor's picture
victor HF Staff
Add 2 files
0b94f90 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Text Flow Drawing Canvas</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
body {
overflow: hidden;
touch-action: none;
font-family: 'Georgia', serif;
}
canvas {
position: absolute;
top: 0;
left: 0;
z-index: 1;
}
.controls {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
z-index: 10;
background: rgba(255, 255, 255, 0.9);
padding: 15px;
border-radius: 15px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
backdrop-filter: blur(5px);
max-width: 90%;
}
.text-palette {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 15px;
justify-content: center;
}
.text-option {
cursor: pointer;
padding: 8px 12px;
border-radius: 8px;
background: white;
border: 1px solid #e2e8f0;
transition: all 0.2s;
font-size: 14px;
white-space: nowrap;
}
.text-option:hover, .text-option.active {
background: #3b82f6;
color: white;
transform: translateY(-2px);
}
.color-picker {
display: flex;
gap: 10px;
margin-bottom: 15px;
justify-content: center;
}
.color-option {
width: 28px;
height: 28px;
border-radius: 50%;
cursor: pointer;
border: 2px solid white;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
transition: transform 0.2s;
}
.color-option:hover, .color-option.active {
transform: scale(1.2);
}
.controls-group {
display: flex;
gap: 15px;
justify-content: center;
margin-bottom: 15px;
flex-wrap: wrap;
}
.control-item {
display: flex;
flex-direction: column;
align-items: center;
}
.control-label {
font-size: 12px;
margin-bottom: 5px;
color: #4b5563;
font-weight: 500;
}
.title {
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
z-index: 10;
background: rgba(255, 255, 255, 0.9);
padding: 12px 25px;
border-radius: 30px;
font-family: 'Playfair Display', serif;
font-weight: 600;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
color: #1e40af;
}
.custom-text {
width: 100%;
padding: 8px 12px;
border-radius: 8px;
border: 1px solid #e2e8f0;
margin-bottom: 10px;
font-size: 14px;
}
.btn {
padding: 8px 16px;
border-radius: 8px;
font-weight: 500;
transition: all 0.2s;
border: none;
cursor: pointer;
}
.btn-primary {
background: #3b82f6;
color: white;
}
.btn-primary:hover {
background: #2563eb;
transform: translateY(-1px);
}
.btn-danger {
background: #ef4444;
color: white;
}
.btn-danger:hover {
background: #dc2626;
transform: translateY(-1px);
}
.btn-group {
display: flex;
gap: 10px;
justify-content: center;
}
</style>
</head>
<body class="bg-gray-50">
<div class="title">Text Flow Drawing Canvas</div>
<canvas id="drawingCanvas"></canvas>
<div class="controls">
<input type="text" id="customText" class="custom-text" placeholder="Type your own text here..." value="Love is patient, love is kind">
<div class="text-palette">
<div class="text-option active">Love is patient</div>
<div class="text-option">Be the change</div>
<div class="text-option">Dream big</div>
<div class="text-option">Stay curious</div>
<div class="text-option">Create magic</div>
<div class="text-option">Find joy</div>
<div class="text-option">Never give up</div>
<div class="text-option">You matter</div>
</div>
<div class="color-picker">
<div class="color-option active" style="background-color: #3b82f6;" data-color="#3b82f6"></div>
<div class="color-option" style="background-color: #ef4444;" data-color="#ef4444"></div>
<div class="color-option" style="background-color: #10b981;" data-color="#10b981"></div>
<div class="color-option" style="background-color: #f59e0b;" data-color="#f59e0b"></div>
<div class="color-option" style="background-color: #8b5cf6;" data-color="#8b5cf6"></div>
<div class="color-option" style="background-color: #000000;" data-color="#000000"></div>
</div>
<div class="controls-group">
<div class="control-item">
<span class="control-label">Font Size</span>
<input type="range" id="sizeSlider" min="12" max="36" value="18" class="w-24">
<span id="sizeValue" class="text-xs mt-1">18px</span>
</div>
<div class="control-item">
<span class="control-label">Spacing</span>
<input type="range" id="spacingSlider" min="0.5" max="2" step="0.1" value="1" class="w-24">
<span id="spacingValue" class="text-xs mt-1">1.0</span>
</div>
<div class="control-item">
<span class="control-label">Opacity</span>
<input type="range" id="opacitySlider" min="0.2" max="1" step="0.1" value="0.8" class="w-24">
<span id="opacityValue" class="text-xs mt-1">80%</span>
</div>
</div>
<div class="btn-group">
<button id="clearBtn" class="btn btn-danger">Clear Canvas</button>
<button id="saveBtn" class="btn btn-primary">Save as Image</button>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const canvas = document.getElementById('drawingCanvas');
const ctx = canvas.getContext('2d');
let isDrawing = false;
let currentText = "Love is patient, love is kind";
let currentColor = '#3b82f6';
let currentSize = 18;
let currentSpacing = 1;
let currentOpacity = 0.8;
let lastX = 0;
let lastY = 0;
let textPosition = 0;
// Set canvas to full window size
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// Add a subtle off-white background
ctx.fillStyle = '#f8fafc';
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
// Drawing functions
function startDrawing(e) {
isDrawing = true;
const pos = getPosition(e);
lastX = pos.x;
lastY = pos.y;
textPosition = 0; // Reset text position for new stroke
draw(e);
}
function stopDrawing() {
isDrawing = false;
}
function getPosition(e) {
let x, y;
if (e.type.includes('touch')) {
x = e.touches[0].clientX;
y = e.touches[0].clientY;
} else {
x = e.clientX;
y = e.clientY;
}
return { x, y };
}
function draw(e) {
if (!isDrawing) return;
const pos = getPosition(e);
const x = pos.x;
const y = pos.y;
// Calculate distance from last point
const distance = Math.sqrt(Math.pow(x - lastX, 2) + Math.pow(y - lastY, 2));
if (distance > currentSize / 3) {
// Calculate angle for text rotation
const angle = Math.atan2(y - lastY, x - lastX);
ctx.save();
ctx.translate(x, y);
ctx.rotate(angle);
// Set text style
ctx.font = `${currentSize}px Georgia, serif`;
ctx.fillStyle = currentColor;
ctx.globalAlpha = currentOpacity;
// Draw the next character in the text
const char = currentText[textPosition % currentText.length];
ctx.fillText(char, 0, 0);
// Move forward in the text and position
textPosition++;
lastX = x;
lastY = y;
// Move to next position based on spacing
ctx.translate(currentSize * currentSpacing, 0);
ctx.restore();
}
}
// Event listeners for drawing
canvas.addEventListener('mousedown', startDrawing);
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('mouseup', stopDrawing);
canvas.addEventListener('mouseout', stopDrawing);
// Touch support
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
startDrawing(e);
});
canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
draw(e);
});
canvas.addEventListener('touchend', stopDrawing);
// Text selection
document.querySelectorAll('.text-option').forEach(option => {
option.addEventListener('click', () => {
document.querySelectorAll('.text-option').forEach(opt => opt.classList.remove('active'));
option.classList.add('active');
currentText = option.textContent;
});
});
// Custom text input
document.getElementById('customText').addEventListener('input', (e) => {
currentText = e.target.value;
// Also activate the custom text field
document.querySelectorAll('.text-option').forEach(opt => opt.classList.remove('active'));
});
// Color selection
document.querySelectorAll('.color-option').forEach(option => {
option.addEventListener('click', () => {
document.querySelectorAll('.color-option').forEach(opt => opt.classList.remove('active'));
option.classList.add('active');
currentColor = option.getAttribute('data-color');
});
});
// Size control
const sizeSlider = document.getElementById('sizeSlider');
const sizeValue = document.getElementById('sizeValue');
sizeSlider.addEventListener('input', () => {
currentSize = parseInt(sizeSlider.value);
sizeValue.textContent = `${currentSize}px`;
});
// Spacing control
const spacingSlider = document.getElementById('spacingSlider');
const spacingValue = document.getElementById('spacingValue');
spacingSlider.addEventListener('input', () => {
currentSpacing = parseFloat(spacingSlider.value);
spacingValue.textContent = currentSpacing.toFixed(1);
});
// Opacity control
const opacitySlider = document.getElementById('opacitySlider');
const opacityValue = document.getElementById('opacityValue');
opacitySlider.addEventListener('input', () => {
currentOpacity = parseFloat(opacitySlider.value);
opacityValue.textContent = `${Math.round(currentOpacity * 100)}%`;
});
// Clear canvas
document.getElementById('clearBtn').addEventListener('click', () => {
if (confirm('Are you sure you want to clear the canvas?')) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Reset background
ctx.fillStyle = '#f8fafc';
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
});
// Save canvas as image
document.getElementById('saveBtn').addEventListener('click', () => {
const link = document.createElement('a');
link.download = 'text-drawing.png';
link.href = canvas.toDataURL('image/png');
link.click();
});
// Prevent scrolling when touching canvas
document.addEventListener('touchmove', (e) => {
if (isDrawing) {
e.preventDefault();
}
}, { passive: false });
});
</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=victor/text-flow" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body>
</html>