Hey, I have a chem final tomorrow my teacher gave questions to an AI and asked it to make a study guide based on the question and this is the attached one it made. I am the dumbest person in chemistry and I have no clue about anything in this. I want you to go through everything in the study guide and teach me and make me ready for the test tomorrow. my goal is to get 80 in tests. Also keep in mind that I am allowed to have one A4 size paper as cheat sheet to have with me during test

Reading File
Running Command

cd /home/daytona/workspace/attachments/c1917670-4af0-466e-9706-58a2ebf9f687 && pโ€ฆ

Running Command

mkdir -p /home/daytona/workspace/chem-cheatsheet && ls /home/daytona/workspace/cโ€ฆ

Writing File

~/chem-cheatsheet/make_cheatsheet.py

from reportlab.lib.pagesizes import A4
from reportlab.lib.units import cm, mm
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib import colors
from reportlab.platypus import (SimpleDocTemplate, Paragraph, Table, TableStyle,
                                 Spacer, HRFlowable, KeepTogether)
from reportlab.lib.enums import TA_CENTER, TA_LEFT
import os

output_path = "/home/daytona/workspace/chem-cheatsheet/HonorsChem_CheatSheet.pdf"

doc = SimpleDocTemplate(
    output_path,
    pagesize=A4,
    leftMargin=0.7*cm,
    rightMargin=0.7*cm,
    topMargin=0.6*cm,
    bottomMargin=0.6*cm,
)

W, H = A4

# โ”€โ”€ Color palette โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
DARK_BLUE   = colors.HexColor("#1a3a5c")
MED_BLUE    = colors.HexColor("#2563a8")
LIGHT_BLUE  = colors.HexColor("#dbeafe")
ACCENT      = colors.HexColor("#e76f00")
PALE_YELLOW = colors.HexColor("#fffbe6")
PALE_GREEN  = colors.HexColor("#eafaf1")
PALE_PINK   = colors.HexColor("#fdecea")
WHITE       = colors.white
LIGHT_GRAY  = colors.HexColor("#f0f4f8")

# โ”€โ”€ Base styles โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
def sty(name, parent="Normal", **kw):
    s = ParagraphStyle(name, parent=getSampleStyleSheet()[parent])
    for k, v in kw.items():
        setattr(s, k, v)
    return s

title_sty   = sty("title",  fontSize=11, textColor=WHITE, fontName="Helvetica-Bold",
                  alignment=TA_CENTER, spaceAfter=1, spaceBefore=1, leading=13)
sec_sty     = sty("sec",    fontSize=7.5, textColor=DARK_BLUE, fontName="Helvetica-Bold",
                  alignment=TA_CENTER, spaceAfter=0, spaceBefore=0, leading=9)
body_sty    = sty("body",   fontSize=6.5, fontName="Helvetica",
                  spaceAfter=0, spaceBefore=0, leading=8)
bold_sty    = sty("bold",   fontSize=6.5, fontName="Helvetica-Bold",
                  spaceAfter=0, spaceBefore=0, leading=8)
formula_sty = sty("formula",fontSize=6.5, fontName="Courier-Bold", textColor=MED_BLUE,
                  spaceAfter=0, spaceBefore=0, leading=8)

def P(txt, s=body_sty):
    return Paragraph(txt, s)

def header_row(label, bg=MED_BLUE):
    return [Paragraph(f"<b>{label}</b>", title_sty)]

def section_table(label, content_rows, col_widths, bg=LIGHT_BLUE, hdr_bg=MED_BLUE, rowbg=None):
    """Build a titled section table."""
    # header
    data = [[Paragraph(f"<b>{label}</b>", title_sty)]] + content_rows
    cw = col_widths
    ncols = len(col_widths)

    style = [
        # header
        ("BACKGROUND", (0,0), (-1,0), hdr_bg),
        ("SPAN",       (0,0), (-1,0)),
        ("ALIGN",      (0,0), (-1,0), "CENTER"),
        ("TOPPADDING", (0,0), (-1,0), 2),
        ("BOTTOMPADDING",(0,0),(-1,0),2),
        # body
        ("FONTNAME",   (0,1), (-1,-1), "Helvetica"),
        ("FONTSIZE",   (0,1), (-1,-1), 6.2),
        ("LEADING",    (0,1), (-1,-1), 7.5),
        ("TOPPADDING", (0,1), (-1,-1), 1.2),
        ("BOTTOMPADDING",(0,1),(-1,-1),1.2),
        ("LEFTPADDING",(0,0), (-1,-1), 2.5),
        ("RIGHTPADDING",(0,0),(-1,-1),2.5),
        ("ROWBACKGROUNDS",(0,1),(-1,-1),[WHITE, LIGHT_GRAY]),
        ("BOX",        (0,0), (-1,-1), 0.4, MED_BLUE),
        ("INNERGRID",  (0,1), (-1,-1), 0.2, colors.HexColor("#c8d8e8")),
        ("VALIGN",     (0,0), (-1,-1), "MIDDLE"),
    ]
    if rowbg:
        for i, c in enumerate(rowbg, start=1):
            if c:
                style.append(("BACKGROUND",(0,i),(-1,i),c))

    t = Table(data, colWidths=cw)
    t.setStyle(TableStyle(style))
    return t

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# Build content in two columns
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

usable_w = W - 1.4*cm          # total usable width
col_w    = (usable_w - 3*mm) / 2  # each column (2 cols with small gap)

# helper to combine two blocks side by side
def two_col(left_items, right_items):
    left_str  = ""
    right_str = ""
    # We'll use a Table with two columns, each holding a sub-table
    L = [left_items]  if not isinstance(left_items,  list) else left_items
    R = [right_items] if not isinstance(right_items, list) else right_items

    inner = Table([[L, R]], colWidths=[col_w, col_w])
    inner.setStyle(TableStyle([
        ("VALIGN",      (0,0),(-1,-1),"TOP"),
        ("LEFTPADDING", (0,0),(-1,-1),0),
        ("RIGHTPADDING",(0,0),(-1,-1),0),
        ("TOPPADDING",  (0,0),(-1,-1),0),
        ("BOTTOMPADDING",(0,0),(-1,-1),0),
    ]))
    return inner

# โ”€โ”€ Spacer shorthand โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
SP = Spacer(1, 1.5*mm)

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# SECTION 1 โ€” Formulas  (left col)
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
def formulas_block():
    rows = [
        [P("<b>Photon freq</b>"),       P("ฮฝ = c / ฮป   (c=3ร—10โธ m/s)", formula_sty)],
        [P("<b>Photon energy</b>"),      P("E = hฮฝ = hc/ฮป   (h=6.626ร—10โปยณโด)", formula_sty)],
        [P("<b>Specific heat</b>"),      P("q = m ร— c ร— ฮ”T", formula_sty)],
        [P("<b>Calorie conv</b>"),       P("1 Cal = 4184 J", formula_sty)],
        [P("<b>Moles</b>"),              P("n = mass / molar mass", formula_sty)],
        [P("<b>% composition</b>"),      P("% = (elem mass / MM) ร— 100", formula_sty)],
        [P("<b>% yield</b>"),            P("% yield = actual/theoretical ร— 100", formula_sty)],
        [P("<b>Avogadro</b>"),           P("6.02 ร— 10ยฒยณ particles/mol", formula_sty)],
    ]
    return section_table("๐Ÿ”ข  KEY FORMULAS  (Qs 3โ€“9, 19โ€“26)", rows,
                         [col_w*0.38, col_w*0.62], hdr_bg=DARK_BLUE)

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# SECTION 2 โ€” Polyatomic Ions  (right col)
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
def ions_block():
    rows = [
        [P("<b>Ion</b>", bold_sty), P("<b>Name</b>", bold_sty), P("<b>Ion</b>", bold_sty), P("<b>Name</b>", bold_sty)],
        [P("SOโ‚„ยฒโป"), P("sulfate"),      P("ClOโป"),  P("hypochlorite")],
        [P("SOโ‚ƒยฒโป"), P("sulfite"),      P("ClOโ‚‚โป"), P("chlorite")],
        [P("NOโ‚ƒโป"),  P("nitrate"),      P("ClOโ‚ƒโป"), P("chlorate")],
        [P("NOโ‚‚โป"),  P("nitrite"),      P("ClOโ‚„โป"), P("perchlorate")],
        [P("POโ‚„ยณโป"), P("phosphate"),    P("OHโป"),   P("hydroxide")],
        [P("NHโ‚„โบ"),  P("ammonium"),     P("COโ‚ƒยฒโป"), P("carbonate")],
    ]
    style_extra = [("BACKGROUND",(0,1),(-1,1), LIGHT_BLUE),
                   ("FONTNAME",(0,1),(-1,1),"Helvetica-Bold")]
    cws = [col_w*0.18, col_w*0.32, col_w*0.18, col_w*0.32]
    t = section_table("โšก  POLYATOMIC IONS", rows, cws, hdr_bg=colors.HexColor("#7c3aed"))
    return t

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# SECTION 3 โ€” Bonding & Geometry  (left)
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
def bonding_block():
    rows = [
        [P("<b>Ionic</b>"),      P("Metal+Nonmetal, eโป transferred, high MP")],
        [P("<b>Covalent</b>"),   P("Nonmetal+Nonmetal, eโป shared")],
        [P("<b>Coord. cov</b>"), P("ONE atom donates BOTH eโป")],
        [P("<b>Single</b>"),     P("1ฯƒ, 0ฯ€")],
        [P("<b>Double</b>"),     P("1ฯƒ, 1ฯ€   (e.g. Cโ‚‚Hโ‚„: 5ฯƒ 1ฯ€)")],
        [P("<b>Triple</b>"),     P("1ฯƒ, 2ฯ€  โ€” shortest & strongest")],
        [P("<b>Nonpolar</b>"),   P("Symmetric (SiHโ‚„ tetrahedral)")],
        [P("<b>Polar</b>"),      P("Lone pairs on center (AsHโ‚ƒ,Hโ‚‚S)")],
        [P("<b>HOFBrINCl</b>"),  P("Diatomic elements: Hโ‚‚Oโ‚‚Fโ‚‚Brโ‚‚Iโ‚‚Nโ‚‚Clโ‚‚")],
    ]
    return section_table("๐Ÿ”—  BONDING  (Qs 32โ€“40)", rows,
                         [col_w*0.28, col_w*0.72], hdr_bg=colors.HexColor("#0f766e"))

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# SECTION 4 โ€” Nomenclature  (right)
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
def nomenclature_block():
    rows = [
        [P("<b>Variable-charge metal</b>"), P("Use Roman numerals: Feโ‚‚(SOโ‚„)โ‚ƒ = Iron(III) sulfate")],
        [P("<b>Crossover rule</b>"),         P("Caยฒโบ+POโ‚„ยณโป โ†’ Caโ‚ƒ(POโ‚„)โ‚‚  (cross charges)")],
        [P("<b>Hydrate</b>"),                P("Compoundโ€ขxHโ‚‚O  e.g. Ba(OH)โ‚‚โ€ข8Hโ‚‚O")],
        [P("<b>Molecular prefixes</b>"),     P("mono di tri tetra penta hexa hepta octa")],
        [P("<b>Acid -ate ion</b>"),          P("-ic acid: Hโ‚‚SOโ‚„ = sulfuric acid")],
        [P("<b>Acid -ite ion</b>"),          P("-ous acid: Hโ‚‚SOโ‚ƒ = sulfurous acid")],
        [P("<b>Nโ‚‚Hโ‚„</b>"),                  P("dinitrogen tetrahydride")],
    ]
    return section_table("๐Ÿ“›  NOMENCLATURE  (Qs 27,35,36,41โ€“43)", rows,
                         [col_w*0.33, col_w*0.67], hdr_bg=colors.HexColor("#b45309"))

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# SECTION 5 โ€” Atomic Structure  (left)
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
def atomic_block():
    rows = [
        [P("<b>Ground state</b>"),   P("Lowest energy โ€” electrons at rest")],
        [P("<b>Excited state</b>"),  P("Electron absorbed energy โ†’ jumped up")],
        [P("<b>Isotopes</b>"),       P("Same element, different # neutrons")],
        [P("<b>Allotropes</b>"),     P("Same element, diff structure (C: diamond/graphite)")],
        [P("<b>Alloy</b>"),          P("Metal mixture (steel=Fe+C, bronze=Cu+Sn)")],
        [P("<b>Fill order</b>"),     P("1s 2s 2p 3s 3p <b>4s</b> 3d 4p 5s 4d 5p โ€ฆ", body_sty)],
        [P("<b>Ge config</b>"),      P("[Ar] 4sยฒ 3dยนโฐ 4pยฒ  (Period 4, Group 14)")],
        [P("<b>Octet rule</b>"),     P("Stable = 8 electrons in outer shell")],
    ]
    return section_table("โš›  ATOMIC STRUCTURE  (Qs 44โ€“50)", rows,
                         [col_w*0.28, col_w*0.72], hdr_bg=colors.HexColor("#6d28d9"))

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# SECTION 6 โ€” Mole/Stoich  (right)
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
def mole_block():
    rows = [
        [P("<b>moles from g</b>"),        P("n = mass รท molar mass")],
        [P("<b>g from moles</b>"),         P("g = mol ร— molar mass")],
        [P("<b>Empirical formula</b>"),    P("% โ†’ g โ†’ รท atomic mass โ†’ รท smallest โ†’ ratio")],
        [P("<b>COโ‚‚ % C</b>"),             P("12/44ร—100 = 27.3% C")],
        [P("<b>Feโ‚‚Oโ‚ƒ MM</b>"),            P("2(56)+3(16) = 159.7 g/mol โ†’ 0.82 mol = 131 g")],
        [P("<b>Clโ‚‚ (100 g)</b>"),         P("100รท71 = 1.41 mol")],
        [P("<b>Hโ‚‚CrOโ‚„ MM</b>"),          P("2+52+64 = 118 g/mol")],
    ]
    return section_table("๐Ÿงฎ  MOLE & MOLAR MASS  (Qs 19โ€“26)", rows,
                         [col_w*0.32, col_w*0.68], hdr_bg=colors.HexColor("#065f46"))

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# SECTION 7 โ€” Reactions & Stoichiometry  (left)
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
def rxn_block():
    rows = [
        [P("<b>Balancing</b>"),    P("SiOโ‚‚+4HFโ†’SiFโ‚„+2Hโ‚‚O  (HF coeff=4)")],
        [P("<b>Precipitate</b>"),  P("Solid (s) product from 2 aq solutions, e.g. AgCl(s)")],
        [P("<b>Net ionic</b>"),    P("Remove spectator ions | Hโบ+OHโปโ†’Hโ‚‚O")],
        [P("<b>Neutralization</b>"),P("Acid+Baseโ†’Salt+Water | Ba(OH)โ‚‚+2HClโ†’BaClโ‚‚+2Hโ‚‚O")],
        [P("<b>Stoich steps</b>"), P("1)Balance eq 2)โ†’mol 3)molar ratio 4)โ†’units")],
        [P("<b>Limiting R.</b>"),  P("Runs out first โ†’ determines max product")],
        [P("<b>% yield</b>"),      P("actualรทtheoreticalร—100  (always โ‰ค100%)")],
        [P("<b>Molar ratio</b>"),  P("Cโ‚ƒHโ‚ˆ+5Oโ‚‚โ†’3COโ‚‚+4Hโ‚‚O : Oโ‚‚:COโ‚‚ = 5:3")],
    ]
    return section_table("โš—  REACTIONS & STOICHIOMETRY  (Qs 11โ€“31)", rows,
                         [col_w*0.28, col_w*0.72], hdr_bg=colors.HexColor("#9f1239"))

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# SECTION 8 โ€” Thermo + Kinetics + Equilibrium  (right)
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
def thermo_block():
    rows = [
        [P("<b>q=mcฮ”T ex.</b>"),       P("30g Al, 20ยฐC rise: 30ร—0.9ร—20=540 J")],
        [P("<b>Always spontaneous</b>"),P("ฮ”H<0 AND ฮ”S>0 โ†’ ฮ”G always negative")],
        [P("<b>Collision theory</b>"),  P("Must collide + enough energy + right orientation")],
        [P("<b>Activated complex</b>"), P("Unstable high-energy intermediate at energy max")],
        [P("<b>โ†‘Temp</b>"),            P("Rate โ†‘ (faster particles, more energetic collisions)")],
        [P("<b>Catalyst</b>"),          P("Rate โ†‘, lowers Ea, does NOT change ฮ”H")],
        [P("<b>Equilibrium</b>"),       P("Fwd rate = Rev rate; both still occurring")],
        [P("<b>Keq</b>"),               P("Valid only at ONE temperature; conc/pressure shift = no Keq change")],
        [P("<b>Le Chatelier</b>"),      P("Stress โ†’ shifts to relieve it (add reactant โ†’ more product)")],
    ]
    return section_table("๐ŸŒก  THERMO ยท KINETICS ยท EQUILIBRIUM  (Qs 1โ€“9)", rows,
                         [col_w*0.28, col_w*0.72], hdr_bg=colors.HexColor("#1e40af"))

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# TITLE BANNER
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
title_data = [[Paragraph(
    "<b>HONORS CHEMISTRY โ€” FINAL EXAM CHEAT SHEET</b>  ยท  All 9 Topics  ยท  Pendleton High School",
    ParagraphStyle("tb", fontSize=9, textColor=WHITE, fontName="Helvetica-Bold",
                   alignment=TA_CENTER, leading=11)
)]]
title_t = Table(title_data, colWidths=[usable_w])
title_t.setStyle(TableStyle([
    ("BACKGROUND", (0,0),(0,0), DARK_BLUE),
    ("TOPPADDING", (0,0),(0,0), 3),
    ("BOTTOMPADDING",(0,0),(0,0),3),
    ("LEFTPADDING",(0,0),(0,0),4),
    ("RIGHTPADDING",(0,0),(0,0),4),
    ("ROUNDEDCORNERS",[3]),
]))

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# Assemble document
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
story = []
story.append(title_t)
story.append(Spacer(1, 1.5*mm))

# Row 1: Formulas | Polyatomic Ions
story.append(two_col(formulas_block(), ions_block()))
story.append(Spacer(1, 1.5*mm))

# Row 2: Bonding | Nomenclature
story.append(two_col(bonding_block(), nomenclature_block()))
story.append(Spacer(1, 1.5*mm))

# Row 3: Atomic Structure | Mole
story.append(two_col(atomic_block(), mole_block()))
story.append(Spacer(1, 1.5*mm))

# Row 4: Reactions & Stoich | Thermo+Kinetics+Equil
story.append(two_col(rxn_block(), thermo_block()))

doc.build(story)
print(f"โœ…  Saved to {output_path}")
Running Command

cd /home/daytona/workspace/chem-cheatsheet && python3 make_cheatsheet.py 2>&1

Reading File
Running Command

ls -lh /home/daytona/workspace/chem-cheatsheet/

Your cheat sheet is ready! Download it here:
๐Ÿ“„

HonorsChem CheatSheet

PDF Document ยท PDF

It's a single A4 page with all 9 topics, color-coded by section, in a two-column layout โ€” small enough to fit everything, big enough to actually read.

Your Game Plan for Tonight

Here's the order I'd study in, from "worth the most questions" to least:
PriorityTopicQuestions on test
๐Ÿ”ด 1stAtomic Structure & LightQs 44โ€“50 (7 Qs)
๐Ÿ”ด 2ndMole & Molar MassQs 19โ€“26 (8 Qs)
๐ŸŸ  3rdStoichiometryQs 11โ€“18 (8 Qs)
๐ŸŸ  4thChemical BondingQs 32โ€“40 (9 Qs)
๐ŸŸก 5thNomenclatureQs 27, 35, 36, 41โ€“43
๐ŸŸก 6thReactions & EquationsQs 28โ€“31
๐ŸŸข 7thThermochemistryQs 7โ€“9
๐ŸŸข 8thKineticsQs 3โ€“6
๐ŸŸข 9thEquilibriumQs 1โ€“2

The 10 Things You Absolutely Cannot Forget

  1. Fill order: 1s 2s 2p 3s 3p 4s 3d 4p (4s before 3d!)
  2. Ge = [Ar] 4sยฒ 3dยนโฐ 4pยฒ
  3. Triple bond = 1ฯƒ + 2ฯ€, Cโ‚‚Hโ‚„ = 5ฯƒ, 1ฯ€
  4. Moles = mass รท molar mass (always)
  5. Net ionic: remove spectator ions โ†’ Hโบ + OHโป โ†’ Hโ‚‚O
  6. % yield = actual รท theoretical ร— 100
  7. q = m ร— c ร— ฮ”T | 1 Cal = 4184 J
  8. ฮ”H < 0 + ฮ”S > 0 = always spontaneous
  9. Catalyst lowers Ea, does NOT change ฮ”H
  10. Equilibrium โ‰  reactions stopped โ€” both directions still running, Keq only changes with temperature
Good luck tomorrow โ€” you've got this! ๐ŸŽฏ

ok, I have A4 size paper and I have half front cover with periodic table. I want complete cheat sheet that tell me what to do with problem. how to answer questions not just give formula. also I want to do metallic, nonmetallic, charges, etc key on periodic table so take time and make a perfect cheet sheet with ability of pass any student who even doesnot belong to classs

Running Command

cd /home/daytona/workspace/chem-cheatsheet && python3 -c " from reportlab.lib.paโ€ฆ

Writing File

~/chem-cheatsheet/cheatsheet_v2.py

"""
Honors Chemistry โ€“ Ultimate Cheat Sheet  (2 pages, A4)
Page 1: Periodic Table with full legend
Page 2: Step-by-step problem-solving guide for all 9 exam topics
"""

from reportlab.lib.pagesizes import A4
from reportlab.lib.units import cm, mm, inch
from reportlab.lib import colors
from reportlab.pdfgen import canvas
from reportlab.platypus import (SimpleDocTemplate, Table, TableStyle,
                                 Paragraph, Spacer, HRFlowable, KeepTogether, PageBreak)
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
import math

W, H = A4   # 595.28 x 841.89

