About

Source: src/about.c · 235 lines · 10 functions.

Module entry points

These functions are exposed to the rest of the project. Open one to follow its decisions and the helpers it calls.

How to read these diagrams

Each function has its own expandable diagram using the actual expressions and statements from this file. Decisions split into True and False; loop bodies are indented. Returns end the function immediately; break and continue follow their C meanings. Arrows between statements show their order when execution reaches them.

These are structural source diagrams before preprocessing, not runtime traces. Conditional compilation and macros remain source expressions. Ternary choices remain inside their statement. Calls are linked separately; callback execution and task scheduling are not inferred.

Functions in this file

select_message() · source line 42
static void select_message(void)
  1. Executeuint8_t next = esp_random() % MESSAGE_COUNT;
  2. Decisionlast_message < MESSAGE_COUNT
    True
      1. Executenext = esp_random() % (MESSAGE_COUNT - 1U);
      2. Decisionnext >= last_message
        True
        1. Execute++next;
        False

        Continue after this decision.

    False

    Continue after this decision.

  3. Executemessage_index = next;
  4. Executelast_message = next;
Read original function source
static void select_message(void)
{
    uint8_t next = esp_random() % MESSAGE_COUNT;
    if (last_message < MESSAGE_COUNT) {
        next = esp_random() % (MESSAGE_COUNT - 1U);
        if (next >= last_message) ++next;
    }
    message_index = next;
    last_message = next;
}
draw_about_text() · source line 53
static void draw_about_text(uint16_t *frame, const char *text, int y, int scale,
                      int x_offset, uint16_t color, unsigned visible)

Calls into this documentation:

  1. Executechar clipped[32];
  2. Executeunsigned length = 0;
  3. WHILE looptext[length] != '\0' && length + 1U < sizeof(clipped)

    Repeat body while the loop condition permits; then continue below.

    1. Execute++length;
  4. Decisionvisible < length
    True
    1. Executelength = visible;
    False

    Continue after this decision.

  5. FOR loopunsigned i = 0; i < length; ++i

    Repeat body while the loop condition permits; then continue below.

    1. Executeclipped[i] = text[i];
  6. Executeclipped[length] = '\0';
  7. Executedraw_text_centred(frame, 160 + x_offset + 2, y + 2, clipped, scale, COLOR_DARKRED);
  8. Executedraw_text_centred(frame, 160 + x_offset, y, clipped, scale, color);
Read original function source
static void draw_about_text(uint16_t *frame, const char *text, int y, int scale,
                      int x_offset, uint16_t color, unsigned visible)
{
    char clipped[32];
    unsigned length = 0;
    while (text[length] != '\0' && length + 1U < sizeof(clipped)) ++length;
    if (visible < length) length = visible;
    for (unsigned i = 0; i < length; ++i) clipped[i] = text[i];
    clipped[length] = '\0';
    draw_text_centred(frame, 160 + x_offset + 2, y + 2, clipped, scale, COLOR_DARKRED);
    draw_text_centred(frame, 160 + x_offset, y, clipped, scale, color);
}
draw_wave_text() · source line 66
static void draw_wave_text(uint16_t *frame, const char *text, int y, int scale,
                           uint16_t color, uint32_t elapsed)

Calls into this documentation:

  1. Executeunsigned length = 0;
  2. WHILE looptext[length] != '\0'

    Repeat body while the loop condition permits; then continue below.

    1. Execute++length;
  3. Executeint x = 160 - ((int)length * UI_FONT_ADVANCE * scale - scale) / 2;
  4. Executeconst float base_phase = ((float)(elapsed % WAVE_CYCLE_MS) / WAVE_CYCLE_MS) * 6.2831853f;
  5. FOR loopunsigned index = 0; index < length; ++index

    Repeat body while the loop condition permits; then continue below.

      1. Executeconst char character[2] = {text[index], '\0'};
      2. Executeconst int wave_y = y + (int)(sinf(base_phase + (float)index * 0.55f) * WAVE_HEIGHT);
      3. Executedraw_text(frame, x + 2, wave_y + 2, character, scale, COLOR_DARKRED);
      4. Executedraw_text(frame, x, wave_y, character, scale, color);
      5. Executex += UI_FONT_ADVANCE * scale;
