PROMPT DIRECTOR v5 — 장면 옵션·5플랫폼 출력·운영 코드 원문

자료 개요

캐릭터·장소·의상·포즈·카메라·조명 옵션을 조합하는 PROMPT DIRECTOR 운영 도구의 원문 코드와 실제 출력입니다.

출처: 마가삼 이미지 프롬프트 옵션 개발버전 v5. 확인일 2026-10-04.

개요와 기획서 위치

캐릭터명/LoRA 태그, 외형, 장소, 의상, 구도, 포즈, 카메라, 조명, 추가 문구, 네거티브의 10영역을 선택하여 5플랫폼의 프롬프트 문자열을 만드는 운영 UI. 게임기획서의 아트·음악·영상 및 기술·개발, 캐릭터 표현 제작 연결 재료다. 자미두수 상태 계산과 별 데이터는 이 코드에 없다.

확인 코드와 실제 실행

공개 페이지의 text/babel 원문 전체를 별도 JSX에 보존. 남성·30대·카페·정장·트래킹을 선택하고 GROK/MJ/FLUX/SDXL/GEMINI 출력 모두 확인했다. 출력은 별도 JSON에 원문 저장했다. 외부 생성 API와 LoRA 학습/로딩은 없다. LoRA 태그 입력은 문자열에 그대로 붙이는 기능이다. React/ReactDOM과 페이지 CSS 의존성이 있으며 JSX 하나가 독립 HTML 전체는 아니다.

실제 차이와 제한

MJ는 –ar 16-9를 출력한다. 코드의 ar.replace(‘:’,’-‘)와 일치하며 생성 플랫폼에서 유효한지는 이번에 검증하지 않았다. GEMINI는 네거티브를 출력하지 않고 tracking shot 또는 slow dolly in 선택일 때만 ‘타입: 동영상’을 붙인다. 다른 카메라 움직임에 동일 판정을 적용하지 않는다. FLUX와 SDXL은 비율을 출력하지 않는다. 초기화 후 실제 출력: 1boy, 30대, korean, 8k, highly detailed, masterpiece

비율: 16:9
초기화는 남성·30대와 플랫폼/비율/네거티브 설정을 원래 기본값으로 돌리지 않는다. 장소·의상·카메라 등은 지운다. 복사와 외부 이미지/영상 생성은 미검증이다. 복수 포즈/장소의 모순 해결 코드는 없다.

원칙 구분과 관련 자료

사용자 확정은 관련 자료를 추적해 독립 원문 글로 보존하라는 현재 지시. 과거 작성 주체·사용자 채택/수정/거부·최초 게시일·대화 ID는 미확인. 출처의 제미나이 표기는 제목에 있는 표시이며 원대화를 확보한 것은 아니다. 기존 캐릭터 일관성 제작 가이드 https://maniju.net/?p=900 의 장면·카메라 설계와 연결하며, 별 표현 조합기 https://maniju.net/?p=905 와 https://maniju.net/?p=907 을 대체하는 같은 데이터셋으로 간주하지 않는다.
Sol 제안: 캐릭터 표현 출력과 장면 옵션을 별도 필드로 보존하고 제작 시 조합하는 후보. 새 핵심 게임 시스템으로 중복 채택하지 않는다. 조사·텍스트 합성 확인 단계이며 기획 확정·콘텐츠 생성 성공은 아니다.

실제 실행 결과 원문

남성·30대·카페·정장·트래킹 선택 후 플랫폼만 바꿨습니다.

GROK

1boy, 30대, korean, cafe, suit, tracking shot, 8k, highly detailed, masterpiece

--ar 16:9

네거티브: bad anatomy, deformed, text, watermark, low quality, worst quality, mutated hands, extra fingers

MJ

/imagine prompt: 1boy, 30대, korean, cafe, suit, tracking shot, 8k, highly detailed, masterpiece --ar 16-9 --v 6.0 --no bad anatomy, deformed, text, watermark, low quality, worst quality, mutated hands, extra fingers

FLUX

1boy, 30대, korean, cafe, suit, tracking shot, 8k, highly detailed, masterpiece, without deformed body, no text or watermark, not low quality, without extra fingers

SDXL