OUT = "/home/daytona/workspace/chem-cheatsheet/HonorsChem_UltimateCheatSheet.pdf"

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# COLOUR PALETTE
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
C = {
    # element categories
    "alkali":       colors.HexColor("#ff8a65"),   # orange-red
    "alkaline":     colors.HexColor("#ffcc02"),   # yellow
    "transition":   colors.HexColor("#90caf9"),   # light blue
    "post_metal":   colors.HexColor("#a5d6a7"),   # green
    "metalloid":    colors.HexColor("#ce93d8"),   # purple
    "nonmetal":     colors.HexColor("#fff176"),   # bright yellow
    "halogen":      colors.HexColor("#80deea"),   # teal
    "noble":        colors.HexColor("#ef9a9a"),   # pink
    "lanthanide":   colors.HexColor("#bcaaa4"),   # brown-grey
    "actinide":     colors.HexColor("#b0bec5"),   # blue-grey
    "unknown":      colors.HexColor("#eeeeee"),
    # ui
    "hdr":          colors.HexColor("#1a237e"),
    "hdr2":         colors.HexColor("#283593"),
    "hdr3":         colors.HexColor("#1565c0"),
    "sec1":         colors.HexColor("#0d47a1"),
    "sec2":         colors.HexColor("#1b5e20"),
    "sec3":         colors.HexColor("#4a148c"),
    "sec4":         colors.HexColor("#b71c1c"),
    "sec5":         colors.HexColor("#e65100"),
    "sec6":         colors.HexColor("#006064"),
    "sec7":         colors.HexColor("#880e4f"),
    "sec8":         colors.HexColor("#33691e"),
    "sec9":         colors.HexColor("#37474f"),
    "step_bg":      colors.HexColor("#e8f5e9"),
    "warn_bg":      colors.HexColor("#fff3e0"),
    "tip_bg":       colors.HexColor("#e3f2fd"),
    "example_bg":   colors.HexColor("#fce4ec"),
    "white":        colors.white,
    "light":        colors.HexColor("#f5f5f5"),
    "border":       colors.HexColor("#90a4ae"),
    "black":        colors.black,
    "darktext":     colors.HexColor("#212121"),
}

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# PERIODIC TABLE DATA
# format: (symbol, atomic_num, category, common_charge)
# category keys match C dict above
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
ELEMENTS = {
    # period 1
    (1,1):  ("H",  1,  "nonmetal",  "1+/1โˆ’"),
    (1,18): ("He", 2,  "noble",     "0"),
    # period 2
    (2,1):  ("Li", 3,  "alkali",    "1+"),
    (2,2):  ("Be", 4,  "alkaline",  "2+"),
    (2,13): ("B",  5,  "metalloid", "3+"),
    (2,14): ("C",  6,  "nonmetal",  "4โˆ’"),
    (2,15): ("N",  7,  "nonmetal",  "3โˆ’"),
    (2,16): ("O",  8,  "nonmetal",  "2โˆ’"),
    (2,17): ("F",  9,  "halogen",   "1โˆ’"),
    (2,18): ("Ne", 10, "noble",     "0"),
    # period 3
    (3,1):  ("Na", 11, "alkali",    "1+"),
    (3,2):  ("Mg", 12, "alkaline",  "2+"),
    (3,13): ("Al", 13, "post_metal","3+"),
    (3,14): ("Si", 14, "metalloid", "4โˆ’"),
    (3,15): ("P",  15, "nonmetal",  "3โˆ’"),
    (3,16): ("S",  16, "nonmetal",  "2โˆ’"),
    (3,17): ("Cl", 17, "halogen",   "1โˆ’"),
    (3,18): ("Ar", 18, "noble",     "0"),
    # period 4
    (4,1):  ("K",  19, "alkali",    "1+"),
    (4,2):  ("Ca", 20, "alkaline",  "2+"),
    (4,3):  ("Sc", 21, "transition","3+"),
    (4,4):  ("Ti", 22, "transition","4+"),
    (4,5):  ("V",  23, "transition","var"),
    (4,6):  ("Cr", 24, "transition","var"),
    (4,7):  ("Mn", 25, "transition","var"),
    (4,8):  ("Fe", 26, "transition","var"),
    (4,9):  ("Co", 27, "transition","var"),
    (4,10): ("Ni", 28, "transition","var"),
    (4,11): ("Cu", 29, "transition","var"),
    (4,12): ("Zn", 30, "transition","2+"),
    (4,13): ("Ga", 31, "post_metal","3+"),
    (4,14): ("Ge", 32, "metalloid", "4โˆ’"),
    (4,15): ("As", 33, "metalloid", "3โˆ’"),
    (4,16): ("Se", 34, "nonmetal",  "2โˆ’"),
    (4,17): ("Br", 35, "halogen",   "1โˆ’"),
    (4,18): ("Kr", 36, "noble",     "0"),
    # period 5
    (5,1):  ("Rb", 37, "alkali",    "1+"),
    (5,2):  ("Sr", 38, "alkaline",  "2+"),
    (5,3):  ("Y",  39, "transition","3+"),
    (5,4):  ("Zr", 40, "transition","4+"),
    (5,5):  ("Nb", 41, "transition","var"),
    (5,6):  ("Mo", 42, "transition","var"),
    (5,7):  ("Tc", 43, "transition","var"),
    (5,8):  ("Ru", 44, "transition","var"),
    (5,9):  ("Rh", 45, "transition","var"),
    (5,10): ("Pd", 46, "transition","var"),
    (5,11): ("Ag", 47, "transition","1+"),
    (5,12): ("Cd", 48, "transition","2+"),
    (5,13): ("In", 49, "post_metal","3+"),
    (5,14): ("Sn", 50, "post_metal","var"),
    (5,15): ("Sb", 51, "metalloid", "3โˆ’"),
    (5,16): ("Te", 52, "metalloid", "2โˆ’"),
    (5,17): ("I",  53, "halogen",   "1โˆ’"),
    (5,18): ("Xe", 54, "noble",     "0"),
    # period 6
    (6,1):  ("Cs", 55, "alkali",    "1+"),
    (6,2):  ("Ba", 56, "alkaline",  "2+"),
    (6,3):  ("La*",57, "lanthanide","3+"),
    (6,4):  ("Hf", 72, "transition","4+"),
    (6,5):  ("Ta", 73, "transition","5+"),
    (6,6):  ("W",  74, "transition","var"),
    (6,7):  ("Re", 75, "transition","var"),
    (6,8):  ("Os", 76, "transition","var"),
    (6,9):  ("Ir", 77, "transition","var"),
    (6,10): ("Pt", 78, "transition","var"),
    (6,11): ("Au", 79, "transition","var"),
    (6,12): ("Hg", 80, "transition","var"),
    (6,13): ("Tl", 81, "post_metal","var"),
    (6,14): ("Pb", 82, "post_metal","var"),
    (6,15): ("Bi", 83, "post_metal","3+"),
    (6,16): ("Po", 84, "metalloid", "2โˆ’"),
    (6,17): ("At", 85, "halogen",   "1โˆ’"),
    (6,18): ("Rn", 86, "noble",     "0"),
    # period 7
    (7,1):  ("Fr", 87, "alkali",    "1+"),
    (7,2):  ("Ra", 88, "alkaline",  "2+"),
    (7,3):  ("Ac*",89, "actinide",  "3+"),
}

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# DRAW PERIODIC TABLE  (Page 1)
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
def draw_page1(c_obj):
    c_obj.setPageSize(A4)
    page_w, page_h = A4
    margin = 6*mm

    # โ”€โ”€ TITLE BAR โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    bar_h = 18
    c_obj.setFillColor(C["hdr"])
    c_obj.rect(0, page_h - bar_h, page_w, bar_h, fill=1, stroke=0)
    c_obj.setFillColor(C["white"])
    c_obj.setFont("Helvetica-Bold", 10)
    c_obj.drawCentredString(page_w/2, page_h - 13, "HONORS CHEMISTRY โ€” ULTIMATE CHEAT SHEET  ยท  PAGE 1: PERIODIC TABLE REFERENCE")

    # โ”€โ”€ Table area โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    top = page_h - bar_h - 3*mm
    table_area_h = page_h * 0.50     # upper half
    legend_area_h = page_h * 0.47    # lower half

    # How many rows/cols: 7 rows (periods) + 2 gap rows for lanthanides/actinides
    # 18 columns (groups)
    num_rows = 9   # 7 periods + blank + 2 lan/act rows
    num_cols = 18
    cell_w = (page_w - 2*margin) / num_cols
    cell_h = table_area_h / (num_rows + 0.5)

    def draw_element(period, group, x_off=0, y_off=0):
        key = (period, group)
        if key not in ELEMENTS:
            return
        sym, anum, cat, charge = ELEMENTS[key]
        # position: col = group-1, row = period-1
        col_idx = group - 1
        row_idx = period - 1
        x = margin + col_idx * cell_w + x_off
        y = top - (row_idx + 1) * cell_h + y_off

        bg = C.get(cat, C["unknown"])
        c_obj.setFillColor(bg)
        c_obj.setStrokeColor(colors.HexColor("#546e7a"))
        c_obj.setLineWidth(0.3)
        c_obj.rect(x, y, cell_w - 0.5, cell_h - 0.5, fill=1, stroke=1)

        # atomic number
        c_obj.setFillColor(colors.HexColor("#37474f"))
        c_obj.setFont("Helvetica", 4.5)
        c_obj.drawString(x + 1, y + cell_h - 6, str(anum))

        # symbol
        c_obj.setFillColor(C["black"])
        sym_size = 7.5 if len(sym) <= 2 else 6.5
        c_obj.setFont("Helvetica-Bold", sym_size)
        c_obj.drawCentredString(x + cell_w/2, y + cell_h/2 - 2, sym)

        # charge (tiny)
        c_obj.setFillColor(colors.HexColor("#b71c1c"))
        c_obj.setFont("Helvetica", 3.8)
        c_obj.drawCentredString(x + cell_w/2, y + 1.5, charge)

    # Draw all elements periods 1-7
    for (period, group) in ELEMENTS:
        if period <= 7:
            draw_element(period, group)

    # โ”€โ”€ Lanthanide / Actinide rows (offset below main table) โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    lan_y_offset = -(1.3 * cell_h)  # shift down 1.3 rows below row 7
    act_y_offset = -(2.0 * cell_h)

    lan_symbols = [("La",57,"lanthanide","3+"),("Ce",58,"lanthanide","3+"),
                   ("Pr",59,"lanthanide","3+"),("Nd",60,"lanthanide","3+"),
                   ("Pm",61,"lanthanide","var"),("Sm",62,"lanthanide","3+"),
                   ("Eu",63,"lanthanide","3+"),("Gd",64,"lanthanide","3+"),
                   ("Tb",65,"lanthanide","3+"),("Dy",66,"lanthanide","3+"),
                   ("Ho",67,"lanthanide","3+"),("Er",68,"lanthanide","3+"),
                   ("Tm",69,"lanthanide","3+"),("Yb",70,"lanthanide","2+/3+"),
                   ("Lu",71,"lanthanide","3+")]

    act_symbols = [("Ac",89,"actinide","3+"),("Th",90,"actinide","4+"),
                   ("Pa",91,"actinide","5+"),("U",92,"actinide","var"),
                   ("Np",93,"actinide","var"),("Pu",94,"actinide","var"),
                   ("Am",95,"actinide","3+"),("Cm",96,"actinide","3+"),
                   ("Bk",97,"actinide","3+"),("Cf",98,"actinide","3+"),
                   ("Es",99,"actinide","3+"),("Fm",100,"actinide","3+"),
                   ("Md",101,"actinide","3+"),("No",102,"actinide","2+"),
                   ("Lr",103,"actinide","3+")]

    for i, (sym, anum, cat, charge) in enumerate(lan_symbols):
        col_idx = i + 3   # start at column 4
        row_idx = 7        # row 8 (below period 7)
        x = margin + col_idx * cell_w
        y = top - (row_idx + 1) * cell_h + lan_y_offset
        bg = C.get(cat, C["unknown"])
        c_obj.setFillColor(bg)
        c_obj.setStrokeColor(colors.HexColor("#546e7a"))
        c_obj.setLineWidth(0.3)
        c_obj.rect(x, y, cell_w - 0.5, cell_h - 0.5, fill=1, stroke=1)
        c_obj.setFillColor(colors.HexColor("#37474f"))
        c_obj.setFont("Helvetica", 4.5)
        c_obj.drawString(x + 1, y + cell_h - 6, str(anum))
        c_obj.setFillColor(C["black"])
        c_obj.setFont("Helvetica-Bold", 7)
        c_obj.drawCentredString(x + cell_w/2, y + cell_h/2 - 2, sym)
        c_obj.setFillColor(colors.HexColor("#b71c1c"))
        c_obj.setFont("Helvetica", 3.8)
        c_obj.drawCentredString(x + cell_w/2, y + 1.5, charge)

    for i, (sym, anum, cat, charge) in enumerate(act_symbols):
        col_idx = i + 3
        row_idx = 8
        x = margin + col_idx * cell_w
        y = top - (row_idx + 1) * cell_h + act_y_offset
        bg = C.get(cat, C["unknown"])
        c_obj.setFillColor(bg)
        c_obj.setStrokeColor(colors.HexColor("#546e7a"))
        c_obj.setLineWidth(0.3)
        c_obj.rect(x, y, cell_w - 0.5, cell_h - 0.5, fill=1, stroke=1)
        c_obj.setFillColor(colors.HexColor("#37474f"))
        c_obj.setFont("Helvetica", 4.5)
        c_obj.drawString(x + 1, y + cell_h - 6, str(anum))
        c_obj.setFillColor(C["black"])
        c_obj.setFont("Helvetica-Bold", 7)
        c_obj.drawCentredString(x + cell_w/2, y + cell_h/2 - 2, sym)
        c_obj.setFillColor(colors.HexColor("#b71c1c"))
        c_obj.setFont("Helvetica", 3.8)
        c_obj.drawCentredString(x + cell_w/2, y + 1.5, charge)

    # โ”€โ”€ Group labels across top โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    groups = ["1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18"]
    group_names = ["IA","IIA","","","","","","","","","","","IIIA","IVA","VA","VIA","VIIA","0"]
    for i, (g, gn) in enumerate(zip(groups, group_names)):
        x = margin + i * cell_w + cell_w/2
        y = top + 1*mm
        c_obj.setFillColor(C["hdr2"])
        c_obj.setFont("Helvetica-Bold", 5.5)
        c_obj.drawCentredString(x, y, g)
        c_obj.setFillColor(C["sec9"])
        c_obj.setFont("Helvetica", 4.5)
        c_obj.drawCentredString(x, y - 5, gn)

    # โ”€โ”€ Period labels โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    for p in range(1, 8):
        y = top - p * cell_h + cell_h/2 - 2
        c_obj.setFillColor(C["hdr2"])
        c_obj.setFont("Helvetica-Bold", 6)
        c_obj.drawRightString(margin - 1, y, str(p))

    # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
    # LEGEND AREA  (lower half of page)
    # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
    # Calculate where periodic table ends
    last_element_y = top - 7 * cell_h + act_y_offset - 2*mm
    legend_top = last_element_y - 4*mm

    # โ”€โ”€ Legend title โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    c_obj.setFillColor(C["hdr"])
    c_obj.rect(margin, legend_top - 10, page_w - 2*margin, 11, fill=1, stroke=0)
    c_obj.setFillColor(C["white"])
    c_obj.setFont("Helvetica-Bold", 8)
    c_obj.drawCentredString(page_w/2, legend_top - 7, "PERIODIC TABLE KEY  โ€”  Everything you need to read and use the table above")

    legend_y = legend_top - 14

    # โ”€โ”€ Category legend โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    cats = [
        ("Alkali Metals\n(Group 1)", "alkali", "Very reactive, 1+ charge\nNa, K, Li, Rb, Cs"),
        ("Alkaline Earth\n(Group 2)", "alkaline", "Reactive, 2+ charge\nMg, Ca, Ba, Sr"),
        ("Transition Metals\n(Groups 3โ€“12)", "transition", "Often variable charge\nFe, Cu, Zn, Cr, Mn"),
        ("Post-Transition\nMetals", "post_metal", "Lower melting metals\nAl(3+), Pb, Sn"),
        ("Metalloids", "metalloid", "Semi-metal properties\nSi, Ge, As, B"),
        ("Nonmetals\n(non-halogens)", "nonmetal", "Form negative ions\nC, N, O, P, S"),
        ("Halogens\n(Group 17)", "halogen", "Very reactive, 1โˆ’ charge\nF, Cl, Br, I"),
        ("Noble Gases\n(Group 18)", "noble", "Inert, 0 charge\nHe, Ne, Ar"),
    ]

    box_w = (page_w - 2*margin) / 4
    box_h = 22
    for i, (name, cat, desc) in enumerate(cats):
        col = i % 4
        row = i // 4
        x = margin + col * box_w
        y = legend_y - row * (box_h + 1.5)

        # color swatch
        c_obj.setFillColor(C[cat])
        c_obj.setStrokeColor(C["border"])
        c_obj.setLineWidth(0.5)
        c_obj.rect(x, y - box_h + 2, box_w - 2, box_h - 2, fill=1, stroke=1)

        c_obj.setFillColor(C["black"])
        c_obj.setFont("Helvetica-Bold", 6.5)
        # split name lines
        lines = name.split("\n")
        for li, ln in enumerate(lines):
            c_obj.drawString(x + 2, y - 8 - li*7, ln)
        c_obj.setFillColor(colors.HexColor("#37474f"))
        c_obj.setFont("Helvetica", 5.5)
        desc_lines = desc.split("\n")
        for li, ln in enumerate(desc_lines):
            c_obj.drawString(x + 2, y - 8 - len(lines)*7 - li*6, ln)

    legend_y2 = legend_y - 2*(box_h + 1.5) - 5*mm

    # โ”€โ”€ Metal vs Nonmetal dividing line arrow โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    c_obj.setFillColor(C["hdr"])
    c_obj.setFont("Helvetica-Bold", 7)
    c_obj.drawString(margin, legend_y2, "METALS (left of staircase) vs  NONMETALS (right of staircase)  vs  METALLOIDS (on the staircase line)")

    # โ”€โ”€ Group charges quick-ref โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    legend_y3 = legend_y2 - 7*mm

    c_obj.setFillColor(C["hdr2"])
    c_obj.rect(margin, legend_y3 - 10, page_w - 2*margin, 11, fill=1, stroke=0)
    c_obj.setFillColor(C["white"])
    c_obj.setFont("Helvetica-Bold", 7.5)
    c_obj.drawCentredString(page_w/2, legend_y3 - 7.5, "COMMON ION CHARGES  โ€”  Memorize these!")

    charges_table_y = legend_y3 - 14

    charge_data = [
        ("Group 1\n(Li,Na,K,Rb,Cs)", "1+", C["alkali"]),
        ("Group 2\n(Be,Mg,Ca,Sr,Ba)", "2+", C["alkaline"]),
        ("Group 13\n(Al,Ga,In)", "3+", C["post_metal"]),
        ("Group 15\n(N,P,As)", "3โˆ’", C["nonmetal"]),
        ("Group 16\n(O,S,Se,Te)", "2โˆ’", C["nonmetal"]),
        ("Group 17\n(F,Cl,Br,I,At)", "1โˆ’", C["halogen"]),
        ("Group 18\n(He,Ne,Ar,Kr)", "0", C["noble"]),
        ("Transition metals\n(Fe,Cu,Cr,Mnโ€ฆ)", "VARIABLE\n(Roman #)", C["transition"]),
    ]

    cbox_w = (page_w - 2*margin) / 8
    cbox_h = 22
    for i, (name, charge, bg) in enumerate(charge_data):
        x = margin + i * cbox_w
        y_c = charges_table_y

        c_obj.setFillColor(bg)
        c_obj.setStrokeColor(C["border"])
        c_obj.setLineWidth(0.5)
        c_obj.rect(x, y_c - cbox_h, cbox_w - 1, cbox_h, fill=1, stroke=1)

        c_obj.setFillColor(C["black"])
        c_obj.setFont("Helvetica", 5.5)
        name_lines = name.split("\n")
        for li, ln in enumerate(name_lines):
            c_obj.drawCentredString(x + cbox_w/2, y_c - 8 - li*6, ln)

        c_obj.setFillColor(C["hdr"])
        c_obj.setFont("Helvetica-Bold", 9)
        c_obj.drawCentredString(x + cbox_w/2, y_c - cbox_h + 3, charge)

    # โ”€โ”€ Special elements box โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    spec_y = charges_table_y - cbox_h - 5*mm

    # box background
    c_obj.setFillColor(colors.HexColor("#e3f2fd"))
    c_obj.setStrokeColor(C["hdr3"])
    c_obj.setLineWidth(0.6)
    col_w2 = (page_w - 2*margin) / 3
    box_height = 30
    c_obj.rect(margin, spec_y - box_height, page_w - 2*margin, box_height, fill=1, stroke=1)

    # Three special notes side by side
    specials = [
        ("โ˜… Diatomic Elements (HOFBrINCl)",
         "These 7 exist as PAIRS in nature:\nHโ‚‚  Oโ‚‚  Fโ‚‚  Brโ‚‚  Iโ‚‚  Nโ‚‚  Clโ‚‚\nAlways write them with subscript 2!"),
        ("โ˜… Noble Gases = MONOATOMIC",
         "He, Ne, Ar, Kr, Xe, Rn\nGo solo โ€” never bond with others.\nDo NOT write Heโ‚‚!"),
        ("โ˜… Variable-Charge Metals (use Roman #)",
         "Fe: 2+ (iron II) or 3+ (iron III)\nCu: 1+ (copper I) or 2+ (copper II)\nPb: 2+ or 4+    Sn: 2+ or 4+"),
    ]
    for i, (title, body) in enumerate(specials):
        x = margin + i * col_w2 + 2
        c_obj.setFillColor(C["hdr"])
        c_obj.setFont("Helvetica-Bold", 6.5)
        c_obj.drawString(x, spec_y - 9, title)
        c_obj.setFillColor(C["darktext"])
        c_obj.setFont("Helvetica", 5.8)
        for li, ln in enumerate(body.split("\n")):
            c_obj.drawString(x, spec_y - 17 - li*6.5, ln)

    # โ”€โ”€ Polyatomic ions โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    ions_y = spec_y - box_height - 4*mm

    c_obj.setFillColor(colors.HexColor("#4a148c"))
    c_obj.rect(margin, ions_y - 10, page_w - 2*margin, 11, fill=1, stroke=0)
    c_obj.setFillColor(C["white"])
    c_obj.setFont("Helvetica-Bold", 7.5)
    c_obj.drawCentredString(page_w/2, ions_y - 7.5, "POLYATOMIC IONS YOU MUST KNOW  (these appear on the exam!)")

    ion_y2 = ions_y - 14
    ions = [
        ("SOโ‚„ยฒโป", "sulfate"),   ("SOโ‚ƒยฒโป", "sulfite"),
        ("NOโ‚ƒโป",  "nitrate"),   ("NOโ‚‚โป",  "nitrite"),
        ("POโ‚„ยณโป", "phosphate"), ("OHโป",   "hydroxide"),
        ("NHโ‚„โบ",  "ammonium"),  ("ClOโป",  "hypochlorite"),
        ("ClOโ‚‚โป", "chlorite"),  ("ClOโ‚ƒโป", "chlorate"),
        ("ClOโ‚„โป", "perchlorate"),("COโ‚ƒยฒโป","carbonate"),
    ]
    ion_col_w = (page_w - 2*margin) / 6
    for i, (ion, name) in enumerate(ions):
        col = i % 6
        row = i // 6
        x = margin + col * ion_col_w
        y_i = ion_y2 - row * 11

        bg_color = colors.HexColor("#ede7f6") if i % 2 == 0 else colors.HexColor("#f3e5f5")
        c_obj.setFillColor(bg_color)
        c_obj.setStrokeColor(colors.HexColor("#7b1fa2"))
        c_obj.setLineWidth(0.4)
        c_obj.rect(x, y_i - 9, ion_col_w - 1, 10, fill=1, stroke=1)
        c_obj.setFillColor(colors.HexColor("#4a148c"))
        c_obj.setFont("Helvetica-Bold", 7)
        c_obj.drawString(x + 2, y_i - 7, ion)
        c_obj.setFillColor(C["darktext"])
        c_obj.setFont("Helvetica", 6)
        c_obj.drawString(x + 30, y_i - 7, name)

    # โ”€โ”€ Memory tricks โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    mem_y = ion_y2 - 2 * 11 - 3*mm

    c_obj.setFillColor(colors.HexColor("#fff8e1"))
    c_obj.setStrokeColor(colors.HexColor("#f57f17"))
    c_obj.setLineWidth(0.7)
    c_obj.rect(margin, mem_y - 24, page_w - 2*margin, 25, fill=1, stroke=1)

    c_obj.setFillColor(colors.HexColor("#e65100"))
    c_obj.setFont("Helvetica-Bold", 7)
    c_obj.drawString(margin + 3, mem_y - 8, "MEMORY TRICKS:")
    c_obj.setFillColor(C["darktext"])
    c_obj.setFont("Helvetica", 6.5)
    tricks = (
        "  -ate = MORE oxygens   |   -ite = FEWER oxygens   (sulfATE=SOโ‚„ยฒโป has MORE O than sulfITE=SOโ‚ƒยฒโป)   "
        "|   hypo- = fewest O (ClOโป)   |   per- = most O (ClOโ‚„โป)   "
        "|   Electron config fill order: 1s 2s 2p 3s 3p 4s 3d 4p  (4s BEFORE 3d!)"
    )
    c_obj.drawString(margin + 3, mem_y - 18, tricks[:110])
    c_obj.drawString(margin + 3, mem_y - 26 + 2, tricks[110:])

    # โ”€โ”€ Footer โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    c_obj.setFillColor(C["hdr"])
    c_obj.rect(0, 0, page_w, 10, fill=1, stroke=0)
    c_obj.setFillColor(C["white"])
    c_obj.setFont("Helvetica", 5.5)
    c_obj.drawCentredString(page_w/2, 3, "Page 1 of 2  ยท  Pendleton High School  ยท  Honors Chemistry Final Exam  ยท  Flip for STEP-BY-STEP PROBLEM SOLVING GUIDE  โ†’")


# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# PAGE 2 โ€” STEP-BY-STEP PROBLEM SOLVING GUIDE
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
def draw_page2(c_obj):
    c_obj.showPage()
    c_obj.setPageSize(A4)
    page_w, page_h = A4
    margin = 6*mm

    # TITLE BAR
    c_obj.setFillColor(C["hdr"])
    c_obj.rect(0, page_h - 18, page_w, 18, fill=1, stroke=0)
    c_obj.setFillColor(C["white"])
    c_obj.setFont("Helvetica-Bold", 10)
    c_obj.drawCentredString(page_w/2, page_h - 13, "PAGE 2: HOW TO SOLVE EVERY PROBLEM TYPE  ยท  Step-by-step for ALL 9 exam topics")

    y = page_h - 22

    col_w = (page_w - 2*margin - 3*mm) / 2
    left_x = margin
    right_x = margin + col_w + 3*mm

    # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    # Helper functions
    # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    def section_bar(x, y, w, title, color):
        c_obj.setFillColor(color)
        c_obj.rect(x, y - 11, w, 11, fill=1, stroke=0)
        c_obj.setFillColor(C["white"])
        c_obj.setFont("Helvetica-Bold", 7.5)
        c_obj.drawString(x + 3, y - 8.5, title)
        return y - 12

    def step_box(x, y, w, steps, bg=colors.HexColor("#e8f5e9"), border=colors.HexColor("#2e7d32")):
        """Draw numbered step box, returns new y"""
        line_h = 8
        padding = 3
        total_h = len(steps) * line_h + 2 * padding
        c_obj.setFillColor(bg)
        c_obj.setStrokeColor(border)
        c_obj.setLineWidth(0.5)
        c_obj.rect(x, y - total_h, w, total_h, fill=1, stroke=1)
        for i, step in enumerate(steps):
            c_obj.setFillColor(border)
            c_obj.setFont("Helvetica-Bold", 6)
            c_obj.drawString(x + 2, y - padding - i*line_h - 5.5, f"{i+1}.")
            c_obj.setFillColor(C["darktext"])
            c_obj.setFont("Helvetica", 6)
            c_obj.drawString(x + 10, y - padding - i*line_h - 5.5, step)
        return y - total_h - 1

    def example_box(x, y, w, text, bg=colors.HexColor("#fce4ec"), border=colors.HexColor("#c62828")):
        lines = text.split("\n")
        line_h = 7.5
        padding = 3
        total_h = len(lines) * line_h + 2 * padding
        c_obj.setFillColor(bg)
        c_obj.setStrokeColor(border)
        c_obj.setLineWidth(0.5)
        c_obj.rect(x, y - total_h, w, total_h, fill=1, stroke=1)
        c_obj.setFillColor(border)
        c_obj.setFont("Helvetica-Bold", 5.5)
        c_obj.drawString(x + 2, y - padding - 5, "EXAMPLE:")
        c_obj.setFillColor(C["darktext"])
        c_obj.setFont("Helvetica", 5.8)
        for i, ln in enumerate(lines):
            c_obj.drawString(x + 2, y - padding - (i+1)*line_h - 2, ln)
        return y - total_h - 1

    def tip_box(x, y, w, text, bg=colors.HexColor("#e3f2fd"), border=colors.HexColor("#1565c0")):
        lines = text.split("\n")
        line_h = 7
        padding = 2.5
        total_h = len(lines) * line_h + 2 * padding
        c_obj.setFillColor(bg)
        c_obj.setStrokeColor(border)
        c_obj.setLineWidth(0.4)
        c_obj.rect(x, y - total_h, w, total_h, fill=1, stroke=1)
        c_obj.setFillColor(border)
        c_obj.setFont("Helvetica-Bold", 5.5)
        c_obj.drawString(x + 2, y - padding - 5, "โ˜… TIP:")
        c_obj.setFillColor(C["darktext"])
        c_obj.setFont("Helvetica", 5.8)
        for i, ln in enumerate(lines):
            c_obj.drawString(x + 30, y - padding - i*line_h - 5, ln)
        return y - total_h - 1

    # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
    # LEFT COLUMN
    # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
    ly = y - 2

    # 1. LIGHT & PHOTON ENERGY (Qs 44-50)
    ly = section_bar(left_x, ly, col_w, "1. LIGHT & PHOTON ENERGY  (Qs 44โ€“50)", C["sec3"])
    ly = step_box(left_x, ly, col_w, [
        "Find wavelength (ฮป) or frequency (ฮฝ)?  Use: ฮฝ = c / ฮป",
        "Find energy (E)?  Use: E = h ร— ฮฝ",
        "c = 3.00ร—10โธ m/s    h = 6.626ร—10โปยณโด Jยทs",
        "Watch units! ฮป must be in METERS (convert nm: ร—10โปโน)",
    ])
    ly = example_box(left_x, ly, col_w,
        "ฮป = 500 nm = 5.00ร—10โปโท m\nฮฝ = 3.00ร—10โธ / 5.00ร—10โปโท = 6.00ร—10ยนโด Hz\nE = 6.626ร—10โปยณโด ร— 6.00ร—10ยนโด = 3.98ร—10โปยนโน J")
    ly -= 1.5*mm

    # 2. ELECTRON CONFIGURATION (Qs 44-50)
    ly = section_bar(left_x, ly, col_w, "2. ELECTRON CONFIGURATION  (Qs 44โ€“50)", C["sec3"])
    ly = step_box(left_x, ly, col_w, [
        "Fill in ORDER: 1sยฒ 2sยฒ 2pโถ 3sยฒ 3pโถ 4sยฒ 3dยนโฐ 4pโถ ...",
        "USE [noble gas] shortcut: find previous noble gas",
        "Then continue filling from where it left off",
        "Check: total electrons = atomic number (neutral atom)",
    ])
    ly = example_box(left_x, ly, col_w,
        "Germanium (Ge, Z=32, Period 4 Group 14)\nPrev. noble gas = Ar (Z=18)  โ†’  [Ar]\nThen add: 4sยฒ 3dยนโฐ 4pยฒ  (32-18=14 more eโป)\nFull config: [Ar] 4sยฒ 3dยนโฐ 4pยฒ")
    ly -= 1.5*mm

    # 3. NAMING IONIC COMPOUNDS
    ly = section_bar(left_x, ly, col_w, "3. NAME IONIC COMPOUNDS  (Qs 27,41โ€“43)", C["sec5"])
    ly = step_box(left_x, ly, col_w, [
        "1st: Write metal name as-is",
        "2nd: Is it a variable-charge metal? (Fe,Cu,Pb,Sn,Hg,Cr,Mn...)",
        "   YES โ†’ figure out charge, add Roman numeral in ()",
        "   NO  โ†’ skip Roman numeral",
        "3rd: Write anion name (-ide for simple, -ate/-ite for polyatomic)",
    ])
    ly = example_box(left_x, ly, col_w,
        "Feโ‚‚(SOโ‚„)โ‚ƒ โ†’ 3 sulfate = 6โˆ’ total โ†’ Fe must be 3+ each\nโ†’ Iron(III) sulfate\nCaโ‚ƒ(POโ‚„)โ‚‚ โ†’ Ca is always 2+, no Roman numeral\nโ†’ Calcium phosphate")
    ly -= 1.5*mm

    # 4. WRITING IONIC FORMULAS
    ly = section_bar(left_x, ly, col_w, "4. WRITE IONIC FORMULAS  (Qs 35โ€“36)", C["sec5"])
    ly = step_box(left_x, ly, col_w, [
        "Write cation (metal, +) first, anion (nonmetal, โˆ’) second",
        "CROSSOVER: swap the charge numbers as subscripts",
        "Reduce if possible (e.g. Caยฒโบ + Oยฒโป โ†’ CaO, not Caโ‚‚Oโ‚‚)",
        "Polyatomic ion with subscript >1? Put it in parentheses",
    ])
    ly = example_box(left_x, ly, col_w,
        "Caยฒโบ + POโ‚„ยณโป  โ†’  cross: Caโ‚ƒ(POโ‚„)โ‚‚\n   Check: 3ร—(2+) = 6+  and  2ร—(3โˆ’) = 6โˆ’  โœ“\nFeยณโบ + Oยฒโป  โ†’  Feโ‚‚Oโ‚ƒ  (cross 3 and 2)")
    ly -= 1.5*mm

    # 5. MOLE CALCULATIONS
    ly = section_bar(left_x, ly, col_w, "5. MOLE CALCULATIONS  (Qs 19โ€“26)", C["sec2"])
    ly = step_box(left_x, ly, col_w, [
        "FIND MOLAR MASS FIRST: add atomic masses of all atoms",
        "Grams โ†’ Moles:  moles = grams รท molar mass",
        "Moles โ†’ Grams:  grams = moles ร— molar mass",
        "Moles โ†’ Particles:  particles = moles ร— 6.02ร—10ยฒยณ",
        "Particles โ†’ Moles:  moles = particles รท 6.02ร—10ยฒยณ",
    ])
    ly = example_box(left_x, ly, col_w,
        "How many moles in 100 g Clโ‚‚?\n  MM of Clโ‚‚ = 2 ร— 35.5 = 71.0 g/mol\n  moles = 100 รท 71.0 = 1.41 mol\nMM of Hโ‚‚CrOโ‚„ = 2(1) + 52 + 4(16) = 118 g/mol")
    ly -= 1.5*mm

    # 6. PERCENT COMPOSITION
    ly = section_bar(left_x, ly, col_w, "6. PERCENT COMPOSITION  (Qs 19โ€“26)", C["sec2"])
    ly = step_box(left_x, ly, col_w, [
        "Calculate molar mass of the compound",
        "For each element: % = (elem. mass in formula / MM) ร— 100",
        "All percentages must add to 100%",
    ])
    ly = example_box(left_x, ly, col_w,
        "% C in COโ‚‚?  MM=44 g/mol\n  % C = 12/44 ร— 100 = 27.3%\n  % O = 32/44 ร— 100 = 72.7%  โœ“ (27.3+72.7=100)")

    # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
    # RIGHT COLUMN
    # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
    ry = y - 2

    # 7. EMPIRICAL FORMULA
    ry = section_bar(right_x, ry, col_w, "7. EMPIRICAL FORMULA FROM % COMP  (Qs 19โ€“26)", C["sec2"])
    ry = step_box(right_x, ry, col_w, [
        "Assume 100 g sample โ†’ % directly becomes grams",
        "Divide each element's grams by its atomic mass โ†’ moles",
        "Divide ALL mole values by the SMALLEST one",
        "Round to nearest whole number โ†’ those are subscripts",
        "If .5 remains, multiply all by 2; if .33, multiply by 3",
    ])
    ry = example_box(right_x, ry, col_w,
        "85.6% C, 14.4% H\n  C: 85.6 รท 12 = 7.13 mol\n  H: 14.4 รท 1  = 14.4 mol\n  Divide by smallest (7.13): C=1, H=2.02โ‰ˆ2\n  Empirical formula: CHโ‚‚")
    ry -= 1.5*mm

    # 8. STOICHIOMETRY (Qs 11-18)
    ry = section_bar(right_x, ry, col_w, "8. STOICHIOMETRY  (Qs 11โ€“18)", C["sec4"])
    ry = step_box(right_x, ry, col_w, [
        "STEP 1: Write and BALANCE the equation (never skip!)",
        "STEP 2: Convert what you're given to MOLES",
        "         (grams รท molar mass)",
        "STEP 3: Multiply by the MOLAR RATIO from equation",
        "         (coefficient wanted / coefficient given)",
        "STEP 4: Convert moles to what they ask for",
        "         (ร— molar mass for grams; ร— 6.02ร—10ยฒยณ for particles)",
    ])
    ry = example_box(right_x, ry, col_w,
        "Cโ‚ƒHโ‚ˆ + 5Oโ‚‚ โ†’ 3COโ‚‚ + 4Hโ‚‚O\nHow many mol COโ‚‚ from 2 mol Cโ‚ƒHโ‚ˆ?\n  ratio COโ‚‚:Cโ‚ƒHโ‚ˆ = 3:1\n  2 mol Cโ‚ƒHโ‚ˆ ร— (3/1) = 6 mol COโ‚‚")
    ry -= 1.5*mm

    # 9. LIMITING REACTANT
    ry = section_bar(right_x, ry, col_w, "9. LIMITING REACTANT  (Qs 11โ€“18)", C["sec4"])
    ry = step_box(right_x, ry, col_w, [
        "Balance the equation",
        "Convert BOTH reactants to moles",
        "Use molar ratio โ†’ find how much product each reactant makes",
        "The one that makes LESS product = LIMITING reactant",
        "Use limiting reactant's amount to find theoretical yield",
    ])
    ry = example_box(right_x, ry, col_w,
        "5 mol Fe + 4 mol Oโ‚‚  โ†’  2Feโ‚‚Oโ‚ƒ?\n  Balanced: 4Fe + 3Oโ‚‚ โ†’ 2Feโ‚‚Oโ‚ƒ\n  Fe: 5 ร— (2/4) = 2.5 mol product\n  Oโ‚‚: 4 ร— (2/3) = 2.67 mol product\n  Fe makes less โ†’ Fe is LIMITING REACTANT")
    ry -= 1.5*mm

    # 10. PERCENT YIELD
    ry = section_bar(right_x, ry, col_w, "10. PERCENT YIELD  (Qs 11โ€“18)", C["sec4"])
    ry = step_box(right_x, ry, col_w, [
        "Theoretical yield = what stoichiometry PREDICTS (max possible)",
        "Actual yield = what you ACTUALLY got in the lab",
        "% yield = (actual รท theoretical) ร— 100",
        "% yield is ALWAYS โ‰ค 100% (can't make more than predicted!)",
    ])
    ry = example_box(right_x, ry, col_w,
        "Theory says 10 g, lab gives 8.2 g\n% yield = (8.2 / 10) ร— 100 = 82%")
    ry -= 1.5*mm

    # 11. THERMOCHEMISTRY
    ry = section_bar(right_x, ry, col_w, "11. HEAT & SPONTANEITY  (Qs 7โ€“9)", C["sec6"])
    ry = step_box(right_x, ry, col_w, [
        "HEAT:  q = m ร— c ร— ฮ”T   (ฮ”T = T_final โˆ’ T_initial)",
        "c for water = 4.18 J/gยทยฐC    c for Al = 0.900 J/gยทยฐC",
        "CALORIES: 1 Cal (food) = 1 kcal = 4184 J",
        "SPONTANEOUS if: ฮ”H<0 AND ฮ”S>0 (always, any temp)",
        "Catalyst: lowers Ea, DOES NOT change ฮ”H",
    ])
    ry = example_box(right_x, ry, col_w,
        "30 g Al heated 20ยฐCโ†’40ยฐC:\n  q = 30 ร— 0.900 ร— (40โˆ’20) = 540 J\n170 Calories to J:\n  170 ร— 4184 = 711,280 J = 7.11ร—10โต J")
    ry -= 1.5*mm

    # 12. KINETICS + EQUILIBRIUM
    ry = section_bar(right_x, ry, col_w, "12. KINETICS & EQUILIBRIUM  (Qs 1โ€“6)", C["sec7"])
    ry = step_box(right_x, ry, col_w, [
        "Rate INCREASES with: โ†‘temp, โ†‘conc, โ†‘surface area, catalyst",
        "Activated complex = unstable high-E intermediate (not prod, not react)",
        "EQUILIBRIUM: forward rate = reverse rate (both still running!)",
        "Keq changes ONLY with temperature",
        "Le Chatelier: add reactant โ†’ shift right (โ†’ more product)",
        "Le Chatelier: add product  โ†’ shift left (โ† more reactant)",
    ])
    ry = tip_box(right_x, ry, col_w,
        "Conc/pressure changes SHIFT equilibrium but DO NOT change Keq value!\nOnly temperature changes Keq itself.")

    # โ”€โ”€ BALANCING & NET IONIC (across bottom, full width) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    bottom_y = min(ly, ry) - 3*mm
    full_w = page_w - 2*margin

    bottom_y = section_bar(left_x, bottom_y, full_w, "13. BALANCING EQUATIONS & NET IONIC  (Qs 28โ€“31)", C["sec9"])
    # two sub-columns
    sub_w = (full_w - 2*mm) / 2

    # balance steps
    balance_steps = [
        "Count atoms of each element on each side",
        "Add COEFFICIENTS (big numbers in front) to balance",
        "NEVER change subscripts inside formulas",
        "Balance in this order: metals first, then nonmetals, H last, O last",
    ]
    for i, s in enumerate(balance_steps):
        c_obj.setFillColor(colors.HexColor("#e8f5e9"))
        c_obj.setStrokeColor(colors.HexColor("#2e7d32"))
        c_obj.setLineWidth(0.3)

    # left balance box
    bx = left_x
    by = bottom_y - 1
    bh = len(balance_steps) * 8 + 6
    c_obj.setFillColor(colors.HexColor("#e8f5e9"))
    c_obj.setStrokeColor(colors.HexColor("#2e7d32"))
    c_obj.setLineWidth(0.5)
    c_obj.rect(bx, by - bh, sub_w, bh, fill=1, stroke=1)
    c_obj.setFillColor(colors.HexColor("#1b5e20"))
    c_obj.setFont("Helvetica-Bold", 6)
    c_obj.drawString(bx+2, by-7, "HOW TO BALANCE:")
    for i, s in enumerate(balance_steps):
        c_obj.setFillColor(colors.HexColor("#1b5e20"))
        c_obj.setFont("Helvetica-Bold", 5.8)
        c_obj.drawString(bx+2, by - 15 - i*8, f"{i+1}.")
        c_obj.setFillColor(C["darktext"])
        c_obj.setFont("Helvetica", 5.8)
        c_obj.drawString(bx+10, by - 15 - i*8, s)

    # example
    ex_y = by - bh - 1
    ex_text = ["EX: SiOโ‚‚ + HF โ†’ SiFโ‚„ + Hโ‚‚O",
               "Need 4F on right โ†’ 4HF on left",
               "4HF โ†’ 4H โ†’ 2Hโ‚‚O โ†’ 2O matches SiOโ‚‚ โœ“",
               "Answer: SiOโ‚‚ + 4HF โ†’ SiFโ‚„ + 2Hโ‚‚O"]
    ex_h = len(ex_text)*7 + 4
    c_obj.setFillColor(colors.HexColor("#fce4ec"))
    c_obj.setStrokeColor(colors.HexColor("#c62828"))
    c_obj.setLineWidth(0.4)
    c_obj.rect(bx, ex_y - ex_h, sub_w, ex_h, fill=1, stroke=1)
    for i, ln in enumerate(ex_text):
        c_obj.setFillColor(C["darktext"])
        c_obj.setFont("Helvetica", 5.8)
        c_obj.drawString(bx+3, ex_y - 7 - i*7, ln)

    # right: net ionic
    nx = left_x + sub_w + 2
    ny = bottom_y - 1
    net_steps = [
        "Write FULL equation with all compounds",
        "Split ALL aqueous (aq) ionic compounds into their ions",
        "Solids (s), liquids (l), gases (g) stay as molecules",
        "Cross out ions that appear UNCHANGED on both sides (spectators)",
        "What remains = NET ionic equation",
    ]
    nh = len(net_steps) * 8 + 6
    c_obj.setFillColor(colors.HexColor("#e3f2fd"))
    c_obj.setStrokeColor(colors.HexColor("#1565c0"))
    c_obj.setLineWidth(0.5)
    c_obj.rect(nx, ny - nh, sub_w, nh, fill=1, stroke=1)
    c_obj.setFillColor(colors.HexColor("#0d47a1"))
    c_obj.setFont("Helvetica-Bold", 6)
    c_obj.drawString(nx+2, ny-7, "NET IONIC EQUATION:")
    for i, s in enumerate(net_steps):
        c_obj.setFillColor(colors.HexColor("#0d47a1"))
        c_obj.setFont("Helvetica-Bold", 5.8)
        c_obj.drawString(nx+2, ny - 15 - i*8, f"{i+1}.")
        c_obj.setFillColor(C["darktext"])
        c_obj.setFont("Helvetica", 5.8)
        c_obj.drawString(nx+10, ny - 15 - i*8, s)

    nex_y = ny - nh - 1
    nex = ["HCl(aq)+KOH(aq)โ†’KCl(aq)+Hโ‚‚O(l)",
           "Split: Hโบ+Clโป+Kโบ+OHโปโ†’Kโบ+Clโป+Hโ‚‚O",
           "Spectators: Kโบ and Clโป  (cancel them)",
           "Net ionic: Hโบ(aq) + OHโป(aq) โ†’ Hโ‚‚O(l)"]
    nex_h = len(nex)*7 + 4
    c_obj.setFillColor(colors.HexColor("#fce4ec"))
    c_obj.setStrokeColor(colors.HexColor("#c62828"))
    c_obj.setLineWidth(0.4)
    c_obj.rect(nx, nex_y - nex_h, sub_w, nex_h, fill=1, stroke=1)
    for i, ln in enumerate(nex):
        c_obj.setFillColor(C["darktext"])
        c_obj.setFont("Helvetica", 5.8)
        c_obj.drawString(nx+3, nex_y - 7 - i*7, ln)

    # FOOTER
    c_obj.setFillColor(C["hdr"])
    c_obj.rect(0, 0, page_w, 10, fill=1, stroke=0)
    c_obj.setFillColor(C["white"])
    c_obj.setFont("Helvetica", 5.5)
    c_obj.drawCentredString(page_w/2, 3, "Page 2 of 2  ยท  Pendleton High School  ยท  Honors Chemistry Final Exam  ยท  โ† See Page 1 for Periodic Table")


# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# BUILD PDF
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
c_obj = canvas.Canvas(OUT, pagesize=A4)
draw_page1(c_obj)
draw_page2(c_obj)
c_obj.save()
print(f"โœ… Saved: {OUT}")
Running Command

cd /home/daytona/workspace/chem-cheatsheet && python3 cheatsheet_v2.py 2>&1

Running Command

ls -lh /home/daytona/workspace/chem-cheatsheet/HonorsChem_UltimateCheatSheet.pdf

Here it is โ€” your ultimate 2-page cheat sheet:
๐Ÿ“„

HonorsChem UltimateCheatSheet

PDF Document ยท PDF


What's on each page:

Page 1 โ€” Periodic Table Reference

  • Full color-coded periodic table โ€” every element, every period and group labeled
  • 8-color category legend: Alkali metals, Alkaline earth, Transition metals, Post-transition metals, Metalloids, Nonmetals, Halogens, Noble gases โ€” each with what charge they form and why
  • Common ion charges quick-ref โ€” all 8 groups with their charges in big bold text
  • Variable-charge metals box โ€” Fe, Cu, Pb, Sn with their possible charges
  • Diatomic elements (HOFBrINCl) vs Noble gases (monoatomic) โ€” clearly explained
  • All 12 polyatomic ions you need, color-coded
  • Memory tricks for -ate vs -ite, hypo/per, electron fill order

Page 2 โ€” Step-by-Step Problem Solver

Every problem type has:
  • Numbered steps (green) โ€” exactly what to do in order
  • Worked example (pink) โ€” real numbers so you can compare
Topics covered:
  1. Light/photon energy โ€” how to calculate ฮฝ and E
  2. Electron configuration โ€” the noble gas shortcut method
  3. Naming ionic compounds โ€” when to use Roman numerals
  4. Writing ionic formulas โ€” the crossover method
  5. Mole calculations โ€” all 5 conversion types
  6. Percent composition
  7. Empirical formula from percentages
  8. Stoichiometry โ€” the 4-step road map
  9. Limiting reactant โ€” full method with example
  10. Percent yield
  11. Heat calculations + spontaneity rules
  12. Kinetics + equilibrium + Le Chatelier
  13. Balancing equations + net ionic equations
Print double-sided on one A4 sheet. Even someone who has never taken chemistry can follow these steps and get the right answer. Good luck tomorrow! ๐ŸŽฏ

this is good but I want something that has everything from both the study guide you provide means theory and how to do. also give example but I am not putting them on cheat sheet

Writing File

~/chem-cheatsheet/studyguide_complete.py

"""
Honors Chemistry โ€“ Complete Study Guide PDF
Full theory + how-to steps + worked examples for all 9 topics
Uses reportlab canvas for full layout control
"""

from reportlab.lib.pagesizes import A4
from reportlab.lib.units import cm, mm
from reportlab.lib import colors
from reportlab.pdfgen import canvas as pdfcanvas
from reportlab.platypus import Paragraph
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.enums import TA_LEFT, TA_CENTER

W, H = A4
OUT = "/home/daytona/workspace/chem-cheatsheet/HonorsChem_StudyGuide_Complete.pdf"

# โ”€โ”€ Colours โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
BG_TITLE   = colors.HexColor("#1a237e")
BG_TOPIC   = colors.HexColor("#1565c0")
BG_THEORY  = colors.HexColor("#e3f2fd")
BG_HOWTO   = colors.HexColor("#e8f5e9")
BG_EXAMPLE = colors.HexColor("#fff3e0")
BG_WARN    = colors.HexColor("#fce4ec")
BG_PAGE    = colors.HexColor("#fafafa")
CLR_THEORY = colors.HexColor("#0d47a1")
CLR_HOWTO  = colors.HexColor("#1b5e20")
CLR_EX     = colors.HexColor("#e65100")
CLR_WARN   = colors.HexColor("#b71c1c")
BLACK      = colors.black
GREY       = colors.HexColor("#37474f")
WHITE      = colors.white
DIVIDER    = colors.HexColor("#90a4ae")

MARGIN = 14*mm
COL_GAP = 5*mm
COL_W = (W - 2*MARGIN - COL_GAP) / 2

class PageMaker:
    def __init__(self, filename):
        self.c = pdfcanvas.Canvas(filename, pagesize=A4)
        self.page_num = 0
        self._new_page()

    def _new_page(self):
        self.page_num += 1
        self.c.setPageSize(A4)
        # light background
        self.c.setFillColor(BG_PAGE)
        self.c.rect(0, 0, W, H, fill=1, stroke=0)
        # footer
        self.c.setFillColor(BG_TITLE)
        self.c.rect(0, 0, W, 11, fill=1, stroke=0)
        self.c.setFillColor(WHITE)
        self.c.setFont("Helvetica", 5.5)
        self.c.drawCentredString(W/2, 3.5,
            f"Honors Chemistry Final Exam โ€” Complete Study Guide  ยท  Page {self.page_num}  ยท  Pendleton High School")

    def new_page(self):
        self.c.showPage()
        self._new_page()

    def save(self):
        self.c.save()
        print(f"โœ…  Saved: {OUT}")

# โ”€โ”€ Drawing helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

def title_bar(pm, y, text, sub=""):
    c = pm.c
    bar_h = 22 if sub else 16
    c.setFillColor(BG_TITLE)
    c.rect(0, y - bar_h, W, bar_h, fill=1, stroke=0)
    c.setFillColor(WHITE)
    c.setFont("Helvetica-Bold", 12 if sub else 10)
    c.drawCentredString(W/2, y - 13, text)
    if sub:
        c.setFont("Helvetica", 8)
        c.drawCentredString(W/2, y - 20, sub)
    return y - bar_h - 3*mm

def topic_header(pm, x, y, w, num, title, qs, color=BG_TOPIC):
    c = pm.c
    c.setFillColor(color)
    c.roundRect(x, y - 14, w, 14, 3, fill=1, stroke=0)
    c.setFillColor(WHITE)
    c.setFont("Helvetica-Bold", 8.5)
    c.drawString(x + 4, y - 10.5, f"TOPIC {num}: {title}")
    c.setFont("Helvetica", 7)
    c.drawRightString(x + w - 4, y - 10.5, f"Exam: {qs}")
    return y - 15

def section_label(pm, x, y, w, label, bg, fg):
    c = pm.c
    c.setFillColor(bg)
    c.rect(x, y - 10, w, 10, fill=1, stroke=0)
    c.setFillColor(fg)
    c.setFont("Helvetica-Bold", 6.5)
    c.drawString(x + 3, y - 7.5, label)
    return y - 10

def text_block(pm, x, y, w, lines, font="Helvetica", size=6.8, color=BLACK,
               bg=None, border=None, line_h=9, padding_x=4, padding_y=3):
    """Draw a block of text lines with optional background box. Returns new y."""
    c = pm.c
    total_h = len(lines) * line_h + 2 * padding_y
    if bg:
        c.setFillColor(bg)
        lw = 0.4 if border else 0
        if border:
            c.setStrokeColor(border)
            c.setLineWidth(lw)
        c.rect(x, y - total_h, w, total_h, fill=1, stroke=1 if border else 0)
    c.setFillColor(color)
    c.setFont(font, size)
    for i, line in enumerate(lines):
        # bold prefix support: lines starting with "โ€ข " get normal treatment
        c.drawString(x + padding_x, y - padding_y - i * line_h - size + 1, line)
    return y - total_h - 1.5*mm

def bullet_block(pm, x, y, w, items, bg=None, border=None,
                 size=6.8, line_h=9, padding_x=4, padding_y=3):
    """Bullet list with optional box."""
    c = pm.c
    total_h = len(items) * line_h + 2 * padding_y
    if bg:
        c.setFillColor(bg)
        if border:
            c.setStrokeColor(border)
            c.setLineWidth(0.4)
        c.rect(x, y - total_h, w, total_h, fill=1, stroke=1 if border else 0)
    for i, item in enumerate(items):
        c.setFillColor(CLR_HOWTO if bg == BG_HOWTO else GREY)
        c.setFont("Helvetica-Bold", size)
        c.drawString(x + padding_x, y - padding_y - i * line_h - size + 1, "โ†’")
        c.setFillColor(BLACK)
        c.setFont("Helvetica", size)
        c.drawString(x + padding_x + 10, y - padding_y - i * line_h - size + 1, item)
    return y - total_h - 1.5*mm

def steps_block(pm, x, y, w, steps, bg=BG_HOWTO, border_color=CLR_HOWTO,
                size=6.8, line_h=9):
    """Numbered steps block."""
    c = pm.c
    padding_x, padding_y = 4, 3
    total_h = len(steps) * line_h + 2 * padding_y
    c.setFillColor(bg)
    c.setStrokeColor(border_color)
    c.setLineWidth(0.5)
    c.rect(x, y - total_h, w, total_h, fill=1, stroke=1)
    for i, step in enumerate(steps):
        c.setFillColor(border_color)
        c.setFont("Helvetica-Bold", size)
        c.drawString(x + padding_x, y - padding_y - i * line_h - size + 1, f"Step {i+1}.")
        c.setFillColor(BLACK)
        c.setFont("Helvetica", size)
        c.drawString(x + padding_x + 32, y - padding_y - i * line_h - size + 1, step)
    return y - total_h - 1.5*mm

def example_block(pm, x, y, w, lines, title="WORKED EXAMPLE"):
    """Orange example box."""
    c = pm.c
    size = 6.8
    line_h = 8.5
    padding_x, padding_y = 4, 3
    total_h = (len(lines) + 1) * line_h + 2 * padding_y
    c.setFillColor(BG_EXAMPLE)
    c.setStrokeColor(CLR_EX)
    c.setLineWidth(0.6)
    c.rect(x, y - total_h, w, total_h, fill=1, stroke=1)
    c.setFillColor(CLR_EX)
    c.setFont("Helvetica-Bold", 6.5)
    c.drawString(x + padding_x, y - padding_y - size + 1, f"โœŽ {title}")
    c.setFillColor(GREY)
    c.setFont("Helvetica", size)
    for i, line in enumerate(lines):
        c.drawString(x + padding_x + 4, y - padding_y - (i+1) * line_h - size + 1, line)
    return y - total_h - 1.5*mm

def warn_box(pm, x, y, w, lines):
    """Red warning/tip box."""
    c = pm.c
    size = 6.8
    line_h = 8.5
    padding_x, padding_y = 4, 3
    total_h = len(lines) * line_h + 2 * padding_y
    c.setFillColor(BG_WARN)
    c.setStrokeColor(CLR_WARN)
    c.setLineWidth(0.6)
    c.rect(x, y - total_h, w, total_h, fill=1, stroke=1)
    c.setFillColor(CLR_WARN)
    c.setFont("Helvetica-Bold", 6.5)
    c.drawString(x + padding_x, y - padding_y - size + 1, "โš  REMEMBER:")
    c.setFont("Helvetica", size)
    for i, line in enumerate(lines):
        c.drawString(x + padding_x + 52, y - padding_y - i * line_h - size + 1, line)
    return y - total_h - 1.5*mm

def table_block(pm, x, y, w, headers, rows, col_ratios=None, size=6.5, line_h=8.5):
    """Simple table."""
    c = pm.c
    ncols = len(headers)
    if col_ratios is None:
        col_ratios = [1/ncols] * ncols
    col_widths = [w * r for r in col_ratios]
    row_h = line_h + 2
    total_h = row_h * (len(rows) + 1)

    # header row
    cx = x
    c.setFillColor(BG_TOPIC)
    c.rect(x, y - row_h, w, row_h, fill=1, stroke=0)
    c.setFillColor(WHITE)
    c.setFont("Helvetica-Bold", size)
    for h, cw in zip(headers, col_widths):
        c.drawString(cx + 3, y - row_h + 3, h)
        cx += cw

    # data rows
    for ri, row in enumerate(rows):
        row_y = y - row_h * (ri + 2)
        bg = colors.HexColor("#eceff1") if ri % 2 == 0 else WHITE
        c.setFillColor(bg)
        c.rect(x, row_y, w, row_h, fill=1, stroke=0)
        c.setStrokeColor(DIVIDER)
        c.setLineWidth(0.2)
        c.line(x, row_y, x + w, row_y)
        cx = x
        c.setFillColor(BLACK)
        c.setFont("Helvetica", size)
        for cell, cw in zip(row, col_widths):
            c.drawString(cx + 3, row_y + 3, str(cell))
            cx += cw

    # outer border
    c.setStrokeColor(BG_TOPIC)
    c.setLineWidth(0.5)
    c.rect(x, y - total_h, w, total_h, fill=0, stroke=1)
    return y - total_h - 1.5*mm

def divider(pm, x, y, w):
    pm.c.setStrokeColor(DIVIDER)
    pm.c.setLineWidth(0.4)
    pm.c.line(x, y, x + w, y)
    return y - 2*mm

def gap(y, n=1):
    return y - n * 2*mm


# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# BUILD THE DOCUMENT
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
pm = PageMaker(OUT)
c = pm.c

# โ”€โ”€ COVER / TITLE PAGE โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
y = H - 15*mm
c.setFillColor(BG_TITLE)
c.rect(0, y - 60, W, 60, fill=1, stroke=0)
c.setFillColor(WHITE)
c.setFont("Helvetica-Bold", 20)
c.drawCentredString(W/2, y - 25, "HONORS CHEMISTRY")
c.setFont("Helvetica-Bold", 14)
c.drawCentredString(W/2, y - 40, "FINAL EXAM โ€” COMPLETE STUDY GUIDE")
c.setFont("Helvetica", 9)
c.drawCentredString(W/2, y - 52, "Theory  ยท  How to Solve  ยท  Worked Examples  ยท  All 9 Exam Topics  ยท  Pendleton High School")
y -= 68

# intro box
c.setFillColor(colors.HexColor("#e8eaf6"))
c.setStrokeColor(BG_TITLE)
c.setLineWidth(0.8)
c.rect(MARGIN, y - 36, W - 2*MARGIN, 36, fill=1, stroke=1)
c.setFillColor(BG_TITLE)
c.setFont("Helvetica-Bold", 8)
c.drawString(MARGIN + 5, y - 11, "HOW TO USE THIS GUIDE:")
c.setFillColor(BLACK)
c.setFont("Helvetica", 7.5)
intro = [
    "1.  Read the THEORY section โ€” understand WHAT a concept is and WHY it matters.",
    "2.  Read HOW TO SOLVE โ€” these are the exact steps to follow on the test.",
    "3.  Follow the WORKED EXAMPLE โ€” trace through it yourself to check you understand.",
    "The cheat sheet you bring to the test has the theory + steps but NOT the examples.",
]
for i, ln in enumerate(intro):
    c.drawString(MARGIN + 5, y - 22 - i * 9, ln)
y -= 44

# โ”€โ”€ Two-column layout from here โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# We'll place topics alternating left/right, flowing down the page.
# Each topic starts fresh in left col if remaining space < threshold.

LX = MARGIN       # left column x
RX = MARGIN + COL_W + COL_GAP  # right column x

def check_space(y_val, needed, pm_obj):
    """If not enough space, start new page and return top y."""
    if y_val < 18*mm + needed:
        pm_obj.new_page()
        return H - 15*mm
    return y_val

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# TOPIC 1: ATOMIC STRUCTURE & THE PERIODIC TABLE
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
y = check_space(y, 40, pm)
y = topic_header(pm, LX, y, W - 2*MARGIN, 1,
                 "ATOMIC STRUCTURE & THE PERIODIC TABLE", "Questions 44โ€“50")

# --- split into left and right below topic header ---
ly = y - 2*mm
ry = y - 2*mm

# LEFT: Vocabulary
ly = section_label(pm, LX, ly, COL_W, "โ–ถ  KEY VOCABULARY", BG_THEORY, CLR_THEORY)
vocab_items = [
    ("Ground state",    "The lowest energy state of an atom โ€” electrons are in the closest possible shells."),
    ("Excited state",   "When an electron ABSORBS energy and jumps to a HIGHER shell."),
    ("Isotopes",        "Same element (same protons), different number of NEUTRONS โ†’ different mass. Example: C-12 vs C-14."),
    ("Allotropes",      "Same element, different physical structure. Diamond vs graphite โ€” both are pure carbon (C)."),
    ("Alloy",           "A MIXTURE with metallic properties. Steel = Fe + C. Bronze = Cu + Sn."),
    ("Octet rule",      "Atoms are most stable when they have 8 electrons in their outer shell."),
]
for term, defn in vocab_items:
    c.setFillColor(BG_THEORY)
    c.rect(LX, ly - 20, COL_W, 20, fill=1, stroke=0)
    c.setFillColor(CLR_THEORY)
    c.setFont("Helvetica-Bold", 7)
    c.drawString(LX + 4, ly - 9, term)
    c.setFillColor(BLACK)
    c.setFont("Helvetica", 6.5)
    # wrap definition
    words = defn.split()
    line1 = ""
    line2 = ""
    for w in words:
        if len(line1) + len(w) + 1 < 52:
            line1 += (" " if line1 else "") + w
        else:
            line2 += (" " if line2 else "") + w
    c.drawString(LX + 4, ly - 16, line1)
    if line2:
        # slightly smaller for overflow
        c.setFont("Helvetica", 6)
        c.drawString(LX + 4, ly - 22.5, line2)
        ly -= 26
    else:
        ly -= 22
    ly -= 1

ly -= 3

# LEFT: Electron configuration
ly = section_label(pm, LX, ly, COL_W, "โ–ถ  ELECTRON CONFIGURATION โ€” THEORY", BG_THEORY, CLR_THEORY)
ly = text_block(pm, LX, ly, COL_W, [
    "Electrons fill orbitals from LOWEST to HIGHEST energy.",
    "Each orbital type holds a set number of electrons:",
    "   s = 2 electrons   |   p = 6   |   d = 10   |   f = 14",
    "Fill order (Aufbau principle):",
    "   1s โ†’ 2s โ†’ 2p โ†’ 3s โ†’ 3p โ†’ 4s โ†’ 3d โ†’ 4p โ†’ 5s โ†’ 4d โ†’ 5p",
    "   โš  4s fills BEFORE 3d โ€” this is a common exam trap!",
    "Shortcut: [noble gas] = use the previous noble gas config",
    "   then continue filling from where it stopped.",
], bg=BG_THEORY, border=CLR_THEORY, line_h=8.5)

# RIGHT: Light & photon energy
ry = section_label(pm, RX, ry, COL_W, "โ–ถ  LIGHT & ELECTROMAGNETIC SPECTRUM โ€” THEORY", BG_THEORY, CLR_THEORY)
ry = text_block(pm, RX, ry, COL_W, [
    "Light travels as both a wave AND packets of energy (photons).",
    "Three related quantities โ€” wavelength (ฮป), frequency (ฮฝ), energy (E):",
    "",
    "   c = ฮป ร— ฮฝ     โ†’  ฮฝ = c / ฮป",
    "   E = h ร— ฮฝ     โ†’  E = hc / ฮป",
    "",
    "   c = 3.00 ร— 10โธ m/s  (speed of light)",
    "   h = 6.626 ร— 10โปยณโด Jยทs  (Planck's constant)",
    "   ฮป in METERS  |  ฮฝ in Hz (sโปยน)  |  E in Joules",
    "",
    "Key relationship: HIGHER frequency = SHORTER wavelength = MORE energy",
], bg=BG_THEORY, border=CLR_THEORY, line_h=8.5)

ry = section_label(pm, RX, ry, COL_W, "โ–ถ  HOW TO SOLVE: PHOTON ENERGY PROBLEMS", BG_HOWTO, CLR_HOWTO)
ry = steps_block(pm, RX, ry, COL_W, [
    "Identify what you're given: ฮป (wavelength) or ฮฝ (frequency)?",
    "If given ฮป, convert to meters first (nm ร— 10โปโน = m)",
    "Find frequency: ฮฝ = c / ฮป",
    "Find energy: E = h ร— ฮฝ",
    "Check units: answer should be in Joules (J)",
])
ry = example_block(pm, RX, ry, COL_W, [
    "ฮป = 500 nm. Find frequency and energy.",
    "Convert: 500 nm = 500 ร— 10โปโน = 5.00 ร— 10โปโท m",
    "ฮฝ = c/ฮป = (3.00ร—10โธ) / (5.00ร—10โปโท) = 6.00 ร— 10ยนโด Hz",
    "E = hฮฝ = (6.626ร—10โปยณโด)(6.00ร—10ยนโด) = 3.98 ร— 10โปยนโน J",
])

# HOW TO SOLVE config โ€” goes in left col
ly = section_label(pm, LX, ly, COL_W, "โ–ถ  HOW TO SOLVE: ELECTRON CONFIGURATION", BG_HOWTO, CLR_HOWTO)
ly = steps_block(pm, LX, ly, COL_W, [
    "Look up (or count) the atomic number Z",
    "Find the PREVIOUS noble gas in the periodic table",
    "Write [that noble gas] as the shortcut",
    "Continue filling: 4s โ†’ 3d โ†’ 4p (remember 4s before 3d!)",
    "Count electrons added = Z minus noble gas Z",
])
ly = example_block(pm, LX, ly, COL_W, [
    "Germanium (Ge): Z = 32, Period 4, Group 14",
    "Previous noble gas: Ar (Z=18)  โ†’  write [Ar]",
    "Need 32 โˆ’ 18 = 14 more electrons",
    "Fill: 4sยฒ(2) โ†’ 3dยนโฐ(12) โ†’ 4pยฒ(14) โœ“",
    "Answer: [Ar] 4sยฒ 3dยนโฐ 4pยฒ",
])
ly = warn_box(pm, LX, ly, COL_W, [
    "4s fills BEFORE 3d โ€” always!",
    "Transition metals start at group 3 (filling 3d subshell)",
])

# sync columns
y = min(ly, ry) - 3*mm
y = divider(pm, MARGIN, y, W - 2*MARGIN)

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# TOPIC 2: CHEMICAL BONDING
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
y = check_space(y, 60, pm)
y = topic_header(pm, LX, y, W - 2*MARGIN, 2,
                 "CHEMICAL BONDING", "Questions 32โ€“40",
                 color=colors.HexColor("#4a148c"))
ly = y - 2*mm
ry = y - 2*mm

ly = section_label(pm, LX, ly, COL_W, "โ–ถ  TYPES OF BONDS โ€” THEORY", BG_THEORY, CLR_THEORY)
ly = table_block(pm, LX, ly, COL_W,
    ["Type", "How it works", "Who", "Property"],
    [
        ["Ionic",      "Metal GIVES eโป to nonmetal",      "Metal + Nonmetal",   "High MP, forms crystals"],
        ["Covalent",   "Both atoms SHARE electrons",       "Nonmetal+Nonmetal",  "Lower MP, no ions"],
        ["Coordinate", "ONE atom donates BOTH electrons",  "Special covalent",   "e.g. NHโ‚„โบ, Hโ‚ƒOโบ"],
    ],
    col_ratios=[0.18, 0.36, 0.28, 0.18]
)

ly = section_label(pm, LX, ly, COL_W, "โ–ถ  SIGMA & PI BONDS โ€” THEORY", BG_THEORY, CLR_THEORY)
ly = text_block(pm, LX, ly, COL_W, [
    "Every bond contains one sigma (ฯƒ) bond (electron density BETWEEN atoms).",
    "Extra bonds in double/triple bonds are pi (ฯ€) bonds (above/below axis).",
    "   Single bond  = 1ฯƒ, 0ฯ€",
    "   Double bond  = 1ฯƒ, 1ฯ€",
    "   Triple bond  = 1ฯƒ, 2ฯ€  โ† strongest and shortest",
], bg=BG_THEORY, border=CLR_THEORY, line_h=8.5)

ly = section_label(pm, LX, ly, COL_W, "โ–ถ  BOND LENGTH & STRENGTH โ€” THEORY", BG_THEORY, CLR_THEORY)
ly = text_block(pm, LX, ly, COL_W, [
    "SHORTER bond = STRONGER bond = HIGHER bond dissociation energy",
    "   Triple > Double > Single  (strength and energy to break)",
    "   Single > Double > Triple  (length)",
], bg=BG_THEORY, border=CLR_THEORY, line_h=8.5)