Read original function source
static void draw_wave_text(uint16_t *frame, const char *text, int y, int scale,
                           uint16_t color, uint32_t elapsed)
{
    unsigned length = 0;
    while (text[length] != '\0') ++length;
    int x = 160 - ((int)length * UI_FONT_ADVANCE * scale - scale) / 2;
    const float base_phase =
        ((float)(elapsed % WAVE_CYCLE_MS) / WAVE_CYCLE_MS) * 6.2831853f;

    for (unsigned index = 0; index < length; ++index) {
        const char character[2] = {text[index], '\0'};
        const int wave_y = y +
            (int)(sinf(base_phase + (float)index * 0.55f) * WAVE_HEIGHT);

        draw_text(frame, x + 2, wave_y + 2, character, scale, COLOR_DARKRED);
        draw_text(frame, x, wave_y, character, scale, color);
        x += UI_FONT_ADVANCE * scale;
    }
}
draw_transformed_text() · source line 86
static void draw_transformed_text(uint16_t *frame, const char *text, int y,
                                  int scale, float zoom, float angle,
                                  uint16_t color)

Calls into this documentation:

  1. Executedraw_text_transformed(frame, 162, y + (UI_FONT_HEIGHT * scale) / 2 + 2, text, scale, zoom, angle, COLOR_DARKRED);
  2. Executedraw_text_transformed(frame, 160, y + (UI_FONT_HEIGHT * scale) / 2, text, scale, zoom, angle, color);
Read original function source
static void draw_transformed_text(uint16_t *frame, const char *text, int y,
                                  int scale, float zoom, float angle,
                                  uint16_t color)
{
    draw_text_transformed(frame, 162, y + (UI_FONT_HEIGHT * scale) / 2 + 2, text,
                             scale, zoom, angle, COLOR_DARKRED);
    draw_text_transformed(frame, 160, y + (UI_FONT_HEIGHT * scale) / 2, text,
                             scale, zoom, angle, color);
}
draw_animated_text() · source line 96
static void draw_animated_text(uint16_t *frame, uint32_t elapsed)

