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 && python3 -c " import pdfplumber with pdfplumber.open('HonorsChem_StudyGuide (2).pdf') as pdf: print(f'Total pages: {len(pdf.pages)}') for i, page in enumerate(pdf.pages): print(f'\\n===== PAGE {i+1} =====') print(page.extract_text()) "

I've read the full study guide. Now I'll create a comprehensive teaching lesson AND a cheat sheet for you. Let me build both.Alright, let's get you to that 80. I'll teach you everything in the study guide in plain English, then give you a cheat sheet. Read this all the way through — it's organized by topic just like the guide.

🧪 Honors Chem Final — Everything You Need to Know


1. Atomic Structure & the Periodic Table (Questions 44–50)

The Big Picture

Every atom has a nucleus (protons + neutrons) surrounded by electrons zipping around in shells/subshells.

Key Vocab

WordWhat it means
Ground stateThe atom is "relaxed" — electrons are in the lowest possible energy positions
Excited stateAn electron absorbed energy and jumped to a higher level (like a kid who ate candy)
IsotopesSame element, different number of neutrons → different mass. Example: Carbon-12 vs Carbon-14 (both are carbon, both have 6 protons, but different neutrons)
AllotropesSame element, different structure. Diamond and graphite are both pure carbon — just arranged differently
AlloyA mixture that has metallic properties. Steel = iron + carbon. Bronze = copper + tin

Electron Configuration

Think of electrons filling seats in a theater — cheapest seats first (lowest energy first).
Fill order: 1s → 2s → 2p → 3s → 3p → 4s → 3d → 4p
⚠️ 4s fills BEFORE 3d — this trips everyone up!
Example — Germanium (Ge), Period 4, Group 14: [Ar] 4s² 3d¹⁰ 4p²
  • [Ar] is a shortcut meaning "same as Argon's configuration"
  • Then add: 2 electrons in 4s, 10 in 3d, 2 in 4p
The Octet Rule: Atoms want 8 electrons in their outermost shell because that makes them stable (like noble gases).

Light & Photon Energy

Light travels as waves AND packets of energy called photons.
Two formulas:
c = λν          → rearranged: ν = c / λ
E = hν          → or: E = hc / λ
What the letters mean:
  • λ (lambda) = wavelength (how long each wave is, in meters)
  • ν (nu) = frequency (how many waves per second, in Hz)
  • c = speed of light = 3.00 × 10⁸ m/s
  • h = Planck's constant = 6.626 × 10⁻³⁴ J·s
  • E = energy of one photon (in Joules)
Key relationship: Higher frequency = shorter wavelength = MORE energy.
Example: If λ = 5.00 × 10⁻⁷ m, find frequency:
ν = c/λ = (3.00 × 10⁸) / (5.00 × 10⁻⁷) = 6.00 × 10¹⁴ Hz

2. Chemical Bonding (Questions 32–40)

Types of Bonds

Bond TypeHow it worksWho does itExample
IonicMetal gives electrons to nonmetalMetal + NonmetalNaCl (table salt)
CovalentTwo nonmetals SHARE electronsNonmetal + NonmetalH₂O, CO₂
Coordinate covalentONE atom donates BOTH electronsSpecial case of covalentNH₄⁺
Ionic bonds form crystals with HIGH melting points because the charges hold the structure together very strongly.

Sigma (σ) and Pi (π) Bonds

Every bond has a sigma bond. Extra bonds add pi bonds.
Bondσ bondsπ bonds
Single (C–C)10
Double (C=C)11
Triple (C≡C)12
Example — Ethene (C₂H₄): Has one C=C double bond + four C–H single bonds
  • C=C → 1σ + 1π
  • Each C–H → 1σ each (×4 = 4σ)
  • Total: 5σ, 1π

Polarity

  • A molecule is nonpolar if its shape is perfectly symmetric and bond dipoles cancel out
    • Example: SiH₄ (tetrahedral, symmetric) = nonpolar
  • A molecule is polar if it has lone pairs on the central atom (they push the shape and create an uneven charge)
    • AsH₃, PH₃, H₂S are all polar