# Right col: polarity + diatomic
ry = section_label(pm, RX, ry, COL_W, "โ–ถ  POLARITY & MOLECULAR GEOMETRY โ€” THEORY", BG_THEORY, CLR_THEORY)
ry = text_block(pm, RX, ry, COL_W, [
    "A bond is polar when two atoms have DIFFERENT electronegativities",
    "(one atom pulls electrons more strongly โ€” creates ฮด+ and ฮดโˆ’ ends).",
    "",
    "NONPOLAR molecule: bond dipoles CANCEL each other out.",
    "   โ†’ Must be perfectly SYMMETRIC geometry.",
    "   โ†’ Example: SiHโ‚„ (tetrahedral, 4 equal bonds) = NONPOLAR",
    "",
    "POLAR molecule: bond dipoles do NOT cancel.",
    "   โ†’ Usually happens when central atom has LONE PAIRS.",
    "   โ†’ Lone pairs push the geometry, making it asymmetric.",
    "   โ†’ Examples: AsHโ‚ƒ, PHโ‚ƒ, Hโ‚‚S โ€” all polar",
], bg=BG_THEORY, border=CLR_THEORY, line_h=8.5)

ry = section_label(pm, RX, ry, COL_W, "โ–ถ  DIATOMIC ELEMENTS โ€” THEORY", BG_THEORY, CLR_THEORY)
ry = text_block(pm, RX, ry, COL_W, [
    "7 elements exist as DIATOMIC pairs in nature โ€” they never go solo.",
    "Memory trick: HOFBrINCl",
    "   Hโ‚‚  Oโ‚‚  Fโ‚‚  Brโ‚‚  Iโ‚‚  Nโ‚‚  Clโ‚‚",
    "Always write these with subscript 2 in chemical equations!",
    "",
    "Noble gases (He, Ne, Ar, Kr, Xe) are MONOATOMIC โ€” they never bond.",
    "Do NOT write Heโ‚‚ or Arโ‚‚.",
], bg=BG_THEORY, border=CLR_THEORY, line_h=8.5)

ry = section_label(pm, RX, ry, COL_W, "โ–ถ  HOW TO SOLVE: COUNT ฯƒ AND ฯ€ BONDS", BG_HOWTO, CLR_HOWTO)
ry = steps_block(pm, RX, ry, COL_W, [
    "Draw or identify all bonds in the molecule",
    "Every single bond โ†’ count 1ฯƒ",
    "Every double bond โ†’ count 1ฯƒ + 1ฯ€",
    "Every triple bond โ†’ count 1ฯƒ + 2ฯ€",
    "Add all ฯƒ together, then all ฯ€ together",
])
ry = example_block(pm, RX, ry, COL_W, [
    "Ethene Cโ‚‚Hโ‚„:  Structure: Hโ‚‚C=CHโ‚‚",
    "   C=C โ†’ 1ฯƒ + 1ฯ€",
    "   4 Cโ€“H single bonds โ†’ 4ฯƒ",
    "   TOTAL: 5ฯƒ bonds, 1ฯ€ bond",
])

y = min(ly, ry) - 3*mm
y = divider(pm, MARGIN, y, W - 2*MARGIN)

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# TOPIC 3: CHEMICAL NOMENCLATURE
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
y = check_space(y, 60, pm)
y = topic_header(pm, LX, y, W - 2*MARGIN, 3,
                 "CHEMICAL NOMENCLATURE & FORMULAS", "Questions 27, 35, 36, 41โ€“43",
                 color=colors.HexColor("#e65100"))
ly = y - 2*mm
ry = y - 2*mm

ly = section_label(pm, LX, ly, COL_W, "โ–ถ  IONIC COMPOUNDS โ€” THEORY", BG_THEORY, CLR_THEORY)
ly = text_block(pm, LX, ly, COL_W, [
    "Ionic compounds: metal + nonmetal, charges must BALANCE to zero.",
    "",
    "Naming rules:",
    "  1. Write METAL name first (unchanged)",
    "  2. If VARIABLE-CHARGE metal (Fe, Cu, Pb, Sn, Hg, Cr, Mn):",
    "     โ†’ Figure out the charge from the formula",
    "     โ†’ Add Roman numeral in parentheses: Fe(III)",
    "  3. Write ANION name:",
    "     Simple nonmetal: add -ide  (Clโป = chloride)",
    "     Polyatomic ion: use its own name (SOโ‚„ยฒโป = sulfate)",
    "",
    "  -ate = MORE oxygens   |   -ite = FEWER oxygens",
    "  hypo- prefix = fewest oxygens  (ClOโป = hypochlorite)",
    "  per- prefix  = most oxygens    (ClOโ‚„โป = perchlorate)",
], bg=BG_THEORY, border=CLR_THEORY, line_h=8.5)

ly = section_label(pm, LX, ly, COL_W, "โ–ถ  POLYATOMIC IONS โ€” MUST MEMORIZE", BG_THEORY, CLR_THEORY)
ly = table_block(pm, LX, ly, COL_W,
    ["Ion", "Name", "Ion", "Name"],
    [
        ["SOโ‚„ยฒโป", "sulfate",      "ClOโป",  "hypochlorite"],
        ["SOโ‚ƒยฒโป", "sulfite",      "ClOโ‚‚โป", "chlorite"],
        ["NOโ‚ƒโป",  "nitrate",      "ClOโ‚ƒโป", "chlorate"],
        ["NOโ‚‚โป",  "nitrite",      "ClOโ‚„โป", "perchlorate"],
        ["POโ‚„ยณโป", "phosphate",    "OHโป",   "hydroxide"],
        ["NHโ‚„โบ",  "ammonium",     "COโ‚ƒยฒโป", "carbonate"],
    ],
    col_ratios=[0.18, 0.32, 0.18, 0.32]
)

ry = section_label(pm, RX, ry, COL_W, "โ–ถ  HOW TO SOLVE: NAME AN IONIC COMPOUND", BG_HOWTO, CLR_HOWTO)
ry = steps_block(pm, RX, ry, COL_W, [
    "Identify the cation (metal) and anion",
    "Is the metal VARIABLE-CHARGE? (Fe, Cu, Pb, Sn, Hg, Cr, Mn)",
    "  If YES: total negative charge รท number of metal atoms = metal charge",
    "  Write the charge as Roman numeral in ()",
    "If NO variable charge: just write the metal name",
    "Write the anion name (-ide, -ate, -ite, or polyatomic name)",
])
ry = example_block(pm, RX, ry, COL_W, [
    "Feโ‚‚(SOโ‚„)โ‚ƒ:  3 sulfate ions ร— 2โˆ’ = 6โˆ’ total negative",
    "  2 Fe atoms must balance: 6รท2 = 3+ per Fe",
    "  โ†’ Iron(III) sulfate",
    "Caโ‚ƒ(POโ‚„)โ‚‚:  Ca is Group 2, always 2+ (NOT variable)",
    "  โ†’ Calcium phosphate  (no Roman numeral needed)",
])

ry = section_label(pm, RX, ry, COL_W, "โ–ถ  HOW TO SOLVE: WRITE AN IONIC FORMULA", BG_HOWTO, CLR_HOWTO)
ry = steps_block(pm, RX, ry, COL_W, [
    "Write cation (positive) first, anion (negative) second",
    "CROSSOVER METHOD: swap the charge numbers as subscripts",
    "If subscript is 1, leave it out (don't write Caโ‚Clโ‚, just CaClโ‚‚)",
    "Reduce if possible: Caยฒโบ + Oยฒโป โ†’ CaO (not Caโ‚‚Oโ‚‚)",
    "Polyatomic ion with subscript > 1? Wrap in parentheses",
])
ry = example_block(pm, RX, ry, COL_W, [
    "Caยฒโบ + POโ‚„ยณโป:  cross charges โ†’ Caโ‚ƒ(POโ‚„)โ‚‚",
    "  Check: 3ร—(2+) = 6+  and  2ร—(3โˆ’) = 6โˆ’  โœ“",
    "Alยณโบ + Oยฒโป:  cross โ†’ Alโ‚‚Oโ‚ƒ",
    "  Check: 2ร—(3+) = 6+  and  3ร—(2โˆ’) = 6โˆ’  โœ“",
])

ry = section_label(pm, RX, ry, COL_W, "โ–ถ  MOLECULAR COMPOUNDS & ACIDS โ€” THEORY", BG_THEORY, CLR_THEORY)
ry = text_block(pm, RX, ry, COL_W, [
    "Molecular compounds: use GREEK PREFIXES for atom count.",
    "   1=mono  2=di  3=tri  4=tetra  5=penta  6=hexa  7=hepta  8=octa",
    "   Nโ‚‚Hโ‚„ = dinitrogen tetrahydride",
    "",
    "Acids (containing H, dissolved in water):",
    "   -ate ion โ†’ -ic acid:  Hโ‚‚SOโ‚„ = sulfuric acid",
    "   -ite ion โ†’ -ous acid: Hโ‚‚SOโ‚ƒ = sulfurous acid",
    "",
    "Hydrates: Compound โ€ข xHโ‚‚O",
    "   Ba(OH)โ‚‚ โ€ข 8Hโ‚‚O = barium hydroxide octahydrate",
], bg=BG_THEORY, border=CLR_THEORY, line_h=8.5)

y = min(ly, ry) - 3*mm
y = divider(pm, MARGIN, y, W - 2*MARGIN)

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# TOPIC 4: CHEMICAL REACTIONS
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
pm.new_page()
y = H - 15*mm
y = topic_header(pm, LX, y, W - 2*MARGIN, 4,
                 "CHEMICAL REACTIONS & EQUATIONS", "Questions 28โ€“31",
                 color=colors.HexColor("#b71c1c"))
ly = y - 2*mm
ry = y - 2*mm

ly = section_label(pm, LX, ly, COL_W, "โ–ถ  BALANCING EQUATIONS โ€” THEORY", BG_THEORY, CLR_THEORY)
ly = text_block(pm, LX, ly, COL_W, [
    "Law of Conservation of Mass: atoms cannot be created or destroyed.",
    "A balanced equation has the SAME number of each atom on both sides.",
    "",
    "RULES:",
    "  โ€ข Only change COEFFICIENTS (numbers in front of formulas)",
    "  โ€ข NEVER change subscripts inside a formula",
    "  โ€ข Coefficients must be the smallest whole-number ratio",
    "  โ€ข Fractions are allowed during balancing but clear them at the end",
], bg=BG_THEORY, border=CLR_THEORY, line_h=8.5)

ly = section_label(pm, LX, ly, COL_W, "โ–ถ  HOW TO SOLVE: BALANCE AN EQUATION", BG_HOWTO, CLR_HOWTO)
ly = steps_block(pm, LX, ly, COL_W, [
    "Count atoms of EACH element on both sides",
    "Start with elements that appear in FEWEST compounds",
    "Add coefficient to balance one element at a time",
    "Leave H and O until last (they appear in almost everything)",
    "Check ALL elements at the end",
    "Clear any fractions by multiplying everything through",
])
ly = example_block(pm, LX, ly, COL_W, [
    "SiOโ‚‚ + HF โ†’ SiFโ‚„ + Hโ‚‚O",
    "Si: 1=1 โœ“  |  F: need 4 on right โ†’ 4HF on left",
    "H: 4 on left โ†’ 2Hโ‚‚O on right  |  O: 2=2 โœ“",
    "Balanced: SiOโ‚‚ + 4HF โ†’ SiFโ‚„ + 2Hโ‚‚O",
    "(coefficient for HF = 4)",
])

ly = section_label(pm, LX, ly, COL_W, "โ–ถ  REACTION TYPES โ€” THEORY", BG_THEORY, CLR_THEORY)
ly = table_block(pm, LX, ly, COL_W,
    ["Type", "Pattern", "Example"],
    [
        ["Synthesis",     "A + B โ†’ AB",          "2Hโ‚‚ + Oโ‚‚ โ†’ 2Hโ‚‚O"],
        ["Decomposition", "AB โ†’ A + B",           "CaCOโ‚ƒ โ†’ CaO + COโ‚‚"],
        ["Single Replace","A + BC โ†’ AC + B",      "Zn + HCl โ†’ ZnClโ‚‚ + Hโ‚‚"],
        ["Double Replace","AB+CD โ†’ AD+CB",        "AgNOโ‚ƒ+NaCl โ†’ AgCl+NaNOโ‚ƒ"],
        ["Combustion",    "HC + Oโ‚‚ โ†’ COโ‚‚ + Hโ‚‚O", "CHโ‚„+2Oโ‚‚โ†’COโ‚‚+2Hโ‚‚O"],
        ["Neutralization","Acid+Baseโ†’Salt+Water", "HCl+NaOHโ†’NaCl+Hโ‚‚O"],
    ],
    col_ratios=[0.25, 0.35, 0.40]
)

ry = section_label(pm, RX, ry, COL_W, "โ–ถ  PRECIPITATES & NET IONIC โ€” THEORY", BG_THEORY, CLR_THEORY)
ry = text_block(pm, RX, ry, COL_W, [
    "Precipitate: a solid (s) product that forms when 2 aqueous",
    "solutions are mixed. Marked with (s) in the equation.",
    "Example: AgNOโ‚ƒ(aq) + NaCl(aq) โ†’ AgCl(s) + NaNOโ‚ƒ(aq)",
    "",
    "Full ionic equation: split all (aq) compounds into their ions.",
    "  AgNOโ‚ƒ splits: Agโบ + NOโ‚ƒโป",
    "  NaCl splits:  Naโบ + Clโป",
    "",
    "Spectator ions: ions that appear UNCHANGED on both sides.",
    "  โ†’ They don't participate in the reaction. Cross them out.",
    "",
    "Net ionic equation: what's left after removing spectators.",
    "  โ†’ Shows only the ACTUAL reaction that occurs.",
], bg=BG_THEORY, border=CLR_THEORY, line_h=8.5)

ry = section_label(pm, RX, ry, COL_W, "โ–ถ  HOW TO SOLVE: NET IONIC EQUATION", BG_HOWTO, CLR_HOWTO)
ry = steps_block(pm, RX, ry, COL_W, [
    "Write and balance the full molecular equation",
    "Separate all (aq) ionic compounds into their individual ions",
    "Leave (s), (l), (g) species as molecules โ€” don't split them",
    "Identify spectator ions: same ion on both sides unchanged",
    "Cross out spectators from both sides",
    "What remains = the net ionic equation",
])
ry = example_block(pm, RX, ry, COL_W, [
    "HCl(aq) + KOH(aq) โ†’ KCl(aq) + Hโ‚‚O(l)",
    "Full ionic: Hโบ + Clโป + Kโบ + OHโป โ†’ Kโบ + Clโป + Hโ‚‚O",
    "Spectators: Kโบ and Clโป (same on both sides โ€” cancel)",
    "Net ionic:  Hโบ(aq) + OHโป(aq) โ†’ Hโ‚‚O(l)",
])

ry = section_label(pm, RX, ry, COL_W, "โ–ถ  NEUTRALIZATION โ€” THEORY & HOW TO", BG_THEORY, CLR_THEORY)
ry = text_block(pm, RX, ry, COL_W, [
    "Neutralization: Acid + Base โ†’ Salt + Water",
    "The acid provides Hโบ, the base provides OHโป.",
    "They combine to form water: Hโบ + OHโป โ†’ Hโ‚‚O",
    "The remaining ions form the salt.",
    "",
    "Example: Ba(OH)โ‚‚ + 2HCl โ†’ BaClโ‚‚ + 2Hโ‚‚O",
    "  Ba(OH)โ‚‚ provides 2 OHโป  |  2 HCl provides 2 Hโบ",
    "  Products: BaClโ‚‚ (salt) + 2Hโ‚‚O (water)",
], bg=BG_THEORY, border=CLR_THEORY, line_h=8.5)

y = min(ly, ry) - 3*mm
y = divider(pm, MARGIN, y, W - 2*MARGIN)

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# TOPIC 5: THE MOLE & MOLAR MASS
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
y = check_space(y, 60, pm)
y = topic_header(pm, LX, y, W - 2*MARGIN, 5,
                 "THE MOLE & MOLAR MASS", "Questions 19โ€“26",
                 color=colors.HexColor("#1b5e20"))
ly = y - 2*mm
ry = y - 2*mm

ly = section_label(pm, LX, ly, COL_W, "โ–ถ  WHAT IS A MOLE? โ€” THEORY", BG_THEORY, CLR_THEORY)
ly = text_block(pm, LX, ly, COL_W, [
    "A mole is just a COUNTING NUMBER, like 'dozen' = 12.",
    "   1 mole = 6.02 ร— 10ยฒยณ particles  (Avogadro's number)",
    "",
    "Why so big? Because atoms are incredibly tiny.",
    "One mole of carbon atoms = 12.01 grams of carbon.",
    "",
    "MOLAR MASS = the mass (in grams) of exactly 1 mole of a substance.",
    "  โ€ข For an element: molar mass = atomic mass from periodic table (g/mol)",
    "  โ€ข For a compound: ADD UP the molar masses of all atoms in formula",
    "  โ€ข The molar mass IS the conversion factor between grams and moles.",
], bg=BG_THEORY, border=CLR_THEORY, line_h=8.5)

ly = section_label(pm, LX, ly, COL_W, "โ–ถ  HOW TO SOLVE: MOLE CALCULATIONS", BG_HOWTO, CLR_HOWTO)
ly = steps_block(pm, LX, ly, COL_W, [
    "FIRST: Find the molar mass of the substance",
    "  (add all atomic masses ร— their subscripts in the formula)",
    "grams โ†’ moles:  moles = grams รท molar mass",
    "moles โ†’ grams:  grams = moles ร— molar mass",
    "moles โ†’ particles:  particles = moles ร— 6.02 ร— 10ยฒยณ",
    "particles โ†’ moles:  moles = particles รท 6.02 ร— 10ยฒยณ",
])
ly = example_block(pm, LX, ly, COL_W, [
    "How many moles in 100 g of Clโ‚‚?",
    "  MM of Clโ‚‚ = 2 ร— 35.5 = 71.0 g/mol",
    "  moles = 100 g รท 71.0 g/mol = 1.41 mol",
    "Mass of 0.82 mol Feโ‚‚Oโ‚ƒ?",
    "  MM = 2(55.85) + 3(16.00) = 159.7 g/mol",
    "  grams = 0.82 ร— 159.7 = 130.9 g โ‰ˆ 131 g",
    "MM of Hโ‚‚CrOโ‚„: 2(1) + 52 + 4(16) = 118 g/mol",
])

ry = section_label(pm, RX, ry, COL_W, "โ–ถ  PERCENT COMPOSITION โ€” THEORY", BG_THEORY, CLR_THEORY)
ry = text_block(pm, RX, ry, COL_W, [
    "Percent composition tells you what FRACTION of a compound's",
    "mass comes from each element.",
    "",
    "   % element = (mass of element in formula / molar mass) ร— 100",
    "",
    "The 'mass of element in formula' = atomic mass ร— subscript",
    "All percentages in a compound must ADD UP TO 100%.",
], bg=BG_THEORY, border=CLR_THEORY, line_h=8.5)

ry = section_label(pm, RX, ry, COL_W, "โ–ถ  HOW TO SOLVE: PERCENT COMPOSITION", BG_HOWTO, CLR_HOWTO)
ry = steps_block(pm, RX, ry, COL_W, [
    "Calculate molar mass of the entire compound",
    "For each element: find its total mass in 1 formula unit",
    "  (atomic mass ร— subscript)",
    "Divide element mass by molar mass",
    "Multiply by 100 to get percent",
    "Verify: all percentages sum to ~100%",
])
ry = example_block(pm, RX, ry, COL_W, [
    "% composition of COโ‚‚ (MM = 12 + 32 = 44 g/mol):",
    "  % C = (12 / 44) ร— 100 = 27.3%",
    "  % O = (32 / 44) ร— 100 = 72.7%",
    "  Check: 27.3 + 72.7 = 100.0% โœ“",
])

ry = section_label(pm, RX, ry, COL_W, "โ–ถ  EMPIRICAL FORMULA โ€” THEORY & HOW TO", BG_THEORY, CLR_THEORY)
ry = text_block(pm, RX, ry, COL_W, [
    "Empirical formula = the SIMPLEST whole-number ratio of atoms.",
    "You can find it from percent composition data.",
], bg=BG_THEORY, border=CLR_THEORY, line_h=8.5)
ry = steps_block(pm, RX, ry, COL_W, [
    "Assume 100 g sample โ†’ percent numbers become grams directly",
    "Divide each element's grams by its atomic mass โ†’ gives moles",
    "Divide ALL mole values by the SMALLEST mole value",
    "Round to nearest whole number โ†’ those are your subscripts",
    "If you get .5 โ†’ multiply all by 2",
    "If you get .33 or .67 โ†’ multiply all by 3",
])
ry = example_block(pm, RX, ry, COL_W, [
    "85.6% C, 14.4% H  โ†’  assume 100 g",
    "  C: 85.6 g รท 12 g/mol = 7.13 mol",
    "  H: 14.4 g รท 1 g/mol  = 14.4 mol",
    "  Divide by smallest (7.13): C=1.00, H=2.02โ‰ˆ2",
    "  Empirical formula: CHโ‚‚",
])

y = min(ly, ry) - 3*mm
y = divider(pm, MARGIN, y, W - 2*MARGIN)

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# TOPIC 6: STOICHIOMETRY
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
pm.new_page()
y = H - 15*mm
y = topic_header(pm, LX, y, W - 2*MARGIN, 6,
                 "STOICHIOMETRY", "Questions 11โ€“18",
                 color=colors.HexColor("#880e4f"))
ly = y - 2*mm
ry = y - 2*mm

ly = section_label(pm, LX, ly, COL_W, "โ–ถ  STOICHIOMETRY โ€” THEORY", BG_THEORY, CLR_THEORY)
ly = text_block(pm, LX, ly, COL_W, [
    "Stoichiometry uses the balanced equation to predict HOW MUCH",
    "of a substance is produced or consumed in a reaction.",
    "",
    "Basis: Law of Conservation of Mass โ€” matter is not created or",
    "destroyed, just rearranged.",
    "",
    "Key idea: the COEFFICIENTS in a balanced equation give the",
    "MOLAR RATIOS โ€” how many moles of each substance react/form.",
    "",
    "Example: Cโ‚ƒHโ‚ˆ + 5Oโ‚‚ โ†’ 3COโ‚‚ + 4Hโ‚‚O",
    "   1 mol propane reacts with 5 mol Oโ‚‚ to produce 3 mol COโ‚‚.",
    "   Oโ‚‚ : COโ‚‚ ratio = 5 : 3  (comes from coefficients only!)",
], bg=BG_THEORY, border=CLR_THEORY, line_h=8.5)

ly = section_label(pm, LX, ly, COL_W, "โ–ถ  HOW TO SOLVE: STOICHIOMETRY", BG_HOWTO, CLR_HOWTO)
ly = steps_block(pm, LX, ly, COL_W, [
    "Write the BALANCED chemical equation (never skip this!)",
    "Convert GIVEN quantity to MOLES:",
    "   grams รท molar mass = moles",
    "Use MOLAR RATIO from balanced equation:",
    "   moles given ร— (coeff wanted / coeff given) = moles wanted",
    "Convert moles to DESIRED UNIT:",
    "   moles ร— molar mass = grams",
    "   moles ร— 6.02ร—10ยฒยณ  = number of particles",
])
ly = example_block(pm, LX, ly, COL_W, [
    "Cโ‚ƒHโ‚ˆ + 5Oโ‚‚ โ†’ 3COโ‚‚ + 4Hโ‚‚O",
    "How many g of COโ‚‚ from 44.0 g Cโ‚ƒHโ‚ˆ?",
    "  MM Cโ‚ƒHโ‚ˆ = 3(12)+8(1) = 44.0 g/mol",
    "  44.0 g รท 44.0 = 1.00 mol Cโ‚ƒHโ‚ˆ",
    "  1.00 mol Cโ‚ƒHโ‚ˆ ร— (3 mol COโ‚‚ / 1 mol Cโ‚ƒHโ‚ˆ) = 3.00 mol COโ‚‚",
    "  3.00 mol ร— 44.0 g/mol = 132 g COโ‚‚",
])