Calls into this documentation:

  1. Executeconst char *subtitle = messages[message_index];
  2. Executeconst unsigned title_length = 9U;
  3. Executeunsigned subtitle_length = 0;
  4. WHILE loopsubtitle[subtitle_length] != '\0'

    Repeat body while the loop condition permits; then continue below.

    1. Execute++subtitle_length;
  5. Executedraw_text_centred(frame, 160, 7, "CODED BY", 1, COLOR_LIGHTGRAY);
  6. Executeconst uint8_t title_effect = effect_preset / 11U;
  7. Executeconst uint8_t subtitle_effect = effect_preset % 11U;
  8. Executeint title_x = 0;
  9. Executeint subtitle_x = 0;
  10. Executeint title_y = 20;
  11. Executeint subtitle_y = 57;
  12. Executeunsigned title_visible = title_length;
  13. Executeunsigned subtitle_visible = subtitle_length;
  14. Executeuint16_t title_color = COLOR_YELLOW;
  15. Executeuint16_t subtitle_color = COLOR_ORANGE;
  16. Executefloat title_angle = 0.0f, subtitle_angle = 0.0f;
  17. Executefloat title_zoom = 1.0f, subtitle_zoom = 1.0f;
  18. Executebool title_wave = false, subtitle_wave = false;
  19. Select a casetitle_effect

    Enter matching case; follow source fall-through until a break, return or end.

      1. Case / labelcase 0:
      2. Executetitle_x = 200 - (int)((elapsed * 55U / 1000U) % 400U);
      3. Exit enclosing loop or switchbreak;
      4. Case / labelcase 1:
      5. Executetitle_x = (int)((elapsed * 55U / 1000U) % 100U) - 50;
      6. Exit enclosing loop or switchbreak;
      7. Case / labelcase 2:
      8. Executetitle_y += (int)(sinf((float)elapsed / 445.0f) * 18.0f);
      9. Exit enclosing loop or switchbreak;
      10. Case / labelcase 3:
      11. Executetitle_wave = true;
      12. Exit enclosing loop or switchbreak;
      13. Case / labelcase 4:
      14. Executetitle_x = (int)((elapsed / 18U) % 7U) - 3;
      15. Exit enclosing loop or switchbreak;
      16. Case / labelcase 5:
      17. Executetitle_visible = (elapsed / 130U) % (title_length + 1U);
      18. Exit enclosing loop or switchbreak;
      19. Case / labelcase 6:
      20. Executetitle_color = (uint16_t[]){COLOR_DARKRED, COLOR_RED, COLOR_ORANGE, COLOR_YELLOW, COLOR_WHITE}[(elapsed / 180U) % 5U];
      21. Exit enclosing loop or switchbreak;
      22. Case / labelcase 7:
      23. Executetitle_angle = sinf((float)elapsed / 382.0f) * 12.0f;
      24. Exit enclosing loop or switchbreak;
      25. Case / labelcase 8:
      26. Executetitle_zoom = 0.88f + (sinf((float)elapsed / 318.0f) + 1.0f) * 0.10f;
      27. Exit enclosing loop or switchbreak;
      28. Case / labelcase 9:
      29. Executetitle_y += (int)(18.0f - fminf(18.0f, (float)(elapsed % 2800U) * 18.0f / 700.0f));
      30. Exit enclosing loop or switchbreak;
      31. Case / labelcase 10:
      32. Executetitle_visible = (elapsed / 90U) % (title_length + 1U);
      33. Exit enclosing loop or switchbreak;
      34. Case / labeldefault:
      35. Exit enclosing loop or switchbreak;
  20. Select a casesubtitle_effect

    Enter matching case; follow source fall-through until a break, return or end.

      1. Case / labelcase 0:
      2. Executesubtitle_x = 220 - (int)((elapsed * 45U / 1000U) % 440U);
      3. Exit enclosing loop or switchbreak;
      4. Case / labelcase 1:
      5. Executesubtitle_x = (int)((elapsed * 55U / 1000U) % 100U) - 50;
      6. Exit enclosing loop or switchbreak;
      7. Case / labelcase 2:
      8. Executesubtitle_y += (int)(sinf((float)elapsed / 445.0f) * 30.0f);
      9. Exit enclosing loop or switchbreak;
      10. Case / labelcase 3:
      11. Executesubtitle_wave = true;
      12. Exit enclosing loop or switchbreak;
      13. Case / labelcase 4:
      14. Executesubtitle_x = (int)((elapsed / 16U) % 7U) - 3;
      15. Exit enclosing loop or switchbreak;
      16. Case / labelcase 5:
      17. Executesubtitle_visible = (elapsed / 130U) % (subtitle_length + 1U);
      18. Exit enclosing loop or switchbreak;
      19. Case / labelcase 6:
      20. Executesubtitle_color = (uint16_t[]){COLOR_DARKRED, COLOR_RED, COLOR_ORANGE, COLOR_YELLOW, COLOR_WHITE}[(elapsed / 180U) % 5U];
      21. Exit enclosing loop or switchbreak;
      22. Case / labelcase 7:
      23. Executesubtitle_angle = sinf((float)elapsed / 382.0f) * 12.0f;
      24. Exit enclosing loop or switchbreak;
      25. Case / labelcase 8:
      26. Executesubtitle_zoom = 0.88f + (sinf((float)elapsed / 318.0f) + 1.0f) * 0.10f;
      27. Exit enclosing loop or switchbreak;
      28. Case / labelcase 9:
      29. Executesubtitle_y += (int)(18.0f - fminf(18.0f, (float)(elapsed % 2800U) * 18.0f / 700.0f));
      30. Exit enclosing loop or switchbreak;
      31. Case / labelcase 10:
      32. Executesubtitle_visible = (elapsed / 90U) % (subtitle_length + 1U);
      33. Exit enclosing loop or switchbreak;
      34. Case / labeldefault:
      35. Exit enclosing loop or switchbreak;
  21. Decisiontitle_wave
    True
      1. Executedraw_wave_text(frame, "JD and MI", title_y, 3, title_color, elapsed);
    False
    1. Decisiontitle_angle != 0.0f || title_zoom != 1.0f
      True
        1. Executedraw_transformed_text(frame, "JD and MI", title_y, 3, title_zoom, title_angle, title_color);
      False
        1. Executedraw_about_text(frame, "JD and MI", title_y, 3, title_x, title_color, title_visible);
  22. Decisionsubtitle_wave
    True
      1. Executedraw_wave_text(frame, subtitle, subtitle_y, 1, subtitle_color, elapsed);
    False
    1. Decisionsubtitle_angle != 0.0f || subtitle_zoom != 1.0f
      True
        1. Executedraw_transformed_text(frame, subtitle, subtitle_y, 1, subtitle_zoom, subtitle_angle, subtitle_color);
      False
        1. Executedraw_about_text(frame, subtitle, subtitle_y, 1, subtitle_x, subtitle_color, subtitle_visible);
