'dramatic photorealistic scene', 'split_metaphor' => 'split-panel photorealistic scene', 'cinematic_poster' => 'cinematic poster', 'photorealistic' => 'photorealistic scene', 'technical_infographic' => 'icon and symbol grid', 'flat_vector' => 'flat vector icon illustration', 'cartoon' => 'cartoon illustration', 'neon_cyberpunk' => 'neon cyberpunk scene', ); /** Retired style => the kept style nearest to it. */ const RETIRED_STYLES = array( 'editorial' => 'photorealistic', 'isometric' => 'flat_vector', 'minimalist' => 'flat_vector', 'blueprint' => 'technical_infographic', 'comic_book' => 'cartoon', 'retro_80s' => 'neon_cyberpunk', 'dark_abstract' => 'neon_cyberpunk', ); /** Styles drawn as icons and symbols rather than a scene. */ const ICON_STYLES = array( 'technical_infographic', 'flat_vector' ); /** Styles that are dark by nature, so "Auto (match style)" gives them a dark background. */ const DARK_STYLES = array( 'neon_cyberpunk' ); /** * A saved or posted image style, with a retired one mapped to its nearest kept style. * * @param string $style Image style value. * @return string */ public static function canonical_style( string $style ): string { return self::RETIRED_STYLES[ $style ] ?? $style; } /** * What the image style is called in the brief. * * @param string $style Image style value. * @return string */ public static function style_name( string $style ): string { return self::STYLE_NAMES[ self::canonical_style( $style ) ] ?? 'dramatic photorealistic scene'; } /** * The background actually used. "Auto (match style)" now does what it says: a style that is * dark by nature gets the dark background, every other style a light one. * * WHY: every prompt carried "bright, high-key, never predominantly black" twice, in the brief * and appended at the end, while Neon Cyberpunk, Dark Abstract and Retro 80s asked for dark * scenes. The image model split the difference and the result was muddy. The Background * drop down is where the editor says light or dark, so it decides, and the brightness rule * only applies when the answer is light. * * @param string $bg_color Background chosen in the dialog. * @param string $style Image style. * @return string A background key, 'dark' when dark. */ public static function effective_background( string $bg_color, string $style ): string { if ( 'auto' === $bg_color || '' === $bg_color ) { return in_array( self::canonical_style( $style ), self::DARK_STYLES, true ) ? 'dark' : 'auto'; } return $bg_color; } /** Background key => how the brief describes it. */ const BACKGROUNDS = array( 'light_grey' => 'pale grey or off-white background', 'warm_cream' => 'warm cream or golden-amber background', 'white' => 'clean pure white background', 'sky_blue' => 'bright open sky or soft blue background', 'gradient' => 'soft light-to-mid-tone gradient background', 'dark' => 'dark charcoal or near-black background with dramatic lighting', ); /** * The brief's background and lighting lines, for the effective background. * * @param string $bg Effective background, from effective_background(). * @return string Two lines, each starting "- ", newline-terminated. */ public static function light_lines( string $bg ): string { $background = isset( self::BACKGROUNDS[ $bg ] ) ? 'Background: ' . self::BACKGROUNDS[ $bg ] . '.' : 'Background: light, pale grey, off-white, warm cream, open sky, or stadium light. Never black or near-black.'; $lighting = 'dark' === $bg ? 'Lighting: a dark, dramatic image is wanted. Keep the main subject strongly and clearly lit against the dark, with vivid accent light, so it stays legible at thumbnail size.' : 'Lighting: bright, high-key and evenly lit; the overall image must read as light and clearly legible at thumbnail size. Even for night, moody or \'dark side\' scenes, keep the subject strongly lit and the exposure bright, never a predominantly black, murky, silhouetted or underexposed image.'; return '- ' . $background . "\n- " . $lighting . "\n"; } /** * What is appended to the writer's finished prompt about background and brightness. * * @param string $bg Effective background, from effective_background(). * @return string Starts with a space. */ public static function final_light_rules( string $bg ): string { $finals = array( 'light_grey' => 'pale grey or off-white background', 'warm_cream' => 'warm cream or golden-amber background', 'white' => 'clean pure white background, no dark areas', 'sky_blue' => 'bright open sky or soft blue background', 'gradient' => 'soft light-to-mid-tone gradient background', 'dark' => 'dark charcoal or near-black background', ); $out = isset( $finals[ $bg ] ) ? ' Background must be: ' . $finals[ $bg ] . '.' : ''; if ( 'dark' === $bg ) { return $out . ' Even on this dark background, the main subject must be brightly and clearly lit and fully legible: no murky, silhouetted or underexposed subject.'; } // gpt-image-2 otherwise tends to render dark, murky, underexposed headers that are // unreadable as a small thumbnail. return $out . ' CRITICAL: the image must be bright, high-key and clearly legible at small thumbnail size, a well-lit subject and a light overall exposure, with NO predominantly black, murky, silhouetted or underexposed areas anywhere in the frame.'; } /** * How each article style is described to the prompt writer. * * @return array */ public static function article_labels(): array { return array( 'technology' => 'technology article covering software, hardware, or developer tools', 'security' => 'cybersecurity or information security article', 'tutorial' => 'tutorial or step-by-step how-to guide', 'news' => 'news article or announcement', 'product_review' => 'product or service review', 'opinion' => 'opinion piece or editorial commentary', 'finance' => 'finance, business, or economics article', 'health' => 'health, wellness, or medical article', self::INFOGRAPHIC => 'article to be explained as a branded, multi-panel infographic', 'general' => 'general interest article', ); } /** * Whether this article style is the infographic. * * @param string $article_style Chosen article style. * @return bool */ public static function is_infographic( string $article_style ): bool { return self::INFOGRAPHIC === $article_style; } /** * Whether this article style must show the brands the post is about. * * @param string $article_style Chosen article style. * @return bool */ public static function pushes_branding( string $article_style ): bool { return in_array( $article_style, self::BRAND_STYLES, true ); } /** * What kind of picture is being asked for. * * @param string $article_style Chosen article style. * @param string $style_name Name of the chosen image style. * @return string */ public static function format_name( string $article_style, string $style_name ): string { return self::is_infographic( $article_style ) ? 'branded multi-panel marketing infographic' : $style_name . ' blog header'; } /** * The composition instruction. The infographic replaces the image style's own. * * @param string $article_style Chosen article style. * @param string $default The image style's instruction. * @return string */ public static function visual_approach( string $article_style, string $default ): string { if ( ! self::is_infographic( $article_style ) ) { return $default; } return 'Design a polished, modern editorial INFOGRAPHIC that explains this article at a glance, not a scene: a clean white or very light layout of soft rounded cards, in the lead brand\'s colours (or blue, violet and green accents when the article is not about one brand). ' . 'Build it from these parts, arranged as a tidy grid across the wide frame, keeping only the parts this article can fill with real content: ' . '(1) HEADLINE BAND across the top: the subject as a very large, heavy headline with its key words in an accent colour, and a one-line subtitle under it; the lead brand\'s logo beside it when there is one. ' . '(2) NUMBERED STEPS: three or four numbered cards joined by arrows, each a one-word title in capitals, one short sentence and a simple line icon, showing how the thing works. ' . '(3) CENTRAL DIAGRAM, the largest part: an illustrated picture of the idea itself, for example a photorealistic person using it with a speech bubble, the product, agent or app at the centre (a friendly character or a crisp phone or screen mock-up), and dotted connector lines out to labelled cards for each system, company or component involved. ' . '(4) RESULT CARDS: two or three cards that each show a concrete outcome as a small table or list of real figures from the article, the best row highlighted with a tick and a pill label under it. ' . '(5) KEY POINTS ROW: a bold section heading, then four to six small icons each with a two-to-three word title and one short line. ' . '(6) FOOTER STRIP: one bold takeaway sentence in a tinted pill. ' . 'Take every heading, label, number and name from THIS article: nothing generic, nothing invented. ' . 'Flat vector cards with soft shadows and rounded corners, generous spacing, one consistent icon style, strong type hierarchy, photorealistic people.'; } /** * The closing requirement lines of the brief: composition, branding and the text rule. * * @param string $article_style Chosen article style. * @param string $known_marks Brand shapes already recognised in the post, or ''. * @return string Lines, each starting "- ", joined with newlines, no trailing newline. */ public static function closing_lines( string $article_style, string $known_marks = '' ): string { $lines = array(); if ( self::is_infographic( $article_style ) ) { $lines[] = '- Compose as one balanced infographic that fills the frame edge to edge, with a clear reading order from the headline down and the headline legible at thumbnail size.'; } else { $lines[] = '- Compose for thumbnail impact: dominant subject filling the frame, bold contrast, strong focal point.'; } if ( self::pushes_branding( $article_style ) ) { // What the owner asked for, and all of it: when the article is about a specific // technology, use that technology's logo. The first version asked for far more (brand // colours setting the palette, the lead logo "one of the largest elements", logos "on // the product, the app screen, the device, the packaging"), and the image model obeyed: // supermarket uniforms on a post about people, the React atom on a post about how people // react, and on an x86 versus ARM post a second laptop with a logo on its screen and its // stand. So one logo, once, as a badge, and none when the article is not about one. if ( self::is_infographic( $article_style ) ) { // An infographic is built from labelled cards, one per system or company, so a // logo belongs on each card it names. Technology's "one badge" rule is wrong // here; what stays the same is that nothing unnamed gets a logo. $lines[] = '- Logos: first list every technology, product and company this article EXPLICITLY names. Use the real logo of each where it belongs in the layout: the main one beside the headline, the others on the card, label or diagram node for that system or company. ' . 'Each logo once, drawn correctly, sized to its card. Name each one in your prompt so the image model can draw it. ' . 'Never add a logo for anything the article does not name in words, never draw an invented or generic logo, and never use a logo as decoration. If the article names none, use no logos at all and plain line icons instead.' . ( '' !== $known_marks ? ' Names found by keyword (a word match, not a judgement: use one only if the article genuinely names it): ' . $known_marks . '.' : '' ); } else { $lines[] = '- Logo: ONLY if this article is about a specific named technology, product or company, show its REAL logo (each one\'s, when the article compares two), once, small to medium, drawn correctly as a single badge or sign in the scene. ' . 'Do not put logos on screens, devices, clothing or packaging, never repeat a logo, never draw an invented or generic logo, and never show a brand the article does not mention. ' . 'If the article is about people, ideas or a general topic rather than a named technology, show no logos at all.' . ( '' !== $known_marks ? ' Names found by keyword (a word match, not a judgement: use one only if the article is genuinely about it): ' . $known_marks . '.' : '' ); } } if ( self::is_infographic( $article_style ) ) { $lines[] = '- Text (required): this image is read, so it must contain text. Write out EVERY piece of text exactly, in double quotes, in your prompt: the headline, the tagline, each heading, each label. ' . 'Keep each item short (headings up to five words, lines up to twelve) and no more than about forty items in all, every one correctly spelled and taken from the article. Say that all text must be sharp, correctly spelled and fully legible.'; } elseif ( self::pushes_branding( $article_style ) ) { $lines[] = '- No text or words in the image, EXCEPT the real logos and wordmarks of the brands named above, which must be spelled correctly. With no brand named, no text at all.'; } else { $lines[] = '- No text or words in the image.'; } return implode( "\n", $lines ); } /** * Reply length allowed for the prompt writer. An infographic prompt spells out its text. * * @param string $article_style Chosen article style. * @return int */ public static function prompt_tokens( string $article_style ): int { return self::is_infographic( $article_style ) ? 1400 : 600; } /** * What is appended to the writer's finished prompt, so the image model cannot ignore the * article style: the text rule and the branding rule. Never appended to the editor's own * prompt, see sent_prompt(). * * @param string $article_style Chosen article style. * @param bool $no_text Whether the editor ticked "no text". * @return string Starts with a space, or is empty. */ public static function final_rules( string $article_style, bool $no_text ): string { $out = ''; if ( self::is_infographic( $article_style ) ) { // An infographic without text is not one, so the tick box does not apply to it. $out .= ' This is an infographic: every heading and label quoted above must appear, sharp, correctly spelled and fully legible, with no garbled or invented words.'; } elseif ( $no_text && self::pushes_branding( $article_style ) ) { $out .= ' Important: NO text, words, letters, numbers, labels, captions, or typography anywhere in the image, EXCEPT the real logos and wordmarks of the brands named above.'; } elseif ( $no_text ) { $out .= ' Important: NO text, words, letters, numbers, labels, captions, or typography of any kind anywhere in the image.'; } // The writer's prompt names no brand as often as not. This used to say "Branding is // essential" about "the brands named above", and the image model supplied a brand when // there was none. if ( self::pushes_branding( $article_style ) ) { $out .= self::is_infographic( $article_style ) ? ' Logos: only the real logos of the technologies and companies this prompt names, each once, on the card or label it belongs to. None for anything it does not name, and none at all if it names none.' : ' Logos: only the real logo of a technology this prompt names, once, as a single badge; never on a screen or device, never repeated. If it names none, show no logos or brand names at all.'; } return $out; } /** * The prompt as it goes to the image model. * * A prompt the editor wrote goes exactly as written: the dialog says so, and on 2026-10-12 * a custom prompt about a room of people came back with a supermarket's logo because the * rules below were appended to it. A prompt the writer produced gets the directive and * the rules, so the image model cannot ignore the choices made in the dialog. * * @param string $prompt The prompt before anything is appended. * @param string $source 'custom' when the editor wrote it, otherwise 'auto'. * @param string $article_style Chosen article style. * @param bool $no_text Whether the editor ticked "no text". * @param string $bg Effective background, from effective_background(). * @param string $directive The editor's directive, or empty. * @return string */ public static function sent_prompt( string $prompt, string $source, string $article_style, bool $no_text, string $bg, string $directive ): string { if ( 'custom' === $source ) { return $prompt; } if ( '' !== $directive ) { $prompt .= ' Editor directive (must be reflected in the image): ' . $directive; } return $prompt . self::final_rules( $article_style, $no_text ) . self::final_light_rules( $bg ); } /** * Whether the post title may be stamped over the finished image. * * @param string $article_style Chosen article style. * @return bool False for an infographic, which carries its own headline. */ public static function allows_title_overlay( string $article_style ): bool { return ! self::is_infographic( $article_style ); } }