ry = section_label(pm, RX, ry, COL_W, "โ–ถ  LIMITING REACTANT โ€” THEORY", BG_THEORY, CLR_THEORY)
ry = text_block(pm, RX, ry, COL_W, [
    "In real reactions, you rarely have the EXACT right amounts.",
    "One reactant runs out first โ€” it LIMITS how much product forms.",
    "",
    "Limiting reactant: the one that is COMPLETELY consumed first.",
    "  โ†’ Determines the maximum amount of product (theoretical yield).",
    "",
    "Excess reactant: what's LEFT OVER after the reaction stops.",
    "  โ†’ Some of this reactant remains unreacted.",
    "",
    "How to identify: see which reactant produces LESS product.",
    "  The one that makes less = the limiting reactant.",
], bg=BG_THEORY, border=CLR_THEORY, line_h=8.5)

ry = section_label(pm, RX, ry, COL_W, "โ–ถ  HOW TO SOLVE: LIMITING REACTANT", BG_HOWTO, CLR_HOWTO)
ry = steps_block(pm, RX, ry, COL_W, [
    "Balance the equation",
    "Convert BOTH given quantities to moles",
    "For EACH reactant separately: use molar ratio to find",
    "  how much product it COULD make (if the other was excess)",
    "The reactant that produces LESS product = LIMITING reactant",
    "Use the LIMITING reactant's moles to calculate final product",
])
ry = example_block(pm, RX, ry, COL_W, [
    "4Fe + 3Oโ‚‚ โ†’ 2Feโ‚‚Oโ‚ƒ  with 5 mol Fe and 4 mol Oโ‚‚",
    "  Fe: 5 mol ร— (2 mol Feโ‚‚Oโ‚ƒ / 4 mol Fe) = 2.50 mol product",
    "  Oโ‚‚: 4 mol ร— (2 mol Feโ‚‚Oโ‚ƒ / 3 mol Oโ‚‚) = 2.67 mol product",
    "  Fe makes LESS โ†’ Fe is the LIMITING REACTANT",
    "  Maximum Feโ‚‚Oโ‚ƒ = 2.50 mol",
])

ry = section_label(pm, RX, ry, COL_W, "โ–ถ  PERCENT YIELD โ€” THEORY & HOW TO", BG_THEORY, CLR_THEORY)
ry = text_block(pm, RX, ry, COL_W, [
    "In real lab experiments, you almost NEVER get the theoretical maximum.",
    "Reasons: side reactions, lost material, incomplete reactions.",
    "",
    "Theoretical yield = max product predicted by stoichiometry (perfect world)",
    "Actual yield      = product you actually collected in the lab",
    "",
    "   % yield = (actual yield / theoretical yield) ร— 100",
    "",
    "% yield is ALWAYS โ‰ค 100%  (if >100% something went wrong!)",
], bg=BG_THEORY, border=CLR_THEORY, line_h=8.5)
ry = example_block(pm, RX, ry, COL_W, [
    "Theoretical = 10.0 g, Actual = 8.2 g",
    "% yield = (8.2 / 10.0) ร— 100 = 82%",
    "",
    "If % yield = 80%, actual = 15 g, find theoretical:",
    "  80 = (15 / theoretical) ร— 100",
    "  theoretical = 15 / 0.80 = 18.75 g",
])

y = min(ly, ry) - 3*mm
y = divider(pm, MARGIN, y, W - 2*MARGIN)

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# TOPIC 7: THERMOCHEMISTRY
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
y = check_space(y, 50, pm)
y = topic_header(pm, LX, y, W - 2*MARGIN, 7,
                 "THERMOCHEMISTRY & THERMODYNAMICS", "Questions 7โ€“9",
                 color=colors.HexColor("#006064"))
ly = y - 2*mm
ry = y - 2*mm

ly = section_label(pm, LX, ly, COL_W, "โ–ถ  SPECIFIC HEAT โ€” THEORY", BG_THEORY, CLR_THEORY)
ly = text_block(pm, LX, ly, COL_W, [
    "When you heat something, energy (q) flows into it.",
    "How much energy depends on:",
    "  โ€ข mass (m) โ€” more mass = more energy needed",
    "  โ€ข specific heat (c) โ€” a property of the material",
    "    (how much energy to raise 1 gram by 1ยฐC)",
    "  โ€ข temperature change (ฮ”T = T_final โˆ’ T_initial)",
    "",
    "   q = m ร— c ร— ฮ”T",
    "",
    "  q > 0 = heat absorbed (endothermic, temperature rises)",
    "  q < 0 = heat released (exothermic, temperature drops)",
    "",
    "Common specific heat values:",
    "  Water: c = 4.18 J/gยทยฐC   |   Aluminum: c = 0.900 J/gยทยฐC",
], bg=BG_THEORY, border=CLR_THEORY, line_h=8.5)

ly = section_label(pm, LX, ly, COL_W, "โ–ถ  HOW TO SOLVE: HEAT PROBLEMS", BG_HOWTO, CLR_HOWTO)
ly = steps_block(pm, LX, ly, COL_W, [
    "Identify m (grams), c (given or use common values), T_i, T_f",
    "Calculate ฮ”T = T_final โˆ’ T_initial  (can be negative!)",
    "Plug into: q = m ร— c ร— ฮ”T",
    "Units: grams ร— J/gยทยฐC ร— ยฐC = J  โœ“",
])
ly = example_block(pm, LX, ly, COL_W, [
    "30.0 g Al heated from 20.0ยฐC to 40.0ยฐC",
    "  c(Al) = 0.900 J/gยทยฐC",
    "  ฮ”T = 40.0 โˆ’ 20.0 = 20.0ยฐC",
    "  q = 30.0 ร— 0.900 ร— 20.0 = 540 J",
])

ry = section_label(pm, RX, ry, COL_W, "โ–ถ  CALORIE CONVERSION โ€” THEORY & HOW TO", BG_THEORY, CLR_THEORY)
ry = text_block(pm, RX, ry, COL_W, [
    "On food labels: 1 Calorie (capital C) = 1 kilocalorie = 1 kcal",
    "In chemistry: 1 calorie (lowercase c) = 4.184 J",
    "",
    "   1 Cal (food) = 1 kcal = 4184 J",
    "",
    "To convert Calories to Joules: multiply by 4184",
    "To convert Joules to Calories: divide by 4184",
], bg=BG_THEORY, border=CLR_THEORY, line_h=8.5)
ry = example_block(pm, RX, ry, COL_W, [
    "170 Calories โ†’ Joules:",
    "  170 ร— 4184 = 711,280 J = 7.11 ร— 10โต J",
])

ry = section_label(pm, RX, ry, COL_W, "โ–ถ  SPONTANEITY (GIBBS FREE ENERGY) โ€” THEORY", BG_THEORY, CLR_THEORY)
ry = text_block(pm, RX, ry, COL_W, [
    "A spontaneous reaction happens ON ITS OWN without constant energy input.",
    "",
    "Two factors determine spontaneity:",
    "  ฮ”H (enthalpy): energy change  |  ฮ”S (entropy): disorder change",
    "  ฮ”H < 0 = exothermic (releases heat) = FAVORS spontaneity",
    "  ฮ”S > 0 = more disorder/randomness  = FAVORS spontaneity",
    "",
    "The Gibbs Free Energy equation (concept only): ฮ”G = ฮ”H โˆ’ Tฮ”S",
    "  ฮ”G < 0 = spontaneous   |   ฮ”G > 0 = non-spontaneous",
    "",
    "MOST IMPORTANT RULE FOR THE EXAM:",
    "  If ฮ”H < 0 AND ฮ”S > 0 โ†’ ALWAYS spontaneous at ANY temperature",
    "  (ฮ”G will always be negative no matter what T is)",
], bg=BG_THEORY, border=CLR_THEORY, line_h=8.5)
ry = table_block(pm, RX, ry, COL_W,
    ["ฮ”H", "ฮ”S", "Spontaneous?"],
    [
        ["< 0 (exo)", "> 0 (more disorder)", "YES, always (any T)"],
        ["< 0 (exo)", "< 0 (less disorder)", "Only at LOW temperature"],
        ["> 0 (endo)", "> 0 (more disorder)", "Only at HIGH temperature"],
        ["> 0 (endo)", "< 0 (less disorder)", "NEVER spontaneous"],
    ],
    col_ratios=[0.22, 0.30, 0.48]
)

y = min(ly, ry) - 3*mm
y = divider(pm, MARGIN, y, W - 2*MARGIN)

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# TOPIC 8: REACTION KINETICS
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
pm.new_page()
y = H - 15*mm
y = topic_header(pm, LX, y, W - 2*MARGIN, 8,
                 "REACTION KINETICS", "Questions 3โ€“6",
                 color=colors.HexColor("#33691e"))
ly = y - 2*mm
ry = y - 2*mm

ly = section_label(pm, LX, ly, COL_W, "โ–ถ  COLLISION THEORY โ€” THEORY", BG_THEORY, CLR_THEORY)
ly = text_block(pm, LX, ly, COL_W, [
    "For a chemical reaction to occur, THREE conditions must ALL be met:",
    "",
    "  1. COLLISION: the particles (atoms, ions, or molecules) must collide.",
    "     If they never meet, no reaction can happen.",
    "",
    "  2. ACTIVATION ENERGY: the collision must have ENOUGH energy to",
    "     break the bonds in the reactants. The minimum energy needed is",
    "     called the activation energy (Ea).",
    "",
    "  3. ORIENTATION: particles must collide at the RIGHT angle/position",
    "     for bonds to form in the product. A head-on crash at the wrong",
    "     part of the molecule won't work.",
    "",
    "Reaction rate = how fast reactants turn into products.",
], bg=BG_THEORY, border=CLR_THEORY, line_h=8.5)

ly = section_label(pm, LX, ly, COL_W, "โ–ถ  ACTIVATED COMPLEX โ€” THEORY", BG_THEORY, CLR_THEORY)
ly = text_block(pm, LX, ly, COL_W, [
    "When reactant particles collide with enough energy, they form a",
    "brief, unstable HIGH-ENERGY arrangement called the ACTIVATED COMPLEX",
    "(also called the transition state).",
    "",
    "  โ€ข It exists ONLY at the PEAK of the energy diagram",
    "  โ€ข It is NEITHER reactant nor product",
    "  โ€ข It immediately either falls apart back to reactants,",
    "    OR rearranges into products",
    "  โ€ข It cannot be isolated in the lab",
    "",
    "On an energy diagram: reactants โ†’ hill (activated complex) โ†’ products",
    "The HEIGHT of the hill = activation energy (Ea)",
], bg=BG_THEORY, border=CLR_THEORY, line_h=8.5)

ly = section_label(pm, LX, ly, COL_W, "โ–ถ  FACTORS AFFECTING RATE โ€” THEORY", BG_THEORY, CLR_THEORY)
ly = table_block(pm, LX, ly, COL_W,
    ["Factor", "Effect on Rate", "Why"],
    [
        ["โ†‘ Temperature",   "INCREASES",  "Faster particles โ†’ more collisions with enough energy"],
        ["Add catalyst",    "INCREASES",  "Lowers Ea (energy hill) โ†’ more collisions succeed"],
        ["โ†‘ Concentration", "INCREASES",  "More particles per volume โ†’ more collisions"],
        ["โ†‘ Surface area",  "INCREASES",  "More exposed particles โ†’ more collisions possible"],
        ["Remove catalyst", "DECREASES",  "Ea goes back up"],
    ],
    col_ratios=[0.25, 0.20, 0.55]
)

ry = section_label(pm, RX, ry, COL_W, "โ–ถ  CATALYSTS โ€” THEORY (IMPORTANT!)", BG_THEORY, CLR_THEORY)
ry = text_block(pm, RX, ry, COL_W, [
    "A catalyst is a substance that SPEEDS UP a reaction WITHOUT being",
    "consumed โ€” it is present at the start and end unchanged.",
    "",
    "How it works: provides an ALTERNATIVE REACTION PATHWAY with a",
    "LOWER activation energy (lower energy hill to climb).",
    "",
    "What a catalyst DOES change:",
    "   โœ“ Lowers activation energy (Ea)",
    "   โœ“ Increases reaction rate",
    "",
    "What a catalyst does NOT change:",
    "   โœ— ฮ”H (overall energy difference between reactants and products)",
    "   โœ— The equilibrium position",
    "   โœ— The identity of products",
], bg=BG_THEORY, border=CLR_THEORY, line_h=8.5)

ry = section_label(pm, RX, ry, COL_W, "โ–ถ  HOW TO ANSWER KINETICS QUESTIONS", BG_HOWTO, CLR_HOWTO)
ry = steps_block(pm, RX, ry, COL_W, [
    "Rate question: identify what factor changed (temp/conc/catalyst)",
    "Temp โ†‘ โ†’ rate โ†‘ (more collisions AND more energy per collision)",
    "Concentration โ†‘ โ†’ rate โ†‘ (more particles to collide)",
    "Catalyst added โ†’ rate โ†‘ + Ea โ†“ (but ฮ”H unchanged!)",
    "Catalyst question: always state BOTH effects (rate up, Ea down)",
    "Activated complex = at peak of energy diagram, unstable, not product",
])
ry = example_block(pm, RX, ry, COL_W, [
    "Q: What happens when a catalyst is added?",
    "A: The reaction rate INCREASES because the activation energy",
    "   (Ea) is LOWERED, so more collisions have enough energy.",
    "   The overall ฮ”H (energy of reaction) does NOT change.",
    "",
    "Q: What is the activated complex?",
    "A: The unstable, high-energy intermediate formed when",
    "   reactant molecules collide โ€” it exists momentarily at",
    "   the energy maximum, it is neither reactant nor product.",
])

y = min(ly, ry) - 3*mm
y = divider(pm, MARGIN, y, W - 2*MARGIN)

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# TOPIC 9: CHEMICAL EQUILIBRIUM
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
y = check_space(y, 50, pm)
y = topic_header(pm, LX, y, W - 2*MARGIN, 9,
                 "CHEMICAL EQUILIBRIUM", "Questions 1โ€“2",
                 color=colors.HexColor("#37474f"))
ly = y - 2*mm
ry = y - 2*mm

ly = section_label(pm, LX, ly, COL_W, "โ–ถ  EQUILIBRIUM โ€” THEORY", BG_THEORY, CLR_THEORY)
ly = text_block(pm, LX, ly, COL_W, [
    "Many reactions are REVERSIBLE โ€” products can react to re-form reactants.",
    "",
    "   A + B โ‡Œ C + D  (double arrow = reversible)",
    "",
    "Equilibrium is reached when:",
    "   RATE of FORWARD reaction = RATE of REVERSE reaction",
    "",
    "CRITICAL: At equilibrium, BOTH reactions are still happening!",
    "  Equilibrium does NOT mean the reactions stopped.",
    "  It means they happen at the SAME rate โ€” so concentrations,",
    "  color, pressure stay CONSTANT (macroscopic properties don't change).",
    "",
    "  The system is DYNAMIC (still changing at the molecular level)",
    "  but STATIC in appearance.",
], bg=BG_THEORY, border=CLR_THEORY, line_h=8.5)

ly = section_label(pm, LX, ly, COL_W, "โ–ถ  EQUILIBRIUM CONSTANT Keq โ€” THEORY", BG_THEORY, CLR_THEORY)
ly = text_block(pm, LX, ly, COL_W, [
    "Keq is a number that describes the RATIO of products to reactants",
    "at equilibrium for a given reaction at a specific temperature.",
    "",
    "  Keq > 1 โ†’ more products than reactants at equilibrium",
    "  Keq < 1 โ†’ more reactants than products at equilibrium",
    "",
    "MOST IMPORTANT RULES:",
    "  1. Keq has ONE valid value at a given TEMPERATURE",
    "  2. Changing TEMPERATURE โ†’ Keq VALUE changes",
    "  3. Changing concentration or pressure โ†’ equilibrium SHIFTS",
    "     (Le Chatelier's Principle), but Keq VALUE stays the SAME",
], bg=BG_THEORY, border=CLR_THEORY, line_h=8.5)

ry = section_label(pm, RX, ry, COL_W, "โ–ถ  LE CHATELIER'S PRINCIPLE โ€” THEORY", BG_THEORY, CLR_THEORY)
ry = text_block(pm, RX, ry, COL_W, [
    "If a stress is applied to a system at equilibrium, the system",
    "will SHIFT to RELIEVE that stress.",
    "",
    "   A + B โ‡Œ C + D",
    "",
    "Types of stresses and how the system responds:",
    "  Add REACTANT (A or B) โ†’ shifts RIGHT (โ†’) to make more C and D",
    "  Add PRODUCT (C or D) โ†’ shifts LEFT (โ†) to remove excess",
    "  Remove REACTANT โ†’ shifts LEFT (โ†) to replace it",
    "  Remove PRODUCT  โ†’ shifts RIGHT (โ†’) to replace it",
    "  Increase PRESSURE โ†’ shifts toward side with FEWER moles of gas",
    "  Decrease PRESSURE โ†’ shifts toward side with MORE moles of gas",
    "  Increase TEMPERATURE (endothermic rxn) โ†’ shifts RIGHT",
    "  Increase TEMPERATURE (exothermic rxn) โ†’ shifts LEFT",
    "",
    "โš  Concentration/pressure shifts DO NOT change Keq!",
    "โš  Only temperature changes the actual VALUE of Keq.",
], bg=BG_THEORY, border=CLR_THEORY, line_h=8.5)

ry = section_label(pm, RX, ry, COL_W, "โ–ถ  HOW TO ANSWER EQUILIBRIUM QUESTIONS", BG_HOWTO, CLR_HOWTO)
ry = steps_block(pm, RX, ry, COL_W, [
    "Identify: is the question about (a) what equilibrium IS,",
    "  (b) what happens when something is changed (Le Chatelier),",
    "  or (c) what affects Keq?",
    "(a) At equilibrium: fwd rate = rev rate. Both still occurring.",
    "(b) Le Chatelier: identify the stress โ†’ state which way it shifts",
    "(c) Keq changes: ONLY temperature changes Keq value.",
    "   Adding concentration/pressure = shift, not Keq change.",
])
ry = example_block(pm, RX, ry, COL_W, [
    "Q: What does equilibrium mean?",
    "A: Forward and reverse reaction rates are EQUAL.",
    "   Both reactions still occur โ€” concentrations stay constant.",
    "",
    "Q: Add more reactant โ€” what happens to Keq?",
    "A: Keq does NOT change. The equilibrium shifts RIGHT to",
    "   consume the added reactant, but Keq value stays the same.",
    "   (Only changing temperature would change Keq.)",
])

y = min(ly, ry) - 3*mm

# Final page โ€” FORMULA QUICK REFERENCE
pm.new_page()
y = H - 15*mm
c = pm.c

# big header
c.setFillColor(BG_TITLE)
c.rect(0, y - 16, W, 16, fill=1, stroke=0)
c.setFillColor(WHITE)
c.setFont("Helvetica-Bold", 11)
c.drawCentredString(W/2, y - 12, "MASTER FORMULA & REFERENCE SHEET โ€” Everything in One Place")
y -= 20

# two column
ly = y
ry = y

# Left: all formulas
ly = section_label(pm, LX, ly, COL_W, "โ–ถ  ALL FORMULAS YOU NEED", BG_THEORY, CLR_THEORY)
formulas = [
    ("Photon frequency",   "ฮฝ = c / ฮป",           "c=3.00ร—10โธ m/s, ฮป in meters"),
    ("Photon energy",      "E = h ร— ฮฝ = hc/ฮป",    "h=6.626ร—10โปยณโด Jยทs"),
    ("Specific heat",      "q = m ร— c ร— ฮ”T",      "ฮ”T = T_final โˆ’ T_initial"),
    ("Calorie conversion", "1 Cal = 4184 J",       "food Calorie = 1 kcal"),
    ("Moles from grams",   "n = mass / MM",        "MM = molar mass (g/mol)"),
    ("Grams from moles",   "g = moles ร— MM",       ""),
    ("Avogadro",           "1 mol = 6.02ร—10ยฒยณ",    "particles per mole"),
    ("% composition",      "% = (elem/MM) ร— 100",  "add all % to check = 100"),
    ("% yield",            "% = actual/theoryร—100","always โ‰ค 100%"),
    ("Molar ratio",        "coeff_want/coeff_have","from balanced equation"),
]
row_h = 12
c.setFillColor(BG_THEORY)
total_h = len(formulas) * row_h + 4
c.rect(LX, ly - total_h, COL_W, total_h, fill=1, stroke=0)
c.setStrokeColor(CLR_THEORY)
c.setLineWidth(0.4)
c.rect(LX, ly - total_h, COL_W, total_h, fill=0, stroke=1)
for i, (name, formula, note) in enumerate(formulas):
    row_y = ly - 2 - i * row_h
    if i % 2 == 0:
        c.setFillColor(colors.HexColor("#ddeeff"))
        c.rect(LX + 0.5, row_y - row_h + 0.5, COL_W - 1, row_h - 0.5, fill=1, stroke=0)
    c.setFillColor(CLR_THEORY)
    c.setFont("Helvetica-Bold", 6.3)
    c.drawString(LX + 3, row_y - 8, name)
    c.setFillColor(colors.HexColor("#b71c1c"))
    c.setFont("Courier-Bold", 7)
    c.drawString(LX + 68, row_y - 8, formula)
    c.setFillColor(GREY)
    c.setFont("Helvetica", 5.8)
    c.drawString(LX + 155, row_y - 8, note)
ly -= total_h + 2*mm

# step-by-step roadmaps summary
ly = section_label(pm, LX, ly, COL_W, "โ–ถ  PROBLEM-SOLVING ROADMAPS", BG_HOWTO, CLR_HOWTO)
roadmaps = [
    "PHOTON: ฮป(nm)โ†’m โ†’ ฮฝ=c/ฮป โ†’ E=hฮฝ",
    "ELECTRON CONFIG: [noble gas] โ†’ fill 4sยฒ3dยนโฐ4pยฒ order",
    "NAME IONIC: metal + (Roman# if variable) + anion name",
    "WRITE FORMULA: crossover charges as subscripts, reduce",
    "MOLE CALC: find MM first โ†’ รทMM for moles โ†’ ร—MM for grams",
    "% COMP: (elem mass / MM) ร— 100 for each element",
    "EMPIRICAL: %โ†’g รท at.mass โ†’ รท smallest โ†’ whole # ratio",
    "STOICH: balance โ†’ to mol โ†’ molar ratio โ†’ to units",
    "LIMITING R: bothโ†’mol โ†’ both make product โ†’ smaller = LR",
    "% YIELD: actual รท theoretical ร— 100",
    "HEAT: q = m ร— c ร— ฮ”T  (ฮ”T = Tf โˆ’ Ti)",
]
ly = text_block(pm, LX, ly, COL_W, roadmaps, bg=BG_HOWTO, border=CLR_HOWTO,
                size=6.5, line_h=8.5, color=BLACK)

