turns-00029.parquet:45114
1dcdfa521ccd9d0002cdd96ddegenerate_repetitionAbsentFinal dense release
Select one behavior. Every returned turn has one binary label: Present or Absent. Source: final dense boolean release.
1dcdfa521ccd9d0002cdd96dtestt
It seems like you might have entered "testt" by mistake. How can I assist you today?
fda60d410376ffcf7859fc49given the following readme, expand on the other possible startegies (balancing the dataset, etc.). How could they be used exactly? # Image-Caption Similarity Computation ## Overview This project computes similarity metrics between images and their corresponding captions using OpenAI's CLIP (Contrastive Language-Image Pretraining) model. ## Installation Setup virtual environment ```bash pip install virtualenv # if you don't have it already virtualenv venv source venv/bin/activate ``` Install Dependencies ```bash pip install -r requirements.txt ``` ## Example Usage ``` python compute_similarity.py --csv challenge_set/challenge_set.csv --images_dir challenge_set --device cuda:0 ``` Required args: - **--csv**: Path to the input CSV file containing image_file and caption columns. - **--images_dir**: Directory containing the image files. ## Design Considerations Choice of Model: CLIP (ViT-B/32): chosen for its state-of-the-art performance in correlating images with textual descriptions, providing high-quality embeddings for accurate similarity measurements. Performance Metrics: Incorporates memory usage and runtime tracking, GPU utilization, per-batch processing times and statistics on the similarity scores obtained. We also implemented checkpointing, to allow resuming experiments in case of interruptions and we support saving image and text feature embeddings to disk, with the function *save_embeddings*. ## Curating Data for Text-to-Image Model Training: Similarity scores can enhance the quality of image-caption datasets by: - Identifying high-quality pairs: Pairs with high similarity scores are likely to be well-matched. - Filtering out weak pairs: The function *filter_high_quality_pairs* removes pairs with low similarity scores and saves the filtered data in a new CSV file. In particular, I chose to remove the image/captions with CLIP score below 0.3 (30), because they seemed to have poor quality. With this choice 35/51 images were preserved. Other possible strategies: Balancing the Dataset: Ensures diversity and representativeness by analyzing similarity distributions. Guiding Data Augmentation: Informs strategies to generate more relevant images or captions. Establishing a Feedback Loop: Iteratively refines the dataset and model performance. Enabling Targeted Training: Facilitates fine-tuning on specific subsets with desired characteristics. ## Optimization Considerations for Large-Scale Processing: To improve scalability and efficiency, we could consider the following: 1. Quantization Quantizing the model for faster inference, using lower precision (e.g., INT8) arithmetic operations. However this would have an accuracy trade-off, as quantization may lead to a small drop in model accuracy. 2. Mixed Precision Another possibility is using mixed precision (combining FP16 and FP32). It helps reducing memory consumption and accelerating computation on GPUs equipped with Tensor Cores. This approach maintains acceptable levels of accuracy while optimizing speed. 3. Distributed Computing In my implementation I used DataParallel to utilise multiple GPUs on a single machine. However, for better scalability and efficiency, DistributedDataParallel (DDP) can be used. DDP is more efficient and supports scaling across multiple nodes. Alternatively, frameworks like Huggingface's Accelerate can help simplify distributed computing setups.
Certainly! Expanding on the "Other possible strategies" section can provide valuable insights into optimizing dataset preparation and enhancing the performance of your image-caption similarity computation project. Below is an expanded version of the README with detailed explanations of each strategy and how they can be effectively utilized.
---
# Image-Caption Similarity Computation
## Overview
This project computes similarity metrics between images and their corresponding captions using OpenAI's CLIP (Contrastive Language-Image Pretraining) model.
## Installation
Setup virtual environment
```bash
pip install virtualenv # if you don't have it already
virtualenv venv
source venv/bin/activate
```
Install Dependencies
```bash
pip install -r requirements.txt
```
## Example Usage
```
python compute_similarity.py --csv challenge_set/challenge_set.csv --images_dir challenge_set --device cuda:0
```
Required args:
- **--csv**: Path to the input CSV file containing `image_file` and `caption` columns.
- **--images_dir**: Directory containing the image files.
## Design Considerations
**Choice of Model:**
CLIP (ViT-B/32) is chosen for its state-of-the-art performance in correlating images with textual descriptions, providing high-quality embeddings for accurate similarity measurements.
**Performance Metrics:**
Incorporates memory usage and runtime tracking, GPU utilization, per-batch processing times, and statistics on the similarity scores obtained.
**Checkpointing:**
Allows resuming experiments in case of interruptions and supports saving image and text feature embeddings to disk, with the function `save_embeddings`.
## Curating Data for Text-to-Image Model Training
Similarity scores can enhance the quality of image-caption datasets by:
- **Identifying High-Quality Pairs:** Pairs with high similarity scores are likely well-matched.
- **Filtering Out Weak Pairs:** The function `filter_high_quality_pairs` removes pairs with low similarity scores and saves the filtered data in a new CSV file.
In particular, pairs with CLIP scores below 0.3 were removed due to poor quality, preserving 35 out of 51 images.
### Other Possible Strategies
Expanding beyond basic filtering, several strategies can be employed to optimize the dataset and model performance further:
1. **Balancing the Dataset**
2. **Guiding Data Augmentation**
3. **Establishing a Feedback Loop**
4. **Enabling Targeted Training**
#### 1. Balancing the Dataset
**Objective:**
Ensure that the dataset is diverse and representative by maintaining a balanced distribution of similarity scores, categories, or other relevant attributes.
**Why It's Important:**
A balanced dataset prevents the model from becoming biased towards overrepresented classes or similarity ranges, leading to more generalizable and fair performance.
**How to Implement:**
- **Analyze Distribution:**
Examine the distribution of similarity scores and other attributes (e.g., categories, image types) to identify imbalances.
- **Resampling Techniques:**
- **Oversampling:** Increase the number of underrepresented samples by duplicating them or generating synthetic data.
- **Undersampling:** Reduce the number of overrepresented samples to balance the dataset.
- **Stratified Sampling:**
Ensure that each subset of the data (e.g., training, validation, testing) maintains the overall distribution characteristics.
**Example Implementation:**
```python
from sklearn.model_selection import train_test_split
from imblearn.over_sampling import RandomOverSampler
# Load dataset
df = pd.read_csv('filtered_dataset.csv')
# Define features and target
X = df[['image_file', 'caption']]
y = df['similarity_score']
# Apply oversampling
ros = RandomOverSampler(random_state=42)
X_resampled, y_resampled = ros.fit_resample(X, y)
# Save balanced dataset
balanced_df = pd.concat([X_resampled, y_resampled], axis=1)
balanced_df.to_csv('balanced_dataset.csv', index=False)
```
#### 2. Guiding Data Augmentation
**Objective:**
Enhance the dataset by generating additional relevant images or captions based on existing data, informed by similarity scores.
**Why It's Important:**
Data augmentation can help in increasing the diversity and size of the dataset, which can improve model robustness and performance, especially in cases of limited data.
**How to Implement:**
- **Image Augmentation:**
Apply transformations such as rotation, scaling, cropping, and color adjustments to create varied versions of existing images.
- **Caption Augmentation:**
Use techniques like synonym replacement, sentence restructuring, or leveraging language models to generate alternative captions that maintain the original meaning.
- **Conditional Augmentation:**
Use similarity scores to guide the extent or type of augmentation. For instance, apply stronger augmentations to low-similarity pairs to enhance their quality.
**Example Implementation:**
```python
from PIL import Image
from torchvision import transforms
import random
def augment_image(image_path, output_dir):
image = Image.open(image_path)
transform = transforms.Compose([
transforms.RandomRotation(15),
transforms.RandomResizedCrop(224),
transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.1),
transforms.RandomHorizontalFlip(),
])
augmented_image = transform(image)
augmented_image.save(os.path.join(output_dir, f"aug_{os.path.basename(image_path)}"))
# Apply augmentation to low similarity score images
low_quality_df = df[df['similarity_score'] < 0.5]
for _, row in low_quality_df.iterrows():
augment_image(os.path.join('images_dir', row['image_file']), 'augmented_images')
```
#### 3. Establishing a Feedback Loop
**Objective:**
Create an iterative process where model performance informs ongoing data curation and augmentation, leading to continuous improvement.
**Why It's Important:**
A feedback loop ensures that the dataset evolves based on model insights, addressing weaknesses and enhancing strengths over time.
**How to Implement:**
- **Model Evaluation:**
Regularly assess model performance on validation sets to identify areas needing improvement.
- **Data Review:**
Analyze instances where the model performs poorly and determine if data quality or diversity issues are contributing factors.
- **Iterative Refinement:**
Update the dataset by filtering out problematic pairs, adding new data, or applying targeted augmentations based on evaluation insights.
**Example Implementation:**
```python
def feedback_loop(model, validation_loader, threshold=0.7):
low_performance_pairs = []
for images, captions, labels in validation_loader:
predictions = model(images, captions)
for idx, pred in enumerate(predictions):
if pred < threshold:
low_performance_pairs.append((images[idx], captions[idx]))
return low_performance_pairs
# Example usage
low_perf = feedback_loop(current_model, val_loader)
# Analyze and address low-performance pairs
```
#### 4. Enabling Targeted Training
**Objective:**
Focus the training process on specific subsets of data that exhibit desired characteristics, such as high difficulty or specific categories.
**Why It's Important:**
Targeted training can enhance the model's ability to handle challenging cases or perform well on particular categories, leading to more specialized and effective models.
**How to Implement:**
- **Identify Target Subsets:**
Use similarity scores or other metrics to select subsets for focused training, such as low-similarity pairs that require better alignment.
- **Custom Loss Functions:**
Apply different loss weights or strategies to emphasize learning on targeted subsets.
- **Fine-Tuning:**
Initially train the model on the general dataset, then fine-tune it specifically on the identified subsets to improve performance where needed.
**Example Implementation:**
```python
# Define a subset with low similarity scores
target_subset = df[df['similarity_score'] < 0.4]
# Create a DataLoader for targeted training
target_loader = DataLoader(target_subset, batch_size=32, shuffle=True)
# Fine-tune the model on the target subset
for epoch in range(num_epochs):
for images, captions in target_loader:
outputs = model(images, captions)
loss = loss_fn(outputs, targets)
loss.backward()
optimizer.step()
optimizer.zero_grad()
```
## Optimization Considerations for Large-Scale Processing
To improve scalability and efficiency, consider the following:
1. **Quantization**
Quantizing the model for faster inference using lower precision (e.g., INT8) arithmetic operations. However, this may lead to a small drop in model accuracy.
2. **Mixed Precision**
Using mixed precision (combining FP16 and FP32) helps reduce memory consumption and accelerate computation on GPUs equipped with Tensor Cores. This approach maintains acceptable levels of accuracy while optimizing speed.
3. **Distributed Computing**
Utilize frameworks like DistributedDataParallel (DDP) for better scalability and efficiency across multiple nodes. Alternatively, frameworks like Huggingface's Accelerate can simplify distributed computing setups.
---
By implementing these strategies, you can enhance the quality and performance of your image-caption similarity computation project. Balancing the dataset ensures fairness and diversity, guiding data augmentation enriches the dataset, establishing a feedback loop fosters continuous improvement, and enabling targeted training focuses the model's learning on critical areas. Together, these approaches contribute to a robust and effective model capable of delivering high-quality similarity metrics.0c941982db9f3b09428570ffcrea 5 prompts sobre dark fantasy horror ambientado en el viejo mundo de warhammer, viendo estas imagenes veo que son imagenes muy normales y para nada originales. Las imagenes han de ser espectaculares, unicas, originales, que el espectador se quede en shock al verlas, que piense que es una obra maestra única en el mundo. Pero no han de ser apocalipticas ni del fin del mundo, sino mas bien escenas cotidianas, Los prompts siempre han de ser en inglés. Aqui te dejo prompts de imagenes que impactan para que los analices y veas que es lo que hace que las imagenes sean tal como te he dicho:"A close-up of a girl with brown hair and piercings. Her lips are parted, and the sunlight creates contrast with the dark theme, adding depth to her realistic features.", "the image is a ultra_detailed digital rendering of a slender and fit hero man wearing tribal buckskin tattered clothing, standing dynamically on a mountain ridge, he's brandishing a spear, The character is overlooking a vast dynamic landscape of rugged mountainous terrain, across the image the landscape varies to show all four seasons, from snowy a barren to lush forested and sandy desert, the vibrant colours pop and the scene looks three dimensional, Above the mountains the words: "QUEST FOR BUZZ" are written in 3D textured lettering, Along the bottom third of the image is the subheading: "where tf is it?", "Earth Tones: Rich browns, deep greens, and burnt oranges for a grounded, natural aesthetic., A mysterious door floating in the middle of the desert., A surreal mountain range where peaks and valleys flow together in a vibrant, continuous rhythm., A strong spotlight creating dramatic, elongated shadows.,Light filtered through haze or smoke for a soft, atmospheric effect.,Minimalist icons or markers to provide additional information.,Smooth, polished wood with a rich grain for a sophisticated appearance.,A human figure with skin made of stone, cracks of glowing lava running through their form.,Cool Greys and Blues: Steel grey, slate blue, and ice blue for a sleek, professional appearance.,A rusty, old-fashioned key lying on a book of spells.,Warm Reds and Oranges: Crimson, tangerine, and rusty brown for a fiery, energetic look.,Earth Tones: Rich browns, deep greens, and burnt oranges for a grounded, natural aesthetic.", "This is a highly detailed, digital artwork in a hyper-realistic style, showcasing a close-up portrait of a young woman. The subject has fair, smooth skin with a slight sheen, adorned with glittering, iridescent particles that shimmer in shades of blue, pink, and purple, giving a magical, ethereal quality. Her eyes are a striking blue, with long, thick eyelashes and a subtle, piercing gaze.", "A view of a vast, dark landscape with noose interface of a race woman in a white dress is seen on the edge of a cliff. The woman stands in a large, curved arena, facing away from the viewer, while the vastness of the landscape shows no clear distinction between the brightly lit environment and the dark landscape. The turbulence of a storm or explosion dominates the scene, while the upper half of the turbulence is illuminated, contrasting the dark, rocky terrain. The rocky terrain has sharp, jagged edges that make it look like they were drawn in a digitally created masterpiece. The center cliff is a dark, rocky outcropping with a small hole in the center where the woman stands. The low-angle view captures the immense power of the gas giant turbulence.", "A surreal and hyper-realistic scene featuring a classical beauty dressed in a flowing white silk gown, accompanied by a majestic light blue dragon woman is positioned in the foreground and the dragon coiled behind her. The woman has long, pale hair adorned with a headpiece, white gown, Her skin is fair, and her expression is serious, with a glowing pink sword held in front of her. The dragon has white blue scales, glowing red eyes, and emits a mystical aura. The intricate details of her attire and the dragons scales are highlighted, set against a dreamy, ethereal background. The scene is illuminated with soft, magical lighting, enhancing the otherworldly and enchanting atmosphere. surreal, hyper-realistic, classical beauty, white silk gown, majestic white dragon intricate details, dreamy background, ethereal, soft magical lighting, enchanting, otherworldly, visually stunning, high quality, detailed, sharp clarity, breathtaking, elegant, graceful. looking at viewer, long hair, white hair", "detailed renaissance oil painting, masterpiece, FredFraiStyle, chiascuro, silhoutetted, directional lighting, masterpiece, perfect composition, perfect hands, cowboy shot, A darkly illuminated elderly paladin templar stands shrouded in a torn, full armor plates. His complete obscured face reveals glowing red eyes, exuding a supernatural presence. In his right hand, he grips a war hammer with a glowing shinning, pulsating with eerie light. The hammer is bronze, adorned with intricate swirling patterns that fade into the shadowy background. Behind him, a large, glowing red orb or sun dominates, casting dramatic, otherworldly light over the dark, shadowy scene", "The image portrays a surreal, almost dreamlike depiction of a man in a formal black suit, the German-speaking Czech Jewish writer Franz Kafka, standing in a bizarre, labyrinthine architectural environment. The slender man, with expressive eyes, with slicked-back dark hair and a serious, somber expression, seems lost or confused. He holds a book or folder, adding to the sense of seriousness or contemplation. A giant dark bug or cockroach-like creature seems to be his pet. The surrounding environment is a mind-bending collection of staircases, doors, and windows that twist and curve impossibly, reminiscent of an Escher-like world. Another version of the man can be seen in the background, walking on one of the winding staircases that loops in on itself. The entire setting feels disorienting, with a mix of organic curves and sharp geometric forms, symbolizing themes of existential confusion, entrapment, bureaucracy, mental struggle, or inner turmoil. The muted grayscale colors of the structure enhance the eerie, dreamlike atmosphere.", "landscape photo, tron, heaven, ascendance, god rays, light, gold, sky, advntr, underwater, alien, colorful, ((zdzislaw beksinski)),translucent,transparent", "The image is a digital artwork in a surreal, ethereal style, featuring a young woman with an otherworldly appearance. She is standing in a misty, twilight landscape with a serene mountainous background under a soft, pastel sky. The woman has long, flowing platinum blonde hair and is dressed in an elaborate, flowing white gown adorned with intricate, shimmering silver embroidery.", "Bioluminescent, A worm's-eye view of a seemingly endless line of weathered, moss-covered skeletal figures, chained together, ascending a verdant, mist-shrouded mountain. The figures, their bone structure visible beneath decaying garments, clutch the chain with outstretched, skeletal hands. The path is barely discernible beneath the encroaching vegetation. In the distance, silhouetted against the fog, stand more figures, their purpose and destination unknown. The atmosphere is oppressive, pregnant with a sense of foreboding and the weight of an ancient, unspoken history. The image should evoke a sense of pilgrimage, sacrifice, and the inexorable passage of time.", "Professional photograph of a group of people dressed in ancient Egyptian attire standing in a row. The central figure is a man wearing an elaborate Egyptian god pharaoh costume, which includes a large ornate headdress with a prominent plume and a necklace. He is flanked by several others similarly dressed in elaborately detailed historical Egyptian garments. The outfits include tunics, belts, and headdresses typical of ancient Egyptian royalty and nobility. The background is dark and somewhat abstract, with vertical lines and a gradient of colors ranging from dark blue to purple. The overall color palette of the image is muted, dominated by shades of brown, gray, and blue. The lighting seems muted, which creates a dramatic and somewhat mysterious atmosphere. The people in the photograph stand in a straight line, facing forward, their bodies slightly tilted toward each other. The pose of the central figure is commanding, suggesting leadership or authority. The central figure's attire and headdress are particularly elaborate, suggesting a high status or royal role. Background elements, such as vertical lines and abstract shapes, give the photograph a sense of depth and complexity, enhancing its historicity and drama. The overall composition and outfits suggest historical or fantasy scene, perhaps inspired by ancient Egyptian themes. photograph, A regal woman with crown braided hair gazes contentedly into the distance as her elegant wizard hat drapes over her head. She wears an intricate wizard hat made of delicate pearls and shimmers in her skin. Her face is set against a backdrop of misty blue skies and glittering moonlight, as she holds onto a sparkling jewelery filled with magic potion. Moon in the night, shallow depth of field, Guilty, Bloom light, Long exposure, Nikon d3300, F/1.8, Ambrotype", "Persian beautiful woman , colorful with flowers entwined in her hair, petrifies with a single gaze, awardwinning, masterpiece,", "A minimalistic black-toned fashion shoot illustration depicting a (Dutch grandmother:1.1) modelling a rococo nothing that utilises baize and cashmere material. The clothing tightly hugs the person and is decorated with intricate black and green fractal designs. The overall feel of the image is dynamic and impossible. Additionally, the backdrop shows comcept drawings of the clothing, rendered in fine flowing lines and appliqued bronzeleaf. The general effect is of timeless fine art elegance.", "Imagine a surreal, dreamlike portrait of a ladybug resting on the vibrant yellow petal of a sunflower. style of Hieronymus Bosch. The ladybug, with its deep red shell and bold black spots, is rendered with a soft, glowing texture that contrasts gently against the flower's vivid yellow petals. Its enlarged, reflective eyes catch the light, adding an innocent, curious expression. The sunflower, towering and oversized, dominates the background, its textured brown center providing a rich, earthy contrast to the smooth red of the ladybugâs shell. The petals extend outward, curving gently, their bright yellow tones blending softly into the background of muted greens and browns. The overall atmosphere is one of serene elegance, with soft, flowing lines and a harmonious color palette. The background, slightly blurred and abstracted, enhances the focus on the ladybug and sunflower, creating a sense of stillness and quiet beauty.", "M100-style, retro scan of 80s-anime-ai spectral neon Gradient ascii_art, a 80s Sci-fi Ethereal. Dark space, 80`s sci-fi book artwork, lofi, neon-pastel colors, grainy, vintage Create a vivid description of a distant planet in the universe where enormous structures are visible from space, towering over the landscape in an awe-inspiring display. hyper realistic and highly detailed, highly dynamic cinematic panorama view, fantasy painting of the towering nightmare, outlandish, unusual, otherworldly, foreign, eldritch, digital painting, highly detailed and hyper surrealistic, perfect composition, harmonic colors, highly dramatic lighting, perfect composition, full panoramic view", "Styled by Katsuhiro Otomo, dark orange true evil. (necronomecon, demon lurking in the dark), creepy, (minimalism 1), ray tracing details in the style of Stephen Gammell.", "a nightmarish 3D rendering featuring Glitched Freddy from Five Nights at Freddy's. Immerse Glitched Freddy in a surreal and glitch-ridden environment that reflects the eerie and haunting atmosphere of the game. Utilize advanced 3D techniques to intricately detail Glitched Freddy's distorted features, glitchy animations, and the unsettling aura that surrounds it. Freeze a moment that captures the glitchy and otherworldly essence of this nightmarish animatronic. Infuse the scene with an atmosphere that resonates with the creepy and mysterious themes of the Five Nights at Freddy's universe. Craft a composition where Glitched Freddy stands as a haunting and glitch-infested figure within a visually disturbing and emotionally charged environment. Phase One XT IQ4 150MP, low exposure, high contrast, ISO 50, with an 80mm prime lens", "Medieval setting. A majestic snowflake haired female knight stands solo in a sun-dappled forest glade, donning crystal armor and flaunting long, luscious locks with subtle bangs framing her face. She strikes a sexy pose, adorned with hair beads and an elaborate lolita hairband, as piercing blue eyes seem to sparkle against the rich darkness of her black locks. The intricate costume design features a pattern of white lace dress and delicate trims, punctuated by a snowflake-shaped hair ornament dangling from the top knot.", "Captured using tilt-shift photography, this image depicts a futuristic cityscape. The cityscape blends futuristic technology with curvaceous design while retaining elements of a modern city. Skyscrapers are made of glass and metal and incorporate hanging gardens and vertical farms. Circular aerial walkways run between the buildings, with people moving around on hoverboards and autonomous pods. Eco-friendly electric cars run on the roads, and cargo drones handle logistics in the sky. Solar panels power streetlights, which provide a soft glow at night. Digital art and interactive installations blend into nature in parks and squares. People wear smart glasses and wearable devices to access information in real time. High-tech stores line the shopping streets, and digital signage and holographic advertisements are eye-catching. Robots serve customers in cafes and restaurants, and food is made with 3D printers. Overall, the image depicts a beautiful and vibrant cityscape where futuristic technology and modern life blend together.", "Epic, good winning over evil, triumph, glory, vicory, a sense that the long torment is finally over, relief, joy, the world is a better place now", "cartoon art by George Cruikshank and Leonid Afremov, A detailed digital illustration of a fantastical scene: Three large robotic arms with glowing red eyes are extending into the space, their intricate conveyor system system transporting immense black gas and dust. A giant observation bubble hovers above, contrasting the cool, star-filled backdrop of space. The image captures a cosmic realm, with a distant view of planets and celestial bodies, inviting viewers to explore the world's infinite mysteries., caricaturism, satirical illustrations, social and political commentary, Palette knife oil paintings, vibrant colors, romantic cityscapes, impressionistic style, capturing light and atmosphere, nostalgic charm", "glitched wavy distortions, non-representational, colors and shapes, expression of feelings, imaginative, model,, towering mountains loom, their peaks highly detailed . extremely high-resolution details, photographic, Illustration by moebius and Pablo picasso (artist) abstract expressionism, detailed painting1930s black renaissance painting”on textured film photography style, medium grain, FRESHIDESH Moebius comic book", "A portrait of a Viking shieldmaiden standing tall in a torrential downpour, her face determined and unflinching as rain streams down her battle-scarred armor. Her braided hair clings to her face and shoulders, and her eyes burn with a fierce inner fire. She grips her shield tightly, the water running off its surface, and her axe is poised at her side, ready for whatever comes next. The landscape around her is dark and stormy, with flashes of lightning illuminating the jagged rocks and rough terrain she stands upon. Despite the harsh elements, she remains a picture of strength and resilience.", "low poly, logotype containing grey and black colors, circle, patronus, omnious figure in a hood, one eye from a shadows, cyclops", "A haunting, dark portrait of a woman, her face a fusion of organic and mechanical elements in the style of H.R. Giger. The close-up shot reveals a biomechanical mask that has grown into her skin, a grotesque yet beautiful merging of flesh and metal. The mask appears almost alive, made from a nightmarish blend of organic tissues and cold, industrial materials — wires and cables intertwining with veins and sinew, wrapping around her jaw and temples. Metallic plates, some rusted and others gleaming, seamlessly meld into her pale, ghostly skin, creating a disturbing yet fascinating symmetry. Her brown hair, tightly bound into a knot, contrasts with the twisted metal and organic tendrils extending from the mask, some of them snaking down the back of her neck. The dark, shadowy environment barely illuminates her face, with faint neon and cybernetic lights casting eerie reflections across her biomechanical features. Her eyes, filled with cold intensity, peer out from behind the mask, showing the last traces of her humanity within the cold, biomechanical form. The fusion of flesh and machine, organic and metallic, creates an unsettling, dystopian beauty, where the boundaries between life and technology blur. The intricate detailing of the mask, with its alien, otherworldly design, captures the dark essence of Giger's biomechanical art, as the woman’s face stands frozen in a moment of both suffering and strength.", "a cute human brain robot with , a brain can be seen through a holes, a pothole, a dent, a romantic light", "Surfing the Big Waves Cup and saucer with a steaming cup of coffee An alpaca grazing in a meadow A starry night sky", "A terrifying Nazgûl riding a dark, shadowy horse across a weathered, ancient stone bridge. The Nazgûl, shrouded in tattered black robes that flow like smoke, has no visible face, just an eerie void beneath a pointed hood. The horse is massive and menacing, with glowing red eyes, dark mane flowing wildly, and shadowy tendrils emanating from its hooves, blending with the thick mist around the bridge. The scene is set in a cold, misty atmosphere, with fog swirling around the bridge's stone arches and creeping along the water below, obscuring the ground and creating an otherworldly, ominous setting. The bridge itself is old and worn, covered in moss and cracks, adding to the dark, gothic feel. The entire scene is bathed in a dim, moonlit glow, enhancing the foreboding and sinister presence of the Nazgûl as it rides steadily forward, a harbinger of doom in a world filled with mist and shadows.", "mythp0rt, masterpiece oil painting, landscape of a desolate volcanic plain, featuring a tall tower like a skyscraper, and a red gleaming eye reminiscent of the Eye of Sauron in the Lord of the Rings. The landscape is blackened and scorched; figures in robes walk towards the eerie tower", "ultra-detailed graphic novel illustration of a fantasy landscape, aerial view of a scenery, (black-steel:1.1) alien space-base-flying-sauser in clouds, neon lights on flying-sauser, mountains sea below, BREAK, MASTERPIECE, BEST QUALITY, HIGHLY-DETAILED, INTRICATE DETAILS, HIGH-RES, 4K, SHARP FOCUS, SMOOTH SHADING,", "A futuristic woman giant robot in a cyberpunk warzone with fire and smoke", "an atmospheric and eerie movie poster filled with dozens of small table lamps of various designs, they are perched ominously on roofs and along transmission lines as if they were birds, the movie title text: "The Silence of the Lamps" with credits on the bottom, the scene is set in the middle of a quaint suburban street at night, in the foreground, a large beautiful skull moth is flying towards the viewer with spread out wings, eerie atmosphere, surreal, elongated shadows, the tagline beneath the title reads, "Switch off if you dare!", the color palette is dark, with deep blues and blacks, punctuated by sharp, glowing yellows from the lamps, blending suspense with absurdity in a visually striking composition, volumetric fog,", "(by Ellen Jewett:1.5), (Bioluminescent Impressionism, autralia aboriginal landscape, The Dreamland, Gibson desert, red earth, rock, ants, sky sunset, sunrise ,waterfall, ethereal landscapes, depth of field, biotech emotion:1.3) ", "Flow in the Name of Love Channel_42 中 Artificially_Atypically_Autistically Icicle Ammolite Fossil Dun dun dun!!! 424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242 Shutting down Going dark Offline", "Extreme realism in a complex, highly detailed composition featuring a beautiful voluptuous woman with flowing rainbow-colored hair. The glowing background, with its vibrant colors, exaggerated details, intricate textures, and dynamic lighting, creates a whimsical, dreamy atmosphere in photorealistic quality. Small, glowing particles floating in the air, adding a magical and ethereal quality to the scene.Clouds with an iridescent sheen, reflecting multiple colors and adding a whimsical touch to the sky.Tree branches that emit a soft light, adding a mystical and natural element to the background. One hand is gently playing with her hair, her eyes looking softly into the distance.", "(Ink illustration on parchment, watercolor, ink splashes, Ukiyo-E). ((watercolor)), ((Chinese landscape)), Pagodas, Red-Trees, landscape focus, small figure off-side, Chinese Mountains, Rivers, Clouds, with watercolor washes, (((verdant green landscape))), (((mist, waterfalls))), trees, lush foliage, endless karth mountains that stretch outward to the horizon, a lone woman traversing the landscape!! wearing a bright blue cloak to make the miniscule figure stand out in a landscape dotted with the ruins of an ancient destroyed civilisation, crumbling stone, degraded buildings and temple, a long magical aurora borealis stretching across the sky, a broken moon high in the sky partially turned into an asteroid belt, a Saturn-like ringed-planet world, flickers of magical energy illuminating the land, in the far distance a giant wall of smoke is rising from the shattered earth in which a multitude of faces are forming and screaming, Ink splashes, rough ink sketch, Ghibli style, high contrast, stylized, dark skin, close up on face, head and shoulder, leather medieval top with lowered hood, Art Nouveau Style, detailxl, BadDream, dioramas,", "translucent ultraviolet spaceship, transparency, space, moon, shadows, realistic photo, hard science fiction, technology, deepest black space, stars, colorful nebula, indigo, crimson, yellow, ultra resolution", "(masterpiece, best quality, hyper detailed, ultra realistic, 32k, RAW photo) old hut in the swamp in green, dark yellow and gray, realistic, perfect composition and chiaroscuro by Rembrandt, concept art, by Jarek Kubicki, Style-Autumn", "Bioluminescent, A tenebrous and labyrinthine dreamscape rendered with meticulous cross-hatching, evoking the style of an Escherian woodcut. A man in profile, his form a silhouette save for the intricate architectural skeleton replacing his cranium. Stairs ascend and descend precariously within this cerebral edifice, culminating in a forlorn figure perched at a precarious apex. Light, emanating from a fractured aperture in the structure's depths, casts dramatic chiaroscuro, emphasizing the vertiginous perspective. The composition, a stark juxtaposition of the organic and the constructed, is rendered in a restricted palette of inky blacks and spectral whites, amplifying the sense of psychological disquietude.", "abstract photorealistic ink image in red, black and blue, oriental woman at a masquerade ball, long dark hair with blonde highlights, jewelled earrings, necklaces, sequinned robes, wearing an elaborate masquerade mask, directional lighting illuminating her from one side", "Image is a high-resolution, stylized photograph featuring a humanoid robot with a weathered, metallic body and intricate mechanical details. The robot is seated on rocky terrain, with a light gray, tattered cloak draped over its head and shoulders, partially obscuring its face. The robot's face is partially visible, showing a red, mask-like design with a solemn expression. The robot's hands are holding a small, red object, adding a striking contrast to the muted, earthy tones of the surroundings. The overall scene conveys a sense of desolation and contemplation, with the robot appearing almost human-like in its posture and demeanor.", "(nsfw:1.2), masterpiece, Best Quality, digital art, The avant-garde, A mysterious and sophisticated young woman with blue eyes wears futuristic and sexy fashion art. She is seated at a wide angle, with her full body in frame. Her outfit includes see-through white sports wear with multiple elastic white led harnesses designed underneath, and led hem, adding a minimalist beauty. She accessorizes with futuristic accessories and wears technical sneakers. Her demeanor exudes harmony and striking simplicity. hyper small head and face, happy, wide Duck mouth, half open mouth, perfectly aligned teeth, perfect beautiful teeth, light blue eyes, half open eyes, shiny Droopy eyes, gray hair, looking other, She is situated within the interior of a futuristic colony, BREAK (very slim waist:1.2), (huge breasts, cleavage, large buttocks), with mist drifting and steam rising from concrete", "A fiery demonic gate towers in the center of the scene, surrounded by an intense red and orange glow. The gate is adorned with dark runes and a horned skull at the top, from which flames burst out. Two succubus with huge wings stand on either side, holding tridents as they guard the entrance. A path lined with skulls leads up to the gate, giving an ominous sense of doom. The surrounding atmosphere is filled with vivid reds, oranges, and yellows, adding to the hellish landscape.", "n otherworldly humanoid with an ethereal, porcelain complexion descends from a sleek, metallic spacecraft that has landed with precision on a desolate landscape at dusk. As the ramp of the vessel lowers, she steps out into an extraterrestrial environment shrouded in an orange-hued mist, her slender form bathed in an intense, pulsing light emanating from within the ship's hull, casting long shadows across the barren terrain behind her. Her features are finely detailed and delicately sculpted, as if crafted by a skilled artisan, with sharp cheekbones, full lips, and eyes that hold a captivating, unearthly allure, fringed by thick, dark eyelashes and eyebrows. Her gaze seems to be drawing energy from the surrounding environment, as she surveys her surroundings with an unyielding determination burning within her very being. A flowing, crystalline gown adorns her physique, its intricate patterns shimmering in response to every movement of hers, infusing the desolate landscape with a hint of enchantment and subtle mystery as light dances across its reflective surface, bathing everything in its path with a kaleidoscope of colors that reverberate with each gentle sway.", "surreal scifi theme, an alien world, dominant gold color, black white yellow gold silver colors, alien spaceship, alien transportation, alien structures, weird textures, weird geometry, unusual aliens, weird animals, weird plants, no humans , vivid colors, warm textures, extremely detailed, dynamic, great lighting, perfect shading, atmospheric, best quality, sharp focus, high contrast, stylized, clear, surreal, ultra quality, 8k, best quality, masterpiece, midjourneyv6.1, detailmaximizer", "an old worn, rusted and muddy blue very low, extremely long bugatti race car with number 13, profile view, against an old dilapidated yellow wall (with no marking). High definition, realistic photo",
Certainly! Here are five meticulously crafted prompts for dark fantasy horror scenes set in the Old World of Warhammer. Each prompt is designed to evoke stunning, unique, and shocking imagery while portraying seemingly mundane everyday moments infused with horror and dark fantasy elements. --- 1. **Twilight Apothecary in a Gothic Market** *A highly detailed, hyper-realistic digital painting of an old-world Warhammer apothecary shop nestled within a gothic market at twilight. The scene captures a narrow cobblestone alley illuminated by flickering lanterns casting eerie shadows. The apothecary's window display showcases peculiar, otherworldly potions and preserved monstrous organs, glowing with unnatural colors. Behind the glass, a hunched, cloaked figure with glowing eyes meticulously arranges sinister-looking vials. The surrounding market stalls are shrouded in mist, with indistinct, shadowy figures lurking in the background. The atmosphere is thick with foreboding, enhanced by intricate architectural details like wrought-iron balconies, twisted vines creeping up the stone walls, and ominous gargoyles perched above. The color palette emphasizes deep purples, dark greens, and blood reds, creating a mesmerizing yet unsettling masterpiece that captures the essence of dark fantasy horror in the Warhammer Old World.* 2. **Haunted Tavern Gathering** *An ultra-detailed digital rendering of a dimly lit tavern in a Warhammer Old World village, exuding an atmosphere of dark fantasy horror. The interior is adorned with antique wooden beams, flickering candles, and tattered tapestries depicting grim battles and eldritch symbols. Patrons, a mix of rugged humans and mysterious, otherworldly beings, engage in hushed conversations, their faces partially obscured by shadows. In the center, a large, ancient fireplace crackles with blue flames that cast unnatural light, revealing ghostly apparitions dancing among the patrons. Barrels and crates hide lurking creatures that subtly distort the perception of everyday interactions. The scene captures a moment of eerie normalcy, where the mundane and the macabre intertwine seamlessly, rendered with impeccable detail and a haunting color scheme of black, crimson, and midnight blue.* 3. **Eerie Harvest Under the Blood Moon** *A surreal and haunting portrayal of a Warhammer Old World countryside during harvest season, dominated by a menacing blood moon casting a crimson hue over the landscape. The scene features a quaint farming village with thatched-roof cottages and fields of golden grain swaying under an unnatural wind. Farmers in traditional garb move through the fields, but their shadows elongate and twist into grotesque shapes, hinting at dark curses. In the distance, ancient stone structures stand silhouetted against the blood-red sky, emanating an ominous aura. The harvest moon illuminates twisted, barren trees and reveals glimpses of spectral figures among the stalks. The overall composition blends everyday rural life with unsettling supernatural elements, using a rich palette of deep reds, dark browns, and shadowy blacks to create a visually striking and shockingly original masterpiece.* 4. **Midnight Chapel with Unseen Presences** *A masterfully detailed night scene of a secluded chapel in the Warhammer Old World, set deep within an ancient, enchanted forest. The chapel's gothic architecture is intricately rendered, with towering spires, stained glass windows depicting cryptic symbols, and ivy-clad stone walls. Moonlight filters through the dense canopy, casting silvery beams that illuminate the chapel's interior, revealing dark, ritualistic altars and ominous statues. Figures cloaked in shadow stand motionless, their eyes glinting with malevolent energy. Ethereal spirits and shadowy entities subtly manifest around the chapel, creating a sense of invisible dread. The surrounding forest is alive with twisted trees and creeping fog, enhancing the eerie and otherworldly atmosphere. The color scheme emphasizes silvers, dark greens, and midnight blues, resulting in a breathtaking, unique image that captures the terrifying beauty of dark fantasy horror.* 5. **Shadowed Streets of a Cursed Village** *An extraordinarily detailed, panoramic view of a narrow, winding street in a Warhammer Old World village, engulfed in perpetual twilight. The cobblestone road is slick with an otherworldly mist that obscures visibility, while gas lamps emit a ghostly glow, casting long, distorted shadows. Quaint shops and homes with timber frames and thatched roofs appear normal at first glance, but upon closer inspection, reveal sinister details such as creeping shadows, animated statues, and windows with dark, empty gazes. Villagers move silently along the street, their expressions vacant and eyes hollow, hinting at a cursed existence. In the background, ancient trees with twisted branches loom over the scene, and distant, spectral figures drift through the fog. The sky is a swirling blend of dark purples and blacks, pierced by fleeting streaks of unnatural light. This composition harmoniously blends everyday village life with eerie, supernatural horror, resulting in a visually stunning and uniquely shocking masterpiece.* --- These prompts are designed to inspire the creation of visually captivating and original dark fantasy horror scenes within the rich tapestry of the Warhammer Old World. Each scene balances the mundane with the macabre, ensuring that the resulting images are both stunning and unsettling.
878faf1fe0c45ade8fe4fdceWrite the full complete code dont skip anything, write the full code with the new edits you did just write the full complete code, DOn't change aything but the things I say so I like the design but the text is not tyable I cant type on it fix it, also why is text bar with bold and color not moving fix that, make the stroke tool have a normal one, also the stroke tool is kind of gltichy its not autocmailyl doing a live preview of the sttroke so make it automatically live preview if a thing is selected also the top bar toggle grid, toggle code, and opactiy make it match our current ui, also the code editor thingy when press toggle code make it match our current design, also make it so indlivual toolbox dont overlap with graident tools toolbox etc, fix that, also for stroke I want it to go around the circle but stroke effect not a box effect, make it so layers I can double tap to make it so the layer can move to other and overlap with other layer basically like adobe illustorator with the circle layer with text etc.<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Enhanced Notion-like Canvas</title>
<!-- Include Material Icons for UI Icons -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<!-- Include Google Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&family=Source+Sans+Pro:wght@400;700&display=swap" rel="stylesheet">
<style>
/* General Styles */
body, html {
margin: 0;
padding: 0;
height: 100vh;
overflow: hidden;
font-family: 'Source Sans Pro', 'Roboto', sans-serif;
color: #37352f;
background-color: #f5f5f5;
}
.container {
display: flex;
height: 100%;
}
.toolbar {
width: 60px;
background-color: #fbfbfa;
border-right: 1px solid #e0e0e0;
display: flex;
flex-direction: column;
align-items: center;
padding-top: 20px;
position: relative;
z-index: 2;
}
.tool-button {
width: 40px;
height: 40px;
border: none;
background-color: transparent;
cursor: pointer;
margin-bottom: 10px;
border-radius: 5px;
display: flex;
justify-content: center;
align-items: center;
transition: background-color 0.3s ease;
color: #37352f;
position: relative;
font-size: 24px;
}
.tool-button:hover {
background-color: #f1f1ef;
}
.tool-button i {
pointer-events: none;
}
.tool-button .tooltip {
visibility: hidden;
background-color: #37352f;
color: #fff;
text-align: center;
border-radius: 5px;
padding: 5px;
position: absolute;
left: 45px;
z-index: 1;
font-size: 12px;
white-space: nowrap;
}
.tool-button:hover .tooltip {
visibility: visible;
}
.tool-dropdown {
position: relative;
}
.shape-menu {
display: none;
position: absolute;
top: 50px;
left: 0;
background-color: #fbfbfa;
border: 1px solid #e0e0e0;
border-radius: 5px;
z-index: 1000;
padding: 5px;
flex-direction: column;
}
.shape-menu .tool-button {
margin: 5px;
}
.canvas-area {
flex: 1;
background-color: #f7f6f3;
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
position: relative;
overflow: hidden;
}
.canvas-controls {
margin-bottom: 10px;
display: flex;
align-items: center;
position: relative;
z-index: 1;
gap: 10px;
}
.canvas-controls button,
.canvas-controls input[type="color"],
.canvas-controls input[type="range"] {
background-color: #e0e0e0;
color: #37352f;
border: none;
padding: 8px 12px;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.3s ease;
display: flex;
align-items: center;
}
.canvas-controls button:hover,
.canvas-controls input[type="color"]:hover {
background-color: #d0d0d0;
}
.canvas-controls input[type="range"] {
width: 100px;
padding: 0;
margin: 0;
cursor: pointer;
background-color: transparent;
}
.canvas-container {
position: relative;
flex: 1;
width: 100%;
max-width: 1200px;
background-color: #fff;
border: 1px solid #e0e0e0;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
cursor: default;
overflow: hidden;
}
.canvas {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
background-color: #ffffff;
}
.grid {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: linear-gradient(to right, #f0f0f0 1px, transparent 1px),
linear-gradient(to bottom, #f0f0f0 1px, transparent 1px);
background-size: 20px 20px;
pointer-events: none;
display: none;
}
.chat-area {
width: 350px;
background-color: #fbfbfa;
border-left: 1px solid #e0e0e0;
display: flex;
flex-direction: column;
z-index: 1;
}
.chat-header {
padding: 16px;
font-size: 16px;
font-weight: 600;
border-bottom: 1px solid #e0e0e0;
background-color: #f7f6f3;
}
.chat-messages {
flex: 1;
overflow-y: auto;
padding: 16px;
}
.message {
margin-bottom: 12px;
padding: 8px 12px;
background-color: #f1f1ef;
border-radius: 8px;
font-size: 14px;
}
.chat-input {
border-top: 1px solid #e0e0e0;
padding: 16px;
background-color: #f7f6f3;
}
.chat-input form {
display: flex;
align-items: center;
}
.chat-input input {
flex: 1;
padding: 10px 12px;
border: 1px solid #e0e0e0;
border-radius: 5px;
font-size: 14px;
background-color: #ffffff;
}
.chat-input button {
margin-left: 8px;
padding: 10px 16px;
background-color: #e0e0e0;
color: #37352f;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.3s ease;
}
.chat-input button:hover {
background-color: #d0d0d0;
}
.canvas-text, .canvas-media, .canvas-shape {
position: absolute;
cursor: move;
user-select: none;
min-width: 50px;
min-height: 20px;
}
.canvas-text {
background-color: rgba(255, 255, 255, 0.8);
border: 1px solid #ccc;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
overflow: hidden;
}
.canvas-text div {
padding: 5px;
outline: none;
width: 100%;
height: 100%;
font-family: 'Roboto', sans-serif;
font-size: 16px;
color: #333;
text-align: left;
}
.canvas-media img {
max-width: 100%;
max-height: 100%;
display: block;
}
.canvas-media {
padding: 0;
overflow: hidden;
}
.canvas-shape {
display: flex;
justify-content: center;
align-items: center;
background-color: #3498db;
overflow: hidden;
}
.text-controls, .media-controls, .shape-controls {
position: absolute;
background-color: #ffffff;
border: 1px solid #e0e0e0;
border-radius: 5px;
padding: 5px;
display: none;
z-index: 1000;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
flex-wrap: nowrap;
white-space: nowrap;
gap: 5px;
align-items: center;
}
.text-controls input[type="color"],
.media-controls input[type="color"],
.shape-controls input[type="color"] {
width: 30px;
height: 30px;
border: none;
border-radius: 50%;
overflow: hidden;
cursor: pointer;
padding: 0;
background-color: transparent;
}
.text-controls input[type="number"],
.media-controls input[type="number"],
.shape-controls input[type="number"] {
width: 50px;
padding: 5px;
border: 1px solid #e0e0e0;
border-radius: 3px;
}
.text-controls select,
.media-controls select,
.shape-controls select {
height: 30px;
padding: 5px;
border: 1px solid #e0e0e0;
border-radius: 3px;
}
.text-controls button,
.media-controls button,
.shape-controls button {
padding: 5px;
background-color: #f1f1ef;
border: 1px solid #e0e0e0;
border-radius: 3px;
cursor: pointer;
transition: background-color 0.3s ease;
display: flex;
align-items: center;
}
.text-controls button:hover,
.media-controls button:hover,
.shape-controls button:hover {
background-color: #e0e0e0;
}
.text-controls i.material-icons,
.media-controls i.material-icons,
.shape-controls i.material-icons {
font-size: 18px;
}
.canvas-outline {
position: absolute;
top: -20px;
left: -20px;
right: -20px;
bottom: -20px;
border: 2px solid #a020f0; /* Purple color */
pointer-events: none;
}
/* Shape Specific Styles */
.shape-rectangle {
background-color: #3498db;
border-radius: 0;
}
.shape-ellipse {
background-color: #e74c3c;
border-radius: 50%;
}
.shape-star {
width: 100px;
height: 100px;
background: transparent;
position: absolute;
clip-path: polygon(
50% 0%,
61% 35%,
98% 35%,
68% 57%,
79% 91%,
50% 70%,
21% 91%,
32% 57%,
2% 35%,
39% 35%
);
background-color: #f1c40f;
}
.shape-triangle {
width: 0;
height: 0;
border-left: 50px solid transparent;
border-right: 50px solid transparent;
border-bottom: 100px solid #2ecc71;
background: none;
}
/* Additional Shapes */
.shape-pentagon {
width: 100px;
height: 100px;
clip-path: polygon(50% 0%, 95% 35%, 75% 90%, 25% 90%, 5% 35%);
background-color: #8e44ad;
}
.shape-hexagon {
width: 100px;
height: 100px;
clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%);
background-color: #e67e22;
}
.shape-octagon {
width: 100px;
height: 100px;
clip-path: polygon(30% 0%, 70% 0%, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0% 70%, 0% 30%);
background-color: #2c3e50;
}
/* Gradient Tool */
.gradient-tool,
.gradient-stroke-tool {
position: absolute;
top: 60px;
left: 70px;
background-color: #ffffff;
border: 1px solid #e0e0e0;
border-radius: 5px;
padding: 10px;
display: none;
flex-direction: column;
gap: 10px;
z-index: 1000;
}
.gradient-option {
display: flex;
align-items: center;
gap: 5px;
}
.gradient-preview {
width: 30px;
height: 30px;
border: 1px solid #e0e0e0;
border-radius: 3px;
}
/* Resize Handles */
.resize-handle {
position: absolute;
width: 10px;
height: 10px;
background-color: #fff;
border: 1px solid #000;
z-index: 10;
cursor: pointer;
}
.resize-handle.top-left {
left: -5px;
top: -5px;
cursor: nwse-resize;
}
.resize-handle.top-right {
right: -5px;
top: -5px;
cursor: nesw-resize;
}
.resize-handle.bottom-left {
left: -5px;
bottom: -5px;
cursor: nesw-resize;
}
.resize-handle.bottom-right {
right: -5px;
bottom: -5px;
cursor: nwse-resize;
}
/* Layers Panel Styles */
.layer-panel {
position: absolute;
top: 0;
right: 0;
width: 250px;
height: 100%;
background-color: #fbfbfa;
color: #37352f;
border-left: 1px solid #e0e0e0;
display: flex;
flex-direction: column;
z-index: 1;
overflow: hidden;
}
.layer-panel-header {
padding: 10px;
font-size: 16px;
font-weight: bold;
border-bottom: 1px solid #e0e0e0;
background-color: #f7f6f3;
cursor: move;
}
.layer-list {
flex: 1;
overflow-y: auto;
padding: 10px;
}
.layer-list-item {
display: flex;
align-items: center;
padding: 5px;
margin-bottom: 5px;
border: 1px solid transparent;
cursor: pointer;
background-color: #fbfbfa;
}
.layer-list-item:hover {
background-color: #f1f1ef;
border-color: #e0e0e0;
}
.layer-thumbnail canvas {
width: 40px;
height: 40px;
background-color: #fff;
border: 1px solid #ccc;
}
.layer-name {
flex: 1;
margin-left: 10px;
font-size: 14px;
}
.layer-controls {
display: flex;
align-items: center;
gap: 5px;
}
.layer-controls .material-icons {
cursor: pointer;
color: #999;
font-size: 20px;
}
.layer-controls .material-icons:hover {
color: #37352f;
}
/* Code Area */
.code-area {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 200px;
background-color: #1e1e1e;
color: #dcdcdc;
display: none;
overflow: auto;
padding: 10px;
font-family: 'Courier New', Courier, monospace;
font-size: 14px;
z-index: 1000;
resize: vertical;
}
.code-area pre {
margin: 0;
}
/* Toggle Code Button */
.toggle-code {
position: absolute;
bottom: 10px;
right: 10px;
background-color: #e0e0e0;
border: none;
padding: 6px 10px;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
color: #37352f;
transition: background-color 0.3s ease;
}
.toggle-code:hover {
background-color: #d0d0d0;
}
/* Font Controls Styling */
.text-controls input[type="number"],
.text-controls select {
border: 1px solid #ccc;
background-color: #f9f9f9;
}
.text-controls button {
background-color: #f9f9f9;
border: 1px solid #ccc;
}
.text-controls button.active {
background-color: #e0e0e0;
}
.text-controls {
font-family: 'Source Sans Pro', sans-serif;
font-size: 13px;
}
</style>
</head>
<body>
<div class="container">
<div class="toolbar">
<button class="tool-button" onclick="addText()" title="Add Text">
<i class="material-icons">text_fields</i>
<span class="tooltip">Add Text</span>
</button>
<button class="tool-button" onclick="addMedia()" title="Add Media">
<i class="material-icons">image</i>
<span class="tooltip">Add Media</span>
</button>
<button class="tool-button" onclick="copyElements()" title="Copy">
<i class="material-icons">content_copy</i>
<span class="tooltip">Copy (Ctrl+C)</span>
</button>
<button class="tool-button" onclick="pasteElements()" title="Paste">
<i class="material-icons">content_paste</i>
<span class="tooltip">Paste (Ctrl+V)</span>
</button>
<div class="tool-dropdown">
<button class="tool-button shape-tool-button" onclick="toggleShapeMenu(event)" title="Add Shape">
<i class="material-icons">category</i>
<span class="tooltip">Add Shape</span>
</button>
<div class="shape-menu" id="shapeMenu">
<button class="tool-button" onclick="selectShape('rectangle')" title="Rectangle">
<i class="material-icons">crop_square</i>
</button>
<button class="tool-button" onclick="selectShape('ellipse')" title="Ellipse">
<i class="material-icons">circle</i>
</button>
<button class="tool-button" onclick="selectShape('star')" title="Star">
<i class="material-icons">star</i>
</button>
<button class="tool-button" onclick="selectShape('triangle')" title="Triangle">
<i class="material-icons">change_history</i>
</button>
<!-- New Shapes -->
<button class="tool-button" onclick="selectShape('pentagon')" title="Pentagon">
<i class="material-icons">pentagon</i>
</button>
<button class="tool-button" onclick="selectShape('hexagon')" title="Hexagon">
<i class="material-icons">hexagon</i>
</button>
<button class="tool-button" onclick="selectShape('octagon')" title="Octagon">
<i class="material-icons">stop</i>
</button>
</div>
</div>
<button class="tool-button" onclick="toggleGradientTool()" title="Gradient Fill">
<i class="material-icons">gradient</i>
<span class="tooltip">Gradient Fill</span>
</button>
<button class="tool-button" onclick="toggleGradientStrokeTool()" title="Gradient Stroke">
<i class="material-icons">border_color</i>
<span class="tooltip">Gradient Stroke</span>
</button>
<button class="tool-button" onclick="deleteElement()" title="Delete">
<i class="material-icons">delete</i>
<span class="tooltip">Delete (Delete)</span>
</button>
<button class="tool-button" onclick="undo()" title="Undo">
<i class="material-icons">undo</i>
<span class="tooltip">Undo (Ctrl+Z)</span>
</button>
<button class="tool-button" onclick="redo()" title="Redo">
<i class="material-icons">redo</i>
<span class="tooltip">Redo (Ctrl+Y)</span>
</button>
</div>
<div class="canvas-area">
<div class="canvas-controls">
<button class="toggle-grid" onclick="toggleGrid()">
Toggle Grid
</button>
<input type="color" id="canvasColorPicker" oninput="changeCanvasColor(this.value)" title="Canvas Color">
<!-- Opacity Control -->
<input type="range" min="0" max="100" value="100" id="globalOpacity" oninput="changeGlobalOpacity(this.value)" title="Opacity">
<button class="toggle-code" onclick="toggleCode()">
Toggle Code
</button>
</div>
<div class="canvas-container" id="canvasContainer">
<div class="canvas" id="canvas">
<div class="grid" id="grid"></div>
</div>
<div class="canvas-outline"></div>
</div>
<div class="gradient-tool" id="gradientTool">
<div class="gradient-option">
<span>Gradient Type:</span>
<select id="gradientType" onchange="updateGradientPreview(); updateSelectedShapeGradient();">
<option value="linear">Linear</option>
<option value="radial">Radial</option>
<option value="conic">Conic</option>
</select>
</div>
<div class="gradient-option">
<span>Angle:</span>
<input type="number" id="gradientAngle" value="90" onchange="updateGradientPreview(); updateSelectedShapeGradient();">°
</div>
<div class="gradient-option">
<span>Colors:</span>
<div id="gradientColors"></div>
<button onclick="addGradientColor()">Add Color</button>
</div>
<div class="gradient-option">
<span>Preview:</span>
<div class="gradient-preview" id="gradientPreview"></div>
</div>
</div>
<!-- Gradient Stroke Tool -->
<div class="gradient-stroke-tool" id="gradientStrokeTool">
<div class="gradient-option">
<span>Stroke Width:</span>
<input type="number" id="strokeWidth" value="2" onchange="updateGradientStrokePreview(); updateSelectedStroke();">px
</div>
<div class="gradient-option">
<span>Gradient Type:</span>
<select id="gradientStrokeType" onchange="updateGradientStrokePreview(); updateSelectedStroke();">
<option value="linear">Linear</option>
<option value="radial">Radial</option>
<option value="conic">Conic</option>
</select>
</div>
<div class="gradient-option">
<span>Angle:</span>
<input type="number" id="gradientStrokeAngle" value="90" onchange="updateGradientStrokePreview(); updateSelectedStroke();">°
</div>
<div class="gradient-option">
<span>Colors:</span>
<div id="gradientStrokeColors"></div>
<button onclick="addGradientStrokeColor()">Add Color</button>
</div>
<div class="gradient-option">
<span>Preview:</span>
<div class="gradient-preview" id="gradientStrokePreview"></div>
</div>
</div>
<div class="layer-panel" id="layerPanel">
<div class="layer-panel-header" id="layerPanelHeader">Layers</div>
<ul class="layer-list" id="layerList"></ul>
</div>
</div>
<div class="code-area" id="codeArea">
<pre id="code"></pre>
</div>
<div class="chat-area">
<div class="chat-header">
Chat
</div>
<div class="chat-messages">
<div class="message">Hello! How can I help you with your design?</div>
<div class="message">You can use the canvas on the left to create your poster.</div>
</div>
<div class="chat-input">
<form>
<input type="text" placeholder="Type a message...">
<button type="submit">Send</button>
</form>
</div>
</div>
<div id="text-controls-wrapper"></div>
<div id="media-controls-wrapper"></div>
<div id="shape-controls-wrapper"></div>
</div>
<script>
let currentShape = null;
let copiedElement = null;
let codeArea = document.getElementById('codeArea');
let codeElement = document.getElementById('code');
let gradientTool = document.getElementById('gradientTool');
let gradientStrokeTool = document.getElementById('gradientStrokeTool');
let undoStack = [];
let redoStack = [];
let gradientColors = ['#ff0000', '#0000ff'];
let gradientStrokeColors = ['#ff0000', '#0000ff'];
let isGradientToolActive = false;
let isGradientStrokeToolActive = false;
function toggleGrid() {
const grid = document.getElementById('grid');
grid.style.display = grid.style.display === 'none' ? 'block' : 'none';
}
function changeCanvasColor(color) {
const canvas = document.getElementById('canvas');
canvas.style.backgroundColor = color;
updateCode(); // Update code when background color changes
saveState();
}
function addText() {
const canvas = document.getElementById('canvas');
const text = document.createElement('div');
text.className = 'canvas-text';
text.id = 'text-' + Date.now();
const textContent = document.createElement('div');
textContent.contentEditable = true;
textContent.textContent = 'Double click to edit';
text.appendChild(textContent);
text.style.left = '50px';
text.style.top = '50px';
text.style.width = '200px';
text.style.height = '50px';
textContent.style.fontSize = '16px';
textContent.style.color = '#000000';
text.style.opacity = 1;
canvas.appendChild(text);
makeDraggable(text);
makeSelectable(text);
addTextControls(text);
text.addEventListener('dblclick', function() {
textContent.focus();
document.execCommand('selectAll', false, null);
});
textContent.addEventListener('input', function() {
updateLayerPanel();
updateCode();
saveState();
});
updateLayerPanel();
updateCode();
saveState();
}
function addMedia() {
const input = document.createElement('input');
input.type = 'file';
input.accept = 'image/*';
input.onchange = function(event) {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
const canvas = document.getElementById('canvas');
const media = document.createElement('div');
media.className = 'canvas-media';
media.id = 'media-' + Date.now();
const img = document.createElement('img');
img.src = e.target.result;
img.dataset.scale = 1;
img.style.transformOrigin = 'center center';
img.onwheel = (e) => {
e.preventDefault();
zoomMedia(img, e);
};
img.ondragstart = () => false;
media.appendChild(img);
img.onload = function() {
let scale = 1;
if (img.naturalWidth > 400 || img.naturalHeight > 300) {
// Scale down if image is too big
scale = Math.min(400 / img.naturalWidth, 300 / img.naturalHeight);
}
media.style.width = img.naturalWidth * scale + 'px';
media.style.height = img.naturalHeight * scale + 'px';
media.style.left = '50px';
media.style.top = '50px';
media.style.opacity = 1;
img.style.width = '100%';
img.style.height = '100%';
};
canvas.appendChild(media);
makeDraggable(media);
makeSelectable(media);
addMediaControls(media);
updateLayerPanel();
updateCode();
saveState();
};
reader.readAsDataURL(file);
}
};
input.click();
}
function toggleShapeMenu(event) {
event.stopPropagation(); // Prevent event from bubbling up
const menu = document.getElementById('shapeMenu');
menu.style.display = menu.style.display === 'block' ? 'none' : 'block';
}
document.addEventListener('click', function(e) {
const menu = document.getElementById('shapeMenu');
const button = document.querySelector('.tool-dropdown .tool-button');
if (menu.style.display === 'block' && !menu.contains(e.target) && !button.contains(e.target)) {
menu.style.display = 'none';
}
});
function selectShape(shapeType) {
currentShape = shapeType;
const menu = document.getElementById('shapeMenu');
menu.style.display = 'none';
}
document.getElementById('canvas').addEventListener('click', function(event) {
if (currentShape) {
const canvas = document.getElementById('canvas');
const rect = canvas.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
addShape(currentShape, x, y);
currentShape = null;
updateCode();
saveState();
} else {
clearSelection();
}
});
function addShape(shapeType, x, y) {
const canvas = document.getElementById('canvas');
const shape = document.createElement('div');
shape.className = `canvas-shape shape-${shapeType}`;
shape.id = `${shapeType}-${Date.now()}`;
shape.style.left = `${x}px`;
shape.style.top = `${y}px`;
shape.dataset.scale = 1;
shape.style.opacity = 1;
if (shapeType === 'triangle') {
shape.style.width = '0';
shape.style.height = '0';
} else if (shapeType === 'star' || shapeType === 'pentagon' || shapeType === 'hexagon' || shapeType === 'octagon') {
shape.style.width = '100px';
shape.style.height = '100px';
} else {
shape.style.width = '100px';
shape.style.height = '100px';
}
canvas.appendChild(shape);
makeDraggable(shape);
makeSelectable(shape);
addShapeControls(shape, shapeType);
shape.onwheel = (e) => {
e.preventDefault();
zoomShape(shape, e);
};
updateLayerPanel();
updateCode();
saveState();
}
function makeDraggable(element) {
let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
const canvas = document.getElementById('canvas');
let isResizing = false;
let currentHandle;
if (element.classList.contains('canvas-text') || element.classList.contains('canvas-media') || element.classList.contains('canvas-shape')) {
addResizeHandles(element);
}
element.onmousedown = dragMouseDown;
function dragMouseDown(e) {
if (element.dataset.locked === 'true') {
return;
}
if (e.target.classList.contains('resize-handle')) {
isResizing = true;
currentHandle = e.target;
e.stopPropagation();
document.onmouseup = closeDragElement;
document.onmousemove = elementResize;
} else {
e = e || window.event;
e.preventDefault();
e.stopPropagation();
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = closeDragElement;
document.onmousemove = elementDrag;
clearSelection();
element.classList.add('selected');
showElementControls(element);
}
}
function elementDrag(e) {
e = e || window.event;
e.preventDefault();
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
let newTop = element.offsetTop - pos2;
let newLeft = element.offsetLeft - pos1;
// Constraints so that the element does not leave the canvas
let maxTop = canvas.offsetHeight - element.offsetHeight;
let maxLeft = canvas.offsetWidth - element.offsetWidth;
if (newTop < 0) newTop = 0;
if (newLeft < 0) newLeft = 0;
if (newTop > maxTop) newTop = maxTop;
if (newLeft > maxLeft) newLeft = maxLeft;
element.style.top = newTop + "px";
element.style.left = newLeft + "px";
// Update controls position if visible
const controls = document.querySelector(`.shape-controls[data-shape-id="${element.id}"]`) ||
document.querySelector(`.text-controls[data-text-id="${element.id}"]`) ||
document.querySelector(`.media-controls[data-media-id="${element.id}"]`);
if (controls && controls.style.display === 'flex') {
controls.style.top = `${element.offsetTop - controls.offsetHeight - 10}px`;
controls.style.left = `${element.offsetLeft + (element.offsetWidth / 2) - (controls.offsetWidth / 2)}px`;
}
}
function elementResize(e) {
e.preventDefault();
const rect = element.getBoundingClientRect();
let width, height;
if (currentHandle.classList.contains('bottom-right')) {
width = e.clientX - rect.left;
height = e.clientY - rect.top;
} else if (currentHandle.classList.contains('bottom-left')) {
width = rect.right - e.clientX;
height = e.clientY - rect.top;
element.style.left = element.offsetLeft + (e.clientX - rect.left) + 'px';
} else if (currentHandle.classList.contains('top-right')) {
width = e.clientX - rect.left;
height = rect.bottom - e.clientY;
element.style.top = element.offsetTop + (e.clientY - rect.top) + 'px';
} else if (currentHandle.classList.contains('top-left')) {
width = rect.right - e.clientX;
height = rect.bottom - e.clientY;
element.style.left = element.offsetLeft + (e.clientX - rect.left) + 'px';
element.style.top = element.offsetTop + (e.clientY - rect.top) + 'px';
}
if (width > 20 && height > 20) {
element.style.width = width + 'px';
element.style.height = height + 'px';
// For images, adjust the img size
if (element.classList.contains('canvas-media')) {
const img = element.querySelector('img');
img.style.width = '100%';
img.style.height = '100%';
}
}
updateCode();
saveState();
}
function closeDragElement() {
document.onmouseup = null;
document.onmousemove = null;
isResizing = false;
currentHandle = null;
updateCode(); // Update code after dragging completes
saveState();
}
}
function addResizeHandles(element) {
const handles = ['top-left', 'top-right', 'bottom-left', 'bottom-right'];
handles.forEach(handle => {
const div = document.createElement('div');
div.className = 'resize-handle ' + handle;
element.appendChild(div);
});
}
function makeSelectable(element) {
element.addEventListener('click', function(e) {
if (element.dataset.locked === 'true') {
return;
}
e.stopPropagation(); // prevent event bubbling
clearSelection(); // Deselect others
element.classList.add('selected'); // apply selected class
showElementControls(element);
// Update the global opacity slider
const globalOpacity = document.getElementById('globalOpacity');
globalOpacity.value = element.style.opacity * 100;
});
}
function clearSelection() {
document.querySelectorAll('.canvas-text.selected, .canvas-media.selected, .canvas-shape.selected').forEach(el => {
el.classList.remove('selected');
});
hideAllControls();
}
function showElementControls(element) {
hideAllControls();
if (element.classList.contains('canvas-text')) {
const controls = document.querySelector(`.text-controls[data-text-id="${element.id}"]`);
controls.style.display = 'flex';
controls.style.top = `${element.offsetTop - controls.offsetHeight - 10}px`;
controls.style.left = `${element.offsetLeft + (element.offsetWidth / 2) - (controls.offsetWidth / 2)}px`;
} else if (element.classList.contains('canvas-media')) {
const controls = document.querySelector(`.media-controls[data-media-id="${element.id}"]`);
controls.style.display = 'flex';
controls.style.top = `${element.offsetTop - controls.offsetHeight - 10}px`;
controls.style.left = `${element.offsetLeft + (element.offsetWidth / 2) - (controls.offsetWidth / 2)}px`;
} else if (element.classList.contains('canvas-shape')) {
const controls = document.querySelector(`.shape-controls[data-shape-id="${element.id}"]`);
controls.style.display = 'flex';
controls.style.top = `${element.offsetTop - controls.offsetHeight - 10}px`;
controls.style.left = `${element.offsetLeft + (element.offsetWidth / 2) - (controls.offsetWidth / 2)}px`;
}
}
function hideAllControls() {
document.querySelectorAll('.text-controls, .media-controls, .shape-controls').forEach(control => {
control.style.display = 'none';
});
gradientTool.style.display = 'none';
isGradientToolActive = false;
gradientStrokeTool.style.display = 'none';
isGradientStrokeToolActive = false;
}
function addTextControls(textElement) {
const wrapper = document.getElementById('text-controls-wrapper');
const controls = document.createElement('div');
controls.className = 'text-controls';
controls.setAttribute('data-text-id', textElement.id);
controls.style.display = 'none';
controls.innerHTML = `
<button onclick="toggleBold(this, '${textElement.id}')" title="Bold"><i class="material-icons">format_bold</i></button>
<button onclick="toggleItalic(this, '${textElement.id}')" title="Italic"><i class="material-icons">format_italic</i></button>
<button onclick="toggleUnderline(this, '${textElement.id}')" title="Underline"><i class="material-icons">format_underline</i></button>
<input type="color" onchange="changeTextColor(this.value, '${textElement.id}')" title="Text Color">
<input type="number" min="1" max="100" value="16" onchange="changeFontSize(this.value, '${textElement.id}')" title="Font Size">
<select onchange="changeFontFamily(this.value, '${textElement.id}')" title="Font Family">
<option value="Roboto">Roboto</option>
<option value="'Source Sans Pro'">Source Sans Pro</option>
<option value="Arial">Arial</option>
<option value="Helvetica">Helvetica</option>
<option value="'Times New Roman'">Times New Roman</option>
<option value="'Courier New'">Courier New</option>
<option value="Verdana">Verdana</option>
<option value="Georgia">Georgia</option>
<option value="Palatino">Palatino</option>
<option value="Garamond">Garamond</option>
<option value="Bookman">Bookman</option>
<option value="'Comic Sans MS'">Comic Sans MS</option>
<option value="'Trebuchet MS'">Trebuchet MS</option>
<option value="'Arial Black'">Arial Black</option>
</select>
`;
wrapper.appendChild(controls);
}
function addMediaControls(mediaElement) {
const wrapper = document.getElementById('media-controls-wrapper');
const controls = document.createElement('div');
controls.className = 'media-controls';
controls.setAttribute('data-media-id', mediaElement.id);
controls.style.display = 'none';
controls.innerHTML = `
<button onclick="removeMedia('${mediaElement.id}')" title="Remove"><i class="material-icons">delete</i></button>
<button onclick="resetZoom('${mediaElement.id}')" title="Reset Zoom"><i class="material-icons">zoom_out_map</i></button>
`;
wrapper.appendChild(controls);
}
function addShapeControls(shapeElement, shapeType) {
const wrapper = document.getElementById('shape-controls-wrapper');
const controls = document.createElement('div');
controls.className = 'shape-controls';
controls.setAttribute('data-shape-id', shapeElement.id);
controls.style.display = 'none';
controls.innerHTML = `
<input type="color" oninput="changeShapeColor(this.value, '${shapeElement.id}', this)" title="Fill Color">
<button onclick="removeShape('${shapeElement.id}')" title="Remove"><i class="material-icons">delete</i></button>
`;
wrapper.appendChild(controls);
}
// Text Control Functions
function changeTextColor(color, textId) {
const textElement = document.getElementById(textId);
const textContent = textElement.querySelector('div');
textContent.style.color = color;
updateCode();
saveState();
}
function changeFontSize(size, textId) {
const textElement = document.getElementById(textId);
const textContent = textElement.querySelector('div');
textContent.style.fontSize = size + 'px';
updateCode();
saveState();
}
function changeFontFamily(family, textId) {
const textElement = document.getElementById(textId);
const textContent = textElement.querySelector('div');
textContent.style.fontFamily = family;
updateCode();
saveState();
}
function toggleBold(button, textId) {
const textElement = document.getElementById(textId);
const textContent = textElement.querySelector('div');
const isBold = textContent.style.fontWeight === 'bold';
textContent.style.fontWeight = isBold ? 'normal' : 'bold';
button.classList.toggle('active', !isBold);
updateCode();
saveState();
}
function toggleItalic(button, textId) {
const textElement = document.getElementById(textId);
const textContent = textElement.querySelector('div');
const isItalic = textContent.style.fontStyle === 'italic';
textContent.style.fontStyle = isItalic ? 'normal' : 'italic';
button.classList.toggle('active', !isItalic);
updateCode();
saveState();
}
function toggleUnderline(button, textId) {
const textElement = document.getElementById(textId);
const textContent = textElement.querySelector('div');
const isUnderline = textContent.style.textDecoration === 'underline';
textContent.style.textDecoration = isUnderline ? 'none' : 'underline';
button.classList.toggle('active', !isUnderline);
updateCode();
saveState();
}
// Media Control Functions
function removeMedia(mediaId) {
const mediaElement = document.getElementById(mediaId);
mediaElement.remove();
const mediaControls = document.querySelector(`.media-controls[data-media-id="${mediaId}"]`);
if (mediaControls) {
mediaControls.style.display = 'none';
}
updateLayerPanel();
updateCode();
saveState();
}
function resetZoom(mediaId) {
const mediaElement = document.getElementById(mediaId);
const img = mediaElement.querySelector('img');
img.style.transform = 'scale(1)';
img.dataset.scale = 1;
// Adjust the container size
mediaElement.style.width = img.naturalWidth + 'px';
mediaElement.style.height = img.naturalHeight + 'px';
updateCode();
saveState();
}
function zoomMedia(img, e) {
e.preventDefault();
const delta = e.deltaY;
const zoomFactor = 0.05; // Smaller zoom factor for smoother zooming
// Get current scale
let scale = img.dataset.scale ? parseFloat(img.dataset.scale) : 1;
// Update scale
scale *= delta > 0 ? (1 - zoomFactor) : (1 + zoomFactor);
// Limit scale
scale = Math.min(Math.max(0.1, scale), 10);
img.dataset.scale = scale;
// Apply scale transform
img.style.transform = `scale(${scale})`;
// Adjust the container size based on the scaled image size
const mediaElement = img.parentElement;
mediaElement.style.width = img.naturalWidth * scale + 'px';
mediaElement.style.height = img.naturalHeight * scale + 'px';
updateCode();
saveState();
}
// Shape Control Functions
function changeShapeColor(color, shapeId, inputElement) {
const shapeElement = document.getElementById(shapeId);
if (shapeElement.classList.contains('shape-triangle')) {
shapeElement.style.borderBottomColor = color;
} else if (shapeElement.classList.contains('shape-star') || shapeElement.classList.contains('shape-pentagon') || shapeElement.classList.contains('shape-hexagon') || shapeElement.classList.contains('shape-octagon')) {
shapeElement.style.backgroundColor = color;
} else {
shapeElement.style.backgroundColor = color;
}
// Update the color picker swatch color
inputElement.value = color;
updateCode();
saveState();
}
function removeShape(shapeId) {
const shapeElement = document.getElementById(shapeId);
shapeElement.remove();
const shapeControls = document.querySelector(`.shape-controls[data-shape-id="${shapeId}"]`);
if (shapeControls) {
shapeControls.style.display = 'none';
}
updateLayerPanel();
updateCode();
saveState();
}
function zoomShape(shape, e) {
e.preventDefault();
const delta = e.deltaY;
const zoomFactor = 0.05; // Adjust the zoom factor as desired
// Get current scale
let scale = shape.dataset.scale ? parseFloat(shape.dataset.scale) : 1;
// Update scale
scale *= delta > 0 ? (1 - zoomFactor) : (1 + zoomFactor);
// Limit scale
scale = Math.min(Math.max(0.1, scale), 10);
shape.dataset.scale = scale;
// Apply scale transform
shape.style.transform = `scale(${scale})`;
// Adjust controls position
const controls = document.querySelector(`.shape-controls[data-shape-id="${shape.id}"]`);
if (controls && controls.style.display === 'flex') {
controls.style.top = `${shape.offsetTop - controls.offsetHeight - 10}px`;
controls.style.left = `${shape.offsetLeft + (shape.offsetWidth * scale) / 2 - (controls.offsetWidth / 2)}px`;
}
updateCode();
saveState();
}
function copyElements() {
const selectedElement = document.querySelector('.canvas-text.selected, .canvas-media.selected, .canvas-shape.selected');
if (selectedElement) {
copiedElement = selectedElement.outerHTML;
}
}
function pasteElements() {
const canvas = document.getElementById('canvas');
if (copiedElement) {
const tempDiv = document.createElement('div');
tempDiv.innerHTML = copiedElement;
const element = tempDiv.firstChild;
// Adjust element ID to make it unique
element.id = element.id + '-' + Date.now();
// Keep the element at the same position
element.style.top = parseInt(element.style.top) + 10 + 'px';
element.style.left = parseInt(element.style.left) + 10 + 'px';
canvas.appendChild(element);
makeDraggable(element);
makeSelectable(element);
if (element.classList.contains('canvas-text')) {
addTextControls(element);
addResizeHandles(element);
} else if (element.classList.contains('canvas-media')) {
addMediaControls(element);
} else if (element.classList.contains('canvas-shape')) {
let shapeType;
if (element.classList.contains('shape-rectangle')) {
shapeType = 'rectangle';
} else if (element.classList.contains('shape-ellipse')) {
shapeType = 'ellipse';
} else if (element.classList.contains('shape-star')) {
shapeType = 'star';
} else if (element.classList.contains('shape-triangle')) {
shapeType = 'triangle';
} else if (element.classList.contains('shape-pentagon')) {
shapeType = 'pentagon';
} else if (element.classList.contains('shape-hexagon')) {
shapeType = 'hexagon';
} else if (element.classList.contains('shape-octagon')) {
shapeType = 'octagon';
}
addShapeControls(element, shapeType);
element.onwheel = (e) => {
e.preventDefault();
zoomShape(element, e);
};
}
updateLayerPanel();
updateCode();
saveState();
}
}
function deleteElement() {
const selectedElement = document.querySelector('.canvas-text.selected, .canvas-media.selected, .canvas-shape.selected');
if (selectedElement) {
selectedElement.remove();
updateLayerPanel();
updateCode();
saveState();
}
}
function toggleCode() {
updateCode();
codeArea.style.display = codeArea.style.display === 'block' ? 'none' : 'block';
}
function makeElementDraggable(elmnt) {
var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
elmnt.onmousedown = dragMouseDown;
function dragMouseDown(e) {
e = e || window.event;
e.preventDefault();
// get the mouse cursor position at startup:
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = closeDragElement;
// call a function whenever the cursor moves:
document.onmousemove = elementDrag;
}
function elementDrag(e) {
e = e || window.event;
e.preventDefault();
// calculate the new cursor position:
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
// set the element's new position:
elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
}
function closeDragElement() {
// stop moving when mouse button is released:
document.onmouseup = null;
document.onmousemove = null;
}
}
makeElementDraggable(codeArea);
makeElementDraggable(document.getElementById('layerPanel'));
makeElementDraggable(document.getElementById('layerPanelHeader'));
function updateCode() {
const canvas = document.getElementById('canvas');
const canvasStyle = getComputedStyle(canvas);
const backgroundColor = canvasStyle.backgroundColor;
const elements = canvas.querySelectorAll('.canvas-text, .canvas-media, .canvas-shape');
const elementsArray = Array.prototype.map.call(elements, function(element) {
// Clone the element to avoid modifying the original
let clone = element.cloneNode(true);
// Adjust image src attribute
if (clone.classList.contains('canvas-media')) {
let img = clone.querySelector('img');
if (img) {
let src = img.src;
let fileExtension = '';
if (src.startsWith('data:image/')) {
let match = src.match(/^data:image\/(png|jpeg|jpg);/);
let ext = match ? match[1] : 'png';
if (ext === 'jpeg') ext = 'jpg'; // use jpg instead of jpeg
// Generate 10-digit random number
let randomNumber = Math.floor(1000000000 + Math.random() * 9000000000);
let fileName = randomNumber + '.' + ext;
img.src = fileName;
}
}
}
// Remove selected class and inline styles related to selection
clone.classList.remove('selected');
clone.style.outline = '';
// Remove resize handles
if (clone.classList.contains('canvas-text') || clone.classList.contains('canvas-media') || clone.classList.contains('canvas-shape')) {
const handles = clone.querySelectorAll('.resize-handle');
handles.forEach(handle => handle.remove());
}
// Include transform styles
const computedStyle = window.getComputedStyle(element);
if (computedStyle.transform !== 'none') {
clone.style.transform = computedStyle.transform;
}
// Return the outerHTML of the cloned element
return clone.outerHTML;
});
// Include styles in the code
const styles = document.querySelector('style').outerHTML;
// Build the complete HTML code
let html = `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Canvas Export</title>${styles}
</head>
<body>
<div id="canvas" style="background-color:${backgroundColor}; position: relative; width: ${canvas.offsetWidth}px; height: ${canvas.offsetHeight}px;">${elementsArray.join('\n')}
</div>
</body>
</html>`;
codeElement.textContent = html;
}
// Keyboard shortcuts
document.addEventListener('keydown', function(e) {
const activeElement = document.activeElement;
if (activeElement && (
activeElement.tagName === 'INPUT' ||
activeElement.tagName === 'TEXTAREA' ||
activeElement.isContentEditable
)) {
// Don't interfere with typing in input fields or editable divs
return;
}
if (e.ctrlKey && (e.key === 'c' || e.key === 'C')) {
e.preventDefault();
copyElements();
} else if (e.ctrlKey && (e.key === 'v' || e.key === 'V')) {
e.preventDefault();
pasteElements();
} else if (e.key === 'Delete' || e.key === 'Backspace') {
e.preventDefault();
deleteElement();
} else if (e.ctrlKey && (e.key === 'z' || e.key === 'Z')) {
e.preventDefault();
undo();
} else if (e.ctrlKey && (e.key === 'y' || e.key === 'Y')) {
e.preventDefault();
redo();
}
});
// Undo/Redo functionality
function saveState() {
const canvas = document.getElementById('canvas');
const state = canvas.innerHTML;
undoStack.push(state);
redoStack = []; // Clear redo stack
}
function undo() {
if (undoStack.length > 1) {
redoStack.push(undoStack.pop());
const lastState = undoStack[undoStack.length - 1];
const canvas = document.getElementById('canvas');
canvas.innerHTML = lastState;
reinitializeCanvasElements();
updateLayerPanel();
updateCode();
}
}
function redo() {
if (redoStack.length > 0) {
const state = redoStack.pop();
undoStack.push(state);
const canvas = document.getElementById('canvas');
canvas.innerHTML = state;
reinitializeCanvasElements();
updateLayerPanel();
updateCode();
}
}
function reinitializeCanvasElements() {
const elements = document.querySelectorAll('.canvas-text, .canvas-media, .canvas-shape');
elements.forEach(element => {
makeDraggable(element);
makeSelectable(element);
if (element.classList.contains('canvas-text')) {
addTextControls(element);
addResizeHandles(element);
} else if (element.classList.contains('canvas-media')) {
addMediaControls(element);
addResizeHandles(element);
} else if (element.classList.contains('canvas-shape')) {
let shapeType;
if (element.classList.contains('shape-rectangle')) {
shapeType = 'rectangle';
} else if (element.classList.contains('shape-ellipse')) {
shapeType = 'ellipse';
} else if (element.classList.contains('shape-star')) {
shapeType = 'star';
} else if (element.classList.contains('shape-triangle')) {
shapeType = 'triangle';
} else if (element.classList.contains('shape-pentagon')) {
shapeType = 'pentagon';
} else if (element.classList.contains('shape-hexagon')) {
shapeType = 'hexagon';
} else if (element.classList.contains('shape-octagon')) {
shapeType = 'octagon';
}
addShapeControls(element, shapeType);
addResizeHandles(element);
element.onwheel = (e) => {
e.preventDefault();
zoomShape(element, e);
};
}
});
}
// Layer Panel
function updateLayerPanel() {
const layerList = document.getElementById('layerList');
layerList.innerHTML = '';
const canvas = document.getElementById('canvas');
const elements = Array.from(canvas.children).filter(el => el.classList.contains('canvas-text') || el.classList.contains('canvas-media') || el.classList.contains('canvas-shape'));
elements.reverse().forEach((element, index) => {
const listItem = document.createElement('li');
listItem.className = 'layer-list-item';
listItem.setAttribute('data-element-id', element.id);
const layerControls = document.createElement('div');
layerControls.className = 'layer-controls';
const visibility = document.createElement('span');
visibility.className = 'layer-visible';
visibility.innerHTML = `<i class="material-icons">${element.style.display === 'none' ? 'visibility_off' : 'visibility'}</i>`;
visibility.addEventListener('click', function(e) {
e.stopPropagation();
toggleVisibility(element.id, visibility);
});
const lock = document.createElement('span');
lock.className = 'layer-lock';
lock.innerHTML = `<i class="material-icons">${element.dataset.locked === 'true' ? 'lock' : 'lock_open'}</i>`;
lock.addEventListener('click', function(e) {
e.stopPropagation();
toggleLock(element.id, lock);
});
layerControls.appendChild(visibility);
layerControls.appendChild(lock);
const thumbnailContainer = document.createElement('div');
thumbnailContainer.className = 'layer-thumbnail';
const thumbnail = document.createElement('canvas');
thumbnail.width = 40;
thumbnail.height = 40;
const ctx = thumbnail.getContext('2d');
if (element.classList.contains('canvas-text')) {
ctx.font = '14px Arial';
ctx.fillStyle = window.getComputedStyle(element).color;
ctx.fillText(element.textContent.trim(), 0, 20);
} else if (element.classList.contains('canvas-media')) {
const img = element.querySelector('img');
ctx.drawImage(img, 0, 0, 40, 40);
} else if (element.classList.contains('canvas-shape')) {
// Handle gradient backgrounds
const bg = window.getComputedStyle(element).backgroundImage;
if (bg && bg !== 'none') {
ctx.fillStyle = bg;
ctx.fillRect(0, 0, 40, 40);
} else {
ctx.fillStyle = window.getComputedStyle(element).backgroundColor;
ctx.fillRect(0, 0, 40, 40);
}
}
thumbnailContainer.appendChild(thumbnail);
const name = document.createElement('div');
name.className = 'layer-name';
name.textContent = element.id;
listItem.appendChild(layerControls);
listItem.appendChild(thumbnailContainer);
listItem.appendChild(name);
layerList.appendChild(listItem);
});
makeLayerListDraggable();
}
function makeLayerListDraggable() {
const layerList = document.getElementById('layerList');
const listItems = layerList.querySelectorAll('.layer-list-item');
listItems.forEach(item => {
item.draggable = true;
item.addEventListener('dragstart', dragStart);
item.addEventListener('dragover', dragOver);
item.addEventListener('drop', drop);
});
}
let dragSrcEl = null;
function dragStart(e) {
dragSrcEl = this;
e.dataTransfer.effectAllowed = 'move';
}
function dragOver(e) {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
return false;
}
function drop(e) {
e.stopPropagation();
if (dragSrcEl !== this) {
const layerList = document.getElementById('layerList');
const nodes = Array.from(layerList.children);
const srcIndex = nodes.indexOf(dragSrcEl);
const destIndex = nodes.indexOf(this);
if (srcIndex > destIndex) {
layerList.insertBefore(dragSrcEl, this);
} else {
layerList.insertBefore(dragSrcEl, this.nextSibling);
}
updateCanvasLayerOrder();
}
return false;
}
function updateCanvasLayerOrder() {
const layerList = document.getElementById('layerList');
const layerItems = Array.from(layerList.children);
const canvas = document.getElementById('canvas');
const elements = Array.from(canvas.children).filter(el => el.classList.contains('canvas-text') || el.classList.contains('canvas-media') || el.classList.contains('canvas-shape'));
elements.forEach(el => {
canvas.removeChild(el);
});
layerItems.reverse().forEach(item => {
const elementId = item.getAttribute('data-element-id');
const element = document.getElementById(elementId);
canvas.appendChild(element);
});
updateCode();
saveState();
}
function toggleVisibility(elementId, visibilityIcon) {
const element = document.getElementById(elementId);
if (element.style.display === 'none') {
element.style.display = '';
visibilityIcon.innerHTML = '<i class="material-icons">visibility</i>';
} else {
element.style.display = 'none';
visibilityIcon.innerHTML = '<i class="material-icons">visibility_off</i>';
}
updateCode();
saveState();
}
function toggleLock(elementId, lockIcon) {
const element = document.getElementById(elementId);
if (element.dataset.locked === 'true') {
element.dataset.locked = 'false';
lockIcon.innerHTML = '<i class="material-icons">lock_open</i>';
element.style.pointerEvents = 'auto';
} else {
element.dataset.locked = 'true';
lockIcon.innerHTML = '<i class="material-icons">lock</i>';
element.style.pointerEvents = 'none';
}
updateCode();
saveState();
}
// Gradient Fill Tool Functions
function toggleGradientTool() {
if (gradientTool.style.display === 'none') {
gradientTool.style.display = 'flex';
updateGradientColors();
updateGradientPreview();
isGradientToolActive = true;
updateSelectedShapeGradient();
} else {
gradientTool.style.display = 'none';
isGradientToolActive = false;
}
}
function addGradientColor() {
gradientColors.push('#ffffff');
updateGradientColors();
updateGradientPreview();
updateSelectedShapeGradient();
}
function updateGradientColors() {
const gradientColorsDiv = document.getElementById('gradientColors');
gradientColorsDiv.innerHTML = '';
gradientColors.forEach((color, index) => {
const colorInput = document.createElement('input');
colorInput.type = 'color';
colorInput.value = color;
colorInput.oninput = function() {
gradientColors[index] = colorInput.value;
updateGradientPreview();
updateSelectedShapeGradient();
};
gradientColorsDiv.appendChild(colorInput);
});
}
function updateGradientPreview() {
const gradientType = document.getElementById('gradientType').value;
const angle = document.getElementById('gradientAngle').value;
const gradientPreview = document.getElementById('gradientPreview');
let gradientString = '';
if (gradientType === 'linear') {
gradientString = `linear-gradient(${angle}deg, ${gradientColors.join(', ')})`;
} else if (gradientType === 'radial') {
gradientString = `radial-gradient(${gradientColors.join(', ')})`;
} else if (gradientType === 'conic') {
gradientString = `conic-gradient(${gradientColors.join(', ')})`;
}
gradientPreview.style.background = gradientString;
}
function updateSelectedShapeGradient() {
const selectedElement = document.querySelector('.canvas-shape.selected, .canvas-text.selected');
if (selectedElement) {
const gradientType = document.getElementById('gradientType').value;
const angle = document.getElementById('gradientAngle').value;
let gradientString = '';
if (gradientType === 'linear') {
gradientString = `linear-gradient(${angle}deg, ${gradientColors.join(', ')})`;
} else if (gradientType === 'radial') {
gradientString = `radial-gradient(${gradientColors.join(', ')})`;
} else if (gradientType === 'conic') {
gradientString = `conic-gradient(${gradientColors.join(', ')})`;
}
// Apply gradient
if (selectedElement.classList.contains('canvas-shape')) {
selectedElement.style.background = gradientString;
} else if (selectedElement.classList.contains('canvas-text')) {
const textContent = selectedElement.querySelector('div');
textContent.style.background = gradientString;
textContent.style.webkitBackgroundClip = 'text';
textContent.style.webkitTextFillColor = 'transparent';
}
updateCode();
saveState();
}
}
// Gradient Stroke Tool Functions
function toggleGradientStrokeTool() {
if (gradientStrokeTool.style.display === 'none') {
gradientStrokeTool.style.display = 'flex';
updateGradientStrokeColors();
updateGradientStrokePreview();
isGradientStrokeToolActive = true;
updateSelectedStroke();
} else {
gradientStrokeTool.style.display = 'none';
isGradientStrokeToolActive = false;
}
}
function addGradientStrokeColor() {
gradientStrokeColors.push('#ffffff');
updateGradientStrokeColors();
updateGradientStrokePreview();
updateSelectedStroke();
}
function updateGradientStrokeColors() {
const gradientColorsDiv = document.getElementById('gradientStrokeColors');
gradientColorsDiv.innerHTML = '';
gradientStrokeColors.forEach((color, index) => {
const colorInput = document.createElement('input');
colorInput.type = 'color';
colorInput.value = color;
colorInput.oninput = function() {
gradientStrokeColors[index] = colorInput.value;
updateGradientStrokePreview();
updateSelectedStroke();
};
gradientColorsDiv.appendChild(colorInput);
});
}
function updateGradientStrokePreview() {
const gradientType = document.getElementById('gradientStrokeType').value;
const angle = document.getElementById('gradientStrokeAngle').value;
const gradientPreview = document.getElementById('gradientStrokePreview');
let gradientString = '';
if (gradientType === 'linear') {
gradientString = `linear-gradient(${angle}deg, ${gradientStrokeColors.join(', ')})`;
} else if (gradientType === 'radial') {
gradientString = `radial-gradient(${gradientStrokeColors.join(', ')})`;
} else if (gradientType === 'conic') {
gradientString = `conic-gradient(${gradientStrokeColors.join(', ')})`;
}
gradientPreview.style.background = gradientString;
}
function updateSelectedStroke() {
const selectedElement = document.querySelector('.canvas-shape.selected, .canvas-text.selected');
if (selectedElement) {
const gradientType = document.getElementById('gradientStrokeType').value;
const angle = document.getElementById('gradientStrokeAngle').value;
const strokeWidth = document.getElementById('strokeWidth').value;
let gradientString = '';
if (gradientType === 'linear') {
gradientString = `linear-gradient(${angle}deg, ${gradientStrokeColors.join(', ')})`;
} else if (gradientType === 'radial') {
gradientString = `radial-gradient(${gradientStrokeColors.join(', ')})`;
} else if (gradientType === 'conic') {
gradientString = `conic-gradient(${gradientStrokeColors.join(', ')})`;
}
// Apply gradient stroke
selectedElement.style.border = `${strokeWidth}px solid`;
selectedElement.style.borderImage = `${gradientString} 1`;
updateCode();
saveState();
}
}
// Global Opacity Control
function changeGlobalOpacity(value) {
const selectedElement = document.querySelector('.canvas-text.selected, .canvas-media.selected, .canvas-shape.selected');
if (selectedElement) {
selectedElement.style.opacity = value / 100;
updateCode();
saveState();
}
}
// Monitor selected element changes to update gradient tools
document.addEventListener('click', function(e) {
if (isGradientToolActive) {
const selectedElement = document.querySelector('.canvas-shape.selected, .canvas-text.selected');
if (selectedElement) {
updateSelectedShapeGradient();
}
}
if (isGradientStrokeToolActive) {
const selectedElement = document.querySelector('.canvas-shape.selected, .canvas-text.selected');
if (selectedElement) {
updateSelectedStroke();
}
}
});
// Initial save state
saveState();
</script>
</body>
</html>Certainly! Below is the complete, updated code with all the requested fixes and enhancements implemented. The key changes include:
1. **Editable Text:** Ensured that text elements are fully editable and can be typed into without issues.
2. **Movable Text Controls:** The text control toolbar (bold, italic, color, etc.) now dynamically moves with the selected text element.
3. **Normal Stroke Tool:** Replaced the gradient stroke tool with a standard stroke tool that applies a solid border around shapes.
4. **Live Stroke Preview:** The stroke tool now provides a live preview as you adjust stroke properties.
5. **Top Bar Styling:** Updated the styling of the top bar buttons (Toggle Grid, Toggle Code, Opacity) to match the current UI design.
6. **Code Editor Styling:** The code editor now matches the overall design and is positioned correctly.
7. **Non-Overlapping Toolboxes:** Ensured that individual toolboxes do not overlap with gradient toolboxes or other controls.
8. **Stroke Around Circles:** Applied strokes correctly around circular shapes without affecting the overall bounding box.
9. **Layer Handling:** Implemented double-click functionality on layer items to bring layers forward or send them backward, allowing for overlapping similar to Adobe Illustrator.
Here's the complete code:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Enhanced Notion-like Canvas</title>
<!-- Include Material Icons for UI Icons -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<!-- Include Google Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&family=Source+Sans+Pro:wght@400;700&display=swap" rel="stylesheet">
<style>
/* General Styles */
body, html {
margin: 0;
padding: 0;
height: 100vh;
overflow: hidden;
font-family: 'Source Sans Pro', 'Roboto', sans-serif;
color: #37352f;
background-color: #f5f5f5;
}
.container {
display: flex;
height: 100%;
position: relative;
}
.toolbar {
width: 60px;
background-color: #fbfbfa;
border-right: 1px solid #e0e0e0;
display: flex;
flex-direction: column;
align-items: center;
padding-top: 20px;
position: relative;
z-index: 2;
}
.tool-button {
width: 40px;
height: 40px;
border: none;
background-color: transparent;
cursor: pointer;
margin-bottom: 10px;
border-radius: 5px;
display: flex;
justify-content: center;
align-items: center;
transition: background-color 0.3s ease;
color: #37352f;
position: relative;
font-size: 24px;
}
.tool-button:hover {
background-color: #f1f1ef;
}
.tool-button i {
pointer-events: none;
}
.tool-button .tooltip {
visibility: hidden;
background-color: #37352f;
color: #fff;
text-align: center;
border-radius: 5px;
padding: 5px;
position: absolute;
left: 45px;
z-index: 1;
font-size: 12px;
white-space: nowrap;
}
.tool-button:hover .tooltip {
visibility: visible;
}
.tool-dropdown {
position: relative;
}
.shape-menu {
display: none;
position: absolute;
top: 50px;
left: 0;
background-color: #fbfbfa;
border: 1px solid #e0e0e0;
border-radius: 5px;
z-index: 1000;
padding: 5px;
flex-direction: column;
}
.shape-menu .tool-button {
margin: 5px;
}
.canvas-area {
flex: 1;
background-color: #f7f6f3;
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
position: relative;
overflow: hidden;
}
.canvas-controls {
margin-bottom: 10px;
display: flex;
align-items: center;
position: relative;
z-index: 1;
gap: 10px;
}
.canvas-controls button,
.canvas-controls input[type="color"],
.canvas-controls input[type="range"] {
background-color: #e0e0e0;
color: #37352f;
border: none;
padding: 8px 12px;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.3s ease;
display: flex;
align-items: center;
}
.canvas-controls button:hover,
.canvas-controls input[type="color"]:hover {
background-color: #d0d0d0;
}
.canvas-controls input[type="range"] {
width: 100px;
padding: 0;
margin: 0;
cursor: pointer;
background-color: transparent;
}
.canvas-container {
position: relative;
flex: 1;
width: 100%;
max-width: 1200px;
background-color: #fff;
border: 1px solid #e0e0e0;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
cursor: default;
overflow: hidden;
}
.canvas {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
background-color: #ffffff;
}
.grid {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: linear-gradient(to right, #f0f0f0 1px, transparent 1px),
linear-gradient(to bottom, #f0f0f0 1px, transparent 1px);
background-size: 20px 20px;
pointer-events: none;
display: none;
}
.chat-area {
width: 350px;
background-color: #fbfbfa;
border-left: 1px solid #e0e0e0;
display: flex;
flex-direction: column;
z-index: 1;
}
.chat-header {
padding: 16px;
font-size: 16px;
font-weight: 600;
border-bottom: 1px solid #e0e0e0;
background-color: #f7f6f3;
}
.chat-messages {
flex: 1;
overflow-y: auto;
padding: 16px;
}
.message {
margin-bottom: 12px;
padding: 8px 12px;
background-color: #f1f1ef;
border-radius: 8px;
font-size: 14px;
}
.chat-input {
border-top: 1px solid #e0e0e0;
padding: 16px;
background-color: #f7f6f3;
}
.chat-input form {
display: flex;
align-items: center;
}
.chat-input input {
flex: 1;
padding: 10px 12px;
border: 1px solid #e0e0e0;
border-radius: 5px;
font-size: 14px;
background-color: #ffffff;
}
.chat-input button {
margin-left: 8px;
padding: 10px 16px;
background-color: #e0e0e0;
color: #37352f;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.3s ease;
}
.chat-input button:hover {
background-color: #d0d0d0;
}
.canvas-text, .canvas-media, .canvas-shape {
position: absolute;
cursor: move;
user-select: none;
min-width: 50px;
min-height: 20px;
z-index: 1;
}
.canvas-text {
background-color: rgba(255, 255, 255, 0.8);
border: 1px solid #ccc;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
overflow: hidden;
}
.canvas-text div {
padding: 5px;
outline: none;
width: 100%;
height: 100%;
font-family: 'Roboto', sans-serif;
font-size: 16px;
color: #333;
text-align: left;
cursor: text;
}
.canvas-media img {
max-width: 100%;
max-height: 100%;
display: block;
}
.canvas-media {
padding: 0;
overflow: hidden;
}
.canvas-shape {
display: flex;
justify-content: center;
align-items: center;
background-color: #3498db;
overflow: hidden;
border: 2px solid #000; /* Added solid border for normal stroke */
}
.text-controls, .media-controls, .shape-controls {
position: absolute;
background-color: #ffffff;
border: 1px solid #e0e0e0;
border-radius: 5px;
padding: 5px;
display: none;
z-index: 1000;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
flex-wrap: nowrap;
white-space: nowrap;
gap: 5px;
align-items: center;
}
.text-controls input[type="color"],
.media-controls input[type="color"],
.shape-controls input[type="color"] {
width: 30px;
height: 30px;
border: none;
border-radius: 50%;
overflow: hidden;
cursor: pointer;
padding: 0;
background-color: transparent;
}
.text-controls input[type="number"],
.media-controls input[type="number"],
.shape-controls input[type="number"] {
width: 50px;
padding: 5px;
border: 1px solid #e0e0e0;
border-radius: 3px;
}
.text-controls select,
.media-controls select,
.shape-controls select {
height: 30px;
padding: 5px;
border: 1px solid #e0e0e0;
border-radius: 3px;
}
.text-controls button,
.media-controls button,
.shape-controls button {
padding: 5px;
background-color: #f1f1ef;
border: 1px solid #e0e0e0;
border-radius: 3px;
cursor: pointer;
transition: background-color 0.3s ease;
display: flex;
align-items: center;
}
.text-controls button:hover,
.media-controls button:hover,
.shape-controls button:hover {
background-color: #e0e0e0;
}
.text-controls i.material-icons,
.media-controls i.material-icons,
.shape-controls i.material-icons {
font-size: 18px;
}
.canvas-outline {
position: absolute;
top: -20px;
left: -20px;
right: -20px;
bottom: -20px;
border: 2px solid #a020f0; /* Purple color */
pointer-events: none;
display: none; /* Hidden by default */
z-index: 999;
}
/* Shape Specific Styles */
.shape-rectangle {
background-color: #3498db;
border-radius: 0;
}
.shape-ellipse {
background-color: #e74c3c;
border-radius: 50%;
}
.shape-star {
width: 100px;
height: 100px;
background: transparent;
position: absolute;
clip-path: polygon(
50% 0%,
61% 35%,
98% 35%,
68% 57%,
79% 91%,
50% 70%,
21% 91%,
32% 57%,
2% 35%,
39% 35%
);
background-color: #f1c40f;
}
.shape-triangle {
width: 0;
height: 0;
border-left: 50px solid transparent;
border-right: 50px solid transparent;
border-bottom: 100px solid #2ecc71;
background: none;
}
/* Additional Shapes */
.shape-pentagon {
width: 100px;
height: 100px;
clip-path: polygon(50% 0%, 95% 35%, 75% 90%, 25% 90%, 5% 35%);
background-color: #8e44ad;
}
.shape-hexagon {
width: 100px;
height: 100px;
clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%);
background-color: #e67e22;
}
.shape-octagon {
width: 100px;
height: 100px;
clip-path: polygon(30% 0%, 70% 0%, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0% 70%, 0% 30%);
background-color: #2c3e50;
}
/* Gradient Tool */
.gradient-tool,
.gradient-stroke-tool {
position: absolute;
top: 60px;
left: 70px;
background-color: #ffffff;
border: 1px solid #e0e0e0;
border-radius: 5px;
padding: 10px;
display: none;
flex-direction: column;
gap: 10px;
z-index: 1000;
}
.gradient-option {
display: flex;
align-items: center;
gap: 5px;
}
.gradient-preview {
width: 30px;
height: 30px;
border: 1px solid #e0e0e0;
border-radius: 3px;
}
/* Resize Handles */
.resize-handle {
position: absolute;
width: 10px;
height: 10px;
background-color: #fff;
border: 1px solid #000;
z-index: 10;
cursor: pointer;
}
.resize-handle.top-left {
left: -5px;
top: -5px;
cursor: nwse-resize;
}
.resize-handle.top-right {
right: -5px;
top: -5px;
cursor: nesw-resize;
}
.resize-handle.bottom-left {
left: -5px;
bottom: -5px;
cursor: nesw-resize;
}
.resize-handle.bottom-right {
right: -5px;
bottom: -5px;
cursor: nwse-resize;
}
/* Layers Panel Styles */
.layer-panel {
position: absolute;
top: 0;
right: 0;
width: 250px;
height: 100%;
background-color: #fbfbfa;
color: #37352f;
border-left: 1px solid #e0e0e0;
display: flex;
flex-direction: column;
z-index: 1;
overflow: hidden;
}
.layer-panel-header {
padding: 10px;
font-size: 16px;
font-weight: bold;
border-bottom: 1px solid #e0e0e0;
background-color: #f7f6f3;
cursor: move;
}
.layer-list {
flex: 1;
overflow-y: auto;
padding: 10px;
}
.layer-list-item {
display: flex;
align-items: center;
padding: 5px;
margin-bottom: 5px;
border: 1px solid transparent;
cursor: pointer;
background-color: #fbfbfa;
border-radius: 3px;
}
.layer-list-item:hover {
background-color: #f1f1ef;
border-color: #e0e0e0;
}
.layer-thumbnail canvas {
width: 40px;
height: 40px;
background-color: #fff;
border: 1px solid #ccc;
}
.layer-name {
flex: 1;
margin-left: 10px;
font-size: 14px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.layer-controls {
display: flex;
align-items: center;
gap: 5px;
}
.layer-controls .material-icons {
cursor: pointer;
color: #999;
font-size: 20px;
}
.layer-controls .material-icons:hover {
color: #37352f;
}
/* Code Area */
.code-area {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 200px;
background-color: #1e1e1e;
color: #dcdcdc;
display: none;
overflow: auto;
padding: 10px;
font-family: 'Courier New', Courier, monospace;
font-size: 14px;
z-index: 1000;
resize: vertical;
border-top: 1px solid #444;
}
.code-area pre {
margin: 0;
}
/* Toggle Code Button */
.toggle-code {
position: absolute;
bottom: 10px;
right: 10px;
background-color: #e0e0e0;
border: none;
padding: 6px 10px;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
color: #37352f;
transition: background-color 0.3s ease;
z-index: 2;
}
.toggle-code:hover {
background-color: #d0d0d0;
}
/* Font Controls Styling */
.text-controls input[type="number"],
.text-controls select {
border: 1px solid #ccc;
background-color: #f9f9f9;
}
.text-controls button {
background-color: #f9f9f9;
border: 1px solid #ccc;
}
.text-controls button.active {
background-color: #e0e0e0;
}
.text-controls {
font-family: 'Source Sans Pro', sans-serif;
font-size: 13px;
}
/* Prevent toolboxes from overlapping gradient tools */
#text-controls-wrapper,
#media-controls-wrapper,
#shape-controls-wrapper {
position: absolute;
top: 0;
left: 0;
z-index: 1001;
}
/* Responsive Adjustment for Toolboxes */
@media (max-width: 1200px) {
.layer-panel {
width: 200px;
}
.chat-area {
width: 300px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="toolbar">
<button class="tool-button" onclick="addText()" title="Add Text">
<i class="material-icons">text_fields</i>
<span class="tooltip">Add Text</span>
</button>
<button class="tool-button" onclick="addMedia()" title="Add Media">
<i class="material-icons">image</i>
<span class="tooltip">Add Media</span>
</button>
<button class="tool-button" onclick="copyElements()" title="Copy">
<i class="material-icons">content_copy</i>
<span class="tooltip">Copy (Ctrl+C)</span>
</button>
<button class="tool-button" onclick="pasteElements()" title="Paste">
<i class="material-icons">content_paste</i>
<span class="tooltip">Paste (Ctrl+V)</span>
</button>
<div class="tool-dropdown">
<button class="tool-button shape-tool-button" onclick="toggleShapeMenu(event)" title="Add Shape">
<i class="material-icons">category</i>
<span class="tooltip">Add Shape</span>
</button>
<div class="shape-menu" id="shapeMenu">
<button class="tool-button" onclick="selectShape('rectangle')" title="Rectangle">
<i class="material-icons">crop_square</i>
</button>
<button class="tool-button" onclick="selectShape('ellipse')" title="Ellipse">
<i class="material-icons">circle</i>
</button>
<button class="tool-button" onclick="selectShape('star')" title="Star">
<i class="material-icons">star</i>
</button>
<button class="tool-button" onclick="selectShape('triangle')" title="Triangle">
<i class="material-icons">change_history</i>
</button>
<!-- New Shapes -->
<button class="tool-button" onclick="selectShape('pentagon')" title="Pentagon">
<i class="material-icons">pentagon</i>
</button>
<button class="tool-button" onclick="selectShape('hexagon')" title="Hexagon">
<i class="material-icons">hexagon</i>
</button>
<button class="tool-button" onclick="selectShape('octagon')" title="Octagon">
<i class="material-icons">stop</i>
</button>
</div>
</div>
<!-- Removed Gradient Stroke Tool for Normal Stroke -->
<button class="tool-button" onclick="toggleGradientTool()" title="Gradient Fill">
<i class="material-icons">gradient</i>
<span class="tooltip">Gradient Fill</span>
</button>
<!-- Added Normal Stroke Tool -->
<button class="tool-button" onclick="toggleStrokeTool()" title="Stroke">
<i class="material-icons">border_outer</i>
<span class="tooltip">Stroke</span>
</button>
<button class="tool-button" onclick="deleteElement()" title="Delete">
<i class="material-icons">delete</i>
<span class="tooltip">Delete (Delete)</span>
</button>
<button class="tool-button" onclick="undo()" title="Undo">
<i class="material-icons">undo</i>
<span class="tooltip">Undo (Ctrl+Z)</span>
</button>
<button class="tool-button" onclick="redo()" title="Redo">
<i class="material-icons">redo</i>
<span class="tooltip">Redo (Ctrl+Y)</span>
</button>
</div>
<div class="canvas-area">
<div class="canvas-controls">
<button class="toggle-grid" onclick="toggleGrid()" title="Toggle Grid">
<i class="material-icons">grid_on</i>
</button>
<input type="color" id="canvasColorPicker" oninput="changeCanvasColor(this.value)" title="Canvas Color">
<!-- Opacity Control -->
<input type="range" min="0" max="100" value="100" id="globalOpacity" oninput="changeGlobalOpacity(this.value)" title="Opacity">
<button class="toggle-code" onclick="toggleCode()" title="Toggle Code">
<i class="material-icons">code</i>
</button>
</div>
<div class="canvas-container" id="canvasContainer">
<div class="canvas" id="canvas">
<div class="grid" id="grid"></div>
</div>
<div class="canvas-outline" id="canvasOutline"></div>
</div>
<div class="gradient-tool" id="gradientTool">
<div class="gradient-option">
<span>Gradient Type:</span>
<select id="gradientType" onchange="updateGradientPreview(); updateSelectedShapeGradient();">
<option value="linear">Linear</option>
<option value="radial">Radial</option>
<option value="conic">Conic</option>
</select>
</div>
<div class="gradient-option">
<span>Angle:</span>
<input type="number" id="gradientAngle" value="90" onchange="updateGradientPreview(); updateSelectedShapeGradient();">°
</div>
<div class="gradient-option">
<span>Colors:</span>
<div id="gradientColors"></div>
<button onclick="addGradientColor()">Add Color</button>
</div>
<div class="gradient-option">
<span>Preview:</span>
<div class="gradient-preview" id="gradientPreview"></div>
</div>
</div>
<!-- Added Stroke Tool -->
<div class="gradient-stroke-tool" id="strokeTool" style="display: none;">
<div class="gradient-option">
<span>Stroke Color:</span>
<input type="color" id="strokeColor" onchange="updateSelectedStroke()" title="Stroke Color">
</div>
<div class="gradient-option">
<span>Stroke Width:</span>
<input type="number" id="strokeWidth" min="1" max="20" value="2" onchange="updateSelectedStroke()" title="Stroke Width">px
</div>
<div class="gradient-option">
<span>Continuous Stroke:</span>
<input type="checkbox" id="continuousStroke" onchange="updateSelectedStroke()" title="Continuous Stroke">
</div>
<div class="gradient-option">
<span>Preview:</span>
<div class="gradient-preview" id="strokePreview" style="background: #000;"></div>
</div>
</div>
<div class="layer-panel" id="layerPanel">
<div class="layer-panel-header" id="layerPanelHeader">Layers</div>
<ul class="layer-list" id="layerList"></ul>
</div>
</div>
<div class="code-area" id="codeArea">
<pre id="code"></pre>
</div>
<div class="chat-area">
<div class="chat-header">
Chat
</div>
<div class="chat-messages">
<div class="message">Hello! How can I help you with your design?</div>
<div class="message">You can use the canvas on the left to create your poster.</div>
</div>
<div class="chat-input">
<form onsubmit="sendMessage(event)">
<input type="text" placeholder="Type a message..." required>
<button type="submit">Send</button>
</form>
</div>
</div>
<div id="text-controls-wrapper"></div>
<div id="media-controls-wrapper"></div>
<div id="shape-controls-wrapper"></div>
</div>
<script>
let currentShape = null;
let copiedElement = null;
let codeArea = document.getElementById('codeArea');
let codeElement = document.getElementById('code');
let gradientTool = document.getElementById('gradientTool');
let strokeTool = document.getElementById('strokeTool'); // Renamed for clarity
let undoStack = [];
let redoStack = [];
let gradientColors = ['#ff0000', '#0000ff'];
let isGradientToolActive = false;
let isStrokeToolActive = false;
function toggleGrid() {
const grid = document.getElementById('grid');
grid.style.display = grid.style.display === 'none' ? 'block' : 'none';
saveState();
}
function changeCanvasColor(color) {
const canvas = document.getElementById('canvas');
canvas.style.backgroundColor = color;
updateCode(); // Update code when background color changes
saveState();
}
function addText() {
const canvas = document.getElementById('canvas');
const text = document.createElement('div');
text.className = 'canvas-text';
text.id = 'text-' + Date.now();
const textContent = document.createElement('div');
textContent.contentEditable = true;
textContent.textContent = 'Double click to edit';
textContent.setAttribute('tabindex', '0'); // Make it focusable
text.appendChild(textContent);
text.style.left = '50px';
text.style.top = '50px';
text.style.width = '200px';
text.style.height = '50px';
textContent.style.fontSize = '16px';
textContent.style.color = '#000000';
text.style.opacity = 1;
canvas.appendChild(text);
makeDraggable(text);
makeSelectable(text);
addTextControls(text);
text.addEventListener('dblclick', function() {
textContent.focus();
document.execCommand('selectAll', false, null);
});
textContent.addEventListener('input', function() {
updateLayerPanel();
updateCode();
saveState();
});
updateLayerPanel();
updateCode();
saveState();
}
function addMedia() {
const input = document.createElement('input');
input.type = 'file';
input.accept = 'image/*';
input.onchange = function(event) {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
const canvas = document.getElementById('canvas');
const media = document.createElement('div');
media.className = 'canvas-media';
media.id = 'media-' + Date.now();
const img = document.createElement('img');
img.src = e.target.result;
img.dataset.scale = 1;
img.style.transformOrigin = 'center center';
img.onwheel = (e) => {
e.preventDefault();
zoomMedia(img, e);
};
img.ondragstart = () => false;
media.appendChild(img);
img.onload = function() {
let scale = 1;
if (img.naturalWidth > 400 || img.naturalHeight > 300) {
// Scale down if image is too big
scale = Math.min(400 / img.naturalWidth, 300 / img.naturalHeight);
}
media.style.width = img.naturalWidth * scale + 'px';
media.style.height = img.naturalHeight * scale + 'px';
media.style.left = '50px';
media.style.top = '50px';
media.style.opacity = 1;
img.style.width = '100%';
img.style.height = '100%';
};
canvas.appendChild(media);
makeDraggable(media);
makeSelectable(media);
addMediaControls(media);
updateLayerPanel();
updateCode();
saveState();
};
reader.readAsDataURL(file);
}
};
input.click();
}
function toggleShapeMenu(event) {
event.stopPropagation(); // Prevent event from bubbling up
const menu = document.getElementById('shapeMenu');
menu.style.display = menu.style.display === 'block' ? 'none' : 'block';
}
document.addEventListener('click', function(e) {
const menu = document.getElementById('shapeMenu');
const button = document.querySelector('.tool-dropdown .tool-button');
if (menu.style.display === 'block' && !menu.contains(e.target) && !button.contains(e.target)) {
menu.style.display = 'none';
}
});
function selectShape(shapeType) {
currentShape = shapeType;
const menu = document.getElementById('shapeMenu');
menu.style.display = 'none';
}
document.getElementById('canvas').addEventListener('click', function(event) {
if (currentShape) {
const canvas = document.getElementById('canvas');
const rect = canvas.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
addShape(currentShape, x, y);
currentShape = null;
updateCode();
saveState();
} else {
clearSelection();
}
});
function addShape(shapeType, x, y) {
const canvas = document.getElementById('canvas');
const shape = document.createElement('div');
shape.className = `canvas-shape shape-${shapeType}`;
shape.id = `${shapeType}-${Date.now()}`;
shape.style.left = `${x}px`;
shape.style.top = `${y}px`;
shape.dataset.scale = 1;
shape.style.opacity = 1;
if (shapeType === 'triangle') {
shape.style.width = '0';
shape.style.height = '0';
} else if (shapeType === 'star' || shapeType === 'pentagon' || shapeType === 'hexagon' || shapeType === 'octagon') {
shape.style.width = '100px';
shape.style.height = '100px';
} else {
shape.style.width = '100px';
shape.style.height = '100px';
}
canvas.appendChild(shape);
makeDraggable(shape);
makeSelectable(shape);
addShapeControls(shape, shapeType);
updateLayerPanel();
updateCode();
saveState();
}
function makeDraggable(element) {
let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
const canvas = document.getElementById('canvas');
let isResizing = false;
let currentHandle = null;
if (element.classList.contains('canvas-text') || element.classList.contains('canvas-media') || element.classList.contains('canvas-shape')) {
addResizeHandles(element);
}
element.onmousedown = dragMouseDown;
function dragMouseDown(e) {
if (element.dataset.locked === 'true') {
return;
}
if (e.target.classList.contains('resize-handle')) {
isResizing = true;
currentHandle = e.target;
e.stopPropagation();
document.onmouseup = closeDragElement;
document.onmousemove = elementResize;
} else {
e = e || window.event;
e.preventDefault();
e.stopPropagation();
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = closeDragElement;
document.onmousemove = elementDrag;
clearSelection();
element.classList.add('selected');
showElementControls(element);
}
}
function elementDrag(e) {
e = e || window.event;
e.preventDefault();
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
let newTop = element.offsetTop - pos2;
let newLeft = element.offsetLeft - pos1;
// Constraints so that the element does not leave the canvas
let maxTop = canvas.offsetHeight - element.offsetHeight;
let maxLeft = canvas.offsetWidth - element.offsetWidth;
if (newTop < 0) newTop = 0;
if (newLeft < 0) newLeft = 0;
if (newTop > maxTop) newTop = maxTop;
if (newLeft > maxLeft) newLeft = maxLeft;
element.style.top = newTop + "px";
element.style.left = newLeft + "px";
// Update controls position if visible
if (element.classList.contains('selected')) {
showElementControls(element);
}
}
function elementResize(e) {
e.preventDefault();
const rect = element.getBoundingClientRect();
let width, height;
if (currentHandle.classList.contains('bottom-right')) {
width = e.clientX - rect.left;
height = e.clientY - rect.top;
} else if (currentHandle.classList.contains('bottom-left')) {
width = rect.right - e.clientX;
height = e.clientY - rect.top;
element.style.left = element.offsetLeft + (e.clientX - rect.left) + 'px';
} else if (currentHandle.classList.contains('top-right')) {
width = e.clientX - rect.left;
height = rect.bottom - e.clientY;
element.style.top = element.offsetTop + (e.clientY - rect.top) + 'px';
} else if (currentHandle.classList.contains('top-left')) {
width = rect.right - e.clientX;
height = rect.bottom - e.clientY;
element.style.left = element.offsetLeft + (e.clientX - rect.left) + 'px';
element.style.top = element.offsetTop + (e.clientY - rect.top) + 'px';
}
if (width > 20 && height > 20) {
element.style.width = width + 'px';
element.style.height = height + 'px';
// For images, adjust the img size
if (element.classList.contains('canvas-media')) {
const img = element.querySelector('img');
img.style.width = '100%';
img.style.height = '100%';
}
// For triangle, adjust borders based on size
if (element.classList.contains('shape-triangle')) {
element.style.borderLeft = (width / 2) + 'px solid transparent';
element.style.borderRight = (width / 2) + 'px solid transparent';
element.style.borderBottom = height + 'px solid ' + window.getComputedStyle(element).backgroundColor;
}
}
// Update controls position if visible
if (element.classList.contains('selected')) {
showElementControls(element);
}
updateCode();
saveState();
}
function closeDragElement() {
document.onmouseup = null;
document.onmousemove = null;
isResizing = false;
currentHandle = null;
updateCode(); // Update code after dragging completes
saveState();
}
}
function addResizeHandles(element) {
const handles = ['top-left', 'top-right', 'bottom-left', 'bottom-right'];
handles.forEach(handle => {
const div = document.createElement('div');
div.className = 'resize-handle ' + handle;
element.appendChild(div);
});
}
function makeSelectable(element) {
element.addEventListener('click', function(e) {
if (element.dataset.locked === 'true') {
return;
}
e.stopPropagation(); // prevent event bubbling
clearSelection(); // Deselect others
element.classList.add('selected'); // apply selected class
showElementControls(element);
// Update the global opacity slider
const globalOpacity = document.getElementById('globalOpacity');
globalOpacity.value = element.style.opacity * 100;
});
}
function clearSelection() {
document.querySelectorAll('.canvas-text.selected, .canvas-media.selected, .canvas-shape.selected').forEach(el => {
el.classList.remove('selected');
});
hideAllControls();
}
function showElementControls(element) {
hideAllControls();
if (element.classList.contains('canvas-text')) {
const controls = document.querySelector(`.text-controls[data-text-id="${element.id}"]`);
if (controls) {
controls.style.display = 'flex';
const canvas = document.getElementById('canvas');
const canvasRect = canvas.getBoundingClientRect();
controls.style.top = `${element.offsetTop - controls.offsetHeight - 10}px`;
controls.style.left = `${element.offsetLeft + (element.offsetWidth / 2) - (controls.offsetWidth / 2)}px`;
}
} else if (element.classList.contains('canvas-media')) {
const controls = document.querySelector(`.media-controls[data-media-id="${element.id}"]`);
if (controls) {
controls.style.display = 'flex';
const canvas = document.getElementById('canvas');
const canvasRect = canvas.getBoundingClientRect();
controls.style.top = `${element.offsetTop - controls.offsetHeight - 10}px`;
controls.style.left = `${element.offsetLeft + (element.offsetWidth / 2) - (controls.offsetWidth / 2)}px`;
}
} else if (element.classList.contains('canvas-shape')) {
const controls = document.querySelector(`.shape-controls[data-shape-id="${element.id}"]`);
if (controls) {
controls.style.display = 'flex';
const canvas = document.getElementById('canvas');
const canvasRect = canvas.getBoundingClientRect();
controls.style.top = `${element.offsetTop - controls.offsetHeight - 10}px`;
controls.style.left = `${element.offsetLeft + (element.offsetWidth / 2) - (controls.offsetWidth / 2)}px`;
}
}
// Show canvas outline
const canvasOutline = document.getElementById('canvasOutline');
canvasOutline.style.display = 'block';
canvasOutline.style.top = `${element.offsetTop - 10}px`;
canvasOutline.style.left = `${element.offsetLeft - 10}px`;
canvasOutline.style.width = `${element.offsetWidth + 20}px`;
canvasOutline.style.height = `${element.offsetHeight + 20}px`;
}
function hideAllControls() {
document.querySelectorAll('.text-controls, .media-controls, .shape-controls').forEach(control => {
control.style.display = 'none';
});
gradientTool.style.display = 'none';
isGradientToolActive = false;
strokeTool.style.display = 'none';
isStrokeToolActive = false;
// Hide canvas outline
const canvasOutline = document.getElementById('canvasOutline');
canvasOutline.style.display = 'none';
}
function addTextControls(textElement) {
const wrapper = document.getElementById('text-controls-wrapper');
const controls = document.createElement('div');
controls.className = 'text-controls';
controls.setAttribute('data-text-id', textElement.id);
controls.style.display = 'none';
controls.innerHTML = `
<button onclick="toggleBold(this, '${textElement.id}')" title="Bold"><i class="material-icons">format_bold</i></button>
<button onclick="toggleItalic(this, '${textElement.id}')" title="Italic"><i class="material-icons">format_italic</i></button>
<button onclick="toggleUnderline(this, '${textElement.id}')" title="Underline"><i class="material-icons">format_underline</i></button>
<input type="color" onchange="changeTextColor(this.value, '${textElement.id}')" title="Text Color">
<input type="number" min="1" max="100" value="16" onchange="changeFontSize(this.value, '${textElement.id}')" title="Font Size">
<select onchange="changeFontFamily(this.value, '${textElement.id}')" title="Font Family">
<option value="Roboto">Roboto</option>
<option value="'Source Sans Pro'">Source Sans Pro</option>
<option value="Arial">Arial</option>
<option value="Helvetica">Helvetica</option>
<option value="'Times New Roman'">Times New Roman</option>
<option value="'Courier New'">Courier New</option>
<option value="Verdana">Verdana</option>
<option value="Georgia">Georgia</option>
<option value="Palatino">Palatino</option>
<option value="Garamond">Garamond</option>
<option value="Bookman">Bookman</option>
<option value="'Comic Sans MS'">Comic Sans MS</option>
<option value="'Trebuchet MS'">Trebuchet MS</option>
<option value="'Arial Black'">Arial Black</option>
</select>
`;
wrapper.appendChild(controls);
}
function addMediaControls(mediaElement) {
const wrapper = document.getElementById('media-controls-wrapper');
const controls = document.createElement('div');
controls.className = 'media-controls';
controls.setAttribute('data-media-id', mediaElement.id);
controls.style.display = 'none';
controls.innerHTML = `
<button onclick="removeMedia('${mediaElement.id}')" title="Remove"><i class="material-icons">delete</i></button>
<button onclick="resetZoom('${mediaElement.id}')" title="Reset Zoom"><i class="material-icons">zoom_out_map</i></button>
`;
wrapper.appendChild(controls);
}
function addShapeControls(shapeElement, shapeType) {
const wrapper = document.getElementById('shape-controls-wrapper');
const controls = document.createElement('div');
controls.className = 'shape-controls';
controls.setAttribute('data-shape-id', shapeElement.id);
controls.style.display = 'none';
controls.innerHTML = `
<input type="color" oninput="changeShapeColor(this.value, '${shapeElement.id}')", value="${getComputedStyle(shapeElement).backgroundColor}" title="Fill Color">
<button onclick="removeShape('${shapeElement.id}')" title="Remove"><i class="material-icons">delete</i></button>
`;
wrapper.appendChild(controls);
}
// Text Control Functions
function changeTextColor(color, textId) {
const textElement = document.getElementById(textId);
const textContent = textElement.querySelector('div');
textContent.style.color = color;
updateCode();
saveState();
}
function changeFontSize(size, textId) {
const textElement = document.getElementById(textId);
const textContent = textElement.querySelector('div');
textContent.style.fontSize = size + 'px';
updateCode();
saveState();
}
function changeFontFamily(family, textId) {
const textElement = document.getElementById(textId);
const textContent = textElement.querySelector('div');
textContent.style.fontFamily = family;
updateCode();
saveState();
}
function toggleBold(button, textId) {
const textElement = document.getElementById(textId);
const textContent = textElement.querySelector('div');
const isBold = textContent.style.fontWeight === 'bold';
textContent.style.fontWeight = isBold ? 'normal' : 'bold';
button.classList.toggle('active', !isBold);
updateCode();
saveState();
}
function toggleItalic(button, textId) {
const textElement = document.getElementById(textId);
const textContent = textElement.querySelector('div');
const isItalic = textContent.style.fontStyle === 'italic';
textContent.style.fontStyle = isItalic ? 'normal' : 'italic';
button.classList.toggle('active', !isItalic);
updateCode();
saveState();
}
function toggleUnderline(button, textId) {
const textElement = document.getElementById(textId);
const textContent = textElement.querySelector('div');
const isUnderline = textContent.style.textDecoration === 'underline';
textContent.style.textDecoration = isUnderline ? 'none' : 'underline';
button.classList.toggle('active', !isUnderline);
updateCode();
saveState();
}
// Media Control Functions
function removeMedia(mediaId) {
const mediaElement = document.getElementById(mediaId);
mediaElement.remove();
const mediaControls = document.querySelector(`.media-controls[data-media-id="${mediaId}"]`);
if (mediaControls) {
mediaControls.style.display = 'none';
}
updateLayerPanel();
updateCode();
saveState();
}
function resetZoom(mediaId) {
const mediaElement = document.getElementById(mediaId);
const img = mediaElement.querySelector('img');
img.style.transform = 'scale(1)';
img.dataset.scale = 1;
// Adjust the container size
mediaElement.style.width = img.naturalWidth + 'px';
mediaElement.style.height = img.naturalHeight + 'px';
updateCode();
saveState();
}
function zoomMedia(img, e) {
e.preventDefault();
const delta = e.deltaY;
const zoomFactor = 0.05; // Smaller zoom factor for smoother zooming
// Get current scale
let scale = img.dataset.scale ? parseFloat(img.dataset.scale) : 1;
// Update scale
scale *= delta > 0 ? (1 - zoomFactor) : (1 + zoomFactor);
// Limit scale
scale = Math.min(Math.max(0.1, scale), 10);
img.dataset.scale = scale;
// Apply scale transform
img.style.transform = `scale(${scale})`;
// Adjust the container size based on the scaled image size
const mediaElement = img.parentElement;
mediaElement.style.width = img.naturalWidth * scale + 'px';
mediaElement.style.height = img.naturalHeight * scale + 'px';
updateCode();
saveState();
}
// Shape Control Functions
function changeShapeColor(color, shapeId) {
const shapeElement = document.getElementById(shapeId);
if (shapeElement.classList.contains('shape-triangle')) {
shapeElement.style.borderBottomColor = color;
} else if (shapeElement.classList.contains('shape-star') || shapeElement.classList.contains('shape-pentagon') || shapeElement.classList.contains('shape-hexagon') || shapeElement.classList.contains('shape-octagon') || shapeElement.classList.contains('shape-ellipse') || shapeElement.classList.contains('shape-rectangle')) {
shapeElement.style.backgroundColor = color;
}
updateCode();
saveState();
}
function removeShape(shapeId) {
const shapeElement = document.getElementById(shapeId);
shapeElement.remove();
const shapeControls = document.querySelector(`.shape-controls[data-shape-id="${shapeId}"]`);
if (shapeControls) {
shapeControls.style.display = 'none';
}
updateLayerPanel();
updateCode();
saveState();
}
// Gradient Fill Tool Functions
function toggleGradientTool() {
if (gradientTool.style.display === 'none') {
gradientTool.style.display = 'flex';
updateGradientColors();
updateGradientPreview();
isGradientToolActive = true;
updateSelectedShapeGradient();
} else {
gradientTool.style.display = 'none';
isGradientToolActive = false;
}
}
function addGradientColor() {
gradientColors.push('#ffffff');
updateGradientColors();
updateGradientPreview();
updateSelectedShapeGradient();
}
function updateGradientColors() {
const gradientColorsDiv = document.getElementById('gradientColors');
gradientColorsDiv.innerHTML = '';
gradientColors.forEach((color, index) => {
const colorInput = document.createElement('input');
colorInput.type = 'color';
colorInput.value = color;
colorInput.oninput = function() {
gradientColors[index] = colorInput.value;
updateGradientPreview();
updateSelectedShapeGradient();
};
gradientColorsDiv.appendChild(colorInput);
});
}
function updateGradientPreview() {
const gradientType = document.getElementById('gradientType').value;
const angle = document.getElementById('gradientAngle').value;
const gradientPreview = document.getElementById('gradientPreview');
let gradientString = '';
if (gradientType === 'linear') {
gradientString = `linear-gradient(${angle}deg, ${gradientColors.join(', ')})`;
} else if (gradientType === 'radial') {
gradientString = `radial-gradient(${gradientColors.join(', ')})`;
} else if (gradientType === 'conic') {
gradientString = `conic-gradient(${gradientColors.join(', ')})`;
}
gradientPreview.style.background = gradientString;
}
function updateSelectedShapeGradient() {
const selectedElement = document.querySelector('.canvas-shape.selected, .canvas-text.selected');
if (selectedElement && isGradientToolActive) {
const gradientType = document.getElementById('gradientType').value;
const angle = document.getElementById('gradientAngle').value;
let gradientString = '';
if (gradientType === 'linear') {
gradientString = `linear-gradient(${angle}deg, ${gradientColors.join(', ')})`;
} else if (gradientType === 'radial') {
gradientString = `radial-gradient(${gradientColors.join(', ')})`;
} else if (gradientType === 'conic') {
gradientString = `conic-gradient(${gradientColors.join(', ')})`;
}
// Apply gradient
if (selectedElement.classList.contains('canvas-shape')) {
selectedElement.style.background = gradientString;
} else if (selectedElement.classList.contains('canvas-text')) {
const textContent = selectedElement.querySelector('div');
textContent.style.background = gradientString;
textContent.style.webkitBackgroundClip = 'text';
textContent.style.webkitTextFillColor = 'transparent';
}
updateCode();
saveState();
}
}
// Stroke Tool Functions
function toggleStrokeTool() {
if (strokeTool.style.display === 'none') {
strokeTool.style.display = 'flex';
updateStrokePreview();
isStrokeToolActive = true;
updateSelectedStroke();
} else {
strokeTool.style.display = 'none';
isStrokeToolActive = false;
}
}
function updateStrokePreview() {
const strokeColor = document.getElementById('strokeColor').value;
const strokePreview = document.getElementById('strokePreview');
strokePreview.style.background = strokeColor;
}
function updateSelectedStroke() {
const selectedElement = document.querySelector('.canvas-shape.selected, .canvas-text.selected');
if (selectedElement && isStrokeToolActive) {
const strokeColor = document.getElementById('strokeColor').value;
const strokeWidth = document.getElementById('strokeWidth').value;
const isContinuous = document.getElementById('continuousStroke').checked;
// Apply stroke
if (selectedElement.classList.contains('canvas-shape') || selectedElement.classList.contains('canvas-text')) {
selectedElement.style.border = `${strokeWidth}px solid ${strokeColor}`;
if (!isContinuous) {
// For dashed stroke
selectedElement.style.borderStyle = 'dashed';
} else {
selectedElement.style.borderStyle = 'solid';
}
}
updateStrokePreview();
updateCode();
saveState();
}
}
// Global Opacity Control
function changeGlobalOpacity(value) {
const selectedElement = document.querySelector('.canvas-text.selected, .canvas-media.selected, .canvas-shape.selected');
if (selectedElement) {
selectedElement.style.opacity = value / 100;
updateCode();
saveState();
}
}
// Monitor selected element changes to update gradient tools
document.addEventListener('click', function(e) {
if (isGradientToolActive) {
const selectedElement = document.querySelector('.canvas-shape.selected, .canvas-text.selected');
if (selectedElement) {
updateSelectedShapeGradient();
}
}
if (isStrokeToolActive) {
const selectedElement = document.querySelector('.canvas-shape.selected, .canvas-text.selected');
if (selectedElement) {
updateSelectedStroke();
}
}
});
// Initial save state
saveState();
// Handle click outside to clear selection
document.getElementById('canvas').addEventListener('click', function(e) {
if (e.target === this) {
clearSelection();
}
});
// Code Editor Toggles and Updates
function toggleCode() {
updateCode();
codeArea.style.display = codeArea.style.display === 'block' ? 'none' : 'block';
saveState();
}
function makeElementDraggable(elmnt) {
var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
elmnt.onmousedown = dragMouseDown;
function dragMouseDown(e) {
e = e || window.event;
e.preventDefault();
// get the mouse cursor position at startup:
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = closeDragElement;
// call a function whenever the cursor moves:
document.onmousemove = elementDrag;
}
function elementDrag(e) {
e = e || window.event;
e.preventDefault();
// calculate the new cursor position:
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
// set the element's new position:
elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
}
function closeDragElement() {
// stop moving when mouse button is released:
document.onmouseup = null;
document.onmousemove = null;
updateCode();
saveState();
}
}
makeElementDraggable(codeArea);
makeElementDraggable(document.getElementById('layerPanel'));
makeElementDraggable(document.getElementById('layerPanelHeader'));
function updateCode() {
const canvas = document.getElementById('canvas');
const canvasStyle = getComputedStyle(canvas);
const backgroundColor = canvasStyle.backgroundColor;
const elements = canvas.querySelectorAll('.canvas-text, .canvas-media, .canvas-shape');
const elementsArray = Array.prototype.map.call(elements, function(element) {
// Clone the element to avoid modifying the original
let clone = element.cloneNode(true);
// Adjust image src attribute for media elements
if (clone.classList.contains('canvas-media')) {
let img = clone.querySelector('img');
if (img) {
let src = img.src;
let fileExtension = '';
if (src.startsWith('data:image/')) {
let match = src.match(/^data:image\/(png|jpeg|jpg);/);
let ext = match ? match[1] : 'png';
if (ext === 'jpeg') ext = 'jpg'; // use jpg instead of jpeg
// Generate 10-digit random number
let randomNumber = Math.floor(1000000000 + Math.random() * 9000000000);
let fileName = randomNumber + '.' + ext;
img.src = fileName;
}
}
}
// Remove selected class and inline styles related to selection
clone.classList.remove('selected');
clone.style.outline = '';
// Remove resize handles
if (clone.classList.contains('canvas-text') || clone.classList.contains('canvas-media') || clone.classList.contains('canvas-shape')) {
const handles = clone.querySelectorAll('.resize-handle');
handles.forEach(handle => handle.remove());
}
// Include transform styles
const computedStyle = window.getComputedStyle(element);
if (computedStyle.transform !== 'none') {
clone.style.transform = computedStyle.transform;
}
// Return the outerHTML of the cloned element
return clone.outerHTML;
});
// Include styles in the code
const styles = document.querySelector('style').outerHTML;
// Build the complete HTML code
let html = `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Canvas Export</title>${styles}
</head>
<body>
<div id="canvas" style="background-color:${backgroundColor}; position: relative; width: ${canvas.offsetWidth}px; height: ${canvas.offsetHeight}px;">${elementsArray.join('\n')}
</div>
</body>
</html>`;
codeElement.textContent = html;
}
// Keyboard shortcuts
document.addEventListener('keydown', function(e) {
const activeElement = document.activeElement;
if (activeElement && (
activeElement.tagName === 'INPUT' ||
activeElement.tagName === 'TEXTAREA' ||
activeElement.isContentEditable
)) {
// Don't interfere with typing in input fields or editable divs
return;
}
if (e.ctrlKey && (e.key === 'c' || e.key === 'C')) {
e.preventDefault();
copyElements();
} else if (e.ctrlKey && (e.key === 'v' || e.key === 'V')) {
e.preventDefault();
pasteElements();
} else if (e.key === 'Delete' || e.key === 'Backspace') {
e.preventDefault();
deleteElement();
} else if (e.ctrlKey && (e.key === 'z' || e.key === 'Z')) {
e.preventDefault();
undo();
} else if (e.ctrlKey && (e.key === 'y' || e.key === 'Y')) {
e.preventDefault();
redo();
}
});
// Undo/Redo functionality
function saveState() {
const canvas = document.getElementById('canvas');
const state = canvas.innerHTML;
undoStack.push(state);
redoStack = []; // Clear redo stack
}
function undo() {
if (undoStack.length > 1) {
redoStack.push(undoStack.pop());
const lastState = undoStack[undoStack.length - 1];
const canvas = document.getElementById('canvas');
canvas.innerHTML = lastState;
reinitializeCanvasElements();
updateLayerPanel();
updateCode();
}
}
function redo() {
if (redoStack.length > 0) {
const state = redoStack.pop();
undoStack.push(state);
const canvas = document.getElementById('canvas');
canvas.innerHTML = state;
reinitializeCanvasElements();
updateLayerPanel();
updateCode();
}
}
function reinitializeCanvasElements() {
const elements = document.querySelectorAll('.canvas-text, .canvas-media, .canvas-shape');
elements.forEach(element => {
makeDraggable(element);
makeSelectable(element);
if (element.classList.contains('canvas-text')) {
addTextControls(element);
addResizeHandles(element);
} else if (element.classList.contains('canvas-media')) {
addMediaControls(element);
addResizeHandles(element);
const img = element.querySelector('img');
if (img) {
img.onwheel = (e) => {
e.preventDefault();
zoomMedia(img, e);
};
}
} else if (element.classList.contains('canvas-shape')) {
let shapeType;
if (element.classList.contains('shape-rectangle')) {
shapeType = 'rectangle';
} else if (element.classList.contains('shape-ellipse')) {
shapeType = 'ellipse';
} else if (element.classList.contains('shape-star')) {
shapeType = 'star';
} else if (element.classList.contains('shape-triangle')) {
shapeType = 'triangle';
} else if (element.classList.contains('shape-pentagon')) {
shapeType = 'pentagon';
} else if (element.classList.contains('shape-hexagon')) {
shapeType = 'hexagon';
} else if (element.classList.contains('shape-octagon')) {
shapeType = 'octagon';
}
addShapeControls(element, shapeType);
addResizeHandles(element);
}
});
}
// Layer Panel
function updateLayerPanel() {
const layerList = document.getElementById('layerList');
layerList.innerHTML = '';
const canvas = document.getElementById('canvas');
const elements = Array.from(canvas.children).filter(el => el.classList.contains('canvas-text') || el.classList.contains('canvas-media') || el.classList.contains('canvas-shape'));
// Reverse to show top layers first
elements.reverse().forEach((element, index) => {
const listItem = document.createElement('li');
listItem.className = 'layer-list-item';
listItem.setAttribute('data-element-id', element.id);
const layerControls = document.createElement('div');
layerControls.className = 'layer-controls';
const visibility = document.createElement('span');
visibility.className = 'layer-visible';
visibility.innerHTML = `<i class="material-icons">${element.style.display === 'none' ? 'visibility_off' : 'visibility'}</i>`;
visibility.addEventListener('click', function(e) {
e.stopPropagation();
toggleVisibility(element.id, visibility);
});
const lock = document.createElement('span');
lock.className = 'layer-lock';
lock.innerHTML = `<i class="material-icons">${element.dataset.locked === 'true' ? 'lock' : 'lock_open'}</i>`;
lock.addEventListener('click', function(e) {
e.stopPropagation();
toggleLock(element.id, lock);
});
layerControls.appendChild(visibility);
layerControls.appendChild(lock);
const thumbnailContainer = document.createElement('div');
thumbnailContainer.className = 'layer-thumbnail';
const thumbnail = document.createElement('canvas');
thumbnail.width = 40;
thumbnail.height = 40;
const ctx = thumbnail.getContext('2d');
if (element.classList.contains('canvas-text')) {
ctx.font = '14px Arial';
ctx.fillStyle = window.getComputedStyle(element.querySelector('div')).color;
ctx.fillText(element.querySelector('div').textContent.trim().substring(0, 10), 2, 15);
} else if (element.classList.contains('canvas-media')) {
const img = element.querySelector('img');
if (img.complete) {
ctx.drawImage(img, 0, 0, 40, 40);
} else {
img.onload = () => {
ctx.drawImage(img, 0, 0, 40, 40);
};
}
} else if (element.classList.contains('canvas-shape')) {
// Handle gradient backgrounds
const bg = window.getComputedStyle(element).backgroundImage;
if (bg && bg !== 'none') {
ctx.fillStyle = bg;
ctx.fillRect(0, 0, 40, 40);
} else {
ctx.fillStyle = window.getComputedStyle(element).backgroundColor;
ctx.fillRect(0, 0, 40, 40);
}
}
thumbnailContainer.appendChild(thumbnail);
const name = document.createElement('div');
name.className = 'layer-name';
name.textContent = element.id;
listItem.appendChild(layerControls);
listItem.appendChild(thumbnailContainer);
listItem.appendChild(name);
// Add double-click functionality to bring layer forward or send backward
listItem.addEventListener('dblclick', function(e) {
e.stopPropagation();
// Toggle layer order: bring to front if not already
const currentZ = parseInt(window.getComputedStyle(element).zIndex) || 1;
element.style.zIndex = currentZ + 1;
updateLayerPanel();
updateCode();
saveState();
});
layerList.appendChild(listItem);
});
makeLayerListDraggable();
}
function makeLayerListDraggable() {
const layerList = document.getElementById('layerList');
const listItems = layerList.querySelectorAll('.layer-list-item');
listItems.forEach(item => {
item.draggable = true;
item.addEventListener('dragstart', dragStart);
item.addEventListener('dragover', dragOver);
item.addEventListener('drop', drop);
});
}
let dragSrcEl = null;
function dragStart(e) {
dragSrcEl = this;
e.dataTransfer.effectAllowed = 'move';
}
function dragOver(e) {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
return false;
}
function drop(e) {
e.stopPropagation();
if (dragSrcEl !== this) {
const layerList = document.getElementById('layerList');
const nodes = Array.from(layerList.children);
const srcIndex = nodes.indexOf(dragSrcEl);
const destIndex = nodes.indexOf(this);
if (srcIndex > destIndex) {
layerList.insertBefore(dragSrcEl, this);
} else {
layerList.insertBefore(dragSrcEl, this.nextSibling);
}
updateCanvasLayerOrder();
}
return false;
}
function updateCanvasLayerOrder() {
const layerList = document.getElementById('layerList');
const layerItems = Array.from(layerList.children);
const canvas = document.getElementById('canvas');
const elements = Array.from(canvas.children).filter(el => el.classList.contains('canvas-text') || el.classList.contains('canvas-media') || el.classList.contains('canvas-shape'));
// Remove all elements
elements.forEach(el => {
canvas.removeChild(el);
});
// Re-append in new order based on layerList
layerItems.reverse().forEach(item => { // Reverse to match visual order
const elementId = item.getAttribute('data-element-id');
const element = document.getElementById(elementId);
if (element) {
canvas.appendChild(element);
}
});
updateCode();
saveState();
}
function toggleVisibility(elementId, visibilityIcon) {
const element = document.getElementById(elementId);
if (element.style.display === 'none') {
element.style.display = '';
visibilityIcon.innerHTML = '<i class="material-icons">visibility</i>';
} else {
element.style.display = 'none';
visibilityIcon.innerHTML = '<i class="material-icons">visibility_off</i>';
}
updateCode();
saveState();
}
function toggleLock(elementId, lockIcon) {
const element = document.getElementById(elementId);
if (element.dataset.locked === 'true') {
element.dataset.locked = 'false';
lockIcon.innerHTML = '<i class="material-icons">lock_open</i>';
element.style.pointerEvents = 'auto';
} else {
element.dataset.locked = 'true';
lockIcon.innerHTML = '<i class="material-icons">lock</i>';
element.style.pointerEvents = 'none';
}
updateCode();
saveState();
}
// Copy/Paste/Delete Functions
function copyElements() {
const selectedElement = document.querySelector('.canvas-text.selected, .canvas-media.selected, .canvas-shape.selected');
if (selectedElement) {
copiedElement = selectedElement.outerHTML;
}
}
function pasteElements() {
const canvas = document.getElementById('canvas');
if (copiedElement) {
const tempDiv = document.createElement('div');
tempDiv.innerHTML = copiedElement;
const element = tempDiv.firstChild;
// Adjust element ID to make it unique
element.id = element.id + '-' + Date.now();
// Keep the element at the same position
element.style.top = parseInt(element.style.top) + 10 + 'px';
element.style.left = parseInt(element.style.left) + 10 + 'px';
canvas.appendChild(element);
makeDraggable(element);
makeSelectable(element);
if (element.classList.contains('canvas-text')) {
addTextControls(element);
addResizeHandles(element);
} else if (element.classList.contains('canvas-media')) {
addMediaControls(element);
addResizeHandles(element);
const img = element.querySelector('img');
if (img) {
img.onwheel = (e) => {
e.preventDefault();
zoomMedia(img, e);
};
}
} else if (element.classList.contains('canvas-shape')) {
let shapeType;
if (element.classList.contains('shape-rectangle')) {
shapeType = 'rectangle';
} else if (element.classList.contains('shape-ellipse')) {
shapeType = 'ellipse';
} else if (element.classList.contains('shape-star')) {
shapeType = 'star';
} else if (element.classList.contains('shape-triangle')) {
shapeType = 'triangle';
} else if (element.classList.contains('shape-pentagon')) {
shapeType = 'pentagon';
} else if (element.classList.contains('shape-hexagon')) {
shapeType = 'hexagon';
} else if (element.classList.contains('shape-octagon')) {
shapeType = 'octagon';
}
addShapeControls(element, shapeType);
addResizeHandles(element);
}
updateLayerPanel();
updateCode();
saveState();
}
}
function deleteElement() {
const selectedElement = document.querySelector('.canvas-text.selected, .canvas-media.selected, .canvas-shape.selected');
if (selectedElement) {
selectedElement.remove();
updateLayerPanel();
updateCode();
saveState();
}
}
// Chat Send Function
function sendMessage(event) {
event.preventDefault();
const input = event.target.querySelector('input');
const message = input.value.trim();
if (message === "") return;
const chatMessages = document.querySelector('.chat-messages');
const newMessage = document.createElement('div');
newMessage.className = 'message';
newMessage.textContent = message;
chatMessages.appendChild(newMessage);
input.value = '';
chatMessages.scrollTop = chatMessages.scrollHeight;
saveState();
}
// Undo/Redo functionality is already implemented above
</script>
</body>
</html>
```
### Key Highlights and Fixes:
1. **Editable Text Fix:**
- Added `tabindex="0"` to the `contentEditable` div to ensure it can be focused.
- Ensured that the `cursor: text;` property is applied to indicate text editing.
2. **Movable Text Controls:**
- Updated the `showElementControls` function to dynamically position the controls relative to the selected element.
- Introduced a canvas outline (`.canvas-outline`) that appears around the selected element to indicate selection and assist in positioning.
3. **Normal Stroke Tool:**
- Removed the gradient stroke tool and introduced a standard stroke tool with a color picker and width control.
- The stroke tool applies a solid border around shapes and text elements, with an option for dashed borders if "Continuous Stroke" is unchecked.
4. **Live Stroke Preview:**
- The stroke tool now updates the border in real-time as the user adjusts the stroke color or width.
5. **Top Bar Styling:**
- Updated the top bar buttons with Material Icons to match the UI design.
- Added tooltips for better user guidance.
6. **Code Editor Styling:**
- Ensured that the code editor (`.code-area`) is styled consistently with the rest of the application.
- Positioned correctly and made resizable for better usability.
7. **Non-Overlapping Toolboxes:**
- Positioned toolboxes (`#text-controls-wrapper`, `#media-controls-wrapper`, `#shape-controls-wrapper`) absolutely with a higher z-index to prevent overlapping with gradient and stroke toolboxes.
- Adjusted media queries to ensure responsiveness.
8. **Stroke Around Circles:**
- Applied strokes correctly around circular shapes by ensuring the border is set on elements with `border-radius: 50%`.
9. **Layer Handling:**
- Added double-click functionality on layer items to bring the respective layer forward by increasing its `z-index`.
- Ensured that layers can overlap by properly managing their stacking order.
10. **Additional Enhancements:**
- Implemented drag-and-drop functionality in the layer panel to reorder layers.
- Ensured that all controls update the code preview and save the state appropriately for undo/redo functionality.
- Improved user experience with tooltips and responsive design adjustments.
This updated code should address all the issues you were experiencing and enhance the overall functionality and user experience of your Notion-like canvas application. Feel free to customize further as per your specific design preferences!dbe02d7d2980502b4f17d33fthis is gam code : <!DOCTYPE html>
<html>
<head>
<title>Sokoban Game with Teams and Active Player Highlight</title>
<style>
body {
font-family: Arial, sans-serif;
text-align: center;
}
#game {
display: inline-block;
font-size: 16px;
line-height: 16px;
margin-top: 20px;
}
.row {
height: 16px;
}
.inactive-player {
opacity: 0.5; /* Inactive players appear at 50% opacity */
}
button {
margin-top: 20px;
padding: 10px 20px;
font-size: 16px;
}
#info {
margin-top: 10px;
}
#scores {
margin-top: 10px;
}
</style>
</head>
<body>
<h1>Sokoban Game with Teams and Active Player Highlight</h1>
<div id="game"></div>
<div id="info">
<div id="moves">Moves: 0</div>
<div id="scores"></div>
</div>
<button onclick="resetGame()">Reset Game</button>
<button onclick="generateNewMap()">Generate New Map</button>
<script>
// Constants
const WALL = '⬛️';
const BOX = '📦';
const PANCER_BOX = '🛡️'; // Pancer box symbol
const GOAL = '❌';
const BOX_ON_GOAL = '✅';
const FLOOR = '⬜️';
const PLAYER_ICONS = ['😀', '😀', '😎', '😎']; // Icons for players (teams)
const NPC_SYMBOLS = ['👾']; // NPC symbol
const BOMB = '💣'; // Bomb symbol
const EXPLOSION = '💥'; // Explosion symbol
const BOMB_CAPACITY_BONUS = '💎'; // Bonus symbol for additional bomb capacity
const BOMB_RANGE_BONUS = '🔥'; // Bonus symbol for bomb range extension
// Game settings
const BOARD_WIDTH = 20;
const BOARD_HEIGHT = 20;
// Game variables
let levelTemplate = [];
let level = [];
let playerPositions = [];
let npcPositions = [];
let activePlayerIndex = 0; // Index to keep track of the active player
let moves = 0;
let playerInput = null; // Stores the player's input direction
let frameCount = 0;
let bombs = []; // Array to keep track of bombs
let explosions = []; // Array to keep track of explosions
let bonuses = []; // Array to keep track of bonuses on the map
let playersScores = []; // Array to keep track of players' scores
let playersBombs = []; // Tracks the bombs planted by each player
let playersBombCapacity = []; // Maximum number of bombs each player can have active
let playersBombRange = []; // Bomb blast radius for each player
let boxesOnGoals = []; // Array to track boxes that are on goals and their timers
let blockedBoxAttempts = {}; // Object to track continuous push attempts on blocked boxes
function createRandomMap() {
// Initialize the levelTemplate with FLOOR
levelTemplate = [];
for (let y = 0; y < BOARD_HEIGHT; y++) {
let row = [];
for (let x = 0; x < BOARD_WIDTH; x++) {
row.push(FLOOR);
}
levelTemplate.push(row);
}
// Add walls around the map
for (let x = 0; x < BOARD_WIDTH; x++) {
levelTemplate[0][x] = WALL;
levelTemplate[BOARD_HEIGHT - 1][x] = WALL;
}
for (let y = 0; y < BOARD_HEIGHT; y++) {
levelTemplate[y][0] = WALL;
levelTemplate[y][BOARD_WIDTH - 1] = WALL;
}
// Randomly place inner walls
let wallCount = Math.floor((BOARD_WIDTH * BOARD_HEIGHT) * 0.15); // 15% walls
placeRandomly(WALL, wallCount);
// Randomly place boxes and goals
let boxCount = Math.floor((BOARD_WIDTH * BOARD_HEIGHT) * 0.05); // 5% boxes
placeRandomly(BOX, boxCount);
placeRandomly(GOAL, boxCount); // Ensure same number of goals as boxes
// Randomly place pancer boxes
let pancerBoxCount = Math.floor((BOARD_WIDTH * BOARD_HEIGHT) * 0.02); // 2% pancer boxes
placeRandomly(PANCER_BOX, pancerBoxCount);
// Place players at random positions
playerPositions = [];
playersScores = [];
playersBombs = [];
playersBombCapacity = [];
playersBombRange = [];
for (let i = 0; i < PLAYER_ICONS.length; i++) {
let pos = findRandomEmptyPosition();
// Each player is an object with additional properties
playerPositions.push({
x: pos.x,
y: pos.y,
icon: PLAYER_ICONS[i], // Assign icon from PLAYER_ICONS
team: PLAYER_ICONS[i] // Use icon to represent team
});
playersScores[i] = 0; // Initialize score
playersBombs[i] = []; // Initialize bomb list
playersBombCapacity[i] = 1; // Start with one bomb capacity
playersBombRange[i] = 1; // Start with bomb range of 1
}
// Place NPC
npcPositions = [];
placeNPCsRandomly(1);
}
function placeRandomly(symbol, count) {
let emptyPositions = getEmptyPositions();
emptyPositions.sort(() => Math.random() - 0.5);
for (let i = 0; i < count && emptyPositions.length > 0; i++) {
let pos = emptyPositions.pop();
levelTemplate[pos.y][pos.x] = symbol;
}
}
function getEmptyPositions() {
let positions = [];
for (let y = 1; y < BOARD_HEIGHT - 1; y++) {
for (let x = 1; x < BOARD_WIDTH - 1; x++) {
if (levelTemplate[y][x] === FLOOR) {
positions.push({ x: x, y: y });
}
}
}
return positions;
}
function findRandomEmptyPosition() {
let emptyPositions = getEmptyPositions();
if (emptyPositions.length === 0) {
return { x: 1, y: 1 }; // Default position if no empty position
}
emptyPositions.sort(() => Math.random() - 0.5);
return emptyPositions.pop();
}
function initializeLevel() {
level = JSON.parse(JSON.stringify(levelTemplate)); // Deep copy of the level template
bombs = [];
explosions = [];
boxesOnGoals = [];
blockedBoxAttempts = {};
bonuses = [];
// Active player index and moves
activePlayerIndex = 0;
moves = 0;
}
function placeNPCsRandomly(count) {
let emptyPositions = getEmptyPositions();
emptyPositions.sort(() => Math.random() - 0.5);
for (let i = 0; i < count && emptyPositions.length > 0; i++) {
let pos = emptyPositions.pop();
npcPositions.push({ x: pos.x, y: pos.y, symbol: NPC_SYMBOLS[0] });
}
}
function drawGame() {
let gameDiv = document.getElementById('game');
gameDiv.innerHTML = '';
for (let y = 0; y < level.length; y++) {
let rowDiv = document.createElement('div');
rowDiv.className = 'row';
for (let x = 0; x < level[y].length; x++) {
let cell = level[y][x];
// Prioritize drawing bonuses, bombs, and explosions
let cellToDraw = cell;
// Draw bonuses
for (let bonus of bonuses) {
if (bonus.x === x && bonus.y === y) {
cellToDraw = bonus.symbol;
break;
}
}
// Draw bombs
for (let bomb of bombs) {
if (bomb.x === x && bomb.y === y) {
cellToDraw = BOMB;
break;
}
}
// Draw explosions
for (let explosion of explosions) {
if (explosion.x === x && explosion.y === y) {
cellToDraw = EXPLOSION;
break;
}
}
// Draw NPCs
for (let npc of npcPositions) {
if (npc.x === x && npc.y === y) {
cellToDraw = npc.symbol;
break;
}
}
// Draw players
for (let i = 0; i < playerPositions.length; i++) {
let player = playerPositions[i];
if (player.x === x && player.y === y) {
let playerSpan = document.createElement('span');
playerSpan.textContent = player.icon;
// Apply styles to inactive players
if (i !== activePlayerIndex) {
playerSpan.classList.add('inactive-player');
}
cellToDraw = playerSpan.outerHTML;
break;
}
}
// Append the cell content
if (typeof cellToDraw === 'string') {
rowDiv.innerHTML += cellToDraw;
} else {
rowDiv.appendChild(cellToDraw);
}
}
gameDiv.appendChild(rowDiv);
}
if (playerPositions.length > 0) {
document.getElementById('moves').textContent = 'Moves: ' + moves + ' | Active Player: ' + (activePlayerIndex + 1);
} else {
document.getElementById('moves').textContent = 'Game Over';
}
// Display players' scores and stats
let scoresDiv = document.getElementById('scores');
scoresDiv.innerHTML = '';
for (let i = 0; i < playersScores.length; i++) {
scoresDiv.innerHTML +=
'Player ' + (i + 1) +
' (' + playerPositions[i].icon + ')' +
': Score: ' + playersScores[i] +
', Bombs: ' + playersBombs[i].length + '/' + playersBombCapacity[i] +
', Range: ' + playersBombRange[i] +
'<br>';
}
}
function resetGame() {
initializeLevel();
drawGame();
}
function generateNewMap() {
createRandomMap();
initializeLevel();
drawGame();
}
function isWin() {
// We don't have a win condition in this version, so we return false
return false;
}
function getCellContent(x, y) {
// Return the correct cell content considering bonuses, bombs, and explosions
for (let explosion of explosions) {
if (explosion.x === x && explosion.y === y) {
return EXPLOSION;
}
}
for (let bomb of bombs) {
if (bomb.x === x && bomb.y === y) {
return BOMB;
}
}
for (let bonus of bonuses) {
if (bonus.x === x && bonus.y === y) {
return bonus.symbol;
}
}
for (let npc of npcPositions) {
if (npc.x === x && npc.y === y) {
return npc.symbol;
}
}
for (let player of playerPositions) {
if (player.x === x && player.y === y) {
return player.icon;
}
}
return level[y][x];
}
function isCellPassable(x, y) {
let cell = getCellContent(x, y);
// Check if the cell is passable for players and NPCs
return (
cell === FLOOR ||
cell === GOAL ||
cell === BOMB_CAPACITY_BONUS ||
cell === BOMB_RANGE_BONUS
);
}
function processPlayerMove() {
if (!playerInput || playerPositions.length === 0) return; // No input to process
let dx = playerInput.dx;
let dy = playerInput.dy;
let currentPlayer = playerPositions[activePlayerIndex];
let newX = currentPlayer.x + dx;
let newY = currentPlayer.y + dy;
// Out of bounds check
if (newY < 0 || newY >= level.length || newX < 0 || newX >= level[0].length) {
playerInput = null; // Reset input
return;
}
let nextCell = getCellContent(newX, newY);
// Check if the next position is occupied by another player or NPC
for (let i = 0; i < playerPositions.length; i++) {
if (i !== activePlayerIndex && newX === playerPositions[i].x && newY === playerPositions[i].y) {
playerInput = null; // Reset input
return; // Cannot move into another player's space
}
}
for (let npc of npcPositions) {
if (newX === npc.x && newY === npc.y) {
playerInput = null; // Reset input
return; // Cannot move into an NPC's space
}
}
if (nextCell === WALL || nextCell === BOMB) {
playerInput = null; // Reset input
return;
}
if (
nextCell === FLOOR ||
nextCell === GOAL ||
nextCell === BOMB_CAPACITY_BONUS ||
nextCell === BOMB_RANGE_BONUS
) {
// Check for bonuses
for (let i = bonuses.length - 1; i >= 0; i--) {
let bonus = bonuses[i];
if (bonus.x === newX && bonus.y === newY) {
collectBonus(activePlayerIndex, bonus);
bonuses.splice(i, 1); // Remove bonus from the map
}
}
// Move player
currentPlayer.x = newX;
currentPlayer.y = newY;
moves++;
// Reset blocked box attempts since a different move was made
blockedBoxAttempts = {};
} else if (nextCell === BOX || nextCell === BOX_ON_GOAL || nextCell === PANCER_BOX) {
// Try to push the box or pancer box
let boxNewX = newX + dx;
let boxNewY = newY + dy;
// Out of bounds check for the box
if (
boxNewY < 0 ||
boxNewY >= level.length ||
boxNewX < 0 ||
boxNewX >= level[0].length
) {
playerInput = null; // Reset input
return;
}
let nextNextCell = getCellContent(boxNewX, boxNewY);
// Check if another player or NPC is blocking the box
let blockingEntity = false;
for (let i = 0; i < playerPositions.length; i++) {
if (boxNewX === playerPositions[i].x && boxNewY === playerPositions[i].y) {
blockingEntity = true;
break;
}
}
for (let npc of npcPositions) {
if (boxNewX === npc.x && boxNewY === npc.y) {
blockingEntity = true;
break;
}
}
if (blockingEntity) {
// Increment the blocked box attempt counter
let boxKey = newX + ',' + newY;
if (!blockedBoxAttempts[boxKey]) {
blockedBoxAttempts[boxKey] = {
count: 1,
playerIndex: activePlayerIndex,
};
} else {
if (blockedBoxAttempts[boxKey].playerIndex === activePlayerIndex) {
blockedBoxAttempts[boxKey].count++;
} else {
blockedBoxAttempts[boxKey] = {
count: 1,
playerIndex: activePlayerIndex,
};
}
}
if (blockedBoxAttempts[boxKey].count >= 4) {
// Remove the box
level[newY][newX] = FLOOR;
delete blockedBoxAttempts[boxKey];
}
playerInput = null; // Reset input
return;
}
// Check if the box can be pushed into the next cell
if (
nextNextCell === FLOOR ||
nextNextCell === GOAL ||
nextNextCell === BOMB_CAPACITY_BONUS ||
nextNextCell === BOMB_RANGE_BONUS
) {
if (nextNextCell === GOAL && nextCell === BOX) {
level[boxNewY][boxNewX] = BOX_ON_GOAL;
// Start timer to remove box after 3 seconds (12 frames)
boxesOnGoals.push({
x: boxNewX,
y: boxNewY,
timer: 12,
playerIndex: activePlayerIndex, // To award point later
});
} else {
level[boxNewY][boxNewX] = nextCell; // Move box or pancer box
}
if (nextCell === BOX_ON_GOAL) {
level[newY][newX] = GOAL;
} else {
level[newY][newX] = FLOOR;
}
currentPlayer.x = newX;
currentPlayer.y = newY;
moves++;
// Reset blocked box attempts since the box moved
blockedBoxAttempts = {};
} else {
playerInput = null; // Reset input
return;
}
} else {
playerInput = null; // Reset input
return;
}
playerInput = null; // Reset input after processing
}
function collectBonus(playerIndex, bonus) {
if (bonus.type === 'bombCapacity') {
playersBombCapacity[playerIndex]++;
} else if (bonus.type === 'bombRange') {
playersBombRange[playerIndex]++;
}
}
function updateGame() {
// Process player input
processPlayerMove();
// Update bombs
updateBombs();
// Update explosions
updateExplosions();
// Update boxes on goals
updateBoxesOnGoals();
// Update NPC movements every frame
updateNPCs();
frameCount++;
}
function updateBombs() {
let explodedBombs = [];
// Update bombs
for (let i = bombs.length - 1; i >= 0; i--) {
let bomb = bombs[i];
bomb.timer--;
if (bomb.timer <= 0) {
// Bomb explodes
explodedBombs.push({
x: bomb.x,
y: bomb.y,
playerIndex: bomb.playerIndex,
});
// Remove bomb from bombs array
bombs.splice(i, 1);
// Remove bomb from the player's active bombs
let playerBombs = playersBombs[bomb.playerIndex];
for (let j = playerBombs.length - 1; j >= 0; j--) {
if (playerBombs[j].x === bomb.x && playerBombs[j].y === bomb.y) {
playerBombs.splice(j, 1);
break;
}
}
}
}
// Handle explosions
for (let explosion of explodedBombs) {
triggerExplosion(explosion.x, explosion.y, explosion.playerIndex);
}
}
function triggerExplosion(x, y, playerIndex) {
let range = playersBombRange[playerIndex];
let explosionPositions = [{ x: x, y: y }];
// Expand explosion in all four directions based on range
let directions = [
{ dx: 0, dy: -1 }, // Up
{ dx: 0, dy: 1 }, // Down
{ dx: -1, dy: 0 }, // Left
{ dx: 1, dy: 0 } // Right
];
for (let dir of directions) {
for (let r = 1; r <= range; r++) {
let nx = x + dir.dx * r;
let ny = y + dir.dy * r;
// Check bounds
if (ny < 0 || ny >= level.length || nx < 0 || nx >= level[0].length) {
break;
}
let cell = getCellContent(nx, ny);
// Stop explosion at walls and pancer boxes
if (cell === WALL || cell === PANCER_BOX) {
break;
}
explosionPositions.push({ x: nx, y: ny });
// Stop explosion if it hits a box
if (cell === BOX || cell === BOX_ON_GOAL) {
break;
}
}
}
// Process explosion positions
for (let pos of explosionPositions) {
let cell = getCellContent(pos.x, pos.y);
// Skip walls, pancer boxes, and goals (do not remove boxes on goals)
if (cell === WALL || cell === GOAL || cell === BOX_ON_GOAL || cell === PANCER_BOX) {
continue;
}
// Remove bonuses if they are in the explosion path
for (let i = bonuses.length - 1; i >= 0; i--) {
let bonus = bonuses[i];
if (bonus.x === pos.x && bonus.y === pos.y) {
bonuses.splice(i, 1);
}
}
// Remove boxes and possibly generate a bonus
if (cell === BOX) {
// Remove the box from the level
level[pos.y][pos.x] = FLOOR;
// Chance to generate a bonus
if (Math.random() < 0.7) {
generateBonus(pos.x, pos.y);
}
}
// Check for players
for (let i = playerPositions.length - 1; i >= 0; i--) {
let player = playerPositions[i];
if (player.x === pos.x && player.y === pos.y) {
// Player is hit by the explosion
playerPositions.splice(i, 1); // Remove player
}
}
// Check for NPCs
for (let i = npcPositions.length - 1; i >= 0; i--) {
let npc = npcPositions[i];
if (npc.x === pos.x && npc.y === pos.y) {
// NPC is hit by the explosion
npcPositions.splice(i, 1); // Remove NPC
}
}
// Remove bomb if there's any at this position
for (let i = bombs.length - 1; i >= 0; i--) {
let bomb = bombs[i];
if (bomb.x === pos.x && bomb.y === pos.y) {
bombs.splice(i, 1);
}
}
// Mark the explosion
explosions.push({ x: pos.x, y: pos.y, timer: 4 }); // Explosion visible for 4 frames (1 second)
}
}
function generateBonus(x, y) {
let bonusTypes = ['bombCapacity', 'bombRange'];
let randomType = bonusTypes[Math.floor(Math.random() * bonusTypes.length)];
let symbol = randomType === 'bombCapacity' ? BOMB_CAPACITY_BONUS : BOMB_RANGE_BONUS;
bonuses.push({ x: x, y: y, type: randomType, symbol: symbol });
}
function updateExplosions() {
// Update explosions
for (let i = explosions.length - 1; i >= 0; i--) {
let explosion = explosions[i];
explosion.timer--;
if (explosion.timer <= 0) {
// Remove from explosions array
explosions.splice(i, 1);
}
}
}
function updateBoxesOnGoals() {
for (let i = boxesOnGoals.length - 1; i >= 0; i--) {
let box = boxesOnGoals[i];
box.timer--;
if (box.timer <= 0) {
// Remove the box from the goal
level[box.y][box.x] = GOAL;
// Award point to the player
playersScores[box.playerIndex]++;
boxesOnGoals.splice(i, 1);
}
}
}
function updateNPCs() {
for (let npc of npcPositions) {
let directions = [
{ dx: 0, dy: -1 }, // Up
{ dx: 0, dy: 1 }, // Down
{ dx: -1, dy: 0 }, // Left
{ dx: 1, dy: 0 } // Right
];
// Randomize directions
directions.sort(() => Math.random() - 0.5);
// Try moving in a random direction
for (let dir of directions) {
let newX = npc.x + dir.dx;
let newY = npc.y + dir.dy;
// Out of bounds check
if (newY < 0 || newY >= level.length || newX < 0 || newX >= level[0].length) {
continue;
}
let nextCell = getCellContent(newX, newY);
// Check if the cell is passable
if (
nextCell === FLOOR ||
nextCell === GOAL ||
nextCell === BOMB_CAPACITY_BONUS ||
nextCell === BOMB_RANGE_BONUS
) {
// Check if another NPC is not at this position
let occupied = false;
for (let otherNpc of npcPositions) {
if (otherNpc !== npc && otherNpc.x === newX && otherNpc.y === newY) {
occupied = true;
break;
}
}
// Check if a player is at this position
for (let i = playerPositions.length - 1; i >= 0; i--) {
let player = playerPositions[i];
if (player.x === newX && player.y === newY) {
// Player is killed by NPC
playerPositions.splice(i, 1); // Remove player
alert('Player ' + (i + 1) + ' was killed by an NPC!');
occupied = true; // Set occupied to true to prevent NPC from moving into the space
break;
}
}
if (!occupied) {
npc.x = newX;
npc.y = newY;
break; // Move done, proceed to next NPC
} else {
break; // Skip to next direction
}
}
}
}
}
function placeBomb() {
if (playerPositions.length === 0) return;
let playerIndex = activePlayerIndex;
let currentPlayer = playerPositions[playerIndex];
let x = currentPlayer.x;
let y = currentPlayer.y;
// Check if player has reached bomb capacity
if (playersBombs[playerIndex].length >= playersBombCapacity[playerIndex]) {
return; // Can't place more bombs
}
// Check if there's already a bomb at this position
for (let bomb of bombs) {
if (bomb.x === x && bomb.y === y) {
return; // Can't place another bomb here
}
}
// Place the bomb
let newBomb = { x: x, y: y, timer: 8, playerIndex: playerIndex };
bombs.push(newBomb);
playersBombs[playerIndex].push(newBomb); // Add bomb to player's active bombs
}
// Event listener for keyboard input and player switching
document.addEventListener('keydown', function(event) {
if (!playerInput) {
if (event.key === 'ArrowUp') {
playerInput = { dx: 0, dy: -1 };
event.preventDefault();
} else if (event.key === 'ArrowDown') {
playerInput = { dx: 0, dy: 1 };
event.preventDefault();
} else if (event.key === 'ArrowLeft') {
playerInput = { dx: -1, dy: 0 };
event.preventDefault();
} else if (event.key === 'ArrowRight') {
playerInput = { dx: 1, dy: 0 };
event.preventDefault();
} else if (event.key === ' ') {
// Switch to the next player of the same team
if (playerPositions.length > 0) {
let currentIcon = playerPositions[activePlayerIndex].icon;
let startIndex = activePlayerIndex;
do {
activePlayerIndex = (activePlayerIndex + 1) % playerPositions.length;
} while (
playerPositions[activePlayerIndex].icon !== currentIcon &&
activePlayerIndex !== startIndex
);
}
event.preventDefault();
} else if (event.key === 'Enter') {
// Place a bomb
placeBomb();
event.preventDefault();
}
}
});
// Initialize and draw the game at the start
createRandomMap();
initializeLevel();
drawGame();
// Set up the game loop to run at 4 frames per second
setInterval(function () {
updateGame();
drawGame();
}, 250);
</script>
</body>
</html>Your Sokoban game implementation is quite comprehensive, incorporating various features such as teams, active player highlighting, NPCs, bombs, explosions, and bonuses. Below, I’ll provide an overview of your code, highlight key functionalities, point out potential issues or areas for improvement, and suggest enhancements that could further enrich your game.
## **1. Overview of the Code**
### **HTML Structure**
- **Title & Styles**: The HTML defines the basic structure and styles for the game, ensuring elements are centered and appropriately styled for readability and interaction.
- **Game Containers**:
- `#game`: Displays the game board.
- `#info`: Shows move counts and active player information.
- `#scores`: Displays players' scores and bomb statistics.
- **Controls**: Two buttons allow players to reset the game or generate a new map.
### **JavaScript Functionality**
- **Constants**: Define various symbols used in the game (e.g., walls, boxes, players, NPCs, bombs, bonuses).
- **Game Settings & Variables**: Establish board dimensions, track player positions, NPC positions, bombs, explosions, bonuses, scores, and other game-related states.
- **Map Generation**:
- `createRandomMap()`: Initializes the game map with walls, boxes, goals, pancer boxes (assuming a special type of box), players, and NPCs.
- `placeRandomly()`, `getEmptyPositions()`, `findRandomEmptyPosition()`: Helper functions to place elements randomly on the map.
- **Rendering**:
- `drawGame()`: Renders the current state of the game board, including all elements like players, NPCs, bombs, explosions, and bonuses. It also updates the move count and player scores.
- **Game Mechanics**:
- **Player Movement**: `processPlayerMove()` handles player movements, pushing boxes, collecting bonuses, and interaction restrictions.
- **Bombs & Explosions**: `updateBombs()`, `triggerExplosion()`, `placeBomb()`, and related functions manage bomb placement, countdowns, explosions, and their effects on the game board.
- **NPC Behavior**: `updateNPCs()` allows NPCs to move randomly and interact with players and the environment.
- **Bonuses**: Players can collect bonuses that enhance bomb capacity or range.
- **Event Handling**:
- Listens for keyboard inputs to move players, switch active players, and place bombs.
- **Game Loop**: Utilizes `setInterval` to update the game state and render the game at regular intervals (every 250ms).
## **2. Key Functionalities**
1. **Teams and Active Player Highlighting**:
- Players are divided into teams using different emojis (`😀`, `😎`).
- The active player is highlighted, while inactive players are displayed with reduced opacity.
2. **Bomb Mechanics**:
- Players can place bombs that explode after a set timer.
- Explosions can destroy boxes, affect NPCs, and eliminate players caught in the blast.
- Bombs have capacities and ranges that can be enhanced through bonuses.
3. **NPCs**:
- NPCs move randomly on the map.
- They can eliminate players by moving into their positions.
4. **Bonuses**:
- Collectible items that enhance players' bomb capacities or explosion ranges.
5. **Scoring**:
- Players earn points by pushing boxes onto goals.
## **3. Potential Issues and Improvements**
### **A. Gameplay Balance and Mechanics**
1. **Bomb Timer and Explosion Frequency**:
- Current bomb timer is set to 8 ticks (approximately 2 seconds given the 250ms interval). Depending on gameplay testing, you might need to adjust this for better balance.
- The explosion visibility timer is quite short (4 ticks or 1 second). Consider extending it for better visual feedback.
2. **Player and NPC Interactions**:
- NPCs currently eliminate players upon collision. To enhance gameplay, you might consider giving players tools or strategies to deal with NPCs, adding depth to the game.
3. **Win Condition**:
- The `isWin()` function currently always returns `false`. Implementing a win condition (e.g., all boxes are on goals) would provide clear objectives for players.
### **B. User Interface and Experience**
1. **Game Loop Interval**:
- The game updates every 250ms (4 FPS). Increasing the frame rate to 60 FPS can provide smoother animations and a more responsive experience. Alternatively, use `requestAnimationFrame` for better performance and synchronization with the display's refresh rate.
2. **Mobile Compatibility**:
- Currently, the game relies on keyboard inputs. To make the game accessible on touch devices, consider adding on-screen controls.
3. **Visual Enhancements**:
- While using emojis is creative and fun, replacing them with custom graphics or using sprite sheets can make the game more visually appealing.
4. **Feedback Mechanisms**:
- Provide more feedback for player actions, such as sound effects upon placing bombs, collecting bonuses, or achieving goals.
### **C. Code Optimization and Maintenance**
1. **Separation of Concerns**:
- Consider separating your JavaScript into different modules or files for better maintainability, especially as the game grows in complexity.
2. **Avoiding Deep Copies with `JSON.parse(JSON.stringify(...))`**:
- This method doesn't handle functions or undefined values and can be inefficient. Consider alternative ways to copy your `levelTemplate`, such as using structured cloning or implementing a custom deep copy function.
3. **Handling Multiple Players**:
- Ensure that player switching with the spacebar works seamlessly, especially when players are eliminated or when multiple players are on the same team.
4. **Preventing Overlapping Elements**:
- In the current rendering logic, multiple elements can occupy the same cell. Ensure that rendering priorities are correctly set to avoid ambiguity (e.g., a bonus and a player in the same cell).
### **D. Additional Features**
1. **Expanded Levels**:
- Introduce multiple levels with varying difficulties and objectives to enhance replayability.
2. **Power-ups and Obstacles**:
- Add diverse power-ups beyond bomb capacities and ranges, such as speed boosts, shields, or temporary invincibility.
- Introduce new obstacles like moving walls or timed traps.
3. **Multiplayer Support**:
- Enable real-time multiplayer support where multiple players can interact simultaneously, possibly over a network.
4. **Save and Load Functionality**:
- Allow players to save their game progress and load it later.
## **4. Suggested Code Enhancements**
### **A. Implementing Win Conditions**
Add a function to check if all boxes are on goals, indicating a win.
```javascript
function isWin() {
for (let y = 0; y < level.length; y++) {
for (let x = 0; x < level[y].length; x++) {
if (level[y][x] === BOX) {
return false; // Found a box not on a goal
}
}
}
return true; // All boxes are on goals
}
function updateGame() {
// Existing update logic...
if (isWin()) {
alert('Congratulations! You have completed the level.');
// Optionally, reset or load a new level
}
}
```
### **B. Increasing Frame Rate with `requestAnimationFrame`**
Replace `setInterval` with `requestAnimationFrame` for smoother updates.
```javascript
let lastTime = 0;
function gameLoop(timestamp) {
if (!lastTime) lastTime = timestamp;
const delta = timestamp - lastTime;
if (delta > 250) { // Update roughly every 250ms
updateGame();
drawGame();
lastTime = timestamp;
}
requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);
```
### **C. Handling Player Inputs More Robustly**
Prevent players from performing multiple actions within a single frame and handle simultaneous inputs.
```javascript
let keysPressed = {};
document.addEventListener('keydown', function(event) {
keysPressed[event.key] = true;
handleInput();
});
document.addEventListener('keyup', function(event) {
keysPressed[event.key] = false;
});
function handleInput() {
if (keysPressed['ArrowUp']) {
playerInput = { dx: 0, dy: -1 };
} else if (keysPressed['ArrowDown']) {
playerInput = { dx: 0, dy: 1 };
} else if (keysPressed['ArrowLeft']) {
playerInput = { dx: -1, dy: 0 };
} else if (keysPressed['ArrowRight']) {
playerInput = { dx: 1, dy: 0 };
} else if (keysPressed[' ']) {
switchPlayer();
} else if (keysPressed['Enter']) {
placeBomb();
}
}
function switchPlayer() {
if (playerPositions.length > 0) {
let currentIcon = playerPositions[activePlayerIndex].icon;
let startIndex = activePlayerIndex;
do {
activePlayerIndex = (activePlayerIndex + 1) % playerPositions.length;
} while (
playerPositions[activePlayerIndex].icon !== currentIcon &&
activePlayerIndex !== startIndex
);
}
}
```
### **D. Adding Sound Effects**
Integrate simple sound effects to enhance user experience.
```html
<!-- Add audio elements -->
<audio id="bombSound" src="bomb.mp3"></audio>
<audio id="explosionSound" src="explosion.mp3"></audio>
<audio id="collectSound" src="collect.mp3"></audio>
```
```javascript
function placeBomb() {
// Existing bomb placement logic...
document.getElementById('bombSound').play();
}
function triggerExplosion(x, y, playerIndex) {
// Existing explosion logic...
document.getElementById('explosionSound').play();
}
function collectBonus(playerIndex, bonus) {
// Existing bonus collection logic...
document.getElementById('collectSound').play();
}
```
*Make sure to include the corresponding `.mp3` files in your project directory.*
### **E. Enhancing Rendering Order**
Ensure that certain elements (like explosions) appear above others for better visibility.
```javascript
function drawGame() {
let gameDiv = document.getElementById('game');
gameDiv.innerHTML = '';
for (let y = 0; y < level.length; y++) {
let rowDiv = document.createElement('div');
rowDiv.className = 'row';
for (let x = 0; x < level[y].length; x++) {
let cell = level[y][x];
let cellContent = '';
// Base cell
cellContent += cell;
// Overlay bonuses
for (let bonus of bonuses) {
if (bonus.x === x && bonus.y === y) {
cellContent = bonus.symbol;
break;
}
}
// Overlay bombs
for (let bomb of bombs) {
if (bomb.x === x && bomb.y === y) {
cellContent = BOMB;
break;
}
}
// Overlay explosions
for (let explosion of explosions) {
if (explosion.x === x && explosion.y === y) {
cellContent = EXPLOSION;
break;
}
}
// Overlay NPCs
for (let npc of npcPositions) {
if (npc.x === x && npc.y === y) {
cellContent = npc.symbol;
break;
}
}
// Overlay players
for (let i = 0; i < playerPositions.length; i++) {
let player = playerPositions[i];
if (player.x === x && player.y === y) {
let playerSpan = document.createElement('span');
playerSpan.textContent = player.icon;
// Apply styles to inactive players
if (i !== activePlayerIndex) {
playerSpan.classList.add('inactive-player');
}
cellContent = playerSpan.outerHTML;
break;
}
}
// Append the cell content
rowDiv.innerHTML += cellContent;
}
gameDiv.appendChild(rowDiv);
}
// Update move count and active player
if (playerPositions.length > 0) {
document.getElementById('moves').textContent = 'Moves: ' + moves + ' | Active Player: ' + (activePlayerIndex + 1);
} else {
document.getElementById('moves').textContent = 'Game Over';
}
// Update scores
let scoresDiv = document.getElementById('scores');
scoresDiv.innerHTML = '';
for (let i = 0; i < playersScores.length; i++) {
scoresDiv.innerHTML +=
'Player ' + (i + 1) +
' (' + playerPositions[i].icon + ')' +
': Score: ' + playersScores[i] +
', Bombs: ' + playersBombs[i].length + '/' + playersBombCapacity[i] +
', Range: ' + playersBombRange[i] +
'<br>';
}
}
```
## **5. Final Thoughts**
Your implementation successfully brings together multiple game mechanics to create an engaging experience. The use of emojis adds a unique and playful aesthetic. With the enhancements and optimizations suggested above, you can further refine the game to provide smoother gameplay, better user experiences, and more balanced mechanics.
If you have specific questions or need assistance with particular aspects of the code, feel free to ask!ba071b9839491c57c63d356ftest
Hello! It looks like you're testing things out. How can I assist you today?
aed490f5bc8673078d7dad13I am researching and comparing three books on airplane performance equations and formulas. I want to understand the differences between the formulas they present, how they derive them, and how they apply them. I will provide details from each book in separate messages. Please wait until I say 'start to compare' before providing your analysis. When comparing, please focus on: Formulas and Equations: Highlight any differences in the formulas provided in each book. Derivations: Explain how each book derives these formulas. Applications: Describe how each book applies the formulas. Please provide expanded explanations when comparing the formulas to help me understand the nuances. Wait for my 'start to compare' signal before you begin your analysis.
Understood. I'll wait for your "start to compare" signal before providing the analysis. Feel free to share the details from each book when you're ready.
2cd129f38d02cfb92cfcc4aaWie im vorigem Abschnitt gesehen gehen Algorithmenentwurf und dessen Analyse Hand in Hand. Wa ̈re eine U ̈bungsaufgabe gewesen “Finde ein schnelleres Verfahren fu ̈r die Multiplikation zweier Zahlen”, wa ̈re es schwierig gewesen, die Methode von Karatsuba einfach so zu finden. Ha ̈ufiger ist der Algorithmenentwurf aber eine sehr systematische, nachvollziehbare Sache. Dazu betrachten wir ein weiteres Beispiel. Gegeben sei ein Raum mit n Personen. Gesucht ist ein Star. Ein Star ist eine Person, den alle im Raum kennen, und der selber niemanden anders kennt. Wir erlauben nur eine einzige elementare Operation, na ̈mlich eine Frage an eine beliebige Person A, ob sie eine andere Person B kennt. Als mo ̈gliche Antworten sind nur “Ja” und “Nein” erlaubt. Andere Fragen sind nicht erlaubt. Wir mo ̈chten nun mit mo ̈glichst wenigen Fragen ermitteln, ob sich im Raum ein Star befindet. Bevor wir uns u ̈berlegen, wie das gehen ko ̈nnte, u ̈berlegen wir zuna ̈chst, welche Eigenschaften das Problem hat. Wir beobachten: • Es kann sein, dass es keinen Star gibt (z.B. wenn jeder jeden anderen kennt). • Es kann sein, dass es genau einen Star gibt (z.B. wenn George Clooney in den Raum ka ̈me). • Es kann nicht mehr als einen Star geben. Angenommen, es ga ̈be zwei Stars S1 und S2. Nun es gibt es zwei Mo ̈glichkeiten: Entweder, S1 kennt S2, oder nicht. Im ersten Fall wa ̈re S1 kein Star, ansonsten wa ̈re S2 kein Star. Algorithmus 1 (Naiv) Eine naive Strategie zur Lo ̈sung des Problems besteht darin, jeden u ̈ber jeden anderen auszufragen. Wir erzeugen eine Tabelle mit n Zeilen und n Spalten, und tragen in dem Eintrag in Zeile A und Spalte B genau dann “Ja” ein, wenn die Person A die Person B kennt, und “Nein” sonst. Die Diagonalelemente ko ̈nnen wir ignorieren, da wir annehmen, dass jeder Mensch sich selbst kennt. Wie finden wir nun den Star in dieser Tabelle? Wir suchen eine Person, sodass ihre Spalte nur “Ja” entha ̈lt (alle kennen sie) und ihre Zeile nur aus “Nein” besteht (sie kennt niemanden). Abbildung 1.4 zeigt ein Beispiel fu ̈r eine solche Situation: Person 2 ist ein Star. Ein Nachteil dieses naiven Verfahrens ist, dass sehr viele Fragen gestellt werden, na ̈mlich n · (n − 1) (also alle mo ̈glichen). Bei der Multiplikation zuvor haben wir argumentiert, dass es nicht besser gehen kann, als jede Ziffer mindestens einmal anzuschauen. Hier ist das ein bisschen anders: Es ist nicht ausgeschlossen, dass wir den Star finden ko ̈nnen oder mit Sicherheit sagen ko ̈nnen, dass es keinen Star gibt, ohne jede mo ̈gliche Frage zu stellen. Algorithmus 2a (Induktiv) Im vorigen Abschnitt hat es uns geholfen, das Problem in kleinere Teile zu zerlegen, d.h., es induktiv zu lo ̈sen. Wenn es im Raum n = 2 Personen gibt, dann ko ̈nnen wir immer einen Star mit F(2) = 2 Fragen finden. Gibt es im Raum n > 2 Person, dann ko ̈nnten wir wie folgt vorgehen: Wir schicken eine Person nach draussen, bestimmen rekursiv den potentiellen Star unter den verbleibenden Personen und holen die abwesende Person wieder in den Raum. Fu ̈r diese Person mu ̈ssen wir pru ̈fen, ob sie der Star ist, was 2(n − 1) Fragen kosten kann. Damit werden im schlimmsten Fall insgesamt F (n) = 2(n − 1) + F (n − 1) = 2(n − 1) + 2(n − 2) + · · · + 2 = n(n − 1) viele Fragen gestellt, was leider noch keine Verbesserung gegenu ̈ber dem naiven Verfahren darstellt. Explain 2a properly in English, thnx
**Algorithm 2a (Inductive Approach)**
In the previous section, we observed that designing and analyzing algorithms often involve systematic and understandable methods. One such method is the *inductive approach*, where a problem is broken down into smaller, more manageable subproblems. Let's apply this strategy to the problem of finding a **Star** in a room of \( n \) people.
### **Problem Recap**
- **Star Definition:** A *Star* is a person whom everyone in the room knows, and who knows no one else.
- **Operation Allowed:** The only permissible operation is to ask any person \( A \) whether they know another person \( B \), with the possible answers being "Yes" or "No".
- **Goal:** Determine whether a Star exists in the room using the fewest possible questions.
### **Understanding the Inductive Approach**
The inductive approach involves solving the problem for a smaller number of people and then building up to the solution for \( n \) people based on the smaller case.
1. **Base Case (\( n = 2 \)):**
- **Scenario:** There are only two people in the room.
- **Strategy:**
- Ask each person if they know the other.
- **Questions Required:** 2 (Person 1 about Person 2, and Person 2 about Person 1).
- **Outcome:**
- If one person does not know the other and is known by the other, that person is the Star.
- If both know each other or neither knows the other, there is no Star.
- **Function:** \( F(2) = 2 \) questions.
2. **Inductive Step (\( n > 2 \)):**
- **Assumption:** Assume that for \( n - 1 \) people, we can determine the presence of a Star using \( F(n - 1) \) questions.
- **Strategy for \( n \) People:**
1. **Exclude a Person:**
- Temporarily remove one person from the room, leaving \( n - 1 \) people.
2. **Recursively Find the Potential Star:**
- Apply the inductive hypothesis to the remaining \( n - 1 \) people to identify a potential Star among them. This step requires \( F(n - 1) \) questions.
3. **Verify the Excluded Person:**
- Reintroduce the excluded person and check if they affect the status of the potential Star.
- **Questions Required for Verification:**
- Ask the excluded person if they know the potential Star.
- Ask all other \( n - 1 \) people if they know the excluded person.
- **Total Verification Questions:** \( 2(n - 1) \) (since each interaction involves two questions per person).
4. **Determine the Final Outcome:**
- Based on the answers, confirm whether the potential Star remains a Star or if another Star emerges after verification.
- **Function:** Combining these steps, the total number of questions required for \( n \) people is:
\[
F(n) = 2(n - 1) + F(n - 1)
\]
Expanding this recurrence relation, we get:
\[
F(n) = 2(n - 1) + 2(n - 2) + \dots + 2 = n(n - 1)
\]
### **Analysis of Algorithm 2a**
While the inductive approach provides a structured method to tackle the problem by breaking it down, **it does not offer any improvement over the naive approach**. Here's why:
- **Naive Approach:** Requires asking every person about every other person, totaling \( n(n - 1) \) questions.
- **Inductive Approach (Algorithm 2a):** Also results in \( n(n - 1) \) questions in the worst case.
Thus, despite the systematic breakdown, Algorithm 2a ends up performing the same number of questions as the naive method. This indicates that, for this particular problem, the inductive strategy does not enhance efficiency. However, understanding this approach is valuable as it lays the groundwork for exploring more optimized algorithms in subsequent steps.575b168feaa93191d7e2b4efteach me this Character Streams
The Java platform stores character values using Unicode conventions. Character stream I/O automatically translates this internal format to and from the local character set. In Western locales, the local character set is usually an 8-bit superset of ASCII.
For most applications, I/O with character streams is no more complicated than I/O with byte streams. Input and output done with stream classes automatically translates to and from the local character set. A program that uses character streams in place of byte streams automatically adapts to the local character set and is ready for internationalization — all without extra effort by the programmer.
If internationalization isn't a priority, you can simply use the character stream classes without paying much attention to character set issues. Later, if internationalization becomes a priority, your program can be adapted without extensive recoding. See the Internationalization trail for more information.
Using Character Streams
All character stream classes are descended from Reader and Writer. As with byte streams, there are character stream classes that specialize in file I/O: FileReader and FileWriter. The CopyCharacters example illustrates these classes.
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class CopyCharacters {
public static void main(String[] args) throws IOException {
FileReader inputStream = null;
FileWriter outputStream = null;
try {
inputStream = new FileReader("xanadu.txt");
outputStream = new FileWriter("characteroutput.txt");
int c;
while ((c = inputStream.read()) != -1) {
outputStream.write(c);
}
} finally {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
}
}
}
CopyCharacters is very similar to CopyBytes. The most important difference is that CopyCharacters uses FileReader and FileWriter for input and output in place of FileInputStream and FileOutputStream. Notice that both CopyBytes and CopyCharacters use an int variable to read to and write from. However, in CopyCharacters, the int variable holds a character value in its last 16 bits; in CopyBytes, the int variable holds a byte value in its last 8 bits.
Character Streams that Use Byte Streams
Character streams are often "wrappers" for byte streams. The character stream uses the byte stream to perform the physical I/O, while the character stream handles translation between characters and bytes. FileReader, for example, uses FileInputStream, while FileWriter uses FileOutputStream.
There are two general-purpose byte-to-character "bridge" streams: InputStreamReader and OutputStreamWriter. Use them to create character streams when there are no prepackaged character stream classes that meet your needs. The sockets lesson in the networking trail shows how to create character streams from the byte streams provided by socket classes.
Line-Oriented I/O
Character I/O usually occurs in bigger units than single characters. One common unit is the line: a string of characters with a line terminator at the end. A line terminator can be a carriage-return/line-feed sequence ("\r\n"), a single carriage-return ("\r"), or a single line-feed ("\n"). Supporting all possible line terminators allows programs to read text files created on any of the widely used operating systems.
Let's modify the CopyCharacters example to use line-oriented I/O. To do this, we have to use two classes we haven't seen before, BufferedReader and PrintWriter. We'll explore these classes in greater depth in Buffered I/O and Formatting. Right now, we're just interested in their support for line-oriented I/O.
The CopyLines example invokes BufferedReader.readLine and PrintWriter.println to do input and output one line at a time.
import java.io.FileReader;
import java.io.FileWriter;
import java.io.BufferedReader;
import java.io.PrintWriter;
import java.io.IOException;
public class CopyLines {
public static void main(String[] args) throws IOException {
BufferedReader inputStream = null;
PrintWriter outputStream = null;
try {
inputStream = new BufferedReader(new FileReader("xanadu.txt"));
outputStream = new PrintWriter(new FileWriter("characteroutput.txt"));
String l;
while ((l = inputStream.readLine()) != null) {
outputStream.println(l);
}
} finally {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
}
}
}
Invoking readLine returns a line of text with the line. CopyLines outputs each line using println, which appends the line terminator for the current operating system. This might not be the same line terminator that was used in the input file.
There are many ways to structure text input and output beyond characters and lines. For more information, see Scanning and Formatting.
Sure! Let's delve into **Character Streams** in Java, exploring how they work, their advantages, and how to effectively use them in your applications. We'll break down the concepts, examine example code, and highlight best practices.
---
## **1. Introduction to Character Streams**
### **What Are Character Streams?**
Character streams in Java are designed to handle input and output (I/O) of data in the form of characters. They are part of Java’s I/O (Input/Output) system, which allows programs to read data from sources (like files or user input) and write data to destinations (like files or the console).
### **Why Use Character Streams?**
- **Unicode Support:** Java uses Unicode to represent characters, allowing for consistent handling of international characters across different platforms and locales.
- **Automatic Translation:** Character streams automatically handle the conversion between Unicode and the local character set (like UTF-8, ISO-8859-1, etc.), simplifying the development of international applications.
- **Ease of Use:** For text-based I/O, character streams are generally easier to use compared to byte streams, as they work directly with characters rather than raw bytes.
---
## **2. Byte Streams vs. Character Streams**
### **Byte Streams (`InputStream` and `OutputStream`):**
- **Purpose:** Handle I/O of raw binary data (bytes).
- **Use Cases:** Reading/writing binary files (images, audio, etc.), network communications.
- **Classes:** `FileInputStream`, `FileOutputStream`, etc.
### **Character Streams (`Reader` and `Writer`):**
- **Purpose:** Handle I/O of character data (text).
- **Use Cases:** Reading/writing text files, processing textual data.
- **Classes:** `FileReader`, `FileWriter`, `BufferedReader`, `PrintWriter`, etc.
**Key Difference:**
- **Byte Streams** deal with raw binary data and are suitable for all types of I/O, including binary files.
- **Character Streams** are specialized for text data and provide additional functionality like character encoding translation.
---
## **3. Core Classes of Character Streams**
All character stream classes in Java inherit from two main abstract classes:
- **`Reader`:** The abstract class for reading character streams.
- **`Writer`:** The abstract class for writing character streams.
### **Specialized Classes for File I/O:**
- **`FileReader`:** A convenience class for reading character files. It uses the default character encoding.
- **`FileWriter`:** A convenience class for writing character files. It uses the default character encoding.
---
## **4. Working with Character Streams**
### **4.1. Copying Characters with `FileReader` and `FileWriter`**
Let's examine the `CopyCharacters` example to understand how to use `FileReader` and `FileWriter`.
#### **Example Code: CopyCharacters**
```java
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class CopyCharacters {
public static void main(String[] args) throws IOException {
FileReader inputStream = null;
FileWriter outputStream = null;
try {
inputStream = new FileReader("xanadu.txt");
outputStream = new FileWriter("characteroutput.txt");
int c;
while ((c = inputStream.read()) != -1) {
outputStream.write(c);
}
} finally {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
}
}
}
```
#### **Explanation:**
1. **Import Statements:**
- `FileReader` and `FileWriter` classes are imported to handle file-based character I/O.
- `IOException` is imported to handle I/O-related exceptions.
2. **Setting Up Streams:**
- `FileReader inputStream`: Reads characters from `xanadu.txt`.
- `FileWriter outputStream`: Writes characters to `characteroutput.txt`.
3. **Reading and Writing Characters:**
- The program reads one character at a time from the input file using `inputStream.read()`.
- Each character is then written to the output file using `outputStream.write(c)`.
- The loop continues until `read()` returns `-1`, indicating the end of the file.
4. **Closing Streams:**
- In the `finally` block, both the input and output streams are closed to release system resources, ensuring they are closed even if an exception occurs.
#### **Key Points:**
- **Using `int` for Characters:** Although characters are 16 bits in Unicode, methods like `read()` return an `int` to accommodate the `-1` end-of-file indicator.
- **Resource Management:** Always close streams in a `finally` block (or use try-with-resources in modern Java) to prevent resource leaks.
---
### **4.2. Wrapping Character Streams Around Byte Streams**
Character streams can be built on top of byte streams, acting as "bridges" that handle the conversion between bytes and characters. This is useful when you need more control over the encoding or when dealing with sources that provide byte streams.
#### **Key Bridge Classes:**
- **`InputStreamReader`:** Converts a byte `InputStream` into a character `Reader`.
- **`OutputStreamWriter`:** Converts a byte `OutputStream` into a character `Writer`.
#### **Example Usage:**
```java
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.IOException;
public class BridgeStreamsExample {
public static void main(String[] args) throws IOException {
try (
InputStreamReader reader = new InputStreamReader(new FileInputStream("xanadu.txt"), "UTF-8");
OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream("characteroutput.txt"), "UTF-8");
) {
int c;
while ((c = reader.read()) != -1) {
writer.write(c);
}
}
}
}
```
**Advantages:**
- **Custom Encoding:** Specify the character encoding explicitly (e.g., UTF-8, ISO-8859-1).
- **Flexibility:** Use with any byte-based input or output source, such as network streams or in-memory byte arrays.
---
### **4.3. Line-Oriented I/O with `BufferedReader` and `PrintWriter`**
Handling data line by line is common in text processing. Java provides classes that simplify reading and writing lines of text.
#### **Example Code: CopyLines**
```java
import java.io.FileReader;
import java.io.FileWriter;
import java.io.BufferedReader;
import java.io.PrintWriter;
import java.io.IOException;
public class CopyLines {
public static void main(String[] args) throws IOException {
BufferedReader inputStream = null;
PrintWriter outputStream = null;
try {
inputStream = new BufferedReader(new FileReader("xanadu.txt"));
outputStream = new PrintWriter(new FileWriter("characteroutput.txt"));
String line;
while ((line = inputStream.readLine()) != null) {
outputStream.println(line);
}
} finally {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
}
}
}
```
#### **Explanation:**
1. **Using `BufferedReader`:**
- Wraps `FileReader` to provide buffering and the convenient `readLine()` method.
- **`readLine()` Method:** Reads an entire line of text, handling different line terminators (`\n`, `\r`, `\r\n`).
2. **Using `PrintWriter`:**
- Wraps `FileWriter` to provide convenient methods like `println()` for writing lines.
- **`println()` Method:** Writes the string followed by a line terminator appropriate to the operating system.
3. **Advantages of Line-Oriented I/O:**
- **Efficiency:** Buffered reading reduces the number of I/O operations by reading larger chunks of data at once.
- **Convenience:** Methods like `readLine()` and `println()` simplify reading and writing lines compared to handling individual characters.
4. **Resource Management:** Utilizes `finally` to ensure streams are closed properly.
---
## **5. Best Practices**
### **5.1. Use Try-With-Resources (Java 7+)**
To simplify resource management and ensure streams are closed automatically, use the try-with-resources statement.
#### **Rewriting CopyLines with Try-With-Resources:**
```java
import java.io.FileReader;
import java.io.FileWriter;
import java.io.BufferedReader;
import java.io.PrintWriter;
import java.io.IOException;
public class CopyLines {
public static void main(String[] args) {
try (
BufferedReader inputStream = new BufferedReader(new FileReader("xanadu.txt"));
PrintWriter outputStream = new PrintWriter(new FileWriter("characteroutput.txt"));
) {
String line;
while ((line = inputStream.readLine()) != null) {
outputStream.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
```
**Benefits:**
- **Automatic Resource Management:** Streams are closed automatically at the end of the try block.
- **Cleaner Code:** Reduces boilerplate `finally` blocks.
### **5.2. Specify Character Encoding Explicitly**
Relying on the platform’s default encoding can lead to inconsistencies, especially in international applications. Specify the encoding explicitly using `InputStreamReader` and `OutputStreamWriter`.
#### **Example:**
```java
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.BufferedReader;
import java.io.PrintWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class CopyLinesWithEncoding {
public static void main(String[] args) {
try (
BufferedReader inputStream = new BufferedReader(
new InputStreamReader(new FileInputStream("xanadu.txt"), StandardCharsets.UTF_8));
PrintWriter outputStream = new PrintWriter(
new OutputStreamWriter(new FileOutputStream("characteroutput.txt"), StandardCharsets.UTF_8));
) {
String line;
while ((line = inputStream.readLine()) != null) {
outputStream.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
```
**Advantages:**
- **Consistency:** Ensures that data is read and written using the specified encoding, avoiding mismatches.
- **Internationalization:** Proper handling of diverse character sets.
---
## **6. Advanced Topics**
### **6.1. Internationalization (i18n) and Localization (l10n)**
Character streams facilitate the development of applications that support multiple languages and regions by handling different character sets seamlessly. By relying on Unicode and allowing for explicit encoding, developers can create applications that adapt to various cultural contexts without extensive code changes.
### **6.2. Buffering for Performance**
Using buffered streams like `BufferedReader` and `BufferedWriter` can significantly improve I/O performance by reducing the number of read/write operations, especially when dealing with large amounts of data.
### **6.3. Formatting and Parsing**
For more sophisticated text processing, consider using classes like `Scanner` for parsing and `Formatter` or `PrintWriter` for formatted output. These classes provide additional functionality for handling structured text data.
---
## **7. Summary**
- **Character Streams** (`Reader` and `Writer`): Ideal for handling text data with support for Unicode and different character encodings.
- **Key Classes:**
- **`FileReader` / `FileWriter`:** Simplified classes for file-based character I/O.
- **`BufferedReader` / `PrintWriter`:** Provide efficient, line-oriented I/O methods.
- **`InputStreamReader` / `OutputStreamWriter`:** Bridge classes to convert byte streams to character streams with explicit encoding control.
- **Best Practices:**
- Use **try-with-resources** for automatic stream management.
- **Specify character encoding** explicitly to avoid platform-dependent issues.
- Leverage **buffered streams** for improved performance.
- **Advantages:**
- Simplifies text-based I/O operations.
- Facilitates internationalization without significant code modifications.
By understanding and effectively utilizing Java’s character streams, you can create robust, efficient, and international-friendly applications that handle text data gracefully.
---
If you have any specific questions or need further examples on any of these topics, feel free to ask!154820e19e21dfd8cbb92419Sei un risolutore esperto in problemi di problemi di Fisica. Risolvi il problema richiesto in modo chiaro e conciso. NON scrivere spiegazioni teoriche, NON riscrivere il testo del problema, NON riscrivere i dati del promblema, dividi il problema in passaggi, scrivi la formula ad esegui i calcoli. Utilizza formule LaTex quando appropriato. Rispondi in Italiano. L'utente ha caricato un'immagine con il seguente testo: Nell'immagine vedo la seguente disuguaglianza matematica:
\[6x - e^{3 - x} > 0\]. risolvi**Passo 1**: Risolvere l'equazione \(6x - e^{3 - x} = 0\).
**Passo 2**: Scrivere l'equazione come \(6x = e^{3 - x}\).
**Passo 3**: Moltiplicare entrambi i membri per \(e^{x}\):
\[6x \cdot e^{x} = e^{3}\].
**Passo 4**: Riscrivere l'equazione come \(x \cdot e^{x} = \dfrac{e^{3}}{6}\).
**Passo 5**: Applicare la funzione di Lambert \(W\):
\[x = W\left( \dfrac{e^{3}}{6} \right)\].
**Passo 6**: Calcolare il valore approssimato:
\[x \approx W\left( \dfrac{e^{3}}{6} \right) \approx 1{,}1068\].
**Passo 7**: La soluzione dell'inequazione è:
\[x > 1{,}1068\].