src/html-generator/golden-poem-indicators.lua

256 lines

1#!/usr/bin/env lua
2
3-- Golden Poem Visual Indicators System
4-- Provides functions for generating golden poem visual indicators and badges
5
6package.path = package.path .. ';./?.lua;./libs/?.lua'
7
8local utils = require("libs.utils")
9
10local M = {}
11
12-- {{{ function M.escape_html
13function M.escape_html(text)
14 if not text then return "" end
15 return tostring(text)
16 :gsub("&", "&")
17 :gsub("<", "&lt;")
18 :gsub(">", "&gt;")
19 :gsub('"', "&quot;")
20 :gsub("'", "&#39;")
21end
22-- }}}
23
24-- {{{ function M.generate_golden_indicator
25function M.generate_golden_indicator(poem, display_type, options)
26 display_type = display_type or "full"
27 options = options or {}
28
29 if not poem or not poem.is_fediverse_golden then
30 return ""
31 end
32
33 local character_count = poem.character_count or poem.length or 1024
34
35 if display_type == "full" then
36 return string.format([[
37<div class="golden-badge" role="banner" aria-label="Perfect Fediverse Length Poem">
38 <span class="sr-only">This poem has achieved perfect fediverse formatting with exactly %d characters</span>
39 <span class="golden-icon" aria-hidden="true">✨</span>
40 <span class="golden-text">Perfect Fediverse Length</span>
41 <span class="golden-count">%d characters</span>
42</div>]], character_count, character_count)
43
44 elseif display_type == "compact" then
45 return string.format([[<span class="golden-compact" title="Perfect Fediverse Length: %d characters" aria-label="Golden poem indicator">
46 <span class="golden-icon" aria-hidden="true">✨</span>Golden
47</span>]], character_count)
48
49 elseif display_type == "list" then
50 return " ✨"
51
52 elseif display_type == "icon" then
53 return '<span class="golden-icon" title="Golden poem" aria-label="Golden poem indicator">✨</span>'
54
55 else
56 return ""
57 end
58end
59-- }}}
60
61-- {{{ function M.generate_character_count_display
62function M.generate_character_count_display(poem, options)
63 options = options or {}
64
65 if not poem.character_count and not poem.length then
66 return ""
67 end
68
69 local count = poem.character_count or poem.length
70 local count_class = poem.is_fediverse_golden and "character-count golden" or "character-count"
71 local achievement_text = poem.is_fediverse_golden and " (Perfect Fediverse Length!)" or ""
72
73 return string.format([[
74<div class="%s" aria-label="Character count: %d%s">
75 <strong>Character Count:</strong> %d%s
76</div>]], count_class, count, achievement_text, count, achievement_text)
77end
78-- }}}
79
80-- {{{ function M.enhance_similarity_list_with_golden
81function M.enhance_similarity_list_with_golden(recommendations, options)
82 options = options or {}
83 local enhanced_html = ""
84
85 for i, rec in ipairs(recommendations) do
86 local item_class = rec.is_golden and ' class="golden-poem"' or ""
87 local golden_indicator = rec.is_golden and " ✨" or ""
88 local title = M.escape_html(rec.title or ("Poem " .. rec.id))
89
90 -- Add aria-label for golden poems
91 local aria_label = rec.is_golden and
92 string.format(' aria-label="%s - Golden poem with perfect fediverse length"', title) or ""
93
94 enhanced_html = enhanced_html .. string.format(
95 '<li%s%s><a href="%s">%s</a>%s <span class="similarity-score">(%.3f similarity)</span></li>\n',
96 item_class,
97 aria_label,
98 rec.url or "",
99 title,
100 golden_indicator,
101 rec.score or 0
102 )
103 end
104
105 return enhanced_html
106end
107-- }}}
108
109-- {{{ function M.generate_golden_statistics_display
110function M.generate_golden_statistics_display(total_poems, golden_count, options)
111 options = options or {}
112
113 if not golden_count or golden_count == 0 then
114 return ""
115 end
116
117 local percentage = (golden_count / total_poems) * 100
118
119 return string.format([[
120<div class="golden-statistics" role="region" aria-label="Golden poem collection statistics">
121 <h4>✨ Golden Poem Collection</h4>
122 <p><strong>%d</strong> poems achieve the perfect fediverse length of 1024 characters</p>
123 <p><em>%.1f%% of the complete poetry collection</em></p>
124 <p><a href="poems/golden/index.html" aria-label="Browse all %d golden poems">Browse all golden poems →</a></p>
125</div>]], golden_count, percentage, golden_count)
126end
127-- }}}
128
129-- {{{ function M.generate_golden_help_tooltip
130function M.generate_golden_help_tooltip(options)
131 options = options or {}
132
133 return [[
134<div class="golden-help" role="tooltip" id="golden-help-tooltip">
135 <button class="help-icon" aria-describedby="golden-help-content" aria-label="Information about golden poems">?</button>
136 <div class="tooltip-content" id="golden-help-content" role="tooltip">
137 <strong>Golden Poems</strong><br>
138 Exactly 1024 characters<br>
139 Perfect for fediverse sharing<br>
140 Artistic constraint achievement
141 </div>
142</div>]]
143end
144-- }}}
145
146-- {{{ function M.generate_golden_poem_teaser
147function M.generate_golden_poem_teaser(options)
148 options = options or {}
149
150 return [[
151<aside class="golden-teaser" role="complementary" aria-label="Golden poem collection information">
152 <h3>✨ Discover Perfect Fediverse Poems</h3>
153 <p>Explore our collection of exactly 1024-character poems,
154 perfectly crafted for sharing on the fediverse.</p>
155 <a href="poems/golden/index.html" class="golden-cta" aria-label="Browse the golden poem collection">
156 Browse Golden Collection →
157 </a>
158</aside>]]
159end
160-- }}}
161
162-- {{{ function M.add_golden_indicators_to_template_data
163function M.add_golden_indicators_to_template_data(poem, template_data)
164 template_data = template_data or {}
165
166 -- Add golden poem indicator (full badge)
167 template_data.GOLDEN_POEM_INDICATOR = M.generate_golden_indicator(poem, "full")
168
169 -- Add character count display with golden styling
170 template_data.CHARACTER_COUNT_DISPLAY = M.generate_character_count_display(poem)
171
172 -- Add compact golden indicator for use in titles or lists
173 template_data.GOLDEN_COMPACT_INDICATOR = M.generate_golden_indicator(poem, "compact")
174
175 -- Add golden icon only
176 template_data.GOLDEN_ICON = M.generate_golden_indicator(poem, "icon")
177
178 -- Add golden status flag for conditional rendering
179 template_data.IS_GOLDEN_POEM = poem.is_fediverse_golden and "true" or "false"
180
181 return template_data
182end
183-- }}}
184
185-- {{{ function M.validate_golden_poem_indicators
186function M.validate_golden_poem_indicators(html_content)
187 if not html_content then
188 return {valid = false, error = "No HTML content provided"}
189 end
190
191 local validation = {
192 valid = true,
193 checks = {},
194 warnings = {}
195 }
196
197 -- Check for golden poem indicators
198 local has_golden_badge = html_content:match('<div class="golden%-badge"')
199 local has_golden_icon = html_content:match('✨') or html_content:match('class="golden%-icon"')
200 local has_golden_list_items = html_content:match('class="golden%-poem"')
201 local has_character_count = html_content:match('class="character%-count')
202
203 -- Validate accessibility attributes
204 local has_aria_labels = html_content:match('aria%-label=') or html_content:match('aria%-describedby=')
205 local has_screen_reader_content = html_content:match('class="sr%-only"')
206 local has_role_attributes = html_content:match('role="')
207
208 -- Record checks
209 table.insert(validation.checks, {name = "Golden badge present", passed = has_golden_badge})
210 table.insert(validation.checks, {name = "Golden icons present", passed = has_golden_icon})
211 table.insert(validation.checks, {name = "Character count display", passed = has_character_count})
212 table.insert(validation.checks, {name = "Accessibility attributes", passed = has_aria_labels})
213 table.insert(validation.checks, {name = "Screen reader support", passed = has_screen_reader_content})
214 table.insert(validation.checks, {name = "ARIA roles", passed = has_role_attributes})
215
216 -- Warnings for missing but optional elements
217 if not has_golden_list_items then
218 table.insert(validation.warnings, "No golden poem list items found (may be expected)")
219 end
220
221 if not has_screen_reader_content then
222 table.insert(validation.warnings, "Screen reader content missing")
223 end
224
225 -- Overall validation
226 local passed_checks = 0
227 for _, check in ipairs(validation.checks) do
228 if check.passed then passed_checks = passed_checks + 1 end
229 end
230
231 validation.valid = passed_checks >= 4 -- Require at least 4/6 checks to pass
232 validation.score = passed_checks / #validation.checks
233
234 return validation
235end
236-- }}}
237
238-- {{{ function M.get_golden_indicator_css_classes
239function M.get_golden_indicator_css_classes()
240 return {
241 "golden-badge",
242 "golden-icon",
243 "golden-text",
244 "golden-count",
245 "golden-poem",
246 "golden-compact",
247 "golden-statistics",
248 "golden-help",
249 "golden-teaser",
250 "character-count",
251 "sr-only"
252 }
253end
254-- }}}
255
256return M