Read original function source
static void draw_animated_text(uint16_t *frame, uint32_t elapsed)
{
    const char *subtitle = messages[message_index];
    const unsigned title_length = 9U;
    unsigned subtitle_length = 0;
    while (subtitle[subtitle_length] != '\0') ++subtitle_length;

    draw_text_centred(frame, 160, 7, "CODED BY", 1, COLOR_LIGHTGRAY);

    const uint8_t title_effect = effect_preset / 11U;
    const uint8_t subtitle_effect = effect_preset % 11U;
    int title_x = 0;
    int subtitle_x = 0;
    int title_y = 20;
    int subtitle_y = 57;
    unsigned title_visible = title_length;
    unsigned subtitle_visible = subtitle_length;
    uint16_t title_color = COLOR_YELLOW;
    uint16_t subtitle_color = COLOR_ORANGE;
    float title_angle = 0.0f, subtitle_angle = 0.0f;
    float title_zoom = 1.0f, subtitle_zoom = 1.0f;
    bool title_wave = false, subtitle_wave = false;

    switch (title_effect) {
    case 0: title_x = 200 - (int)((elapsed * 55U / 1000U) % 400U); break;
    case 1: title_x = (int)((elapsed * 55U / 1000U) % 100U) - 50; break;
    case 2: title_y += (int)(sinf((float)elapsed / 445.0f) * 18.0f); break;
    case 3: title_wave = true; break;
    case 4: title_x = (int)((elapsed / 18U) % 7U) - 3; break;
    case 5: title_visible = (elapsed / 130U) % (title_length + 1U); break;
    case 6: title_color = (uint16_t[]){COLOR_DARKRED, COLOR_RED, COLOR_ORANGE, COLOR_YELLOW, COLOR_WHITE}[(elapsed / 180U) % 5U]; break;
    case 7: title_angle = sinf((float)elapsed / 382.0f) * 12.0f; break;
    case 8: title_zoom = 0.88f + (sinf((float)elapsed / 318.0f) + 1.0f) * 0.10f; break;
    case 9: title_y += (int)(18.0f - fminf(18.0f, (float)(elapsed % 2800U) * 18.0f / 700.0f)); break;
    case 10: title_visible = (elapsed / 90U) % (title_length + 1U); break;
    default: break;
    }
    switch (subtitle_effect) {
    case 0: subtitle_x = 220 - (int)((elapsed * 45U / 1000U) % 440U); break;
    case 1: subtitle_x = (int)((elapsed * 55U / 1000U) % 100U) - 50; break;
    case 2: subtitle_y += (int)(sinf((float)elapsed / 445.0f) * 30.0f); break;
    case 3: subtitle_wave = true; break;
    case 4: subtitle_x = (int)((elapsed / 16U) % 7U) - 3; break;
    case 5: subtitle_visible = (elapsed / 130U) % (subtitle_length + 1U); break;
    case 6: subtitle_color = (uint16_t[]){COLOR_DARKRED, COLOR_RED, COLOR_ORANGE, COLOR_YELLOW, COLOR_WHITE}[(elapsed / 180U) % 5U]; break;
    case 7: subtitle_angle = sinf((float)elapsed / 382.0f) * 12.0f; break;
    case 8: subtitle_zoom = 0.88f + (sinf((float)elapsed / 318.0f) + 1.0f) * 0.10f; break;
    case 9: subtitle_y += (int)(18.0f - fminf(18.0f, (float)(elapsed % 2800U) * 18.0f / 700.0f)); break;
    case 10: subtitle_visible = (elapsed / 90U) % (subtitle_length + 1U); break;
    default: break;
    }

    if (title_wave) {
        draw_wave_text(frame, "JD and MI", title_y, 3, title_color, elapsed);
    } else if (title_angle != 0.0f || title_zoom != 1.0f) {
        draw_transformed_text(frame, "JD and MI", title_y, 3, title_zoom,
                              title_angle, title_color);
    } else {
        draw_about_text(frame, "JD and MI", title_y, 3, title_x, title_color, title_visible);
    }
    if (subtitle_wave) {
        draw_wave_text(frame, subtitle, subtitle_y, 1, subtitle_color, elapsed);
    } else if (subtitle_angle != 0.0f || subtitle_zoom != 1.0f) {
        draw_transformed_text(frame, subtitle, subtitle_y, 1, subtitle_zoom,
                              subtitle_angle, subtitle_color);
    } else {
        draw_about_text(frame, subtitle, subtitle_y, 1, subtitle_x, subtitle_color, subtitle_visible);
    }
}
startAbout() · source line 166
void startAbout(uint32_t now_ms)