# RIGHT col: polyatomic ions table + memory tricks + bonding table + spontaneity
ry = section_label(pm, RX, ry, COL_W, "โ–ถ  POLYATOMIC IONS โ€” COMPLETE LIST", BG_THEORY, CLR_THEORY)
ry = table_block(pm, RX, ry, COL_W,
    ["Ion", "Name", "Ion", "Name"],
    [
        ["SOโ‚„ยฒโป","sulfate",     "ClOโป",  "hypochlorite"],
        ["SOโ‚ƒยฒโป","sulfite",     "ClOโ‚‚โป", "chlorite"],
        ["NOโ‚ƒโป", "nitrate",     "ClOโ‚ƒโป", "chlorate"],
        ["NOโ‚‚โป", "nitrite",     "ClOโ‚„โป", "perchlorate"],
        ["POโ‚„ยณโป","phosphate",   "OHโป",   "hydroxide"],
        ["NHโ‚„โบ", "ammonium",    "COโ‚ƒยฒโป", "carbonate"],
        ["CrOโ‚„ยฒโป","chromate",   "MnOโ‚„โป", "permanganate"],
    ],
    col_ratios=[0.20, 0.30, 0.20, 0.30]
)

ry = section_label(pm, RX, ry, COL_W, "โ–ถ  BOND & CHARGE QUICK REFERENCE", BG_THEORY, CLR_THEORY)
ry = table_block(pm, RX, ry, COL_W,
    ["Bond", "ฯƒ bonds", "ฯ€ bonds", "Strength", "Length"],
    [
        ["Single", "1", "0", "weakest",  "longest"],
        ["Double", "1", "1", "medium",   "medium"],
        ["Triple", "1", "2", "strongest","shortest"],
    ],
    col_ratios=[0.20, 0.18, 0.18, 0.22, 0.22]
)

ry = section_label(pm, RX, ry, COL_W, "โ–ถ  COMMON ION CHARGES", BG_THEORY, CLR_THEORY)
ry = table_block(pm, RX, ry, COL_W,
    ["Group", "Charge", "Examples"],
    [
        ["1 (alkali metals)",    "1+", "Li, Na, K, Rb, Cs"],
        ["2 (alkaline earth)",   "2+", "Be, Mg, Ca, Sr, Ba"],
        ["13 (Al group)",        "3+", "Al, Ga"],
        ["15 (N group)",         "3โˆ’", "N, P, As"],
        ["16 (O group)",         "2โˆ’", "O, S, Se, Te"],
        ["17 (halogens)",        "1โˆ’", "F, Cl, Br, I"],
        ["18 (noble gases)",     "0",  "He, Ne, Ar, Kr"],
        ["Transition metals",    "var","Fe(2+/3+) Cu(1+/2+)"],
    ],
    col_ratios=[0.38, 0.15, 0.47]
)

ry = section_label(pm, RX, ry, COL_W, "โ–ถ  SPONTANEITY SUMMARY", BG_THEORY, CLR_THEORY)
ry = table_block(pm, RX, ry, COL_W,
    ["ฮ”H", "ฮ”S", "Spontaneous?"],
    [
        ["< 0", "> 0", "ALWAYS (any T)"],
        ["< 0", "< 0", "Low T only"],
        ["> 0", "> 0", "High T only"],
        ["> 0", "< 0", "NEVER"],
    ],
    col_ratios=[0.22, 0.22, 0.56]
)

ry = section_label(pm, RX, ry, COL_W, "โ–ถ  GREEK PREFIXES FOR MOLECULAR COMPOUNDS", BG_THEORY, CLR_THEORY)
ry = table_block(pm, RX, ry, COL_W,
    ["#", "Prefix", "#", "Prefix", "#", "Prefix"],
    [
        ["1","mono", "4","tetra","7","hepta"],
        ["2","di",   "5","penta","8","octa"],
        ["3","tri",  "6","hexa", "9","nona"],
    ],
    col_ratios=[0.08, 0.17, 0.08, 0.17, 0.08, 0.17]
)

pm.save()
Running Command

cd /home/daytona/workspace/chem-cheatsheet && python3 studyguide_complete.py 2>&โ€ฆ

Writing File

~/chem-cheatsheet/cheatsheet_final.py

"""
Honors Chemistry โ€” CHEAT SHEET (2 pages, A4)
Page 1: Periodic Table + full legend (metal/nonmetal/charges/polyatomic ions)
Page 2: Theory + How-To steps for ALL 9 topics. NO worked examples.
Designed to pack maximum useful content into one A4 sheet (print double-sided).
"""

from reportlab.lib.pagesizes import A4
from reportlab.lib.units import cm, mm
from reportlab.lib import colors
from reportlab.pdfgen import canvas as pdfcanvas

W, H = A4
OUT = "/home/daytona/workspace/chem-cheatsheet/HonorsChem_CheatSheet_Final.pdf"

# โ”€โ”€ Palette โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
P = {
    "hdr":       colors.HexColor("#0d1b4b"),
    "hdr2":      colors.HexColor("#1a3a7c"),
    "topic1":    colors.HexColor("#0d47a1"),  # atomic
    "topic2":    colors.HexColor("#4a148c"),  # bonding
    "topic3":    colors.HexColor("#b45309"),  # nomenclature
    "topic4":    colors.HexColor("#991b1b"),  # reactions
    "topic5":    colors.HexColor("#065f46"),  # mole
    "topic6":    colors.HexColor("#7c2d12"),  # stoich
    "topic7":    colors.HexColor("#164e63"),  # thermo
    "topic8":    colors.HexColor("#14532d"),  # kinetics
    "topic9":    colors.HexColor("#374151"),  # equilibrium
    "thy_bg":    colors.HexColor("#eff6ff"),
    "thy_bd":    colors.HexColor("#3b82f6"),
    "how_bg":    colors.HexColor("#f0fdf4"),
    "how_bd":    colors.HexColor("#16a34a"),
    "warn_bg":   colors.HexColor("#fff7ed"),
    "warn_bd":   colors.HexColor("#ea580c"),
    "tbl_hdr":   colors.HexColor("#334155"),
    "tbl_alt":   colors.HexColor("#f1f5f9"),
    # periodic table categories
    "alkali":    colors.HexColor("#fca5a5"),
    "alkaline":  colors.HexColor("#fcd34d"),
    "transition":colors.HexColor("#93c5fd"),
    "postmetal": colors.HexColor("#86efac"),
    "metalloid": colors.HexColor("#c4b5fd"),
    "nonmetal":  colors.HexColor("#fef08a"),
    "halogen":   colors.HexColor("#67e8f9"),
    "noble":     colors.HexColor("#f9a8d4"),
    "lan":       colors.HexColor("#d6b4a0"),
    "act":       colors.HexColor("#b0c4de"),
    "white":     colors.white,
    "black":     colors.black,
    "grey":      colors.HexColor("#374151"),
    "lgrey":     colors.HexColor("#e5e7eb"),
    "border":    colors.HexColor("#94a3b8"),
}

c = pdfcanvas.Canvas(OUT, pagesize=A4)

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# HELPER FUNCTIONS
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

def hdr_bar(y, text, h=15):
    c.setFillColor(P["hdr"])
    c.rect(0, y - h, W, h, fill=1, stroke=0)
    c.setFillColor(P["white"])
    c.setFont("Helvetica-Bold", 8.5)
    c.drawCentredString(W/2, y - h + 3.5, text)
    return y - h

def section_bar_full(x, y, w, text, color, h=10):
    c.setFillColor(color)
    c.rect(x, y - h, w, h, fill=1, stroke=0)
    c.setFillColor(P["white"])
    c.setFont("Helvetica-Bold", 6.5)
    c.drawString(x + 3, y - h + 2.5, text)
    return y - h

def mini_table(x, y, w, headers, rows, col_ratios, size=5.8, rh=8):
    col_widths = [w * r for r in col_ratios]
    # header
    c.setFillColor(P["tbl_hdr"])
    c.rect(x, y - rh, w, rh, fill=1, stroke=0)
    c.setFillColor(P["white"])
    c.setFont("Helvetica-Bold", size)
    cx = x
    for h_txt, cw in zip(headers, col_widths):
        c.drawString(cx + 2, y - rh + 2, h_txt)
        cx += cw
    # rows
    for ri, row in enumerate(rows):
        ry2 = y - rh * (ri + 2)
        c.setFillColor(P["tbl_alt"] if ri % 2 == 0 else P["white"])
        c.rect(x, ry2, w, rh, fill=1, stroke=0)
        c.setFillColor(P["black"])
        c.setFont("Helvetica", size)
        cx = x
        for cell, cw in zip(row, col_widths):
            c.drawString(cx + 2, ry2 + 2, str(cell))
            cx += cw
    c.setStrokeColor(P["border"])
    c.setLineWidth(0.3)
    c.rect(x, y - rh * (len(rows) + 1), w, rh * (len(rows) + 1), fill=0, stroke=1)
    return y - rh * (len(rows) + 1) - 1.5*mm

def theory_box(x, y, w, lines, lh=7.5, pad=2.5):
    total_h = len(lines) * lh + 2 * pad
    c.setFillColor(P["thy_bg"])
    c.setStrokeColor(P["thy_bd"])
    c.setLineWidth(0.4)
    c.rect(x, y - total_h, w, total_h, fill=1, stroke=1)
    c.setFillColor(P["grey"])
    c.setFont("Helvetica", 6)
    for i, ln in enumerate(lines):
        c.drawString(x + 3, y - pad - i * lh - 5, ln)
    return y - total_h - 1*mm

def howto_box(x, y, w, steps, lh=7.5, pad=2.5):
    total_h = len(steps) * lh + 2 * pad
    c.setFillColor(P["how_bg"])
    c.setStrokeColor(P["how_bd"])
    c.setLineWidth(0.5)
    c.rect(x, y - total_h, w, total_h, fill=1, stroke=1)
    for i, step in enumerate(steps):
        c.setFillColor(P["how_bd"])
        c.setFont("Helvetica-Bold", 6)
        c.drawString(x + 3, y - pad - i * lh - 5, f"{i+1}.")
        c.setFillColor(P["black"])
        c.setFont("Helvetica", 6)
        c.drawString(x + 12, y - pad - i * lh - 5, step)
    return y - total_h - 1*mm

def warn_line(x, y, w, text):
    c.setFillColor(P["warn_bg"])
    c.setStrokeColor(P["warn_bd"])
    c.setLineWidth(0.4)
    c.rect(x, y - 9, w, 9, fill=1, stroke=1)
    c.setFillColor(P["warn_bd"])
    c.setFont("Helvetica-Bold", 5.5)
    c.drawString(x + 2, y - 6.5, "โš ")
    c.setFillColor(P["black"])
    c.setFont("Helvetica", 5.8)
    c.drawString(x + 10, y - 6.5, text)
    return y - 10

def topic_bar(x, y, w, num, title, qs, color, h=11):
    c.setFillColor(color)
    c.roundRect(x, y - h, w, h, 2, fill=1, stroke=0)
    c.setFillColor(P["white"])
    c.setFont("Helvetica-Bold", 7)
    c.drawString(x + 3, y - h + 2.5, f"T{num}: {title}")
    c.setFont("Helvetica", 6)
    c.drawRightString(x + w - 3, y - h + 2.5, f"Qs {qs}")
    return y - h - 1*mm


# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# PAGE 1 โ€” PERIODIC TABLE + LEGEND
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

margin = 5*mm
y = H - margin

y = hdr_bar(y, "HONORS CHEMISTRY โ€” CHEAT SHEET  ยท  Page 1: Periodic Table Reference  ยท  Pendleton High School", h=14)
y -= 2*mm

# โ”€โ”€ PERIODIC TABLE โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
table_h_area = H * 0.465
n_rows = 9    # 7 periods + gap + 2 lan/act
n_cols = 18
cw = (W - 2*margin) / n_cols
ch = table_h_area / (n_rows + 0.3)

ELEM = {}
raw = [
    # (period, group, symbol, Z, cat, charge)
    (1,1,"H",1,"nonmetal","1+/โˆ’"),(1,18,"He",2,"noble","0"),
    (2,1,"Li",3,"alkali","1+"),(2,2,"Be",4,"alkaline","2+"),
    (2,13,"B",5,"metalloid","3+"),(2,14,"C",6,"nonmetal","4โˆ’"),
    (2,15,"N",7,"nonmetal","3โˆ’"),(2,16,"O",8,"nonmetal","2โˆ’"),
    (2,17,"F",9,"halogen","1โˆ’"),(2,18,"Ne",10,"noble","0"),
    (3,1,"Na",11,"alkali","1+"),(3,2,"Mg",12,"alkaline","2+"),
    (3,13,"Al",13,"postmetal","3+"),(3,14,"Si",14,"metalloid","4โˆ’"),
    (3,15,"P",15,"nonmetal","3โˆ’"),(3,16,"S",16,"nonmetal","2โˆ’"),
    (3,17,"Cl",17,"halogen","1โˆ’"),(3,18,"Ar",18,"noble","0"),
    (4,1,"K",19,"alkali","1+"),(4,2,"Ca",20,"alkaline","2+"),
    (4,3,"Sc",21,"transition","3+"),(4,4,"Ti",22,"transition","4+"),
    (4,5,"V",23,"transition","var"),(4,6,"Cr",24,"transition","var"),
    (4,7,"Mn",25,"transition","var"),(4,8,"Fe",26,"transition","var"),
    (4,9,"Co",27,"transition","var"),(4,10,"Ni",28,"transition","var"),
    (4,11,"Cu",29,"transition","var"),(4,12,"Zn",30,"transition","2+"),
    (4,13,"Ga",31,"postmetal","3+"),(4,14,"Ge",32,"metalloid","4โˆ’"),
    (4,15,"As",33,"metalloid","3โˆ’"),(4,16,"Se",34,"nonmetal","2โˆ’"),
    (4,17,"Br",35,"halogen","1โˆ’"),(4,18,"Kr",36,"noble","0"),
    (5,1,"Rb",37,"alkali","1+"),(5,2,"Sr",38,"alkaline","2+"),
    (5,3,"Y",39,"transition","3+"),(5,4,"Zr",40,"transition","4+"),
    (5,5,"Nb",41,"transition","var"),(5,6,"Mo",42,"transition","var"),
    (5,7,"Tc",43,"transition","var"),(5,8,"Ru",44,"transition","var"),
    (5,9,"Rh",45,"transition","var"),(5,10,"Pd",46,"transition","var"),
    (5,11,"Ag",47,"transition","1+"),(5,12,"Cd",48,"transition","2+"),
    (5,13,"In",49,"postmetal","3+"),(5,14,"Sn",50,"postmetal","var"),
    (5,15,"Sb",51,"metalloid","3โˆ’"),(5,16,"Te",52,"metalloid","2โˆ’"),
    (5,17,"I",53,"halogen","1โˆ’"),(5,18,"Xe",54,"noble","0"),
    (6,1,"Cs",55,"alkali","1+"),(6,2,"Ba",56,"alkaline","2+"),
    (6,3,"La*",57,"lan","3+"),(6,4,"Hf",72,"transition","4+"),
    (6,5,"Ta",73,"transition","5+"),(6,6,"W",74,"transition","var"),
    (6,7,"Re",75,"transition","var"),(6,8,"Os",76,"transition","var"),
    (6,9,"Ir",77,"transition","var"),(6,10,"Pt",78,"transition","var"),
    (6,11,"Au",79,"transition","var"),(6,12,"Hg",80,"transition","var"),
    (6,13,"Tl",81,"postmetal","var"),(6,14,"Pb",82,"postmetal","var"),
    (6,15,"Bi",83,"postmetal","3+"),(6,16,"Po",84,"metalloid","2โˆ’"),
    (6,17,"At",85,"halogen","1โˆ’"),(6,18,"Rn",86,"noble","0"),
    (7,1,"Fr",87,"alkali","1+"),(7,2,"Ra",88,"alkaline","2+"),
    (7,3,"Ac*",89,"act","3+"),
]

cat_color = {
    "alkali":P["alkali"],"alkaline":P["alkaline"],"transition":P["transition"],
    "postmetal":P["postmetal"],"metalloid":P["metalloid"],"nonmetal":P["nonmetal"],
    "halogen":P["halogen"],"noble":P["noble"],"lan":P["lan"],"act":P["act"],
}

def draw_cell(period, group, sym, Z, cat, charge, x_extra=0, y_extra=0):
    col = group - 1
    row = period - 1
    x = margin + col * cw + x_extra
    ey = y - (row + 1) * ch + y_extra
    bg = cat_color.get(cat, P["lgrey"])
    c.setFillColor(bg)
    c.setStrokeColor(P["border"])
    c.setLineWidth(0.25)
    c.rect(x + 0.3, ey + 0.3, cw - 0.6, ch - 0.6, fill=1, stroke=1)
    # atomic number
    c.setFillColor(colors.HexColor("#475569"))
    c.setFont("Helvetica", 3.8)
    c.drawString(x + 1.5, ey + ch - 5, str(Z))
    # symbol
    c.setFillColor(P["black"])
    fsz = 7 if len(sym) <= 2 else 5.5
    c.setFont("Helvetica-Bold", fsz)
    c.drawCentredString(x + cw/2, ey + ch/2 - 2.5, sym)
    # charge
    c.setFillColor(colors.HexColor("#dc2626"))
    c.setFont("Helvetica", 3.5)
    c.drawCentredString(x + cw/2, ey + 1.5, charge)

# Group labels
for g in range(1, 19):
    gx = margin + (g - 1) * cw + cw/2
    c.setFillColor(P["hdr2"])
    c.setFont("Helvetica-Bold", 5)
    c.drawCentredString(gx, y + 0.5*mm, str(g))

# Period labels
for p in range(1, 8):
    py2 = y - p * ch + ch/2 - 2
    c.setFillColor(P["hdr2"])
    c.setFont("Helvetica-Bold", 5.5)
    c.drawRightString(margin - 1, py2, str(p))

# Draw main table
for (period, group, sym, Z, cat, charge) in raw:
    draw_cell(period, group, sym, Z, cat, charge)

# Lanthanides / Actinides
lan_data = [
    ("La",57,"lan","3+"),("Ce",58,"lan","3+"),("Pr",59,"lan","3+"),
    ("Nd",60,"lan","3+"),("Pm",61,"lan","var"),("Sm",62,"lan","3+"),
    ("Eu",63,"lan","3+"),("Gd",64,"lan","3+"),("Tb",65,"lan","3+"),
    ("Dy",66,"lan","3+"),("Ho",67,"lan","3+"),("Er",68,"lan","3+"),
    ("Tm",69,"lan","3+"),("Yb",70,"lan","2+"),("Lu",71,"lan","3+"),
]
act_data = [
    ("Ac",89,"act","3+"),("Th",90,"act","4+"),("Pa",91,"act","5+"),
    ("U",92,"act","var"),("Np",93,"act","var"),("Pu",94,"act","var"),
    ("Am",95,"act","3+"),("Cm",96,"act","3+"),("Bk",97,"act","3+"),
    ("Cf",98,"act","3+"),("Es",99,"act","3+"),("Fm",100,"act","3+"),
    ("Md",101,"act","3+"),("No",102,"act","2+"),("Lr",103,"act","3+"),
]

lan_y_off = -1.35 * ch
act_y_off = -2.1 * ch

for i, (sym, Z, cat, charge) in enumerate(lan_data):
    col = i + 3
    row = 7
    x = margin + col * cw
    ey = y - (row + 1) * ch + lan_y_off
    bg = cat_color.get(cat, P["lgrey"])
    c.setFillColor(bg)
    c.setStrokeColor(P["border"])
    c.setLineWidth(0.25)
    c.rect(x + 0.3, ey + 0.3, cw - 0.6, ch - 0.6, fill=1, stroke=1)
    c.setFillColor(P["black"])
    c.setFont("Helvetica-Bold", 6.5)
    c.drawCentredString(x + cw/2, ey + ch/2 - 2.5, sym)
    c.setFillColor(colors.HexColor("#dc2626"))
    c.setFont("Helvetica", 3.5)
    c.drawCentredString(x + cw/2, ey + 1.5, charge)

for i, (sym, Z, cat, charge) in enumerate(act_data):
    col = i + 3
    row = 8
    x = margin + col * cw
    ey = y - (row + 1) * ch + act_y_off
    bg = cat_color.get(cat, P["lgrey"])
    c.setFillColor(bg)
    c.setStrokeColor(P["border"])
    c.setLineWidth(0.25)
    c.rect(x + 0.3, ey + 0.3, cw - 0.6, ch - 0.6, fill=1, stroke=1)
    c.setFillColor(P["black"])
    c.setFont("Helvetica-Bold", 6.5)
    c.drawCentredString(x + cw/2, ey + ch/2 - 2.5, sym)
    c.setFillColor(colors.HexColor("#dc2626"))
    c.setFont("Helvetica", 3.5)
    c.drawCentredString(x + cw/2, ey + 1.5, charge)

# Figure out where table ends
table_bottom = y - 9 * ch + act_y_off - 3*mm

# โ”€โ”€ LEGEND โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
legend_y = table_bottom - 2*mm
c.setFillColor(P["hdr"])
c.rect(margin, legend_y - 11, W - 2*margin, 11, fill=1, stroke=0)
c.setFillColor(P["white"])
c.setFont("Helvetica-Bold", 7)
c.drawCentredString(W/2, legend_y - 8, "PERIODIC TABLE KEY  โ€”  Colors, Charges, and Special Groups")
legend_y -= 13

# Category swatches (2 rows ร— 4)
cats = [
    ("Alkali Metals Gp1", "alkali",     "1+ charge  (Li,Na,K)"),
    ("Alkaline Earth Gp2","alkaline",   "2+ charge  (Mg,Ca,Ba)"),
    ("Transition Metals", "transition", "Variable charge (Fe,Cu,Cr)"),
    ("Post-Trans. Metals","postmetal",  "Al(3+),Pb,Sn,Tl var"),
    ("Metalloids",        "metalloid",  "Staircase line: B,Si,Ge,As"),
    ("Nonmetals",         "nonmetal",   "Neg ions (Oยฒโป,Nยณโป,Sยฒโป)"),
    ("Halogens Gp17",     "halogen",    "1โˆ’ charge (F,Cl,Br,I)"),
    ("Noble Gases Gp18",  "noble",      "0 charge โ€” never bond"),
]
bw = (W - 2*margin) / 4
bh = 18
for i, (name, cat, desc) in enumerate(cats):
    col = i % 4
    row = i // 4
    bx = margin + col * bw
    by = legend_y - row * (bh + 2)
    c.setFillColor(cat_color[cat])
    c.setStrokeColor(P["border"])
    c.setLineWidth(0.4)
    c.rect(bx, by - bh, bw - 2, bh, fill=1, stroke=1)
    c.setFillColor(P["black"])
    c.setFont("Helvetica-Bold", 6)
    c.drawString(bx + 2, by - 8, name)
    c.setFillColor(P["grey"])
    c.setFont("Helvetica", 5.5)
    c.drawString(bx + 2, by - 15, desc)

legend_y -= 2 * (bh + 2) + 2*mm

# Metal / nonmetal divider note
c.setFillColor(P["hdr2"])
c.setFont("Helvetica-Bold", 6.5)
c.drawString(margin, legend_y, "METALS (left of staircase line)  โ†โ†’  METALLOIDS (on staircase)  โ†โ†’  NONMETALS (right of staircase)")
legend_y -= 6*mm

# Ion charges table
c.setFillColor(P["hdr"])
c.rect(margin, legend_y - 10, W - 2*margin, 10, fill=1, stroke=0)
c.setFillColor(P["white"])
c.setFont("Helvetica-Bold", 7)
c.drawCentredString(W/2, legend_y - 7.5, "COMMON ION CHARGES  โ€”  What charge does each group form?")
legend_y -= 12

