File size: 26,803 Bytes
5f07a23
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
import { useRef, useState, useMemo } from 'react';
import { Upload, Edit, RefreshCw, RefreshCcw, Sparkles, ImageIcon, Plus, Check } from 'lucide-react';
import { addMaterialToLibrary } from './StyleSelector';

const AddMaterialModal = ({ 
  showModal, 
  onClose, 
  onAddMaterial, 
  newMaterialName, 
  setNewMaterialName,
  generatedMaterialName,
  setGeneratedMaterialName,
  generatedPrompt,
  setGeneratedPrompt,
  customPrompt,
  setCustomPrompt,
  previewThumbnail,
  customImagePreview,
  useCustomImage,
  isGeneratingPreview,
  isGeneratingText,
  showMaterialNameEdit,
  setShowMaterialNameEdit,
  showCustomPrompt,
  setShowCustomPrompt,
  handleRefreshThumbnail,
  handleReferenceImageUpload,
  handleNewMaterialDescription,
  onStyleSelected,
  materials
}) => {
  const fileInputRef = useRef(null);
  const [isDragging, setIsDragging] = useState(false);
  const [inputValue, setInputValue] = useState(newMaterialName);
  const [hoveredMaterial, setHoveredMaterial] = useState(null);

  // Sample materials for the library
  const sampleMaterials = [
    {
      name: 'Topographic',
      image: '/samples/topographic.jpeg',
      prompt: "Transform this sketch into a sculptural form composed of precisely stacked, thin metallic rings or layers. Render it with warm copper/bronze tones with each layer maintaining equal spacing from adjacent layers, creating a topographic map effect. The form should appear to flow and undulate while maintaining the precise parallel structure. Use dramatic studio lighting against a pure black background to highlight the metallic luster and dimensional quality. Render it in a high-end 3D visualization style with perfect definition between each ring layer."
    },
    {
      name: 'Glow Jelly',
      image: '/samples/glowjelly.png',
      prompt: "Transform this sketch into a boobly material. Render it in a high-end 3D visualization style with professional studio lighting against a pure black background. Make it look like an elegant Cinema 4D and Octane rendering with detailed material properties and characteristics. The final result should be an elegant visualization with perfect studio lighting, crisp shadows, and high-end material definition. Specifically, the boobly material should exhibit noticeable internal light scattering and a high degree of dynamic deformation on impact. To achieve the boobly effect, use a combination of Octane's Subsurface Scattering (SSS) and a Dynamic Mesh simulation in Cinema 4D, feeding the displacement output into the Octane material via an Octane Displacement node. The SSS should be tuned for a soft, fleshy translucency with a slight color shift towards red in thinner areas. Implement a high-frequency, low-amplitude noise pattern within the SSS radius to simulate subcutaneous textures. The material's surface should possess a subtle sheen, achieved through a thin-film interference effect in the Octane Glossy material, adding a rainbow iridescence that shifts with the viewing angle. The Dynamic Mesh should simulate realistic jiggle physics; consider using Cinema 4D's Soft Body Dynamics with carefully tuned spring and dampening values. Optimize the Octane scene for render efficiency, focusing on minimizing noise in the SSS and glossy reflections with adequate sample counts and kernel settings. The boobly material possesses a unique combination of squishiness and internal illumination, resulting in a captivating visual texture reminiscent of soft, organic forms. Rendering requires careful balancing of SSS settings and dynamic mesh calculations to achieve a realistic and visually appealing final result."
    },
    {
      name: 'Gold',
      image: '/samples/gold.png',
      prompt: "Transform this sketch into a sculptural form composed of precisely stacked, thin metallic rings or layers. Render it with warm copper/bronze tones with each layer maintaining equal spacing from adjacent layers, creating a topographic map effect. The form should appear to flow and undulate while maintaining the precise parallel structure. Use dramatic studio lighting against a pure black background to highlight the metallic luster and dimensional quality. Render it in a high-end 3D visualization style with perfect definition between each ring layer."
    },
    {
      name: 'Chinese Porcelain',
      image: '/samples/porcelain.png',
      prompt: "Transform this sketch into a ceramic vase featuring traditional blue and white Chinese porcelain designs, including dragons and cloud motifs. Pay close attention to the subtle variations in the blue pigment, creating a hand-painted effect with soft edges and intricate details. The vase should have a slightly glossy surface with gentle reflections. Render in Cinema 4D with Octane, using professional studio lighting to showcase the form and texture against a pure black background. Simulate subtle imperfections and surface irregularities typical of hand-crafted porcelain."
    },
    {
      name: 'Voxels',
      image: '/samples/voxels.png',
      prompt: "Transform this sketch into a physical manifestation of 8-bit voxel art. The rendering should capture the distinct, blocky nature of the medium, with each 'voxel' clearly defined and possessing a matte, slightly rough surface texture. Render the object as if constructed from these individual, quantized blocks, highlighting the stepped edges and discrete color transitions inherent to the 8-bit aesthetic. The professional studio lighting should emphasize the geometric forms and reveal subtle variations in tone across the voxel surfaces. Use Cinema 4D and Octane to create a high-resolution visualization that showcases the material's unique properties against a pure black background, ensuring crisp shadows and a clear definition of each individual voxel. The final result should be an elegant visualization that celebrates the charm and simplicity of 8-bit graphics in a physical form."
    },
    {
      name: 'Studio Ghibli',
      image: '/samples/ghibli.png',
      prompt: "Render the specific subject/scene/character detailed in the provided drawing/text. Faithfully interpret the core elements, composition, and figures present in the input. THEN, apply the following Studio Ghibli stylistic treatment: Aesthetic: Convert the rendering into the characteristic hand-painted look of Studio Ghibli films. Backgrounds: Use lush, painterly backgrounds that complement the subject (if the input lacks a background, create one in the Ghibli style; if it has one, render that background in the Ghibli style). Linework: Employ soft, expressive linework, avoiding harsh digital lines. Color: Utilize a warm, evocative, and harmonious color palette. Atmosphere: Infuse the scene with a Ghibli sense of wonder, nostalgia, or gentle melancholy, appropriate to the subject matter drawn. Details: Add subtle environmental details like wind, detailed foliage, or atmospheric effects typical of Ghibli, where suitable for the depicted scene. Lighting: Implement soft, natural, and atmospheric lighting (dappled sunlight, golden hour, overcast) integrated into the painted style. Goal: The final image must look like a high-quality Ghibli keyframe or background plate that is a clear and recognizable interpretation of the original provided drawing/text, not a generic Ghibli scene."
    },
    {
      name: 'Purple Fur',
      image: '/samples/purplefur.png',
      prompt: "Transform this sketch into a 3D model with a stylized fur material, featuring long, soft, vibrant purple strands with subtle dark grey roots. Pay close attention to the direction and clumping of the fur to mimic the animal form's texture. Render it in Cinema 4D with Octane, using professional studio lighting against a pure black background to accentuate the texture and color depth of the fur, creating soft shadows and highlights."
    },
    {
      name: 'Water',
      image: '/samples/water.png',
      prompt: "Transform this sketch into a dynamic water sculpture. Render it in a high-end 3D visualization style with professional studio lighting against a pure black background. Make it look like an elegant Cinema 4D and Octane rendering with detailed material properties and characteristics, capturing the essence of flowing water. The final result should be an elegant visualization with perfect studio lighting, crisp shadows, and high-end material definition, showcasing the water's transparency, refractive index, and subtle surface ripples. Emphasize the interplay of light and shadow as it passes through the water, highlighting its fluid and dynamic nature."
    },
    {
      name: 'Shrek',
      image: '/samples/shrek.png',
      prompt: "Transform this sketch into a Shrek-like material, capturing the essence of his green, slightly bumpy skin texture. Render it in a high-end 3D visualization style with professional studio lighting against a pure black background. Emphasize the subtle variations in skin tone, the slight translucency that allows for subsurface scattering, and the almost mossy, organic feel. Make it look like an elegant Cinema 4D and Octane rendering with detailed material properties and characteristics, highlighting the way light interacts with the unique surface. The final result should be an elegant visualization with perfect studio lighting, crisp shadows, and high-end material definition, showcasing the Shrek-like skin properties."
    },
  ];

  // Create a memoized list of already added material names
  const addedMaterialNames = useMemo(() => {
    if (!materials) return [];
    
    // Extract all material names (convert to lowercase for case-insensitive comparison)
    return Object.values(materials)
      .map(material => material.name?.toLowerCase())
      .filter(Boolean); // Filter out undefined/null
  }, [materials]);

  // Check if a material is already in the user's library
  const isMaterialAlreadyAdded = (materialName) => {
    return addedMaterialNames.includes(materialName.toLowerCase());
  };

  // Handle clicking outside of the modal to close it
  const handleClickOutsideModal = (e) => {
    if (e.target.classList.contains('modalBackdrop')) {
      onClose();
    }
  };

  // Handle selecting a material from the library
  const handleAddMaterialFromLibrary = (material) => {
    // Skip if already added
    if (isMaterialAlreadyAdded(material.name)) {
      return;
    }
    
    // Create a material object for the library
    const materialObj = {
      name: material.name,
      prompt: material.prompt,
      image: material.image
    };
    
    // Add material to library and get the key
    const materialKey = addMaterialToLibrary(materialObj);
    
    // Notify parent to select this material if the callback exists
    if (onStyleSelected && typeof onStyleSelected === 'function') {
      onStyleSelected(materialKey);
    }
    
    // Close the modal
    onClose();
  };

  // Handle drag and drop events
  const handleDragEnter = (e) => {
    e.preventDefault();
    e.stopPropagation();
    setIsDragging(true);
  };

  const handleDragLeave = (e) => {
    e.preventDefault();
    e.stopPropagation();
    setIsDragging(false);
  };

  const handleDragOver = (e) => {
    e.preventDefault();
    e.stopPropagation();
  };

  const handleDrop = (e) => {
    e.preventDefault();
    e.stopPropagation();
    setIsDragging(false);

    const files = e.dataTransfer.files;
    if (files.length > 0) {
      const file = files[0];
      if (file.type.startsWith('image/')) {
        // Create a synthetic event to pass to handleReferenceImageUpload
        const syntheticEvent = {
          target: {
            files: [file]
          }
        };
        handleReferenceImageUpload(syntheticEvent);
      } else {
        alert('Please drop an image file');
      }
    }
  };

  // Create a debounced version of the material description handler
  const handleSafeMaterialDescription = (description) => {
    // Don't process if already generating
    if (isGeneratingText || isGeneratingPreview) {
      return;
    }
    
    // Update the name and generate material - this should trigger just one workflow
    setNewMaterialName(description);
    handleNewMaterialDescription(description);
  };

  if (!showModal) return null;

  return (
    <div 
      className="fixed inset-0 bg-black bg-opacity-30 h-screen flex items-center justify-center z-50 modalBackdrop overflow-y-auto p-0"
      onClick={handleClickOutsideModal}
      onKeyDown={(e) => {
        if (e.key === 'Escape') {
          onClose();
        }
      }}
      role="dialog"
      aria-modal="true"
    >
      <div className="bg-white rounded-xl shadow-medium w-full max-w-6xl mx-auto my-auto flex flex-col md:flex-row">
        {/* Material Library Section */}
        <div className="p-4 md:p-6 border-b md:border-b-0 md:border-r border-gray-100 w-full md:w-1/2 max-h-[50vh] md:max-h-none overflow-auto">
          <div className="mb-3 md:mb-4">
            <h2 className="text-lg md:text-xl font-medium text-gray-800" style={{ fontFamily: "'Google Sans Text', sans-serif" }}>Material Library</h2>
          </div>
          
          <div className="grid grid-cols-3 md:grid-cols-5 gap-2">
            {sampleMaterials.map((material, index) => {
              const isAlreadyAdded = isMaterialAlreadyAdded(material.name);
              return (
                <button
                  key={index}
                  onClick={() => handleAddMaterialFromLibrary(material)}
                  type="button" 
                  aria-label={`Add ${material.name} material`}
                  className={`focus:outline-none group ${isAlreadyAdded ? 'opacity-50 cursor-not-allowed' : ''}`}
                  disabled={isAlreadyAdded}
                >
                  <div className={`w-full border overflow-hidden rounded-xl ${isAlreadyAdded ? 'bg-gray-100 border-gray-200' : 'bg-gray-50 border-gray-200 group-hover:bg-white group-hover:border-gray-300'}`}>
                    <div className="w-full relative" style={{ aspectRatio: '1/1' }}>
                      <img 
                        src={material.image}
                        alt={`${material.name} style example`}
                        className={`w-full h-full object-cover ${isAlreadyAdded ? 'opacity-70' : ''}`}
                        onError={(e) => {
                          console.error(`Error loading thumbnail for ${material.name}`);
                          e.target.src = '/samples/chrome.jpeg';
                        }}
                      />
                      <div className="absolute inset-0">
                        {isAlreadyAdded ? (
                          <div className="absolute inset-0 flex items-center justify-center">
                            <div className="flex items-center gap-1 bg-gray-500 px-2.5 py-1.5 text-white rounded-full text-xs font-medium">
                              <Check className="w-3 h-3" />
                              <span>Added</span>
                            </div>
                          </div>
                        ) : (
                          <div className="opacity-0 group-hover:opacity-100 hover:cursor-pointer transition-opacity">
                            <div className="absolute inset-0 bg-gray-500 bg-opacity-60 flex items-center justify-center">
                              <div className="flex items-center gap-1 bg-blue-500 px-2.5 py-1.5 text-white rounded-full text-xs font-medium">
                                <Plus className="w-3 h-3" />
                                <span>Add</span>
                              </div>
                            </div>
                          </div>
                        )}
                      </div>
                    </div>
                    <div className={`px-1 py-1 text-left text-xs font-medium ${isAlreadyAdded ? 'bg-gray-100 text-gray-400' : 'bg-gray-50 text-gray-500 group-hover:bg-white group-hover:text-gray-600'}`}>
                      <div className="truncate">
                        {material.name}
                      </div>
                    </div>
                  </div>
                </button>
              );
            })}
          </div>
        </div>
        
        {/* Add Material Form Section */}
        <div className="p-4 md:p-6 w-full md:w-1/2">
          <div className="flex items-center justify-between mb-4 md:mb-6">
            <h2 className="text-lg md:text-xl font-medium text-gray-800" style={{ fontFamily: "'Google Sans Text', sans-serif" }}>Add Material</h2>
          </div>

          {/* Input Section */}
          <div className="mb-6 md:mb-8 pb-6 md:pb-8 border-b border-gray-100">
            <div className="space-y-4">
              {/* Text input */}
              <div>
                <label htmlFor="materialDescription" className="block text-sm font-medium text-gray-700 mb-1">
                  Describe your material
                </label>
                <div className="relative flex items-center">
                  <input
                    id="materialDescription"
                    type="text"
                    value={inputValue}
                    onChange={(e) => {
                      setInputValue(e.target.value);
                    }}
                    onKeyDown={(e) => {
                      if (e.key === 'Enter' && inputValue.trim()) {
                        e.preventDefault();
                        handleSafeMaterialDescription(inputValue);
                      }
                    }}
                    className="w-full px-3 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 text-black pr-10"
                    placeholder="Eg. Bubbles, glass etc"
                    disabled={isGeneratingText || isGeneratingPreview}
                  />
                  <button
                    type="button"
                    onClick={() => {
                      if (inputValue.trim() && !isGeneratingText && !isGeneratingPreview) {
                        handleSafeMaterialDescription(inputValue);
                      }
                    }}
                    className="absolute right-2 p-1.5 bg-blue-500 rounded-md hover:bg-blue-400 transition-colors"
                    title="Generate material preview"
                    disabled={isGeneratingText || isGeneratingPreview}
                  >
                    {isGeneratingText || isGeneratingPreview ? (
                      <RefreshCw className="w-5 h-5 text-white animate-spin" />
                    ) : (
                      <div className="flex text-sm text-white items-center gap-1">
                         <Sparkles className="w-4 h-4 text-white " /> Generate
                      </div>
                    )}
                  </button>
                </div>
              </div>

              {/* Divider with "or" */}
              <div className="relative">
                <div className="absolute inset-0 flex items-center">
                  <div className="w-full border-t border-gray-200"></div>
                </div>
                <div className="relative flex justify-center text-sm">
                  <span className="px-2 bg-white text-gray-500">Or upload a reference image</span>
                </div>
              </div>

              {/* Image upload */}
              <div>
                <div 
                  className={`border-2 border-dashed rounded-lg p-4 flex flex-col items-center justify-center cursor-pointer transition-colors ${
                    isDragging 
                      ? 'border-blue-500 bg-blue-50' 
                      : 'border-gray-300 hover:bg-gray-50'
                  } ${isGeneratingText && useCustomImage ? 'opacity-70 pointer-events-none' : ''}`}
                  onClick={() => !isGeneratingText && fileInputRef.current?.click()}
                  onKeyDown={(e) => {
                    if (!isGeneratingText && (e.key === 'Enter' || e.key === ' ')) {
                      e.preventDefault();
                      fileInputRef.current?.click();
                    }
                  }}
                  onDragEnter={handleDragEnter}
                  onDragLeave={handleDragLeave}
                  onDragOver={handleDragOver}
                  onDrop={handleDrop}
                  style={{ minHeight: "100px" }}
                  role="button"
                  tabIndex={0}
                >
                  {isGeneratingText && useCustomImage ? (
                    <div className="flex flex-col items-center gap-2 py-2">
                      <RefreshCw className="w-6 h-6 text-blue-500 animate-spin" />
                      <p className="text-sm text-blue-500 font-medium">Analyzing image...</p>
                    </div>
                  ) : (
                    <>
                      <div className="flex flex-col items-center gap-2">
                        <ImageIcon className={`w-6 h-6 ${isDragging ? 'text-blue-500' : 'text-gray-400'}`} />
                        <p className={`text-sm ${isDragging ? 'text-blue-500' : 'text-gray-500'} text-center font-medium`}>
                          {isDragging ? 'Drop your image here' : 'Upload your reference image here'}
                        </p>
                      </div>
                      <input
                        id="referenceImage"
                        ref={fileInputRef}
                        type="file"
                        accept="image/*"
                        className="hidden"
                        onChange={handleReferenceImageUpload}
                        disabled={isGeneratingText && useCustomImage}
                      />
                    </>
                  )}
                </div>
              </div>
            </div>
          </div>

          {/* Review Section */}
          {(generatedMaterialName || previewThumbnail) && (
            <div className="mb-6 md:mb-8">
              <div className="flex gap-4">
                {/* Left column: Name and Prompt */}
                <div className="flex-1 space-y-3">
                  {/* Material Name - smaller and lighter text */}
                  <div className="group relative">
                    <input
                      type="text"
                      value={generatedMaterialName}
                      onChange={(e) => setGeneratedMaterialName(e.target.value)}
                      className="w-full px-3 py-2 bg-white border border-gray-200 rounded-lg 
                        focus:outline-none focus:ring-2 focus:ring-blue-500 focus:bg-white 
                        transition-all text-gray-900 focus:text-gray-900 text-sm focus:text-base
                        placeholder-gray-400"
                      placeholder="Material Name"
                    />
                  </div>

                  {/* Prompt - smaller and lighter text */}
                  <div className="group relative">
                    <textarea
                      value={customPrompt || generatedPrompt}
                      onChange={(e) => setCustomPrompt(e.target.value)}
                      className="w-full px-3 py-2 bg-white border border-gray-200 rounded-lg 
                        focus:outline-none focus:ring-2 focus:ring-blue-500 focus:bg-white 
                        transition-all text-gray-900 text-sm
                        h-[100px] md:h-[180px] resize-none placeholder-gray-400 leading-relaxed"
                      placeholder="Prompt"
                    />
                  </div>
                </div>

                {/* Right column: Preview */}
                <div className="w-28 md:w-36">
                  <div className="w-28 h-28 md:w-36 md:h-36 bg-gray-50 border border-gray-200 rounded-lg overflow-hidden relative flex items-center justify-center">
                    {/* Preview content */}
                    {((previewThumbnail || customImagePreview) && !isGeneratingPreview) ? (
                      <img 
                        src={useCustomImage ? customImagePreview : previewThumbnail} 
                        alt="Material preview"
                        className="w-full h-full object-cover"
                      />
                    ) : (
                      <div className="w-full h-full bg-white flex flex-col items-center justify-center">
                        {isGeneratingPreview ? (
                          <>
                            <RefreshCw className="w-6 h-6 md:w-8 md:h-8 text-blue-500 animate-spin mb-2" />
                            <p className="text-xs md:text-sm text-blue-500 text-center px-2 md:px-4">
                              Generating preview...
                            </p>
                          </>
                        ) : (
                          <p className="text-xs md:text-sm text-gray-400 text-center px-2 md:px-4">
                            Preview
                          </p>
                        )}
                      </div>
                    )}
                    
                    {/* Refresh button */}
                    {(previewThumbnail || customImagePreview) && !isGeneratingPreview && !useCustomImage && (
                      <button
                        type="button"
                        onClick={() => {
                          if (!isGeneratingPreview) {
                            handleRefreshThumbnail(customPrompt);
                          }
                        }}
                        className="absolute top-2 right-2 bg-white/80 rounded-full p-1 hover:bg-white transition-colors"
                        title="Refresh thumbnail"
                      >
                        <RefreshCcw className="w-3 h-3 md:w-4 md:h-4 text-gray-600" />
                      </button>
                    )}
                  </div>
                </div>
              </div>
            </div>
          )}
          
          {/* Action buttons */}
          <div className="flex justify-end gap-3 mt-4 md:mt-6">
            <button
              type="button"
              onClick={onClose}
              className="px-3 py-2 border border-gray-200 rounded-lg text-gray-600 hover:border-gray-300 hover:bg-gray-50 text-sm font-medium transition-colors"
            >
              Cancel
            </button>
            <button
              type="button"
              onClick={onAddMaterial}
              disabled={isGeneratingPreview || !newMaterialName.trim()}
              className="px-3 py-2 border border-blue-500 rounded-lg text-white bg-blue-500 hover:bg-blue-400 hover:cursor-pointer disabled:opacity-50 text-sm font-medium transition-colors"
            >
              + Add Material
            </button>
          </div>
        </div>
      </div>
    </div>
  );
};

export default AddMaterialModal;