Calls into this documentation:

  1. ExecuteflameEffectInitialize(&fire, heat, FIRE_COLUMNS, FIRE_ROWS, FIRE_PIXEL_WIDTH, FIRE_PIXEL_HEIGHT, true, esp_random());
  2. Executeselect_message();
  3. Executeeffect_preset = esp_random() % EFFECT_PRESET_COUNT;
  4. Executestarted_at = last_frame = now_ms;
  5. ExecutestartMoonlight(now_ms);
  6. Executeshowing_credits = false;
  7. Executerunning = true;
Read original function source
void startAbout(uint32_t now_ms)
{
    flameEffectInitialize(&fire, heat, FIRE_COLUMNS, FIRE_ROWS, FIRE_PIXEL_WIDTH,
                          FIRE_PIXEL_HEIGHT, true, esp_random());
    select_message();
    effect_preset = esp_random() % EFFECT_PRESET_COUNT;
    started_at = last_frame = now_ms;
    startMoonlight(now_ms);
    showing_credits = false;
    running = true;
}
stopAbout() · source line 178
void stopAbout(void)

Calls into this documentation:

  1. ExecutestopCredits();
  2. ExecutestopMoonlight();
  3. Executeshowing_credits = false;
  4. Executerunning = false;
Read original function source
void stopAbout(void)
{
    stopCredits();
    stopMoonlight();
    showing_credits = false;
    running = false;
}
updateAbout() · source line 186
void updateAbout(uint32_t now_ms)

Calls into this documentation:

  1. Decision!running || showing_credits || now_ms - last_frame < FRAME_MS
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  2. ExecuteupdateMoonlight(now_ms);
  3. Executelast_frame = now_ms;
  4. ExecuteflameEffectUpdate(&fire);
Read original function source
void updateAbout(uint32_t now_ms)
{
    if (!running || showing_credits || now_ms - last_frame < FRAME_MS) return;
    updateMoonlight(now_ms);
    last_frame = now_ms;
    flameEffectUpdate(&fire);
}
aboutAction() · source line 194
void aboutAction(bool next, bool select, uint32_t now_ms)

Calls into this documentation:

  1. Decision!running
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  2. Decisionshowing_credits
    True
      1. DecisioncreditsAction(next, select)
        True
          1. Executeshowing_credits = false;
          2. Executestarted_at = last_frame = now_ms;
          3. Executeselect_message();
          4. ExecutestartMoonlight(now_ms);
        False

        Continue after this decision.

      2. Exit this functionreturn;
    False

    Continue after this decision.

  3. Decisionselect
    True
      1. ExecutestopMoonlight();
      2. Executeshowing_credits = true;
      3. ExecutestartCredits();
      4. Exit this functionreturn;
    False

    Continue after this decision.

  4. Decisionnext
    True
      1. Executeeffect_preset = (effect_preset + 1U) % EFFECT_PRESET_COUNT;
      2. Executestarted_at = last_frame = now_ms;
    False

    Continue after this decision.

Read original function source
void aboutAction(bool next, bool select, uint32_t now_ms)
{
    if (!running) return;
    if (showing_credits) {
        if (creditsAction(next, select)) {
            showing_credits = false;
            started_at = last_frame = now_ms;
            select_message();
            startMoonlight(now_ms);
        }
        return;
    }
    if (select) {
        stopMoonlight();
        showing_credits = true;
        startCredits();
        return;
    }
    if (next) {
        effect_preset = (effect_preset + 1U) % EFFECT_PRESET_COUNT;
        started_at = last_frame = now_ms;
    }
}
drawAbout() · source line 218
void drawAbout(uint16_t *frame)

