# Image Generation

A shimmering canvas placeholder while an image is being generated.

- Category: Tool & Action States
- Source: AICSS (https://www.aicss.dev/components/image-generation)
- Author: @kvnkld (https://x.com/kvnkld)
- Styling: Self-contained CSS with CSS custom properties (design tokens). Theme-aware via [data-theme] (light/dark).

## Instructions

Add this component to the project. Keep the styling self-contained. Map the design tokens (CSS custom properties) to the project's theme if they are not already defined.

## Code

### React - `ImageGeneration.tsx`

```tsx
import styles from "./ImageGeneration.module.css";

export function ImageGeneration({
  prompt = "a calm mountain lake at dawn",
  resolution = "1024 × 1024",
}: {
  prompt?: string;
  resolution?: string;
}) {
  return (
    <div className={styles.igWrap}>
      <div className={styles.igCanvas} role="img" aria-label="Generating image">
        <span className={styles.igDots} aria-hidden />
        <span className={styles.igGlow} aria-hidden />
        <span className={styles.igRes}>{resolution}</span>
      </div>
      <div className={styles.igMeta}>
        <span className={styles.igLabel}>Generating image</span>
        <span className={styles.igPrompt}>“{prompt}”</span>
      </div>
    </div>
  );
}
```

### React - `ImageGeneration.module.css`

```css
.igWrap { display: flex; flex-direction: column; align-items: center; gap: 14px; }
.igCanvas { position: relative; width: 100%; max-width: 208px; aspect-ratio: 1 / 1; border-radius: 12px; background: #fafafa; overflow: hidden; }
.igDots { position: absolute; inset: 2px; background-image: radial-gradient(circle, #a1a1a1 0.7px, transparent 1.3px); background-size: 11px 11px; background-repeat: space; opacity: 0.22; }
.igGlow { position: absolute; inset: 2px; background-image: radial-gradient(circle, #0b0d12 1.1px, transparent 1.6px); background-size: 11px 11px; background-repeat: space; -webkit-mask-image: radial-gradient(ellipse at center, #000 0%, transparent 60%), radial-gradient(ellipse at center, #000 0%, transparent 62%); mask-image: radial-gradient(ellipse at center, #000 0%, transparent 60%), radial-gradient(ellipse at center, #000 0%, transparent 62%); -webkit-mask-repeat: no-repeat, no-repeat; mask-repeat: no-repeat, no-repeat; -webkit-mask-size: 72% 62%, 52% 52%; mask-size: 72% 62%, 52% 52%; -webkit-mask-position: 16% 20%, 30% 32%; mask-position: 16% 20%, 30% 32%; animation: ig-morph 4.2s cubic-bezier(0.35, 1.55, 0.65, 1) infinite, ig-breathe 1.9s cubic-bezier(0.66, 0, 0.34, 1) infinite; }
@keyframes ig-morph {
  0% { -webkit-mask-size: 52% 46%, 40% 40%; mask-size: 52% 46%, 40% 40%; -webkit-mask-position: 16% 20%, 30% 32%; mask-position: 16% 20%, 30% 32%; }
  25% { -webkit-mask-size: 46% 58%, 44% 38%; mask-size: 46% 58%, 44% 38%; -webkit-mask-position: 84% 16%, 66% 30%; mask-position: 84% 16%, 66% 30%; }
  50% { -webkit-mask-size: 60% 44%, 38% 46%; mask-size: 60% 44%, 38% 46%; -webkit-mask-position: 82% 84%, 62% 68%; mask-position: 82% 84%, 62% 68%; }
  75% { -webkit-mask-size: 48% 54%, 46% 40%; mask-size: 48% 54%, 46% 40%; -webkit-mask-position: 14% 82%, 34% 66%; mask-position: 14% 82%, 34% 66%; }
  100% { -webkit-mask-size: 52% 46%, 40% 40%; mask-size: 52% 46%, 40% 40%; -webkit-mask-position: 16% 20%, 30% 32%; mask-position: 16% 20%, 30% 32%; }
}
@keyframes ig-breathe { 0%, 100% { opacity: 0.55; } 50% { opacity: 1; } }
.igRes { position: absolute; top: 8px; right: 8px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; color: #a1a1a1; background: rgba(255, 255, 255, 0.72); padding: 2px 7px; border-radius: 999px; backdrop-filter: blur(4px); }
.igMeta { display: flex; flex-direction: column; gap: 2px; align-items: flex-start; text-align: left; width: 100%; max-width: 208px; }
.igLabel { background: linear-gradient(90deg, #1a1a1a 0%, #1a1a1a 30%, rgba(26, 26, 26, 0.45) 45%, rgba(26, 26, 26, 0.45) 55%, #1a1a1a 70%, #1a1a1a 100%); background-size: 300% 100%; -webkit-background-clip: text; background-clip: text; color: transparent; -webkit-text-fill-color: transparent; animation: ig-shine 2.25s cubic-bezier(0.25, 0.1, 0.25, 1) infinite; font-weight: 550; font-size: 14px; }
@keyframes ig-shine { 0%, 18% { background-position: 100% 0; } 82%, 100% { background-position: 0% 0; } }
.igPrompt { color: #a1a1a1; font-size: 13px; }
@media (prefers-reduced-motion: reduce) {
  .igGlow { animation: none; opacity: 0.7; }
  .igLabel { animation: none; -webkit-text-fill-color: #1a1a1a; color: #1a1a1a; }
}
@media (prefers-color-scheme: dark) {
  .igCanvas { background: #1f1f1f; }
  .igGlow { background-image: radial-gradient(circle, #f5f5f5 1.1px, transparent 1.6px); }
  .igRes { color: #a3a3a3; background: rgba(26, 26, 26, 0.72); }
  .igLabel { background: linear-gradient(90deg, #f5f5f5 0%, #f5f5f5 30%, rgba(245, 245, 245, 0.45) 45%, rgba(245, 245, 245, 0.45) 55%, #f5f5f5 70%, #f5f5f5 100%); background-size: 300% 100%; -webkit-background-clip: text; background-clip: text; }
}
```

### Vue - `ImageGeneration.vue`

```vue
<template>
  <div class="ig-wrap">
    <div class="ig-canvas" role="img" aria-label="Generating image">
      <span class="ig-dots" aria-hidden="true" />
      <span class="ig-glow" aria-hidden="true" />
      <span class="ig-res">{{ resolution }}</span>
    </div>
    <div class="ig-meta">
      <span class="ig-label">Generating image</span>
      <span class="ig-prompt">“{{ prompt }}”</span>
    </div>
  </div>
</template>

<script setup>
defineProps({
  prompt: { type: String, default: "a calm mountain lake at dawn" },
  resolution: { type: String, default: "1024 × 1024" },
});
</script>

<style scoped>
.ig-wrap { display: flex; flex-direction: column; align-items: center; gap: 14px; }
.ig-canvas { position: relative; width: 100%; max-width: 208px; aspect-ratio: 1 / 1; border-radius: 12px; background: #fafafa; overflow: hidden; }
.ig-dots { position: absolute; inset: 2px; background-image: radial-gradient(circle, #a1a1a1 0.7px, transparent 1.3px); background-size: 11px 11px; background-repeat: space; opacity: 0.22; }
.ig-glow { position: absolute; inset: 2px; background-image: radial-gradient(circle, #0b0d12 1.1px, transparent 1.6px); background-size: 11px 11px; background-repeat: space; -webkit-mask-image: radial-gradient(ellipse at center, #000 0%, transparent 60%), radial-gradient(ellipse at center, #000 0%, transparent 62%); mask-image: radial-gradient(ellipse at center, #000 0%, transparent 60%), radial-gradient(ellipse at center, #000 0%, transparent 62%); -webkit-mask-repeat: no-repeat, no-repeat; mask-repeat: no-repeat, no-repeat; -webkit-mask-size: 72% 62%, 52% 52%; mask-size: 72% 62%, 52% 52%; -webkit-mask-position: 16% 20%, 30% 32%; mask-position: 16% 20%, 30% 32%; animation: ig-morph 4.2s cubic-bezier(0.35, 1.55, 0.65, 1) infinite, ig-breathe 1.9s cubic-bezier(0.66, 0, 0.34, 1) infinite; }
@keyframes ig-morph {
  0% { -webkit-mask-size: 52% 46%, 40% 40%; mask-size: 52% 46%, 40% 40%; -webkit-mask-position: 16% 20%, 30% 32%; mask-position: 16% 20%, 30% 32%; }
  25% { -webkit-mask-size: 46% 58%, 44% 38%; mask-size: 46% 58%, 44% 38%; -webkit-mask-position: 84% 16%, 66% 30%; mask-position: 84% 16%, 66% 30%; }
  50% { -webkit-mask-size: 60% 44%, 38% 46%; mask-size: 60% 44%, 38% 46%; -webkit-mask-position: 82% 84%, 62% 68%; mask-position: 82% 84%, 62% 68%; }
  75% { -webkit-mask-size: 48% 54%, 46% 40%; mask-size: 48% 54%, 46% 40%; -webkit-mask-position: 14% 82%, 34% 66%; mask-position: 14% 82%, 34% 66%; }
  100% { -webkit-mask-size: 52% 46%, 40% 40%; mask-size: 52% 46%, 40% 40%; -webkit-mask-position: 16% 20%, 30% 32%; mask-position: 16% 20%, 30% 32%; }
}
@keyframes ig-breathe { 0%, 100% { opacity: 0.55; } 50% { opacity: 1; } }
.ig-res { position: absolute; top: 8px; right: 8px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; color: #a1a1a1; background: rgba(255, 255, 255, 0.72); padding: 2px 7px; border-radius: 999px; backdrop-filter: blur(4px); }
.ig-meta { display: flex; flex-direction: column; gap: 2px; align-items: flex-start; text-align: left; width: 100%; max-width: 208px; }
.ig-label { background: linear-gradient(90deg, #1a1a1a 0%, #1a1a1a 30%, rgba(26, 26, 26, 0.45) 45%, rgba(26, 26, 26, 0.45) 55%, #1a1a1a 70%, #1a1a1a 100%); background-size: 300% 100%; -webkit-background-clip: text; background-clip: text; color: transparent; -webkit-text-fill-color: transparent; animation: ig-shine 2.25s cubic-bezier(0.25, 0.1, 0.25, 1) infinite; font-weight: 550; font-size: 14px; }
@keyframes ig-shine { 0%, 18% { background-position: 100% 0; } 82%, 100% { background-position: 0% 0; } }
.ig-prompt { color: #a1a1a1; font-size: 13px; }
@media (prefers-reduced-motion: reduce) {
  .ig-glow { animation: none; opacity: 0.7; }
  .ig-label { animation: none; -webkit-text-fill-color: #1a1a1a; color: #1a1a1a; }
}
@media (prefers-color-scheme: dark) {
  .ig-canvas { background: #1f1f1f; }
  .ig-glow { background-image: radial-gradient(circle, #f5f5f5 1.1px, transparent 1.6px); }
  .ig-res { color: #a3a3a3; background: rgba(26, 26, 26, 0.72); }
  .ig-label { background: linear-gradient(90deg, #f5f5f5 0%, #f5f5f5 30%, rgba(245, 245, 245, 0.45) 45%, rgba(245, 245, 245, 0.45) 55%, #f5f5f5 70%, #f5f5f5 100%); background-size: 300% 100%; -webkit-background-clip: text; background-clip: text; }
}
</style>
```

### Svelte - `ImageGeneration.svelte`

```svelte
<script>
  export let prompt = "a calm mountain lake at dawn";
  export let resolution = "1024 × 1024";
</script>

<div class="ig-wrap">
  <div class="ig-canvas" role="img" aria-label="Generating image">
    <span class="ig-dots" aria-hidden="true" />
    <span class="ig-glow" aria-hidden="true" />
    <span class="ig-res">{resolution}</span>
  </div>
  <div class="ig-meta">
    <span class="ig-label">Generating image</span>
    <span class="ig-prompt">“{prompt}”</span>
  </div>
</div>

<style>
  .ig-wrap { display: flex; flex-direction: column; align-items: center; gap: 14px; }
  .ig-canvas { position: relative; width: 100%; max-width: 208px; aspect-ratio: 1 / 1; border-radius: 12px; background: #fafafa; overflow: hidden; }
  .ig-dots { position: absolute; inset: 2px; background-image: radial-gradient(circle, #a1a1a1 0.7px, transparent 1.3px); background-size: 11px 11px; background-repeat: space; opacity: 0.22; }
  .ig-glow { position: absolute; inset: 2px; background-image: radial-gradient(circle, #0b0d12 1.1px, transparent 1.6px); background-size: 11px 11px; background-repeat: space; -webkit-mask-image: radial-gradient(ellipse at center, #000 0%, transparent 60%), radial-gradient(ellipse at center, #000 0%, transparent 62%); mask-image: radial-gradient(ellipse at center, #000 0%, transparent 60%), radial-gradient(ellipse at center, #000 0%, transparent 62%); -webkit-mask-repeat: no-repeat, no-repeat; mask-repeat: no-repeat, no-repeat; -webkit-mask-size: 72% 62%, 52% 52%; mask-size: 72% 62%, 52% 52%; -webkit-mask-position: 16% 20%, 30% 32%; mask-position: 16% 20%, 30% 32%; animation: ig-morph 4.2s cubic-bezier(0.35, 1.55, 0.65, 1) infinite, ig-breathe 1.9s cubic-bezier(0.66, 0, 0.34, 1) infinite; }
  @keyframes ig-morph {
    0% { -webkit-mask-size: 52% 46%, 40% 40%; mask-size: 52% 46%, 40% 40%; -webkit-mask-position: 16% 20%, 30% 32%; mask-position: 16% 20%, 30% 32%; }
    25% { -webkit-mask-size: 46% 58%, 44% 38%; mask-size: 46% 58%, 44% 38%; -webkit-mask-position: 84% 16%, 66% 30%; mask-position: 84% 16%, 66% 30%; }
    50% { -webkit-mask-size: 60% 44%, 38% 46%; mask-size: 60% 44%, 38% 46%; -webkit-mask-position: 82% 84%, 62% 68%; mask-position: 82% 84%, 62% 68%; }
    75% { -webkit-mask-size: 48% 54%, 46% 40%; mask-size: 48% 54%, 46% 40%; -webkit-mask-position: 14% 82%, 34% 66%; mask-position: 14% 82%, 34% 66%; }
    100% { -webkit-mask-size: 52% 46%, 40% 40%; mask-size: 52% 46%, 40% 40%; -webkit-mask-position: 16% 20%, 30% 32%; mask-position: 16% 20%, 30% 32%; }
  }
  @keyframes ig-breathe { 0%, 100% { opacity: 0.55; } 50% { opacity: 1; } }
  .ig-res { position: absolute; top: 8px; right: 8px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; color: #a1a1a1; background: rgba(255, 255, 255, 0.72); padding: 2px 7px; border-radius: 999px; backdrop-filter: blur(4px); }
  .ig-meta { display: flex; flex-direction: column; gap: 2px; align-items: flex-start; text-align: left; width: 100%; max-width: 208px; }
  .ig-label { background: linear-gradient(90deg, #1a1a1a 0%, #1a1a1a 30%, rgba(26, 26, 26, 0.45) 45%, rgba(26, 26, 26, 0.45) 55%, #1a1a1a 70%, #1a1a1a 100%); background-size: 300% 100%; -webkit-background-clip: text; background-clip: text; color: transparent; -webkit-text-fill-color: transparent; animation: ig-shine 2.25s cubic-bezier(0.25, 0.1, 0.25, 1) infinite; font-weight: 550; font-size: 14px; }
  @keyframes ig-shine { 0%, 18% { background-position: 100% 0; } 82%, 100% { background-position: 0% 0; } }
  .ig-prompt { color: #a1a1a1; font-size: 13px; }
  @media (prefers-reduced-motion: reduce) {
    .ig-glow { animation: none; opacity: 0.7; }
    .ig-label { animation: none; -webkit-text-fill-color: #1a1a1a; color: #1a1a1a; }
  }
  @media (prefers-color-scheme: dark) {
    .ig-canvas { background: #1f1f1f; }
    .ig-glow { background-image: radial-gradient(circle, #f5f5f5 1.1px, transparent 1.6px); }
    .ig-res { color: #a3a3a3; background: rgba(26, 26, 26, 0.72); }
    .ig-label { background: linear-gradient(90deg, #f5f5f5 0%, #f5f5f5 30%, rgba(245, 245, 245, 0.45) 45%, rgba(245, 245, 245, 0.45) 55%, #f5f5f5 70%, #f5f5f5 100%); background-size: 300% 100%; -webkit-background-clip: text; background-clip: text; }
  }
</style>
```