Bond Length & Strength

Shorter = Stronger = Higher energy to break
  • Triple bond > Double bond > Single bond (in terms of strength)
  • Triple bond < Double bond < Single bond (in terms of length)

Diatomic Elements — HOFBrINCl

These 7 elements exist as pairs in nature: H₂, O₂, F₂, Br₂, I₂, N₂, Cl₂
  • Noble gases (He, Ne, Ar) are monoatomic — they go solo

3. Chemical Nomenclature & Formulas (Questions 27, 35, 36, 41–43)

Naming Ionic Compounds (Metal + Nonmetal)

  1. Write the metal name first
  2. If the metal can have different charges (Fe, Cu, Pb, etc.), put the charge in Roman numerals
  3. Write the nonmetal/anion name second (usually ends in -ide, -ate, -ite)
Example: Fe₂(SO₄)₃
  • Fe must have a 3+ charge (because 3 sulfate ions each carry 2-, total = 6-, so Fe total = 6+, two Fe = 3+ each)
  • Name: Iron(III) sulfate

Writing Ionic Formulas — The Crossover Method

Ca²⁺ + PO₄³⁻ → Ca₃(PO₄)₂
  • Cross the charges: Ca gets subscript 3, PO₄ gets subscript 2
  • Check: 3×(2+) = 6+ and 2×(3-) = 6- ✓

Polyatomic Ions You MUST Know

IonNameMemory trick
SO₄²⁻sulfatemore O = -ate
SO₃²⁻sulfiteless O = -ite
NO₃⁻nitrate
NO₂⁻nitrite
PO₄³⁻phosphate
OH⁻hydroxide
NH₄⁺ammonium+ charge (rare!)
ClO⁻hypochloritehypo = fewest O
ClO₂⁻chlorite
ClO₃⁻chlorate
ClO₄⁻perchlorateper = most O

Hydrates

Written as: Compound • xH₂O
  • Barium hydroxide octahydrate = Ba(OH)₂ • 8H₂O
  • "octa" = 8 (use the Greek prefix table!)

Naming Molecular (Covalent) Compounds

Use Greek prefixes for how many atoms:
#Prefix
1mono-
2di-
3tri-
4tetra-
5penta-
6hexa-
7hepta-
8octa-
Example: N₂H₄ = dinitrogen tetrahydride

Acids

  • Ends in -ate ion → acid gets -ic acid: H₂SO₄ = sulfuric acid
  • Ends in -ite ion → acid gets -ous acid: H₂SO₃ = sulfurous acid

4. Chemical Reactions & Equations (Questions 28–31)

Balancing Equations

Rule: You cannot change subscripts — only add coefficients (the big numbers in front).
Example walkthrough: SiO₂ + HF → SiF₄ + H₂O
  • Left: 1 Si, 2 O, 1 H, 1 F
  • Right: 1 Si, 4 F, 1 H, 1 O
  • Balance F first: need 4 F on left → 4HF
  • Now H: 4 H on left → need 2H₂O on right
  • Check O: 2 on left (SiO₂), 2 on right (2H₂O) ✓
  • Balanced: SiO₂ + 4HF → SiF₄ + 2H₂O → coefficient for HF = 4

Precipitates

A precipitate is a solid that forms when two solutions are mixed. It's marked with (s).
  • Example: When silver nitrate + sodium chloride are mixed → AgCl(s) falls out of solution

Net Ionic Equations

  1. Write the full equation with all ions separated
  2. Cross out "spectator ions" — ions that don't change and appear on both sides
  3. What's left = the net ionic equation
Example: HCl(aq) + KOH(aq) → KCl(aq) + H₂O(l)
  • Split: H⁺ + Cl⁻ + K⁺ + OH⁻ → K⁺ + Cl⁻ + H₂O
  • Cross out K⁺ and Cl⁻ (spectators)
  • Net ionic: H⁺(aq) + OH⁻(aq) → H₂O(l)

Neutralization

Acid + Base → Salt + Water
  • Ba(OH)₂ + 2HCl → BaCl₂ + 2H₂O