Calls into this documentation:

  1. Decision!running
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  2. Decisionshowing_credits
    True
      1. ExecutedrawCredits(frame);
      2. Exit this functionreturn;
    False

    Continue after this decision.

  3. Executeclear(frame, COLOR_BLACK);
  4. Executedraw_animated_text(frame, last_frame - started_at);
  5. ExecuteflameEffectDraw(frame, &fire, 0, FIRE_TOP, NULL);
  6. Executedraw_text_centred(frame, 157, 158, "A NEXT BOOT EXIT", 2, COLOR_DARKGRAY);
  7. Executedraw_text_centred(frame, 158, 157, "A NEXT BOOT EXIT", 2, COLOR_LIGHTGRAY);
Read original function source
void drawAbout(uint16_t *frame)
{
    if (!running) return;
    if (showing_credits) {
        drawCredits(frame);
        return;
    }
    clear(frame, COLOR_BLACK);
    draw_animated_text(frame, last_frame - started_at);
    // NULL preserves About's original Doom-style orange flame palette.
    flameEffectDraw(frame, &fire, 0, FIRE_TOP, NULL);
    // A one-pixel offset gives the compact built-in font a readable bold edge.
    draw_text_centred(frame, 157, 158, "A NEXT BOOT EXIT", 2, COLOR_DARKGRAY);
    draw_text_centred(frame, 158, 157, "A NEXT BOOT EXIT", 2, COLOR_LIGHTGRAY);
}
Complete original file, including constants and data
/* Coded By JD(2026) */
#error "Reference listing only — not provided as build-ready source."
#include "about.h"

#include "game_graphics.h"
#include "PET2001_font.h"
#include "credits.h"
#include "flame_effect.h"
#include "moonlight.h"
#include "esp_random.h"
#include <math.h>
#include <stddef.h>

#define FIRE_TOP (LCD_HEIGHT / 2)
#define FIRE_PIXEL_WIDTH 2
#define FIRE_PIXEL_HEIGHT 2
#define FIRE_COLUMNS (LCD_WIDTH / FIRE_PIXEL_WIDTH)
#define FIRE_ROWS ((LCD_HEIGHT - FIRE_TOP + FIRE_PIXEL_HEIGHT - 1) / FIRE_PIXEL_HEIGHT)
#define FIRE_CELL_COUNT (FIRE_COLUMNS * FIRE_ROWS)
#define FRAME_MS 20U
#define MESSAGE_COUNT 6U
#define EFFECT_PRESET_COUNT 121U
#define WAVE_CYCLE_MS 900U
#define WAVE_HEIGHT 5

static const char *const messages[MESSAGE_COUNT] = {
    "FORGED IN FIRE - 2026",
    "CODED IN HELL - 2026",
    "TWO MINDS. ONE MACHINE.",
    "BUILT TO SURVIVE - 2026",
    "POWERED BY BAD IDEAS",
    "IT COMPILED. SHIP IT.",
};

static uint8_t heat[FIRE_CELL_COUNT];
static flame_effect_t fire;
static uint8_t message_index;
static uint8_t last_message = MESSAGE_COUNT;
static uint8_t effect_preset;
static uint32_t started_at;
static uint32_t last_frame;
static bool running;
static bool showing_credits;

static void select_message(void)
{
    uint8_t next = esp_random() % MESSAGE_COUNT;
    if (last_message < MESSAGE_COUNT) {
        next = esp_random() % (MESSAGE_COUNT - 1U);
        if (next >= last_message) ++next;
    }
    message_index = next;
    last_message = next;
}

static void draw_about_text(uint16_t *frame, const char *text, int y, int scale,
                      int x_offset, uint16_t color, unsigned visible)
{
    char clipped[32];
    unsigned length = 0;
    while (text[length] != '\0' && length + 1U < sizeof(clipped)) ++length;
    if (visible < length) length = visible;
    for (unsigned i = 0; i < length; ++i) clipped[i] = text[i];
    clipped[length] = '\0';
    drawPet2001TextCentredTransparent(frame, 160 + x_offset + 2, y + 2, clipped,
                                       COLOR_DARKRED, scale);
    drawPet2001TextCentredTransparent(frame, 160 + x_offset, y, clipped, color, scale);
}