charge_data = [
    ("Group 1","1+","Li,Na,K,Rb,Cs"),
    ("Group 2","2+","Be,Mg,Ca,Sr,Ba"),
    ("Group 13","3+","Al,Ga,In"),
    ("Group 15","3โˆ’","N,P,As"),
    ("Group 16","2โˆ’","O,S,Se,Te"),
    ("Group 17","1โˆ’","F,Cl,Br,I"),
    ("Group 18","0","He,Ne,Ar,Kr"),
    ("Transition","VAR","Fe,Cu,Cr,Mn,Pb"),
]
ccw = (W - 2*margin) / 8
cch = 20
for i, (grp, chg, ex) in enumerate(charge_data):
    cx2 = margin + i * ccw
    bg = P["alkali"] if i == 0 else P["alkaline"] if i == 1 else P["postmetal"] if i == 2 else \
         P["nonmetal"] if i < 6 else P["noble"] if i == 6 else P["transition"]
    c.setFillColor(bg)
    c.setStrokeColor(P["border"])
    c.setLineWidth(0.4)
    c.rect(cx2, legend_y - cch, ccw - 1, cch, fill=1, stroke=1)
    c.setFillColor(P["black"])
    c.setFont("Helvetica-Bold", 6)
    c.drawCentredString(cx2 + ccw/2, legend_y - 8, grp)
    c.setFillColor(P["hdr"])
    c.setFont("Helvetica-Bold", 9)
    c.drawCentredString(cx2 + ccw/2, legend_y - 16, chg)
legend_y -= cch + 1*mm

# examples row
c.setFillColor(P["grey"])
c.setFont("Helvetica", 5.5)
for i, (grp, chg, ex) in enumerate(charge_data):
    cx2 = margin + i * ccw + ccw/2
    c.drawCentredString(cx2, legend_y, ex)
legend_y -= 6*mm

# Special elements + polyatomic ions in two columns
half = (W - 2*margin - 3*mm) / 2
lx2 = margin
rx2 = margin + half + 3*mm

# LEFT: special elements
c.setFillColor(colors.HexColor("#dbeafe"))
c.setStrokeColor(P["topic1"])
c.setLineWidth(0.5)
spec_h = 36
c.rect(lx2, legend_y - spec_h, half, spec_h, fill=1, stroke=1)
c.setFillColor(P["hdr"])
c.setFont("Helvetica-Bold", 6.5)
c.drawString(lx2 + 3, legend_y - 9, "โ˜… DIATOMIC (HOFBrINCl) โ€” always paired:")
c.setFillColor(P["black"])
c.setFont("Helvetica", 6.5)
c.drawString(lx2 + 3, legend_y - 17, "Hโ‚‚  Oโ‚‚  Fโ‚‚  Brโ‚‚  Iโ‚‚  Nโ‚‚  Clโ‚‚")
c.setFillColor(P["hdr"])
c.setFont("Helvetica-Bold", 6.5)
c.drawString(lx2 + 3, legend_y - 26, "โ˜… NOBLE GASES โ€” monoatomic, never bond:")
c.setFillColor(P["black"])
c.setFont("Helvetica", 6.5)
c.drawString(lx2 + 3, legend_y - 34, "He  Ne  Ar  Kr  Xe  Rn   (write alone, never as Heโ‚‚!)")

# RIGHT: variable charge metals
c.setFillColor(colors.HexColor("#fef3c7"))
c.setStrokeColor(P["warn_bd"])
c.setLineWidth(0.5)
c.rect(rx2, legend_y - spec_h, half, spec_h, fill=1, stroke=1)
c.setFillColor(P["warn_bd"])
c.setFont("Helvetica-Bold", 6.5)
c.drawString(rx2 + 3, legend_y - 9, "โ˜… VARIABLE-CHARGE METALS (need Roman numerals):")
c.setFillColor(P["black"])
c.setFont("Helvetica", 6.3)
c.drawString(rx2 + 3, legend_y - 17, "Fe: 2+ (iron II) or 3+ (iron III)")
c.drawString(rx2 + 3, legend_y - 24, "Cu: 1+ (copper I) or 2+ (copper II)")
c.drawString(rx2 + 3, legend_y - 31, "Pb: 2+ or 4+    Sn: 2+ or 4+    Hg: 1+ or 2+")

legend_y -= spec_h + 2*mm

# Polyatomic ions โ€” full width, compact
c.setFillColor(colors.HexColor("#4a148c"))
c.rect(margin, legend_y - 10, W - 2*margin, 10, fill=1, stroke=0)
c.setFillColor(P["white"])
c.setFont("Helvetica-Bold", 7)
c.drawCentredString(W/2, legend_y - 7.5, "POLYATOMIC IONS  (memorize all of these!)")
legend_y -= 12

ions = [
    ("SOโ‚„ยฒโป","sulfate"),("SOโ‚ƒยฒโป","sulfite"),("NOโ‚ƒโป","nitrate"),
    ("NOโ‚‚โป","nitrite"),("POโ‚„ยณโป","phosphate"),("OHโป","hydroxide"),
    ("NHโ‚„โบ","ammonium"),("COโ‚ƒยฒโป","carbonate"),("ClOโป","hypochlorite"),
    ("ClOโ‚‚โป","chlorite"),("ClOโ‚ƒโป","chlorate"),("ClOโ‚„โป","perchlorate"),
]
icw = (W - 2*margin) / 6
irh = 10
for i, (ion, name) in enumerate(ions):
    col = i % 6
    row = i // 6
    ix = margin + col * icw
    iy = legend_y - row * irh
    ibg = colors.HexColor("#ede7f6") if col % 2 == 0 else colors.HexColor("#f3e5f5")
    c.setFillColor(ibg)
    c.setStrokeColor(colors.HexColor("#7b1fa2"))
    c.setLineWidth(0.3)
    c.rect(ix, iy - irh, icw - 0.5, irh, fill=1, stroke=1)
    c.setFillColor(colors.HexColor("#4a148c"))
    c.setFont("Helvetica-Bold", 6.5)
    c.drawString(ix + 2, iy - irh + 2.5, ion)
    c.setFillColor(P["grey"])
    c.setFont("Helvetica", 6)
    c.drawString(ix + 28, iy - irh + 2.5, name)

legend_y -= 2 * irh + 2*mm

# Memory trick strip
c.setFillColor(colors.HexColor("#fff7ed"))
c.setStrokeColor(P["warn_bd"])
c.setLineWidth(0.5)
trick_h = 14
c.rect(margin, legend_y - trick_h, W - 2*margin, trick_h, fill=1, stroke=1)
c.setFillColor(P["warn_bd"])
c.setFont("Helvetica-Bold", 6)
c.drawString(margin + 3, legend_y - 8, "MEMORY TRICKS:")
c.setFillColor(P["black"])
c.setFont("Helvetica", 6)
c.drawString(margin + 75, legend_y - 8,
    "-ate = MORE oxygens  |  -ite = FEWER oxygens  |  hypo- = fewest O  |  per- = most O")
c.drawString(margin + 3, legend_y - trick_h + 2,
    "Electron config fill order: 1s 2s 2p 3s 3p 4s 3d 4p 5s 4d 5p ... (4s BEFORE 3d!)")

# Footer
c.setFillColor(P["hdr"])
c.rect(0, 0, W, 10, fill=1, stroke=0)
c.setFillColor(P["white"])
c.setFont("Helvetica", 5.5)
c.drawCentredString(W/2, 3, "Page 1 of 2  ยท  Flip for Step-by-Step Problem Solving  โ†’  Pendleton High School  ยท  Honors Chemistry Final Exam")


# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# PAGE 2 โ€” THEORY + HOW-TO (NO EXAMPLES)
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
c.showPage()
c.setPageSize(A4)

# light bg
c.setFillColor(colors.HexColor("#f8fafc"))
c.rect(0, 0, W, H, fill=1, stroke=0)

y = H - margin
y = hdr_bar(y, "PAGE 2: THEORY + HOW-TO STEPS FOR ALL 9 TOPICS  ยท  No examples โ€” study those separately", h=14)
y -= 2*mm

MARGIN2 = margin
COL = (W - 2*MARGIN2 - 3*mm) / 2
LX = MARGIN2
RX = MARGIN2 + COL + 3*mm

ly = y
ry = y

# โ”€โ”€ TOPIC 1: Atomic Structure โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
ly = topic_bar(LX, ly, COL, 1, "ATOMIC STRUCTURE & PERIODIC TABLE", "44โ€“50", P["topic1"])
ly = theory_box(LX, ly, COL, [
    "Ground state = lowest energy; Excited = electron absorbed energy, jumped up",
    "Isotopes = same element, diff neutrons โ†’ diff mass (e.g. C-12 vs C-14)",
    "Allotropes = same element, diff structure (diamond/graphite = both carbon)",
    "Alloy = metallic mixture (steel=Fe+C, bronze=Cu+Sn)",
    "Octet rule: atoms are stable with 8 electrons in outer shell",
    "Fill order: 1s 2s 2p 3s 3p [4s 3d] 4p 5s 4d 5p ... (4s BEFORE 3d!)",
])
ly = howto_box(LX, ly, COL, [
    "Electron config: find Z, find prev. noble gas, write [NG]",
    "Continue filling: 4sยฒ โ†’ 3dยนโฐ โ†’ 4pยฒ  (32โˆ’18=14 eโป for Ge)",
    "Ge (Z=32): [Ar] 4sยฒ 3dยนโฐ 4pยฒ",
    "Light: ฮฝ = c/ฮป  then  E = hฮฝ  (ฮป MUST be in meters!)",
    "c=3.00ร—10โธ m/s  h=6.626ร—10โปยณโด Jยทs",
])

# โ”€โ”€ TOPIC 2: Chemical Bonding โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
ly = topic_bar(LX, ly, COL, 2, "CHEMICAL BONDING", "32โ€“40", P["topic2"])
ly = theory_box(LX, ly, COL, [
    "Ionic: Metal GIVES eโป to nonmetal โ†’ forms crystals, HIGH melting point",
    "Covalent: Nonmetal+Nonmetal SHARE eโป",
    "Coordinate covalent: ONE atom donates BOTH electrons (e.g. NHโ‚„โบ)",
    "Single=1ฯƒ,0ฯ€  |  Double=1ฯƒ,1ฯ€  |  Triple=1ฯƒ,2ฯ€",
    "Shorter bond = Stronger = Higher energy to break",
    "Nonpolar: symmetric (SiHโ‚„ tetrahedral)  |  Polar: has lone pairs (Hโ‚‚S,PHโ‚ƒ)",
    "HOFBrINCl: 7 diatomic elements Hโ‚‚ Oโ‚‚ Fโ‚‚ Brโ‚‚ Iโ‚‚ Nโ‚‚ Clโ‚‚",
])
ly = howto_box(LX, ly, COL, [
    "Count ฯƒ/ฯ€: every bond has 1ฯƒ; double adds 1ฯ€; triple adds 2ฯ€",
    "Cโ‚‚Hโ‚„: C=C(1ฯƒ+1ฯ€) + 4 C-H(4ฯƒ) = 5ฯƒ total, 1ฯ€ total",
    "Polarity: lone pairs on central atom โ†’ polar molecule",
    "Bond type from formula: = is double, โ‰ก is triple",
])

# โ”€โ”€ TOPIC 3: Nomenclature โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
ly = topic_bar(LX, ly, COL, 3, "NOMENCLATURE & FORMULAS", "27,35,36,41โ€“43", P["topic3"])
ly = theory_box(LX, ly, COL, [
    "Ionic naming: metal name + (Roman# if variable charge) + anion name",
    "Variable charge metals: Fe, Cu, Pb, Sn, Hg, Cr, Mn",
    "-ate = more O  |  -ite = fewer O  |  hypo=fewest  |  per=most",
    "Hydrate: Compoundโ€ขxHโ‚‚O  (e.g. Ba(OH)โ‚‚โ€ข8Hโ‚‚O = octahydrate)",
    "Molecular: Greek prefixes  (di-Nโ‚‚, tetra-4, hexa-6, octa-8...)",
    "Acids: -ate ion โ†’ -ic acid (Hโ‚‚SOโ‚„=sulfuric)  |  -ite โ†’ -ous (Hโ‚‚SOโ‚ƒ=sulfurous)",
])
ly = howto_box(LX, ly, COL, [
    "Name ionic: identify metal, check if variable charge",
    "Variable? Count total negative charge รท # metal atoms = charge",
    "Write Roman numeral in (): Feโ‚‚(SOโ‚„)โ‚ƒ โ†’ 3ร—2โˆ’=6โˆ’ โ†’ Fe=3+ โ†’ Iron(III) sulfate",
    "Write formula: CROSSOVER method โ€” swap charges as subscripts",
    "Caยฒโบ+POโ‚„ยณโป โ†’ Caโ‚ƒ(POโ‚„)โ‚‚   |   reduce if both even (Caโ‚‚Oโ‚‚โ†’CaO)",
])
ly = warn_line(LX, ly, COL, "Polyatomic with subscript > 1 MUST have parentheses: (POโ‚„)โ‚‚ not POโ‚„โ‚‚")

# โ”€โ”€ TOPIC 4: Reactions โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
ly = topic_bar(LX, ly, COL, 4, "CHEMICAL REACTIONS & EQUATIONS", "28โ€“31", P["topic4"])
ly = theory_box(LX, ly, COL, [
    "Conservation of Mass: same atoms on both sides โ€” only coefficients change",
    "Precipitate: solid (s) product from 2 aq. solutions (e.g. AgCl(s))",
    "Spectator ions: appear UNCHANGED on both sides โ†’ remove for net ionic",
    "Neutralization: Acid + Base โ†’ Salt + Water",
    "Net ionic for any neutralization: Hโบ(aq) + OHโป(aq) โ†’ Hโ‚‚O(l)",
])
ly = howto_box(LX, ly, COL, [
    "Balance: count each element, add coefficients (NOT subscripts)",
    "Balance metals first, then nonmetals, then H, then O last",
    "Net ionic: split all (aq), keep (s)(l)(g) whole, cancel spectators",
    "SiOโ‚‚+4HFโ†’SiFโ‚„+2Hโ‚‚O  (coeff of HF=4)",
    "Neutralization: Ba(OH)โ‚‚+2HClโ†’BaClโ‚‚+2Hโ‚‚O",
])

# RIGHT COLUMN starts here
# โ”€โ”€ TOPIC 5: Mole โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
ry = topic_bar(RX, ry, COL, 5, "THE MOLE & MOLAR MASS", "19โ€“26", P["topic5"])
ry = theory_box(RX, ry, COL, [
    "1 mole = 6.02ร—10ยฒยณ particles (Avogadro's number)",
    "Molar mass = grams per mole of a substance (from periodic table)",
    "Compound MM: add atomic masses ร— subscripts for each element",
    "Stoichiometry based on Law of Conservation of Mass",
    "Conversions: gโ†’mol (รทMM)  |  molโ†’g (ร—MM)  |  molโ†’particles (ร—Avogadro)",
])
ry = howto_box(RX, ry, COL, [
    "Step 1: Calculate molar mass (add all atomic masses in formula)",
    "Grams โ†’ Moles:  n = mass รท molar mass",
    "Moles โ†’ Grams:  g = moles ร— molar mass",
    "Moles โ†’ Particles:  N = moles ร— 6.02ร—10ยฒยณ",
    "% comp:  % = (elem. mass in formula / MM) ร— 100  โ†’  must sum to 100",
    "Empirical: %โ†’g โ†’ รทat.mass โ†’ รทsmallest โ†’ whole # ratio",
])
ry = warn_line(RX, ry, COL, "Hโ‚‚CrOโ‚„: MM=2(1)+52+4(16)=118  |  Clโ‚‚: 71.0  |  Feโ‚‚Oโ‚ƒ: 159.7 g/mol")

# โ”€โ”€ TOPIC 6: Stoichiometry โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
ry = topic_bar(RX, ry, COL, 6, "STOICHIOMETRY", "11โ€“18", P["topic6"])
ry = theory_box(RX, ry, COL, [
    "Coefficients in balanced equation = MOLAR RATIOS",
    "Limiting reactant: runs out first, determines max product",
    "Excess reactant: what's left over after reaction stops",
    "Theoretical yield: max product from stoichiometry (perfect conditions)",
    "Actual yield: what you really get in the lab (always โ‰ค theoretical)",
    "% yield = (actual รท theoretical) ร— 100",
])
ry = howto_box(RX, ry, COL, [
    "ALWAYS STEP 1: write and balance the equation",
    "Stoich: given(g) รท MM โ†’ mol โ†’ ร— molar ratio โ†’ mol wanted โ†’ ร— MM",
    "Limiting R: convert BOTH reactants to mol โ†’ each makes product",
    "          the one making LESS product = LIMITING REACTANT",
    "% yield: actual รท theoretical ร— 100  (must be โ‰ค 100%)",
    "Molar ratio Cโ‚ƒHโ‚ˆ+5Oโ‚‚โ†’3COโ‚‚+4Hโ‚‚O: Oโ‚‚:COโ‚‚ = 5:3",
])

# โ”€โ”€ TOPIC 7: Thermochemistry โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
ry = topic_bar(RX, ry, COL, 7, "THERMOCHEMISTRY", "7โ€“9", P["topic7"])
ry = theory_box(RX, ry, COL, [
    "q = m ร— c ร— ฮ”T  (q=heat J, m=mass g, c=spec.heat J/gยฐC, ฮ”T=Tfโˆ’Ti)",
    "Water: c=4.18 J/gยฐC   |   Aluminum: c=0.900 J/gยฐC",
    "1 Cal (food) = 1 kcal = 4184 J",
    "Spontaneous if ฮ”H<0 AND ฮ”S>0 โ†’ always spontaneous (any temp)",
    "ฮ”G = ฮ”H โˆ’ Tฮ”S  |  ฮ”G<0 = spontaneous  |  ฮ”G>0 = non-spontaneous",
])
ry = mini_table(RX, ry, COL,
    ["ฮ”H", "ฮ”S", "Spontaneous?"],
    [["<0","<0","Low T only"],["<0",">0","ALWAYS โœ“"],
     [">0","<0","NEVER โœ—"],[">0",">0","High T only"]],
    [0.22,0.22,0.56], size=5.8, rh=7.5
)
ry = howto_box(RX, ry, COL, [
    "Heat: find m,c,Ti,Tf โ†’ ฮ”T=Tfโˆ’Ti โ†’ q=mร—cร—ฮ”T (answer in J)",
    "Calโ†’J: multiply by 4184  |  Jโ†’Cal: divide by 4184",
    "Spontaneity: state both ฮ”H and ฮ”S signs, use table above",
])

# โ”€โ”€ TOPIC 8: Kinetics โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
ry = topic_bar(RX, ry, COL, 8, "REACTION KINETICS", "3โ€“6", P["topic8"])
ry = theory_box(RX, ry, COL, [
    "Collision theory: must (1) collide, (2) have โ‰ฅ activation energy, (3) right orientation",
    "Activated complex: unstable high-E intermediate at energy maximum",
    "  โ†’ NOT a reactant, NOT a product โ€” exists only momentarily",
    "โ†‘Temp โ†’ rate โ†‘ (faster particles, more energetic collisions)",
    "Catalyst โ†’ rate โ†‘, Ea โ†“  BUT ฮ”H (overall energy) UNCHANGED",
    "โ†‘Concentration or โ†‘Surface area โ†’ rate โ†‘ (more collisions)",
])
ry = howto_box(RX, ry, COL, [
    "Rate question: identify which factor changed โ†’ state effect on rate",
    "Catalyst question: rate INCREASES + Ea DECREASES + ฮ”H unchanged",
    "Activated complex = at PEAK of energy diagram, unstable, not product",
    "Temp up = rate up because MORE collisions have ENOUGH energy",
])

# โ”€โ”€ TOPIC 9: Equilibrium โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
ry = topic_bar(RX, ry, COL, 9, "CHEMICAL EQUILIBRIUM", "1โ€“2", P["topic9"])
ry = theory_box(RX, ry, COL, [
    "Equilibrium: forward rate = reverse rate (BOTH still occurring!)",
    "NOT stopped โ€” still dynamic, but concentrations/pressure stay constant",
    "Keq valid ONLY at ONE specific temperature",
    "Only TEMPERATURE changes the VALUE of Keq",
    "Concentration/pressure changes SHIFT equilibrium (not Keq value)",
    "Le Chatelier: stress โ†’ system shifts to relieve it",
    "  Add reactant โ†’ shifts right  |  Add product โ†’ shifts left",
    "  โ†‘Pressure โ†’ shifts to fewer moles of gas",
])
ry = howto_box(RX, ry, COL, [
    "What is equilibrium? Fwd=Rev rate, both occurring, conc. constant",
    "Something added: identify it โ†’ state which direction eq. shifts",
    "Does Keq change? Only if TEMPERATURE changes. Conc/pressure = no",
    "Le Chat: add more A+Bโ‡ŒC+D reactant โ†’ shifts โ†’ makes more C,D",
])

# โ”€โ”€ BOTTOM STRIP: Formula quick-ref โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
bottom_y = min(ly, ry) - 2*mm
c.setFillColor(P["hdr"])
c.rect(MARGIN2, bottom_y - 11, W - 2*MARGIN2, 11, fill=1, stroke=0)
c.setFillColor(P["white"])
c.setFont("Helvetica-Bold", 7)
c.drawCentredString(W/2, bottom_y - 8, "ALL FORMULAS IN ONE LINE:  ฮฝ=c/ฮป  ยท  E=hฮฝ  ยท  q=mcฮ”T  ยท  1Cal=4184J  ยท  n=mass/MM  ยท  %comp=(elem/MM)ร—100  ยท  %yield=(actual/theoretical)ร—100  ยท  Avogadro=6.02ร—10ยฒยณ")
bottom_y -= 13

# Footer
c.setFillColor(P["hdr"])
c.rect(0, 0, W, 10, fill=1, stroke=0)
c.setFillColor(P["white"])
c.setFont("Helvetica", 5.5)
c.drawCentredString(W/2, 3, "Page 2 of 2  ยท  Pendleton High School  ยท  Honors Chemistry Final Exam  ยท  Print double-sided on one A4 sheet")

c.save()
print(f"โœ… Saved: {OUT}")
Running Command

cd /home/daytona/workspace/chem-cheatsheet && python3 cheatsheet_final.py 2>&1

Running Command

ls -lh /home/daytona/workspace/chem-cheatsheet/*.pdf

Here are your two files:

๐Ÿ“– Study Document (use tonight to learn)

๐Ÿ“„

HonorsChem StudyGuide Complete

PDF Document ยท PDF

5 pages. For every topic: full theory explanation in plain English + numbered how-to steps + worked examples you can trace through yourself. Covers all 9 topics + a master formula/reference page at the end.

๐Ÿ“‹ Cheat Sheet (print double-sided, bring to test)

๐Ÿ“„

HonorsChem CheatSheet Final

PDF Document ยท PDF

2 pages, print double-sided on one A4 sheet:
  • Page 1 โ€” Full color-coded periodic table (every element labeled with charge), category legend, common ion charges by group, variable-charge metals, diatomic vs monoatomic elements, all 12 polyatomic ions, memory tricks
  • Page 2 โ€” All 9 topics, each with a theory summary (what it is + key facts) and how-to steps (exactly what to do in sequence). No examples โ€” those are for studying, not the test sheet.

Tonight's plan: Read the study guide โ†’ trace every worked example โ†’ then look at the cheat sheet and check you understand what every line means. If anything is unclear, ask me! ๐ŸŽฏ
This is a shared conversation. Sign in to Orris to start your own chat.