local M = {} local PADDING = 24 local COLOR_BG = 0x282828FF local COLOR_TEXT = 0xE0E0E0FF local COLOR_DIM = 0xA0A0A0FF local COLOR_OK = 0x80C080FF local COLOR_WARN = 0xE0C040FF local COLOR_ERR = 0xE05040FF local FONT_TITLE = 28 local FONT_SUB = 16 local FONT_BODY = 14 local FONT_DEP = 14 local md -- lazy-loaded markdown renderer local function ensure_md(launcher_dir) if not md then md = dofile(launcher_dir .. "/markdown.lua") end end local function status_glyph(dep_status) if dep_status == "ok" then return "OK", COLOR_OK end if dep_status == "warn" then return "WARN", COLOR_WARN end if dep_status == "error" then return "ERR", COLOR_ERR end return "?", COLOR_DIM end local function find_entry(ctx, id) if id == "news" then return nil end for _, e in ipairs(ctx.module_entries) do if e.id == id then return e end end return nil end function M.render(ctx) local p = ctx.detail_panel_rect engine.render.draw_rect(p.x, p.y, p.w, p.h, COLOR_BG) if not ctx.selected_entry then if ctx.bg_texture then engine.render.draw_texture(ctx.bg_texture, p.x, p.y) end local hint = ctx.t("empty_hint") local w, h = engine.render.measure_text(hint, FONT_BODY) engine.render.draw_text(hint, p.x + (p.w - w) / 2, p.y + (p.h - h) / 2, FONT_BODY, COLOR_DIM) return end -- News: render news.md. if ctx.selected_entry == "news" then ensure_md(ctx.launcher_dir) local body = ctx.news_body_blocks or {} md.render(body, p.x + PADDING, p.y + PADDING, p.w - 2 * PADDING, nil) return end local entry = find_entry(ctx, ctx.selected_entry) if not entry then return end local mc = ctx.manifest_cache[entry.id] or { meta = {}, blocks = {} } -- Carousel hero placeholder: L.5 swaps this in. For now: a strip. local hero_h = 160 if ctx.carousel_state[entry.id] and ctx.carousel_mod then -- L.5 implements this; render call lands later. else engine.render.draw_rect(p.x, p.y, p.w, hero_h, 0x404040FF) end local cur_y = p.y + hero_h + PADDING -- Title row. local title = entry.summary or entry.name or entry.id engine.render.draw_text(title, p.x + PADDING, cur_y, FONT_TITLE, COLOR_TEXT) local _, title_h = engine.render.measure_text(title, FONT_TITLE) -- Version + author right-aligned. local sub = string.format("v%s %s", entry.version, mc.meta.author or "") local sub_w, _ = engine.render.measure_text(sub, FONT_SUB) engine.render.draw_text(sub, p.x + p.w - PADDING - sub_w, cur_y + 6, FONT_SUB, COLOR_DIM) cur_y = cur_y + title_h + 12 -- Description (rendered MD). ensure_md(ctx.launcher_dir) cur_y = md.render(mc.blocks, p.x + PADDING, cur_y, p.w - 2 * PADDING, entry.id) cur_y = cur_y + 12 -- Dep list — load-order from manifest.module per L-Q6. local r = entry.status.dep_check_result local dep_count = (r and r.deps and #r.deps) or 0 engine.render.draw_text( string.format("%s (%d)", ctx.t("dependencies"), dep_count), p.x + PADDING, cur_y, FONT_SUB, COLOR_TEXT) cur_y = cur_y + 24 if r and r.deps then for _, d in ipairs(r.deps) do local glyph, col = status_glyph(d.status) engine.render.draw_text(glyph, p.x + PADDING, cur_y, FONT_DEP, col) engine.render.draw_text(string.format("%s %s", d.id, d.version), p.x + PADDING + 40, cur_y, FONT_DEP, COLOR_TEXT) cur_y = cur_y + 20 end end -- Whole-list tint hint: bubble up to ctx.list_tint so panels/list.lua picks it up. if r and r.kind == "error" then ctx.list_tint = "error" elseif r and r.kind == "warn" then ctx.list_tint = "warn" end end function M.handle_click(ctx, mx, my) -- L.5/L.7 add carousel-arrow + launch-button hit-tests. return false end return M