static void draw_wave_text(uint16_t *frame, const char *text, int y, int scale,
                           uint16_t color, uint32_t elapsed)
{
    unsigned length = 0;
    while (text[length] != '\0') ++length;
    int x = 160 - (int)length * PET2001_FONT_GLYPH_WIDTH * scale / 2;
    const float base_phase =
        ((float)(elapsed % WAVE_CYCLE_MS) / WAVE_CYCLE_MS) * 6.2831853f;

    for (unsigned index = 0; index < length; ++index) {
        const int wave_y = y +
            (int)(sinf(base_phase + (float)index * 0.55f) * WAVE_HEIGHT);

        drawPet2001AsciiCharTransformed(frame, x + 2 + 4 * scale, wave_y + 2 + 4 * scale,
                                         text[index], (float)scale, 0.0f, COLOR_DARKRED);
        drawPet2001AsciiCharTransformed(frame, x + 4 * scale, wave_y + 4 * scale,
                                         text[index], (float)scale, 0.0f, color);
        x += PET2001_FONT_GLYPH_WIDTH * scale;
    }
}

static void draw_transformed_text(uint16_t *frame, const char *text, int y,
                                  int scale, float zoom, float angle,
                                  uint16_t color)
{
    drawPet2001TextTransformed(frame, 162, y + (PET2001_FONT_GLYPH_HEIGHT * scale) / 2 + 2,
                                text, scale, zoom, angle, COLOR_DARKRED);
    drawPet2001TextTransformed(frame, 160, y + (PET2001_FONT_GLYPH_HEIGHT * scale) / 2,
                                text, scale, zoom, angle, color);
}

static void draw_animated_text(uint16_t *frame, uint32_t elapsed)
{
    const char *subtitle = messages[message_index];
    const unsigned title_length = 9U;
    unsigned subtitle_length = 0;
    while (subtitle[subtitle_length] != '\0') ++subtitle_length;

    drawPet2001TextCentredTransparent(frame, 160, 7, "CODED BY", COLOR_LIGHTGRAY, 1);

    const uint8_t title_effect = effect_preset / 11U;
    const uint8_t subtitle_effect = effect_preset % 11U;
    int title_x = 0;
    int subtitle_x = 0;
    int title_y = 20;
    int subtitle_y = 57;
    unsigned title_visible = title_length;
    unsigned subtitle_visible = subtitle_length;
    uint16_t title_color = COLOR_YELLOW;
    uint16_t subtitle_color = COLOR_ORANGE;
    float title_angle = 0.0f, subtitle_angle = 0.0f;
    float title_zoom = 1.0f, subtitle_zoom = 1.0f;
    bool title_wave = false, subtitle_wave = false;

    switch (title_effect) {
    case 0: title_x = 200 - (int)((elapsed * 55U / 1000U) % 400U); break;
    case 1: title_x = (int)((elapsed * 55U / 1000U) % 100U) - 50; break;
    case 2: title_y += (int)(sinf((float)elapsed / 445.0f) * 18.0f); break;
    case 3: title_wave = true; break;
    case 4: title_x = (int)((elapsed / 18U) % 7U) - 3; break;
    case 5: title_visible = (elapsed / 130U) % (title_length + 1U); break;
    case 6: title_color = (uint16_t[]){COLOR_DARKRED, COLOR_RED, COLOR_ORANGE, COLOR_YELLOW, COLOR_WHITE}[(elapsed / 180U) % 5U]; break;
    case 7: title_angle = sinf((float)elapsed / 382.0f) * 12.0f; break;
    case 8: title_zoom = 0.88f + (sinf((float)elapsed / 318.0f) + 1.0f) * 0.10f; break;
    case 9: title_y += (int)(18.0f - fminf(18.0f, (float)(elapsed % 2800U) * 18.0f / 700.0f)); break;
    case 10: title_visible = (elapsed / 90U) % (title_length + 1U); break;
    default: break;
    }
    switch (subtitle_effect) {
    case 0: subtitle_x = 220 - (int)((elapsed * 45U / 1000U) % 440U); break;
    case 1: subtitle_x = (int)((elapsed * 55U / 1000U) % 100U) - 50; break;
    case 2: subtitle_y += (int)(sinf((float)elapsed / 445.0f) * 30.0f); break;
    case 3: subtitle_wave = true; break;
    case 4: subtitle_x = (int)((elapsed / 16U) % 7U) - 3; break;
    case 5: subtitle_visible = (elapsed / 130U) % (subtitle_length + 1U); break;
    case 6: subtitle_color = (uint16_t[]){COLOR_DARKRED, COLOR_RED, COLOR_ORANGE, COLOR_YELLOW, COLOR_WHITE}[(elapsed / 180U) % 5U]; break;
    case 7: subtitle_angle = sinf((float)elapsed / 382.0f) * 12.0f; break;
    case 8: subtitle_zoom = 0.88f + (sinf((float)elapsed / 318.0f) + 1.0f) * 0.10f; break;
    case 9: subtitle_y += (int)(18.0f - fminf(18.0f, (float)(elapsed % 2800U) * 18.0f / 700.0f)); break;
    case 10: subtitle_visible = (elapsed / 90U) % (subtitle_length + 1U); break;
    default: break;
    }

    if (title_wave) {
        draw_wave_text(frame, "JD and MI", title_y, 3, title_color, elapsed);
    } else if (title_angle != 0.0f || title_zoom != 1.0f) {
        draw_transformed_text(frame, "JD and MI", title_y, 3, title_zoom,
                              title_angle, title_color);
    } else {
        draw_about_text(frame, "JD and MI", title_y, 3, title_x, title_color, title_visible);
    }
    if (subtitle_wave) {
        draw_wave_text(frame, subtitle, subtitle_y, 1, subtitle_color, elapsed);
    } else if (subtitle_angle != 0.0f || subtitle_zoom != 1.0f) {
        draw_transformed_text(frame, subtitle, subtitle_y, 1, subtitle_zoom,
                              subtitle_angle, subtitle_color);
    } else {
        draw_about_text(frame, subtitle, subtitle_y, 1, subtitle_x, subtitle_color, subtitle_visible);
    }
}

