<?php final class PageGenerator { private const COMMON_WORDS = [ 'all', 'and', 'boy', 'car', 'cat', 'day', 'end', 'family', 'home', 'it', 'man', 'name', 'one', 'people', 'read', 'school', 'speak', 'the', 'this', 'you', 'ask', 'book', 'can', 'dog', 'eye', 'first', 'go', 'he', 'child', 'in', 'learn', 'morning', 'open', 'play', 'question', 'room', 'say', 'start', 'today', 'word', 'about', 'at', 'brother', 'drink', 'easy', 'father', 'girl', 'help', 'chair', 'know', 'my', 'new', 'paper', 'please', 'rich', 'she', 'show', 'son', 'they', 'what', 'always', 'be', 'body', 'careful', 'cry', 'door', 'everything', 'face', 'her', 'if', 'many', 'no', 'pen', 'place', 'road', 'stop', 'student', 'two', 'want', 'where', 'answer', 'between', 'clear', 'country', 'dance', 'do', 'each', 'friend', 'his', 'job', 'life', 'more', 'park', 'person', 'ready', 'second', 'soon', 'that', 'we', 'why', 'able', 'before', 'but', 'clean', 'close', 'dream', 'eight', 'for', 'hand', 'inside', 'now', 'or', 'picture', 'river', 'ship', 'shop', 'sit', 'table', 'very', 'write', 'air', 'black', 'cinema', 'daughter', 'eat', 'from', 'good', 'head', 'cheese', 'important', 'land', 'money', 'pay', 'problem', 'run', 'same', 'see', 'send', 'thing', 'work', 'any', 'as', 'better', 'cold', 'come', 'doctor', 'find', 'game', 'idea', 'kind', 'live', 'make', 'peace', 'popular', 'right', 'small', 'so', 'some', 'there', 'wait', 'again', 'back', 'could', 'document', 'egg', 'fire', 'give', 'chance', 'information', 'light', 'may', 'often', 'prefer', 'put', 'red', 'stone', 'such', 'think', 'understand', 'visit', 'around', 'best', 'call', 'cut', 'dinner', 'down', 'explain', 'get', 'interesting', 'long', 'move', 'out', 'page', 'reach', 'rest', 'set', 'should', 'stand', 'time', 'up', 'age', 'because', 'big', 'camera', 'city', 'dress', 'evening', 'free', 'have', 'ill', 'like', 'mother', 'old', 'police', 'remember', 'street', 'study', 'teacher', 'voice', 'water', 'also', 'box', 'class', 'difficult', 'drive', 'food', 'great', 'happy', 'change', 'juice', 'meet', 'need', 'pretty', 'quite', 'real', 'sad', 'spring', 'star', 'take', 'yes', 'action', 'alone', 'breakfast', 'continue', 'dead', 'enjoy', 'full', 'garden', 'house', 'journey', 'much', 'nothing', 'phone', 'price', 'result', 'sister', 'sun', 'tell', 'view', 'with', 'against', 'bus', 'company', 'desert', 'expensive', 'flower', 'green', 'church', 'impossible', 'leave', 'month', 'on', 'plan', 'possible', 'return', 'save', 'sea', 'something', 'together', 'woman', 'anything', 'army', 'bad', 'cover', 'culture', 'decision', 'example', 'feel', 'how', 'island', 'member', 'next', 'position', 'present', 'record', 'sleep', 'sweet', 'try', 'under', 'world', 'after', 'bed', 'buy', 'catch', 'corner', 'distance', 'education', 'fast', 'here', 'interest', 'letter', 'never', 'part', 'president', 'round', 'several', 'sound', 'story', 'talk', 'week', 'almost', 'bread', 'control', 'dear', 'every', 'few', 'gold', 'chief', 'invite', 'late', 'most', 'only', 'product', 'public', 'receive', 'sorry', 'strong', 'then', 'too', 'way', 'across', 'art', 'bring', 'carry', 'confirm', 'die', 'east', 'group', 'hope', 'industry', 'look', 'must', 'own', 'personal', 'reason', 'service', 'shall', 'stay', 'their', 'wife', 'away', 'beautiful', 'care', 'cost', 'deep', 'enough', 'fight', 'garage', 'into', 'keep', 'miss', 'other', 'player', 'rather', 'remain', 'side', 'south', 'true', 'use', 'who', 'already', 'become', 'cause', 'certain', 'describe', 'dry', 'expect', 'fact', 'hard', 'include', 'let', 'moment', 'power', 'provide', 'report', 'seat', 'single', 'system', 'through', 'which', 'apple', 'blue', 'clock', 'colour', 'different', 'earth', 'film', 'glad', 'hour', 'just', 'love', 'number', 'pencil', 'quick', 'rain', 'simple', 'summer', 'town', 'tree', 'window', 'address', 'building', 'computer', 'cross', 'desk', 'ear', 'fish', 'glass', 'ice', 'key', 'minute', 'office', 'parent', 'post', 'rock', 'search', 'sport', 'tea', 'valley', 'walk', 'airport', 'baby', 'card', 'central', 'direction', 'dollar', 'fruit', 'gift', 'high', 'illness', 'milk', 'not', 'piece', 'protect', 'race', 'since', 'slow', 'smile', 'ticket', 'well', 'accident', 'blood', 'business', 'during', 'even', 'floor', 'general', 'choose', 'inform', 'little', 'meeting', 'order', 'party', 'pink', 'reply', 'snow', 'sugar', 'travel', 'virus', 'watch', 'another', 'believe', 'both', 'crazy', 'cup', 'decide', 'ever', 'field', 'heart', 'imagine', 'line', 'meat', 'over', 'pull', 'ring', 'sell', 'similar', 'speed', 'than', 'your', 'above', 'begin', 'century', 'consider', 'dangerous', 'dark', 'exchange', 'government', 'hear', 'jump', 'material', 'near', 'past', 'produce', 'remove', 'secret', 'song', 'television', 'value', 'when', 'arm', 'behind', 'case', 'collect', 'draw', 'examine', 'fall', 'grow', 'immediately', 'low', 'mind', 'off', 'pass', 'radio', 'shoe', 'station', 'sure', 'test', 'usual', 'while', 'ago', 'along', 'bear', 'condition', 'direct', 'edge', 'fine', 'half', 'chicken', 'increase', 'magazine', 'nature', 'plate', 'poor', 'respect', 'sharp', 'sometimes', 'still', 'tall', 'would', 'add', 'among', 'built', 'common', 'depend', 'early', 'fly', 'happen', 'check', 'introduce', 'less', 'mark', 'patient', 'perhaps', 'rise', 'sense', 'short', 'state', 'turn', 'will', 'act', 'appear', 'break', 'course', 'court', 'discuss', 'effect', 'form', 'hold', 'insect', 'mean', 'once', 'purpose', 'really', 'ride', 'situation', 'success', 'though', 'upon', 'war', 'afternoon', 'busy', 'coffee', 'detail', 'especially', 'finish', 'ground', 'holiday', 'choice', 'kitchen', 'lesson', 'music', 'orange', 'perfect', 'request', 'season', 'sick', 'tomorrow', 'welcome', 'yesterday', 'agree', 'bridge', 'cake', 'customer', 'date', 'enter', 'future', 'gentleman', 'hair', 'image', 'language', 'market', 'plane', 'private', 'restaurant', 'size', 'sky', 'smart', 'thank', 'weather', 'actor', 'bottle', 'cloth', 'coat', 'destroy', 'everywhere', 'finger', 'guide', 'improve', 'knife', 'large', 'mistake', 'ocean', 'plant', 'repeat', 'salt', 'special', 'teach', 'uncle', 'winter', 'angry', 'article', 'build', 'dirty', 'except', 'famous', 'gas', 'hotel', 'cheap', 'interview', 'moon', 'nice', 'prepare', 'prison', 'rice', 'seem', 'skirt', 'strange', 'train', 'warm', 'account', 'bird', 'cloud', 'comfortable', 'damage', 'dust', 'exercise', 'favourite', 'hospital', 'joke', 'message', 'night', 'paint', 'pleasure', 'relationship', 'science', 'serious', 'spend', 'tired', 'wrong', 'amount', 'bank', 'brown', 'crowd', 'deal', 'engine', 'follow', 'chocolate', 'individual', 'left', 'meal', 'oil', 'pain', 'probably', 'replace', 'society', 'square', 'step', 'temperature', 'university', 'accept', 'advance', 'bag', 'captain', 'centre', 'demand', 'enemy', 'factory', 'hungry', 'illegal', 'law', 'nose', 'petrol', 'proud', 'responsible', 'store', 'successful', 'swim', 'top', 'win', 'available', 'boat', 'borrow', 'coast', 'cream', 'design', 'expression', 'farm', 'history', 'injure', 'map', 'obtain', 'peaceful', 'practise', 'recently', 'shape', 'silver', 'smoke', 'touch', 'wash', 'advantage', 'attack', 'butter', 'club', 'college', 'degree', 'escape', 'gate', 'independent', 'listen', 'marry', 'object', 'path', 'quiet', 'refuse', 'subject', 'supply', 'taste', 'usually', 'vegetable', 'arrange', 'below', 'cigarette', 'cottage', 'department', 'earn', 'front', 'gentle', 'hat', 'instrument', 'machine', 'newspaper', 'parcel', 'religion', 'repair', 'serve', 'shoulder', 'trip', 'village', 'wall', 'arrive', 'born', 'clothes', 'correct', 'double', 'english', 'forget', 'goal', 'hate', 'kill', 'last', 'main', 'pair', 'promise', 'regular', 'somewhere', 'space', 'these', 'useful', 'without', 'animal', 'beer', 'calm', 'copy', 'dish', 'express', 'foreign', 'guess', 'husband', 'lie', 'mine', 'opinion', 'passenger', 'press', 'rule', 'sign', 'support', 'those', 'wonderful', 'year', 'afraid', 'board', 'circle', 'count', 'death', 'discover', 'funny', 'guest', 'horse', 'lake', 'modern', 'necessary', 'plenty', 'profit', 'reduce', 'share', 'steal', 'trust', 'wish', 'young', 'admire', 'allow', 'battle', 'climb', 'complete', 'divide', 'effort', 'fresh', 'hole', 'indeed', 'marriage', 'outside', 'pleasant', 'point', 'recent', 'secretary', 'sing', 'soft', 'third', 'various', 'adventure', 'although', 'bottom', 'coin', 'comfort', 'drop', 'equal', 'gun', 'intelligent', 'join', 'laugh', 'middle', 'perform', 'plain', 'row', 'soldier', 'surface', 'thick', 'until', 'wild', 'attempt', 'bill', 'breathe', 'cook', 'defend', 'fat', 'grey', 'hot', 'character', 'import', 'lose', 'mountain', 'operation', 'prize', 'risk', 'safe', 'suddenly', 'suit', 'type', 'wood', 'area', 'asleep', 'bath', 'careless', 'delay', 'event', 'foot', 'hide', 'chain', 'international', 'match', 'nervous', 'pity', 'prove', 'raise', 'shut', 'smell', 'straight', 'trade', 'variety', 'admit', 'attend', 'branch', 'coal', 'consist', 'declare', 'exact', 'farmer', 'instead', 'jacket', 'leg', 'metal', 'opposite', 'pound', 'roll', 'score', 'shoot', 'speech', 'toilet', 'whose', 'attitude', 'brave', 'contain', 'doubt', 'experience', 'flat', 'guard', 'heavy', 'charge', 'iron', 'medicine', 'noise', 'politics', 'pour', 'rush', 'smooth', 'spread', 'suggest', 'trouble', 'west', 'average', 'belong', 'certainly', 'crime', 'duty', 'either', 'fail', 'health', 'influence', 'leader', 'measure', 'offer', 'pile', 'regard', 'rough', 'series', 'spoil', 'spot', 'thin', 'vote', ]; private const ARTICLES = ['the', 'this', 'that', 'some', 'many', 'all', 'each', 'no']; private const PRONOUNS = ['he', 'she', 'it', 'they', 'we', 'you', 'who']; private const ADJECTIVES = [ 'big', 'small', 'red', 'blue', 'green', 'good', 'bad', 'happy', 'sad', 'fast', 'slow', 'hot', 'cold', 'rich', 'poor', 'old', ]; private const NOUNS = [ 'boy', 'girl', 'man', 'woman', 'child', 'dog', 'cat', 'car', 'book', 'room', 'family', 'water', 'money', 'time', 'day', ]; private const VERBS = [ 'eat', 'drink', 'play', 'read', 'speak', 'learn', 'say', 'start', 'know', 'help', 'show', 'cry', 'stop', 'want', ]; private const AUXILIARIES = ['will', 'can', 'must', 'may', 'should', 'could', 'would']; private const ADVERBS = ['today', 'always', 'never', 'soon', 'now', 'often', 'again']; private const CONJUNCTIONS = ['and', 'but', 'or', 'so', 'because']; private const FONTS = [ 'sans-serif', 'serif', 'Georgia, serif', 'Verdana, sans-serif', '"Courier New", monospace', 'Tahoma, sans-serif', ]; private const BACKGROUNDS = ['#ffffff', '#f9f9f9', '#f0f4f8', '#fff8f0', '#f5f5f5', '#fefce8']; private const FOREGROUNDS = ['#111', '#222', '#333', '#1a1a2e']; private const ACCENTS = ['#0066cc', '#2a9d8f', '#e63946', '#6a4c93', '#457b9d', '#e76f51']; private const IMAGE_WIDTHS = [150, 200, 300, 400, 600]; private const IMAGE_HEIGHTS = [100, 150, 200, 300]; public function generate(): string { $title = $this->title(); $sections = random_int( 2, 5 ); $html = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n" . "<meta charset=\"utf-8\">\n" . "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n" . '<title>' . self::esc( $title ) . "</title>\n" . '<style>' . $this->css() . "</style>\n" . "</head>\n<body>\n"; if( random_int( 0, 1 ) ) { $html .= $this->nav(); } $html .= '<h1>' . self::esc( $title ) . "</h1>\n" . '<p>' . $this->paragraph( 3, 5 ) . "</p>\n"; for( $i = 0; $i < $sections; $i++ ) { $html .= $this->section() . "\n"; } $html .= $this->footer() . "\n</body>\n</html>"; return $html; } private static function esc( string $s ): string { return htmlspecialchars( $s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8' ); } private static function pick( array $pool ): string { return $pool[array_rand( $pool )]; } private function words( int $min, int $max ): string { $count = random_int( $min, $max ); $out = ''; for( $i = 0; $i < $count; $i++ ) { $out .= self::pick( self::COMMON_WORDS ); if( $i < $count - 1 ) { $out .= ' '; } } return $out; } private function sentence(): string { $structs = [ [self::ARTICLES, self::ADJECTIVES, self::NOUNS, self::AUXILIARIES, self::VERBS, self::ARTICLES, self::ADJECTIVES, self::NOUNS, self::ADVERBS], [self::PRONOUNS, self::AUXILIARIES, self::VERBS, self::ARTICLES, self::ADJECTIVES, self::NOUNS], [self::ARTICLES, self::ADJECTIVES, self::NOUNS, self::VERBS, self::NOUNS, self::ADVERBS], [self::PRONOUNS, self::VERBS, self::NOUNS, self::CONJUNCTIONS, self::ADJECTIVES, self::NOUNS], [self::ADVERBS, self::PRONOUNS, self::VERBS, self::ARTICLES, self::NOUNS], ]; $pattern = self::pick( $structs ); $out = []; foreach( $pattern as $pool ) { $out[] = self::pick( $pool ); } return ucfirst( implode( ' ', $out ) ) . '.'; } private function paragraph( int $minSentences = 2, int $maxSentences = 6 ): string { $count = random_int( $minSentences, $maxSentences ); $sentences = []; for( $i = 0; $i < $count; $i++ ) { $sentences[] = $this->sentence(); } return implode( ' ', $sentences ); } private function title(): string { return ucwords( $this->words( 2, 6 ) ); } private function heading( int $level = 2 ): string { $text = $this->title(); return "<h{$level}>{$text}</h{$level}>"; } private function itemList( bool $ordered = false ): string { $tag = $ordered ? 'ol' : 'ul'; $count = random_int( 3, 7 ); $items = ''; for( $i = 0; $i < $count; $i++ ) { $items .= '<li>' . ucfirst( $this->words( 2, 8 ) ) . '</li>'; } return "<{$tag}>{$items}</{$tag}>"; } private function table(): string { $cols = random_int( 2, 4 ); $rows = random_int( 2, 5 ); $html = '<table border="1" cellpadding="6" cellspacing="0"><tr>'; for( $c = 0; $c < $cols; $c++ ) { $html .= '<th>' . ucfirst( self::pick( self::COMMON_WORDS ) ) . '</th>'; } $html .= '</tr>'; for( $r = 0; $r < $rows; $r++ ) { $html .= '<tr>'; for( $c = 0; $c < $cols; $c++ ) { $html .= '<td>' . ucfirst( $this->words( 1, 3 ) ) . '</td>'; } $html .= '</tr>'; } $html .= '</table>'; return $html; } private function blockquote(): string { return '<blockquote><p>' . $this->sentence() . '</p></blockquote>'; } private function link(): string { $text = ucfirst( $this->words( 1, 4 ) ); $slug = str_replace( ' ', '-', strtolower( $this->words( 2, 4 ) ) ); return '<a href="/' . self::esc( $slug ) . '">' . self::esc( $text ) . '</a>'; } private function nav(): string { $count = random_int( 3, 6 ); $links = []; for( $i = 0; $i < $count; $i++ ) { $links[] = $this->link(); } return '<nav>' . implode( ' | ', $links ) . '</nav><hr>'; } private function imagePlaceholder(): string { $w = self::pick( self::IMAGE_WIDTHS ); $h = self::pick( self::IMAGE_HEIGHTS ); $alt = $this->words( 2, 5 ); return '<p><img src="https://placehold.co/' . $w . 'x' . $h . '" alt="' . self::esc( $alt ) . '" width="' . $w . '" height="' . $h . '"></p>'; } private function section(): string { $html = $this->heading(); $html .= '<p>' . $this->paragraph( 2, 4 ) . '</p>'; $extra = random_int( 0, 5 ); $html .= match( $extra ) { 0 => $this->itemList( false ), 1 => $this->itemList( true ), 2 => $this->blockquote(), 3 => $this->table(), 4 => $this->imagePlaceholder(), 5 => '', }; return $html; } private function footer(): string { return '<hr><footer><p>© ' . date( 'Y' ) . ' ' . ucwords( $this->words( 2, 4 ) ) . '. ' . ucfirst( $this->words( 3, 6 ) ) . '.</p></footer>'; } private function css(): string { $font = self::pick( self::FONTS ); $bg = self::pick( self::BACKGROUNDS ); $fg = self::pick( self::FOREGROUNDS ); $accent = self::pick( self::ACCENTS ); return "body{font-family:{$font};max-width:720px;margin:2rem auto;" . "padding:0 1rem;background:{$bg};color:{$fg};line-height:1.6}" . "h1,h2,h3{color:{$accent}}" . "a{color:{$accent}}" . "blockquote{border-left:4px solid {$accent};margin:1rem 0;" . "padding:.5rem 1rem;background:#f0f0f0}" . "table{border-collapse:collapse;margin:1rem 0}" . "th{background:#eee}" . "nav{padding:.5rem 0}" . "footer{color:#666;font-size:.85rem}" . "img{max-width:100%;height:auto}"; } }