5. The Mole & Molar Mass (Questions 19–26)

What is a Mole?

A mole is just a counting number, like "dozen" = 12. Except: 1 mole = 6.02 × 10²³ particles (Avogadro's number)
It's huge because atoms are tiny.

Molar Mass

The molar mass is the mass of 1 mole of a substance, in g/mol.
  • Just look at the atomic masses on the periodic table.
  • Carbon (C) = 12.01 g/mol
  • To find molar mass of a compound, add up all the atoms.
Example — Fe₂O₃:
  • 2 × Fe = 2 × 55.85 = 111.7
  • 3 × O = 3 × 16.00 = 48.0
  • Total = 159.7 g/mol

The Three Conversions

grams ÷ molar mass = moles
moles × molar mass = grams
moles × 6.02×10²³ = number of particles
Practice problems from the guide:
  1. Moles in 100 g of Cl₂? → Cl₂ = 71.0 g/mol → 100 ÷ 71.0 = 1.41 mol
  2. Grams in 0.82 mol Fe₂O₃? → 0.82 × 159.7 = ~131 g
  3. Molar mass of H₂CrO₄? → 2(1) + 52 + 4(16) = 2 + 52 + 64 = 118 g/mol

Percent Composition

"What percent of this compound's mass is from each element?"
% element = (mass of element in formula / molar mass of compound) × 100
Example — CO₂ (molar mass = 44 g/mol):
  • % C = 12/44 × 100 = 27.3%
  • % O = 32/44 × 100 = 72.7%

Empirical Formula from % Composition

  1. Treat percentages as grams (assume 100 g sample)
  2. Divide each element's mass by its atomic mass → gives you moles
  3. Divide all by the smallest number → gives you the ratio
  4. Round to whole numbers → that's your empirical formula
Example (85.6% C, 14.4% H):
  • C: 85.6 ÷ 12 = 7.13
  • H: 14.4 ÷ 1 = 14.4
  • Divide by smallest (7.13): C = 1, H ≈ 2
  • Empirical formula: CH₂

6. Stoichiometry (Questions 11–18)

The 4-Step Process (Always do this order!)

  1. Write a balanced equation (never skip this!)
  2. Convert given to moles (grams ÷ molar mass)
  3. Use molar ratio (from the balanced equation coefficients)
  4. Convert moles to what they asked for (× molar mass for grams, × 6.02×10²³ for particles)

Limiting Reactant

When two reactants react, one runs out first — that's the limiting reactant. The other is the excess reactant.
How to find it:
  1. Convert both reactants to moles
  2. Use the molar ratio to see how much product each could make
  3. Whichever makes LESS product = the limiting reactant

Percent Yield

% yield = (actual yield / theoretical yield) × 100
  • Theoretical yield = what stoichiometry says you SHOULD get (perfect world)
  • Actual yield = what you actually got in the lab (always ≤ theoretical)
Example: You should get 10 g, but actually got 8 g → % yield = 8/10 × 100 = 80%

Molar Ratios

From the balanced equation C₃H₈ + 5O₂ → 3CO₂ + 4H₂O:
  • O₂ : CO₂ = 5 : 3
  • If you use 5 mol O₂, you produce 3 mol CO₂

7. Thermochemistry (Questions 7–9)

Specific Heat Formula

q = m × c × ΔT
  • q = heat (Joules)
  • m = mass (grams)
  • c = specific heat capacity (J/g·°C) — a property of the material
  • ΔT = change in temperature = T_final − T_initial
Example: 30.0 g of aluminum, heated from 20°C to 40°C (c for Al = 0.900 J/g·°C)
q = 30.0 × 0.900 × (40 − 20) = 30.0 × 0.900 × 20 = 540 J

Calorie Conversion

1 nutritional Calorie (Cal) = 1 kcal = 4184 J
Example: 170 Calories → 170 × 4184 = 7.11 × 10⁵ J

Spontaneity (Gibbs Free Energy)

A reaction is spontaneous if it happens on its own without a constant energy input.
Two factors determine this:
  • ΔH (enthalpy change): negative ΔH = exothermic = releases heat = favors spontaneity
  • ΔS (entropy change): positive ΔS = more disorder = favors spontaneity
The golden rule for the exam:
If ΔH < 0 (exothermic) AND ΔS > 0 (more disorder) → ALWAYS spontaneous at any temperature. ΔG is always negative.

8. Reaction Kinetics (Questions 3–6)

Collision Theory

For a reaction to happen, particles must:
  1. Collide with each other
  2. Have enough energy (≥ activation energy)
  3. Have the right orientation (hit each other the right way)

Activated Complex (Transition State)

When particles collide with enough energy, they briefly form an activated complex — an unstable, high-energy structure that exists for just a moment at the "top of the energy hill." It's neither reactant nor product.

What Affects Reaction Rate?

FactorEffectWhy
↑ TemperatureRate increasesParticles move faster → more collisions with enough energy
Add a catalystRate increasesLowers the activation energy (lower energy hill)
↑ ConcentrationRate increasesMore particles → more collisions
↑ Surface areaRate increasesMore particles exposed to react
Key catalyst fact: A catalyst lowers activation energy but does NOT change ΔH (it doesn't change the overall energy difference between reactants and products).

9. Chemical Equilibrium (Questions 1–2)

What is Equilibrium?

A reversible reaction reaches equilibrium when the rate of the forward reaction = rate of the reverse reaction.
Important: Equilibrium does NOT mean the reactions stopped. Both directions are still happening — just at the same rate, so concentrations stay constant.

Equilibrium Constant (K_eq)

  • K_eq only has one correct value at a given temperature
  • Change the temperature → K_eq changes
  • Changing concentration or pressure? → shifts the equilibrium (Le Chatelier's Principle), but K_eq stays the same

Le Chatelier's Principle

"If you stress an equilibrium system, it will shift to relieve that stress."
  • Add more reactant → shifts forward (makes more product)
  • Add more product → shifts backward
  • Increase pressure → shifts toward the side with fewer moles of gas
  • Increase temperature → shifts toward the endothermic direction

✅ Quick Practice Quiz (Test Yourself!)

Try these before looking at the answers:
  1. What is the electron configuration of Ge?
  2. How many σ and π bonds are in a triple bond?
  3. Name: Fe₂(SO₄)₃
  4. What is the net ionic equation for HCl + KOH?
  5. How many moles are in 100 g of Cl₂?
  6. C₃H₈ + 5O₂ → 3CO₂ + 4H₂O. Molar ratio of O₂ to CO₂?
  7. q for 30 g Al heated by 20°C? (c = 0.900)
  8. What makes a reaction always spontaneous regardless of temperature?
Answers: 1) [Ar] 4s² 3d¹⁰ 4p² | 2) 1σ, 2π | 3) Iron(III) sulfate | 4) H⁺ + OH⁻ → H₂O | 5) 1.41 mol | 6) 5:3 | 7) 540 J | 8) ΔH < 0 AND ΔS > 0

Now let me build your cheat sheet!
Running Command

mkdir -p /home/daytona/workspace/chem-cheatsheet && ls /home/daytona/workspace/chem-cheatsheet

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

Got it. Here's the plan:
  • Page 1 (half you have) = Periodic table with color-coded legend: metals/nonmetals/metalloids, common ion charges, group charges, diatomic elements, noble gases
  • Page 2 (your writing side — the full other half) = Step-by-step "what to DO" guide for every problem type, not just formulas
Let me build this properly.
Running Command

cd /home/daytona/workspace/chem-cheatsheet && python3 -c " from reportlab.lib.pagesizes import A4 print('A4:', A4) # A4 = 595.27 x 841.89 points # Half A4 height = 420.94 points # Full A4 = both pages "

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

Got it. You want two separate things:
  1. A complete study document — theory + how-to + worked examples (to study from tonight)
  2. A cheat sheet — theory + how-to only, NO examples (to bring to the test)
Let me build both right now.
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>&1

Now let me build the cheat sheet — same layout as before but with theory + how-to steps, zero examples.
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.