void startAbout(uint32_t now_ms)
{
    flameEffectInitialize(&fire, heat, FIRE_COLUMNS, FIRE_ROWS, FIRE_PIXEL_WIDTH,
                          FIRE_PIXEL_HEIGHT, true, esp_random());
    select_message();
    effect_preset = esp_random() % EFFECT_PRESET_COUNT;
    started_at = last_frame = now_ms;
    startMoonlight(now_ms);
    showing_credits = false;
    running = true;
}

void stopAbout(void)
{
    stopCredits();
    stopMoonlight();
    showing_credits = false;
    running = false;
}

void updateAbout(uint32_t now_ms)
{
    if (!running || showing_credits || now_ms - last_frame < FRAME_MS) return;
    updateMoonlight(now_ms);
    last_frame = now_ms;
    flameEffectUpdate(&fire);
}

void aboutAction(bool next, bool select, uint32_t now_ms)
{
    if (!running) return;
    if (showing_credits) {
        if (creditsAction(next, select)) {
            showing_credits = false;
            started_at = last_frame = now_ms;
            select_message();
            startMoonlight(now_ms);
        }
        return;
    }
    if (select) {
        stopMoonlight();
        showing_credits = true;
        startCredits();
        return;
    }
    if (next) {
        effect_preset = (effect_preset + 1U) % EFFECT_PRESET_COUNT;
        started_at = last_frame = now_ms;
    }
}

void drawAbout(uint16_t *frame)
{
    if (!running) return;
    if (showing_credits) {
        drawCredits(frame);
        return;
    }
    clear(frame, COLOR_BLACK);
    draw_animated_text(frame, last_frame - started_at);
    // NULL preserves About's original Doom-style orange flame palette.
    flameEffectDraw(frame, &fire, 0, FIRE_TOP, NULL);
    // A one-pixel offset gives the PET control label a readable shadow.
    drawPet2001TextCentredTransparent(frame, 157, 158, "A NEXT BOOT EXIT", COLOR_DARKGRAY, 2);
    drawPet2001TextCentredTransparent(frame, 158, 157, "A NEXT BOOT EXIT", COLOR_LIGHTGRAY, 2);
}