Positive:
1boy, 30대, korean, cafe, suit, tracking shot, 8k, highly detailed, masterpiece

Negative:
bad anatomy, deformed, text, watermark, low quality, worst quality, mutated hands, extra fingers

GEMINI

1boy, 30대, korean, cafe, suit, tracking shot, 8k, highly detailed, masterpiece

비율: 16:9
타입: 동영상

초기화 후 GEMINI

1boy, 30대, korean, 8k, highly detailed, masterpiece

비율: 16:9

운영 JSX 원문 전체

공개 페이지의 text/babel 스크립트 전체입니다. 아래 코드는 읽기용으로 보존하며 이 글에서 실행하지 않습니다.

const { useState, useEffect } = React;

  // UI 헬퍼 컴포넌트
  const Btn = ({ on, onClick, children, size = 'md' }) => (
    <button 
      onClick={onClick} 
      className={`rounded border transition-all ${
        size === 'sm' ? 'px-2 py-1 text-xs' : 'px-3 py-2 text-sm'
      } ${
        on 
          ? 'bg-amber-500/20 text-amber-400 border-amber-500 font-bold' 
          : 'bg-zinc-900 text-zinc-400 border-zinc-700 hover:border-zinc-500 hover:text-zinc-300'
      }`}
    >
      {children}
    </button>
  );

  const Section = ({ num, icon, title, children }) => (
    <div className="border-b border-zinc-800 pb-4">
      <div className="text-amber-500 font-bold text-sm mb-3 flex items-center gap-2">
        <span className="bg-amber-500/20 text-amber-400 w-6 h-6 rounded flex items-center justify-center text-xs font-bold">{num}</span>
        <span>{icon}</span>
        {title}
      </div>
      {children}
    </div>
  );

  // 메인 컴포넌트
  function PromptDirector() {
    // 1. 기본
    const [gender, setGender] = useState('1girl');
    const [age, setAge] = useState('20대');
    const [nation, setNation] = useState('korean');
    const [charName, setCharName] = useState('');
    const [loraTag, setLoraTag] = useState('');
    
    // 2. 신체
    const [body, setBody] = useState({});
    
    // 3. 장소/시간/배경
    const [place, setPlace] = useState({});
    const [time, setTime] = useState('');
    
    // 4. 소도구/의상
    const [outfit, setOutfit] = useState({});
    const [props, setProps] = useState({});
    
    // 5. 상대/구도
    const [composition, setComposition] = useState('solo');
    
    // 6. 행동/포즈
    const [pose, setPose] = useState({});
    const [action, setAction] = useState({});
    
    // 7. 카메라
    const [cam, setCam] = useState({});
    
    // 8. 조명/효과
    const [light, setLight] = useState('');
    const [fx, setFx] = useState({ detail: true });
    
    // 9. 추가 문구
    const [extra, setExtra] = useState('');
    
    // 10. 네거티브
    const [neg, setNeg] = useState({ anatomy: true, text: true, quality: true, hands: true });
    
    // 출력 설정
    const [mode, setMode] = useState('GROK');
    const [ar, setAr] = useState('16:9');

    // 프롬프트 생성
    const generate = () => {
      let p = [];
      
      // 1. 기본
      p.push(gender);
      if (age) p.push(age);
      if (nation) p.push(nation);
      if (charName) p.push(charName);
      if (loraTag) p.push(loraTag);
      
      // 2. 신체
      Object.entries(body).forEach(([k,v]) => v && p.push(k));
      
      // 3. 장소/시간
      Object.entries(place).forEach(([k,v]) => v && p.push(k));
      if (time) p.push(time);
      
      // 4. 의상/소도구
      Object.entries(outfit).forEach(([k,v]) => v && p.push(k));
      Object.entries(props).forEach(([k,v]) => v && p.push(k));
      
      // 5. 구도
      if (composition && composition !== 'solo') p.push(composition);
      
      // 6. 포즈/행동
      Object.entries(pose).forEach(([k,v]) => v && p.push(k));
      Object.entries(action).forEach(([k,v]) => v && p.push(k));
      
      // 7. 카메라
      Object.entries(cam).forEach(([k,v]) => v && p.push(k));
      
      // 8. 조명/효과
      if (light) p.push(light);
      Object.entries(fx).forEach(([k,v]) => {
        if (v) {
          if (k === 'detail') p.push('8k, highly detailed, masterpiece');
          else if (k === 'bokeh') p.push('bokeh, shallow depth of field');
          else if (k === 'blur') p.push('motion blur');
          else if (k === 'grain') p.push('film grain');
          else if (k === 'flare') p.push('lens flare');
          else if (k === 'vignette') p.push('vignette');
        }
      });
      
      // 9. 추가 문구
      if (extra.trim()) p.push(extra.trim());
      
      const pos = p.join(', ');
      
      // 네거티브 (플랫폼별)
      let negList = [];
      if (neg.anatomy) negList.push('bad anatomy, deformed');
      if (neg.text) negList.push('text, watermark');
      if (neg.quality) negList.push('low quality, worst quality');
      if (neg.hands) negList.push('mutated hands, extra fingers');
      
      // 플랫폼별 출력
      if (mode === 'MJ') {
        const negStr = negList.join(', ');
        return `/imagine prompt: ${pos} --ar ${ar.replace(':','-')} --v 6.0${negStr ? ' --no ' + negStr : ''}`;
      }
      if (mode === 'FLUX') {
        let fluxNeg = [];
        if (neg.anatomy) fluxNeg.push('without deformed body');
        if (neg.text) fluxNeg.push('no text or watermark');
        if (neg.quality) fluxNeg.push('not low quality');
        if (neg.hands) fluxNeg.push('without extra fingers');
        return pos + (fluxNeg.length ? ', ' + fluxNeg.join(', ') : '');
      }
      if (mode === 'SDXL') {
        return `Positive:\n${pos}\n\nNegative:\n${negList.join(', ')}`;
      }
      if (mode === 'GEMINI') {
        return `${pos}\n\n비율: ${ar}${cam['tracking shot'] || cam['slow dolly in'] ? '\n타입: 동영상' : ''}`;
      }
      // GROK
      return `${pos}\n\n--ar ${ar}${negList.length ? '\n\n네거티브: ' + negList.join(', ') : ''}`;
    };

    const prompt = generate();

    return (
      <div className="bg-zinc-950 text-zinc-200 font-sans text-left">
        <div className="max-w-3xl mx-auto p-4 space-y-4">
          
          {/* 헤더 */}
          <div className="sticky top-0 bg-zinc-950/95 backdrop-blur border-b border-zinc-800 py-3 z-50">
            <div className="flex justify-between items-center">
              <h1 className="font-bold text-lg text-white m-0">🎬 PROMPT DIRECTOR</h1>
              <button 
                onClick={() => {
                  setBody({}); setPlace({}); setTime(''); setOutfit({}); setProps({});
                  setPose({}); setAction({}); setCam({}); setLight(''); setFx({detail:true});
                  setExtra(''); setCharName(''); setLoraTag('');
                }} 
                className="px-3 py-1 bg-red-600 text-white text-xs rounded hover:bg-red-500 border-none cursor-pointer"
              >
                초기화
              </button>
            </div>
          </div>

          <div className="bg-zinc-900 border border-zinc-800 rounded-lg p-4 space-y-4">

            {/* 1. 기본 정보 */}
            <Section num="1" icon="👤" title="기본 정보">
              <div className="space-y-3">
                <div className="flex flex-wrap gap-2">
                  <Btn on={gender==='1girl'} onClick={() => setGender('1girl')}>여성</Btn>
                  <Btn on={gender==='1boy'} onClick={() => setGender('1boy')}>남성</Btn>
                  <span className="text-zinc-600 mx-1 self-center">|</span>
                  {['10대','20대','30대','40대'].map(a => 
                    <Btn key={a} on={age===a} onClick={() => setAge(a)} size="sm">{a}</Btn>
                  )}
                  <span className="text-zinc-600 mx-1 self-center">|</span>
                  {[{v:'korean',l:'한국인'},{v:'japanese',l:'일본인'},{v:'chinese',l:'중국인'},{v:'western',l:'서양인'}].map(n =>
                    <Btn key={n.v} on={nation===n.v} onClick={() => setNation(n.v)} size="sm">{n.l}</Btn>
                  )}
                </div>
                <div className="flex gap-2">
                  <input 
                    value={charName} 
                    onChange={e => setCharName(e.target.value)} 
                    placeholder="캐릭터명 (예: 엽무천)" 
                    className="flex-1 bg-black border border-zinc-700 text-white text-sm p-2 rounded"
                  />
                  <input 
                    value={loraTag} 
                    onChange={e => setLoraTag(e.target.value)} 
                    placeholder="LoRA 태그 (예: yms_f)" 
                    className="flex-1 bg-black border border-zinc-700 text-white text-sm p-2 rounded"
                  />
                </div>
              </div>
            </Section>

            {/* 2. 신체 묘사 */}
            <Section num="2" icon="💃" title="신체 묘사">
              <div className="flex flex-wrap gap-2">
                {[
                  {v:'glamorous body',l:'글래머'},{v:'slim body',l:'슬림'},{v:'muscular',l:'근육질'},
                  {v:'curvy body',l:'볼륨감'},{v:'petite',l:'아담한'},{v:'tall',l:'키큰'},
                  {v:'big breasts',l:'거유'},{v:'long legs',l:'긴다리'},{v:'sexy',l:'섹시'},
                  {v:'beautiful face',l:'미인'},{v:'pretty face',l:'예쁜얼굴'},{v:'cold beauty',l:'냉미녀'},
                  {v:'long hair',l:'긴머리'},{v:'short hair',l:'단발'},{v:'black hair',l:'흑발'},
                  {v:'blonde hair',l:'금발'},{v:'brown hair',l:'갈색머리'},{v:'ponytail',l:'포니테일'},
                ].map(o => <Btn key={o.v} on={body[o.v]} onClick={() => setBody(p=>({...p,[o.v]:!p[o.v]}))} size="sm">{o.l}</Btn>)}
              </div>
            </Section>

            {/* 3. 장소/시간/배경 */}
            <Section num="3" icon="🏠" title="장소 / 시간 / 배경">
              <div className="space-y-2">
                <div className="flex flex-wrap gap-2">
                  {[
                    {v:'bedroom',l:'침실'},{v:'bathroom',l:'욕실'},{v:'living room',l:'거실'},
                    {v:'kitchen',l:'주방'},{v:'office',l:'사무실'},{v:'cafe',l:'카페'},
                    {v:'bar',l:'바'},{v:'club',l:'클럽'},{v:'hotel room',l:'호텔'},
                    {v:'beach',l:'해변'},{v:'pool',l:'수영장'},{v:'park',l:'공원'},
                    {v:'street',l:'거리'},{v:'rooftop',l:'옥상'},{v:'forest',l:'숲'},
                    {v:'gym',l:'헬스장'},{v:'studio',l:'스튜디오'},{v:'school',l:'학교'},
                  ].map(o => <Btn key={o.v} on={place[o.v]} onClick={() => setPlace(p=>({...p,[o.v]:!p[o.v]}))} size="sm">{o.l}</Btn>)}
                </div>
                <div className="flex flex-wrap gap-2 pt-2 border-t border-zinc-800">
                  {[
                    {v:'',l:'(시간무관)'},{v:'daytime',l:'낮'},{v:'night',l:'밤'},
                    {v:'sunset',l:'석양'},{v:'morning light',l:'아침빛'},{v:'golden hour',l:'골든아워'},
                  ].map(o => <Btn key={o.v} on={time===o.v} onClick={() => setTime(o.v)} size="sm">{o.l}</Btn>)}
                </div>
              </div>
            </Section>

            {/* 4. 의상/소도구 */}
            <Section num="4" icon="👗" title="의상 / 소도구">
              <div className="space-y-2">
                <div className="flex flex-wrap gap-2">
                  {[
                    {v:'bikini',l:'비키니'},{v:'underwear',l:'속옷'},{v:'lingerie',l:'란제리'},
                    {v:'dress',l:'드레스'},{v:'mini skirt',l:'미니스커트'},{v:'suit',l:'정장'},
                    {v:'uniform',l:'교복'},{v:'nurse outfit',l:'간호사복'},{v:'maid outfit',l:'메이드복'},
                    {v:'white t-shirt',l:'흰티셔츠'},{v:'jeans',l:'청바지'},{v:'shorts',l:'반바지'},
                    {v:'high heels',l:'하이힐'},{v:'stockings',l:'스타킹'},{v:'barefoot',l:'맨발'},
                    {v:'glasses',l:'안경'},{v:'choker',l:'초커'},{v:'earrings',l:'귀걸이'},
                  ].map(o => <Btn key={o.v} on={outfit[o.v]} onClick={() => setOutfit(p=>({...p,[o.v]:!p[o.v]}))} size="sm">{o.l}</Btn>)}
                </div>
                <div className="flex flex-wrap gap-2 pt-2 border-t border-zinc-800">
                  {[
                    {v:'on bed',l:'침대위'},{v:'on sofa',l:'소파위'},{v:'on chair',l:'의자위'},
                    {v:'against wall',l:'벽에기대'},{v:'near window',l:'창가'},{v:'in bathtub',l:'욕조안'},
                    {v:'holding phone',l:'폰들고'},{v:'holding cup',l:'컵들고'},{v:'holding bag',l:'가방들고'},
                  ].map(o => <Btn key={o.v} on={props[o.v]} onClick={() => setProps(p=>({...p,[o.v]:!p[o.v]}))} size="sm">{o.l}</Btn>)}
                </div>
              </div>
            </Section>

            {/* 5. 구도/상대 */}
            <Section num="5" icon="👥" title="구도 / 상대">
              <div className="flex flex-wrap gap-2">
                {[
                  {v:'solo',l:'혼자'},{v:'couple',l:'커플'},{v:'2girls',l:'여자둘'},
                  {v:'1boy 1girl',l:'남녀커플'},{v:'group',l:'그룹'},{v:'pov',l:'POV (1인칭)'},
                ].map(o => <Btn key={o.v} on={composition===o.v} onClick={() => setComposition(o.v)}>{o.l}</Btn>)}
              </div>
            </Section>

            {/* 6. 포즈/행동 */}
            <Section num="6" icon="🎭" title="포즈 / 행동">
              <div className="space-y-2">
                <div className="text-zinc-500 text-xs">기본 포즈</div>
                <div className="flex flex-wrap gap-2">
                  {[
                    {v:'standing',l:'서있기'},{v:'sitting',l:'앉기'},{v:'lying down',l:'눕기'},
                    {v:'kneeling',l:'무릎꿇기'},{v:'squatting',l:'쪼그려앉기'},{v:'leaning forward',l:'앞으로숙이기'},
                    {v:'arched back',l:'뒤로젖히기'},{v:'on all fours',l:'엎드리기'},{v:'lying on side',l:'옆으로눕기'},
                  ].map(o => <Btn key={o.v} on={pose[o.v]} onClick={() => setPose(p=>({...p,[o.v]:!p[o.v]}))} size="sm">{o.l}</Btn>)}
                </div>
                <div className="text-zinc-500 text-xs pt-2">팔/손</div>
                <div className="flex flex-wrap gap-2">
                  {[
                    {v:'arms up',l:'만세'},{v:'arms crossed',l:'팔짱'},{v:'arms behind back',l:'뒷짐'},
                    {v:'arms behind head',l:'머리뒤깍지'},{v:'hand on hip',l:'허리에손'},{v:'hand on face',l:'얼굴에손'},
                    {v:'peace sign',l:'브이'},{v:'finger to lips',l:'입술에손가락'},{v:'waving',l:'손흔들기'},
                  ].map(o => <Btn key={o.v} on={action[o.v]} onClick={() => setAction(p=>({...p,[o.v]:!p[o.v]}))} size="sm">{o.l}</Btn>)}
                </div>
                <div className="text-zinc-500 text-xs pt-2">다리/발</div>
                <div className="flex flex-wrap gap-2">
                  {[
                    {v:'crossed legs',l:'다리꼬기'},{v:'spread legs',l:'다리벌리기'},{v:'legs up',l:'다리올리기'},
                    {v:'lifted leg',l:'한쪽다리들기'},{v:'tiptoe',l:'까치발'},{v:'kicking',l:'발차기'},
                  ].map(o => <Btn key={o.v} on={action[o.v]} onClick={() => setAction(p=>({...p,[o.v]:!p[o.v]}))} size="sm">{o.l}</Btn>)}
                </div>
                <div className="text-zinc-500 text-xs pt-2">표정/시선</div>
                <div className="flex flex-wrap gap-2">
                  {[
                    {v:'smiling',l:'미소'},{v:'laughing',l:'웃음'},{v:'serious face',l:'진지'},
                    {v:'seductive look',l:'유혹'},{v:'shy expression',l:'수줍음'},{v:'looking at viewer',l:'카메라응시'},
                    {v:'looking away',l:'시선회피'},{v:'closed eyes',l:'눈감음'},{v:'winking',l:'윙크'},
                  ].map(o => <Btn key={o.v} on={action[o.v]} onClick={() => setAction(p=>({...p,[o.v]:!p[o.v]}))} size="sm">{o.l}</Btn>)}
                </div>
              </div>
            </Section>

            {/* 7. 카메라/앵글 */}
            <Section num="7" icon="📷" title="카메라 / 앵글">
              <div className="space-y-2">
                <div className="text-zinc-500 text-xs">샷 사이즈</div>
                <div className="flex flex-wrap gap-2">
                  {[
                    {v:'extreme close-up',l:'익스트림클로즈업'},{v:'close-up',l:'클로즈업(얼굴)'},
                    {v:'medium shot',l:'미디엄(상반신)'},{v:'cowboy shot',l:'카우보이(허벅지)'},
                    {v:'full body shot',l:'풀샷(전신)'},{v:'wide shot',l:'와이드(배경포함)'},
                  ].map(o => <Btn key={o.v} on={cam[o.v]} onClick={() => setCam(p=>({...p,[o.v]:!p[o.v]}))} size="sm">{o.l}</Btn>)}
                </div>
                <div className="text-zinc-500 text-xs pt-2">앵글</div>
                <div className="flex flex-wrap gap-2">
                  {[
                    {v:'front view',l:'정면'},{v:'side view',l:'옆면'},{v:'three quarter view',l:'3/4(얼짱각)'},
                    {v:'from behind',l:'뒷모습'},{v:'from above',l:'위에서'},{v:'from below',l:'아래에서'},
                    {v:'low angle',l:'로우앵글(웅장)'},{v:'high angle',l:'하이앵글'},{v:'dutch angle',l:'더치앵글(기울임)'},
                  ].map(o => <Btn key={o.v} on={cam[o.v]} onClick={() => setCam(p=>({...p,[o.v]:!p[o.v]}))} size="sm">{o.l}</Btn>)}
                </div>
                <div className="text-zinc-500 text-xs pt-2">카메라 무브 (영상용)</div>
                <div className="flex flex-wrap gap-2">
                  {[
                    {v:'slow dolly in',l:'돌리인'},{v:'dolly out',l:'돌리아웃'},
                    {v:'tracking shot',l:'트래킹'},{v:'pan shot',l:'팬'},
                    {v:'orbit shot',l:'오빗(회전)'},{v:'handheld',l:'핸드헬드'},
                  ].map(o => <Btn key={o.v} on={cam[o.v]} onClick={() => setCam(p=>({...p,[o.v]:!p[o.v]}))} size="sm">{o.l}</Btn>)}
                </div>
              </div>
            </Section>

            {/* 8. 조명/효과 */}
            <Section num="8" icon="💡" title="조명 / 효과">
              <div className="space-y-2">
                <div className="flex flex-wrap gap-2">
                  {[
                    {v:'',l:'(자연광)'},{v:'cinematic lighting',l:'시네마틱'},{v:'studio lighting',l:'스튜디오'},
                    {v:'soft lighting',l:'소프트'},{v:'dramatic lighting',l:'드라마틱'},{v:'rim lighting',l:'림라이트'},
                    {v:'neon lighting',l:'네온'},{v:'candlelight',l:'촛불'},{v:'backlight',l:'역광'},
                  ].map(o => <Btn key={o.v} on={light===o.v} onClick={() => setLight(o.v)} size="sm">{o.l}</Btn>)}
                </div>
                <div className="flex flex-wrap gap-2 pt-2 border-t border-zinc-800">
                  {[
                    {v:'detail',l:'8K 고퀄'},{v:'bokeh',l:'보케(배경흐림)'},{v:'blur',l:'모션블러'},
                    {v:'grain',l:'필름그레인'},{v:'flare',l:'렌즈플레어'},{v:'vignette',l:'비네팅'},
                  ].map(o => <Btn key={o.v} on={fx[o.v]} onClick={() => setFx(p=>({...p,[o.v]:!p[o.v]}))} size="sm">{o.l}</Btn>)}
                </div>
              </div>
            </Section>

            {/* 9. 추가 문구 */}
            <Section num="9" icon="✏️" title="추가 문구 (직접 입력)">
              <textarea 
                value={extra} 
                onChange={e => setExtra(e.target.value)} 
                placeholder="버튼에 없는 내용 직접 입력... (예: wet hair, sweating, detailed skin texture)" 
                className="w-full bg-black border border-zinc-700 text-white text-sm p-3 rounded h-20 resize-none"
              />
            </Section>

            {/* 10. 네거티브 */}
            <Section num="10" icon="🚫" title="네거티브 (제외할 것)">
              <div className="flex flex-wrap gap-2">
                <Btn on={neg.anatomy} onClick={() => setNeg(p=>({...p,anatomy:!p.anatomy}))} size="sm">기형/변형</Btn>
                <Btn on={neg.text} onClick={() => setNeg(p=>({...p,text:!p.text}))} size="sm">텍스트/워터마크</Btn>
                <Btn on={neg.quality} onClick={() => setNeg(p=>({...p,quality:!p.quality}))} size="sm">저퀄리티</Btn>
                <Btn on={neg.hands} onClick={() => setNeg(p=>({...p,hands:!p.hands}))} size="sm">손가락이상</Btn>
              </div>
            </Section>

          </div>

          {/* 최종 출력 */}
          <div className="bg-zinc-900 border border-amber-500/30 rounded-lg p-4 sticky bottom-4 z-40 shadow-lg">
            <div className="flex items-center justify-between mb-3">
              <div className="text-amber-500 font-bold text-sm">📋 최종 프롬프트</div>
              <div className="flex gap-2">
                <div className="flex rounded overflow-hidden border border-zinc-700">
                  {['GROK','MJ','FLUX','SDXL','GEMINI'].map(m => 
                    <button 
                      key={m} 
                      onClick={() => setMode(m)} 
                      className={`px-2 py-1 text-xs cursor-pointer border-none ${mode===m ? 'bg-amber-500 text-black font-bold' : 'bg-zinc-800 text-zinc-400 hover:text-white'}`}
                    >
                      {m}
                    </button>
                  )}
                </div>
                <select 
                  value={ar} 
                  onChange={e => setAr(e.target.value)} 
                  className="bg-black border border-zinc-700 text-white text-xs px-2 rounded"
                >
                  {['16:9','9:16','4:3','3:4','1:1','2.35:1'].map(a => <option key={a} value={a}>{a}</option>)}
                </select>
              </div>
            </div>
            
            <textarea 
              value={prompt} 
              readOnly 
              className="w-full h-32 bg-black border border-zinc-700 text-green-400 font-mono text-xs p-3 rounded resize-none mb-3"
            />
            
            <button 
              onClick={() => navigator.clipboard.writeText(prompt)} 
              className="w-full bg-amber-500 hover:bg-amber-400 text-black font-bold py-3 rounded text-sm transition-colors cursor-pointer border-none"
            >
              📋 복사하기
            </button>
          </div>

        </div>
      </div>
    );
  }

  // 4. 렌더링 실행
  const rootElement = document.getElementById('prompt-director-root');
  const root = ReactDOM.createRoot(rootElement);
  root.render(<PromptDirector />);

다음 연결

기획 담당 GPT는 원문과 실제 출력을 읽어 캐릭터 표현과 장면 옵션의 공통·차이를 판단할 수 있습니다. 원대화 채택 이력과 생성된 이미지·영상은 후속 추적 대상입니다.

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다