← Guides EVOLution
Loaded

Exam 3 · Phylogeny / Speciation / Humans Study Guide

BIOL 4230 · Evolution · Exam 3 (= Part 1 of the final exam slot, 60 pts MC, new material only) · Mon May 4, 2026 · 5–7 PM · Dr. Travis Robbins · Part 2 (60 pts, cumulative + short-answer) follows in same slot
Lectures: L14 · L15 · L16 · L17 · L18 · L19 · L20

This guide is fully editable — type anywhere, format with the toolbar, and your edits autosave to this browser. Reset restores the seeded content. Print to PDF (8.5×11 portrait) anytime.

L14 · History of Life (Ch 3)

A 4-billion-year overview of life on Earth — when, where, and roughly how. The lecture covers Earth's age, key evolutionary milestones (origin of life, photosynthesis, eukaryotes, multicellularity, Cambrian explosion), mass extinctions, and the dating tools that anchor it all.

§A — Earth's age and dating methods

Earth is ~4.568 billion years old. We know this from radiometric dating of Earth and meteorite materials. Dating life's milestones depends on the same physics — measuring ratios of radioactive isotopes in rocks.

Key points

Key terms

Exam traps
  • Carbon-14 dating only works back ~50,000 years (5 half-lives × 5,730 yr ≈ 28k yr; method becomes unreliable beyond that). For deep time, you need U-Pb, K-Ar, etc.
  • Earth's age is ~4.568 billion years — close to but distinct from the universe's age (~13.8 Gyr) or the age of the oldest rocks on Earth (~4.0 Gyr).

§B — Major milestones in life's history

Life appeared early. Then for a couple billion years, very little visible happened. Then in a few hundred million years, all the modern phyla appeared. The timeline matters.

Key points

Key terms

Exam traps
  • The Cambrian explosion is RAPID on a geological timescale (~20 MYR) but is NOT instant. It also represents the appearance of HARD PARTS that fossilize, not necessarily the origin of phyla.
  • Don't confuse the Permian-Triassic extinction (~252 MYA, biggest) with the K-T / K-Pg extinction (~66 MYA, dinosaurs).

§C — Geological time periods to recognize

The study guide explicitly lists Ordovician, Silurian, Devonian, Permian. Be ready to associate each period with its key biological event.

Key points

Key terms

Exam traps
  • The study guide explicitly tests these period-event associations. Memorize the canonical pairings.
  • The Devonian = 'Age of Fishes'; the Permian-Triassic boundary = the largest mass extinction.

§D — Mass extinctions

Five 'Big Five' mass extinctions punctuate the Phanerozoic. Each reset evolutionary trajectories — opening new niches for survivors. Mass extinction is not unidirectional; it shapes which lineages get to radiate next.

Key points

Key terms

Exam traps
  • The study guide directly asks about the K-T boundary's significance — be ready: asteroid impact, end-Cretaceous, non-avian dinosaur extinction, mammal radiation thereafter.
  • K-T (K-Pg) is the dinosaur extinction; the Permian-Triassic is the bigger one. Don't confuse them.

L15 · Phylogenetics and the Tree of Life (Ch 4)

Phylogenetic trees are hypotheses about evolutionary relationships built from shared derived characters. The skill on this exam is reading trees correctly and choosing the right kind of character (synapomorphy, not symplesiomorphy or homoplasy) to define groups.

§A — Reading phylogenetic trees

A phylogenetic tree depicts hypothesized evolutionary relationships among taxa. Tips are extant or extinct organisms; internal nodes are common ancestors; branches represent lineages over time.

Key points

Key terms

Exam traps
  • Visual proximity on the page does NOT equal evolutionary closeness — trees can be drawn with branches in any rotation.
  • Don't assume tips on adjacent branches are sister groups; trace back to the node and see what it joins.

§B — Synapomorphies vs. symplesiomorphies vs. homoplasies

Only SYNAPOMORPHIES — shared derived characters — define monophyletic groups. Symplesiomorphies (shared ancestral traits) and homoplasies (independent acquisitions) mislead.

Key points

Key terms

Exam traps
  • 'Has a backbone' is a synapomorphy of vertebrates as a whole, but a SYMPLESIOMORPHY for any subgroup (e.g., mammals + birds) — i.e., the same character can play different roles depending on the level you're working at.
  • If two distantly related lineages share a feature, suspect homoplasy — check whether the common ancestor likely had it.

§C — Monophyletic, paraphyletic, polyphyletic

Modern systematics requires named groups to be monophyletic. Para- and polyphyletic groups are rejected because they don't reflect evolutionary history.

Key points

Key terms

Exam traps
  • Birds nest within reptiles — calling 'Reptilia' (without birds) a valid group makes it paraphyletic. Modern usage either includes birds or uses 'Sauropsida'.
  • Polyphyletic groups are diagnosed by SHARED CONVERGENT FEATURES, not common ancestry.

§D — Species concepts

Different species concepts emphasize different criteria. The Biological Species Concept (BSC) is the most famous, but it has limits.

Key points

Key terms

Exam traps
  • The BSC requires reproductive isolation — but two morphologically distinct populations that occasionally hybridize can still be 'good' biological species if hybrids have reduced fitness.
  • Asexual organisms (bacteria, parthenogenetic animals) are not species under the BSC. Use morphology or phylogeny instead.

L16 · Species Concepts and Reproductive Isolation (Ch 13)

What IS a species, exactly? Different concepts emphasize different criteria, each with strengths and limits. This lecture covers species concepts and the reproductive isolation mechanisms — pre- and postzygotic — that keep species apart.

§A — Major species concepts

There is no single universal species definition. The Biological Species Concept dominates animal biology; alternatives handle cases where the BSC fails.

Key points

Key terms

Exam traps
  • The BSC fails for asexual organisms (bacteria, parthenogenetic species) and for fossils — you can't test interbreeding with extinct populations.
  • Different concepts can disagree about what counts as a species in the same case (e.g., a hybridizing species pair). The 'right' concept depends on what you need it for.

§B — Reproductive isolation — prezygotic vs postzygotic

The BSC requires reproductive isolation. Mechanisms split into PREZYGOTIC (preventing zygote formation) and POSTZYGOTIC (acting after zygote formation).

Key points

Key terms

Exam traps
  • Pre-zygotic = before zygote (no successful fertilization). Post-zygotic = after zygote (hybrid problems). The dividing line is fertilization.
  • Hybrid INVIABILITY ≠ hybrid STERILITY. Inviability: hybrids die. Sterility: hybrids live but can't reproduce.

§C — Speciation — how new species arise

Speciation is the evolutionary process by which one species splits into two or more reproductively isolated descendants. Geography is often (but not always) involved.

Key points

Key terms

Exam traps
  • Allopatric is the DEFAULT speciation mode for most animals. Sympatric is rare in animals but more common in plants (polyploidy is sympatric).
  • Reinforcement is a SECONDARY-CONTACT phenomenon — it only operates AFTER initial divergence in allopatry, when populations meet again.

§D — Hybrid zones and viable hybrids

Real populations don't always fit clean species boundaries. Hybrid zones — where two species meet and interbreed — reveal incomplete reproductive isolation.

Key points

Key terms

Exam traps
  • Hybrid existence does NOT disprove species status under the BSC — hybrids with reduced fitness still leave the parental species reproductively isolated in the long run.
  • Hybrid speciation (e.g., in sunflowers) is a real but special case where hybrids form a stable third lineage.

L17 · Biogeography, Speciation, and Extinction (Ch 14)

Why are species where they are? This lecture covers the geographic distribution of life — how dispersal, vicariance, and continental drift shape biodiversity, plus what determines diversity in a given place over time.

§A — What is biogeography?

Biogeography is the study of WHERE species live and WHY. Past geological history (continental drift, glaciation) and ecological factors (climate, dispersal) jointly shape modern distributions.

Key points

Key terms

Exam traps
  • Biogeography unites geology, ecology, and evolution — questions can pull from any of these.
  • Patterns of distribution are clues to history; similar species in distant places may share ancestry (vicariance) or have dispersed.

§B — Dispersal vs. vicariance

Two opposing explanations for why related species are found in different places: organisms moved (dispersal) or the geography moved underneath them (vicariance).

Key points

Key terms

Exam traps
  • Distinguishing dispersal from vicariance requires DATING — vicariant splits should match the geological barrier formation; dispersal splits can be any age.
  • Don't assume vicariance for every pair of similar species on different continents — some are dispersal events (especially birds, oceanic taxa).

§C — Standing diversity and species turnover

STANDING DIVERSITY is how many species are present in a region at one time. TURNOVER is the rate at which species enter (originate, immigrate) and leave (go extinct, emigrate).

Key points

Key terms

Exam traps
  • Standing diversity is a SNAPSHOT; turnover is a RATE. Don't conflate them.
  • Two communities with the same standing diversity can have very different turnover rates.

§D — Mass extinctions and their consequences

Mass extinctions reset the trajectory of life by clearing dominant lineages and opening niches for survivors to radiate into. The Big Five share certain features but had different causes.

Key points

Key terms

Exam traps
  • End-Permian, NOT end-Cretaceous, is the largest mass extinction.
  • The K-Pg extinction is closely linked to the Chicxulub impact, but additional factors (Deccan Trap volcanism) likely contributed.

§E — Adaptive radiations

When a lineage colonizes a new environment or survives a mass extinction, it can rapidly diversify into many species exploiting different niches. Galápagos finches and Hawaiian honeycreepers are classic examples.

Key points

Key terms

Exam traps
  • Adaptive radiation is fast diversification INTO DIFFERENT NICHES — not just speciation in general.
  • Mammalian radiation didn't START at the K-Pg; mammals existed earlier but were small and ecologically restricted. The K-Pg removed dinosaur competitors and let mammals diversify.

L18 · Conservation and Humans as a Selective Force (Ch 8)

Humans are now a dominant evolutionary force — through habitat alteration, hunting, fishing, antibiotic use, and climate change, we are reshaping selection pressures on most species on Earth. This lecture is about that, plus what conservation biology can do.

§A — Humans as selective force

Human activities apply strong, directional selection on countless species. Wherever we hunt, harvest, or otherwise selectively kill or capture organisms, we drive evolutionary change.

Key points

Key terms

Exam traps
  • Human selection is the same evolutionary process as natural selection; we just play the selecting role.
  • Selective harvest can REVERSE pre-existing selection (e.g., natural selection for large body, fishing for small) — populations evolve away from natural-equilibrium phenotypes.

§B — Habitat destruction, fragmentation, and conservation genetics

Habitat loss and fragmentation reduce population sizes and gene flow — making drift stronger, inbreeding more likely, and adaptive evolution slower.

Key points

Key terms

Exam traps
  • Small populations are vulnerable to BOTH demographic and genetic problems. Conservation must address both.
  • Genetic rescue can fail if the introduced population is so distantly related that hybrids are maladapted (outbreeding depression).

§C — Climate change as selective pressure

Anthropogenic climate change is a major and ongoing selective force. Some species track suitable climate by shifting range; others must evolve in place; many will fail to do either fast enough.

Key points

Key terms

Exam traps
  • Species can RESPOND to climate change by shifting range, evolving, going plastic, or going extinct. The mix depends on dispersal ability, generation time, and severity.
  • Phenological shifts driven by climate are evidence of climate change effects on biology, even when range hasn't shifted yet.

§D — Conservation strategies

Conservation biology applies evolutionary thinking to preserving biodiversity. The toolkit ranges from habitat protection to captive breeding to genetic rescue.

Key points

Key terms

Exam traps
  • Conservation is ALWAYS about both populations and the genes within them. A demographically large population with no genetic diversity is still vulnerable.
  • Captive breeding can introduce its own selection pressures (adaptation to captivity) that may reduce wild fitness on release.

L19 · Human Evolution (Ch 17)

Where we came from. The hominin lineage diverged from chimpanzees ~6–7 million years ago in Africa; bipedalism came first, then expanding brain, then long migration out of Africa. This lecture also touches evolutionary medicine — how our evolutionary history shapes modern disease.

§A — The hominin lineage and bipedalism

Humans share a common ancestor with chimpanzees ~6–7 MYA. The first major hominin innovation was BIPEDAL LOCOMOTION, which preceded brain enlargement.

Key points

Key terms

Exam traps
  • The study guide explicitly tests bipedalism: it began ~6–7 MYA. Brain enlargement is later.
  • Sahelanthropus from Chad is the early hominin example the study guide flags. Recognize the Chad connection.

§B — Australopithecines and early Homo

After the earliest hominins, several species of Australopithecus dominated for several million years. Genus Homo appears ~2.5 MYA.

Key points

Key terms

Exam traps
  • Homo erectus is the FIRST hominin to leave Africa, not Homo sapiens.
  • Brain size increased gradually through the hominin lineage; H. erectus had ~1000 cc, H. sapiens ~1350 cc.

§C — Out-of-Africa migrations and Neanderthal/Denisovan introgression

Modern humans (Homo sapiens) originated in Africa, then dispersed worldwide ~70 KYA, encountering and partly interbreeding with archaic populations like Neanderthals and Denisovans.

Key points

Key terms

Exam traps
  • All non-African modern humans carry small amounts of Neanderthal DNA; African populations have very little or none (because the introgression happened after the migration out of Africa).
  • The 'Out of Africa' model is now better called 'Recent African Origin' with admixture — it's not a simple replacement.

§D — Evolutionary medicine — disease in evolutionary context

Many modern health problems make more sense when viewed through an evolutionary lens. Pathogens evolve in response to our defenses; our bodies are evolutionary compromises shaped by ancestral environments different from modern ones.

Key points

Key terms

Exam traps
  • Pathogens evolve. Antibiotic resistance is one application; antigenic shift in flu is another.
  • Evolutionary medicine doesn't say 'we should live like cave-people' — it says understanding the evolutionary mismatch helps explain susceptibility to certain modern diseases.

L20 · Evolutionary Medicine (Ch 18)

Evolution doesn't stop at the species boundary — it explains why we get sick, why pathogens evolve, and why some 'designs' of the human body are flawed. This lecture frames disease, drug resistance, virulence evolution, and modern lifestyle illnesses through an evolutionary lens.

§A — What evolutionary medicine is

Evolutionary medicine applies evolutionary principles to human health. The central insight: many disease vulnerabilities make sense only as products of past selection in environments very different from the one we live in now.

Key points

Key terms

Exam traps
  • Evolutionary medicine doesn't claim modern medicine is wrong — it adds the WHY behind the HOW. Both are needed.
  • 'Why we get sick' is a question with at least six different evolutionary answer categories. Don't reduce all disease to one explanation.

§B — Pathogen evolution and antibiotic resistance

Pathogens evolve fast — large populations, short generation times, strong selection. The most consequential evolutionary process in clinical medicine is antibiotic resistance, but the same logic applies to antiviral resistance, vaccine escape, and host-immune evasion.

Key points

Key terms

Exam traps
  • Antibiotics do NOT cause resistance mutations — they SELECT for already-existing rare resistant variants. Random mutation + non-random selection.
  • Stopping a course early can leave partially-resistant survivors and worsen the resistance problem. Complete the prescribed course (current best evidence is more nuanced than the old 'always finish' rule, but the evolutionary logic still favors fully suppressing the infection).
  • Combination therapy works because the joint probability of multi-drug resistance is the product of single-drug resistance probabilities — for example, 10⁻⁹ × 10⁻⁹ = 10⁻¹⁸ per generation.

§C — Virulence evolution and transmission mode

How harmful (virulent) a pathogen is to its host is itself an evolutionary trait — and it depends on how the pathogen transmits. Mode of transmission shapes the cost-benefit balance for the pathogen.

Key points

Key terms

Exam traps
  • Virulence is NOT 'always' selected to decrease — that old folk-rule (pathogens evolve to be benign) was wrong. The direction depends on transmission mode.
  • Sexually-transmitted, respiratory, and vector-borne pathogens all face DIFFERENT virulence-transmission trade-offs. Predicting virulence evolution requires identifying the transmission route.

§D — Host-pathogen coevolution

Hosts and pathogens are locked in mutual evolutionary arms races. Each side's adaptations select for counter-adaptations in the other — driving rapid evolution on both sides.

Key points

Key terms

Exam traps
  • Sickle-cell anemia is a classic POPULATION-LEVEL example of pathogen-driven host evolution. The allele persists not despite the homozygous cost, but because of the heterozygous benefit.
  • MHC diversity is maintained by BALANCING SELECTION — distinct from directional or stabilizing selection. Rare alleles benefit from being unfamiliar to pathogens.

§E — Mismatch hypothesis — modern environments and chronic disease

Many chronic diseases of the modern industrialized world (obesity, type 2 diabetes, atherosclerosis, certain autoimmune disorders) reflect a mismatch between traits that were adaptive in ancestral environments and the conditions of modern life.

Key points

Key terms

Exam traps
  • Mismatch DOESN'T mean we should live like cave-people; it means understanding our evolutionary history helps explain why certain modern foods and habits are pathogenic.
  • Diseases of old age (most cancers, Alzheimer's) escape selection because they manifest after reproduction. They aren't 'designed' against — selection just can't see them.

§F — Cancer as somatic evolution

Cancer is evolution playing out within an individual body. Cells acquire mutations, some of which give a growth advantage; selection within the body favors those cells; tumors are the result. This view reshapes treatment strategies.

Key points

Key terms

Exam traps
  • Cancer is evolution writ small — the same processes (variation, selection, heritability) operating on cell lineages.
  • Aggressive cancer therapy can SELECT for resistance just like aggressive antibiotic use does. Treatment strategy is also an evolutionary problem.
'; const blob = new Blob([html], {type:'text/html'}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'Exam3_StudyGuide_BIOL4230.html'; document.body.appendChild(a); a.click(); a.remove(); setTimeout(() => URL.revokeObjectURL(url), 1000); } function applyBlockStyle(){ const el = document.getElementById('styleSelect'); if(!el) return; const tag = el.value; if(!tag) return; try{ document.execCommand('formatBlock', false, '<' + tag + '>'); doc.focus(); markDirty(); }catch(e){ console.warn('formatBlock failed', e); } el.selectedIndex = 0; } function getSelectedBlocks(){ const sel = window.getSelection(); if(!sel || sel.rangeCount === 0) return []; const range = sel.getRangeAt(0); const blocks = new Set(); function climb(node){ while(node && node !== doc){ if(node.nodeType === 1){ const d = getComputedStyle(node).display; if(d === 'block' || d === 'list-item' || /^(P|H[1-6]|LI|BLOCKQUOTE|PRE|FIGURE|TABLE|TR|TD|TH|DIV)$/.test(node.tagName)){ blocks.add(node); return; } } node = node.parentNode; } } climb(range.startContainer); climb(range.endContainer); const it = document.createTreeWalker(doc, NodeFilter.SHOW_ELEMENT, { acceptNode: n => range.intersectsNode(n) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP }); let n; while((n = it.nextNode())){ const d = getComputedStyle(n).display; if(d === 'block' || d === 'list-item') blocks.add(n); } return Array.from(blocks); } function changeCase(){ const choice = prompt('Change case to:\n 1) UPPERCASE\n 2) lowercase\n 3) Title Case\n 4) Sentence case\nEnter 1, 2, 3, or 4:'); const sel = window.getSelection(); if(!sel || sel.isCollapsed){ alert('Select some text first.'); return; } const text = sel.toString(); let out = text; if(choice === '1') out = text.toUpperCase(); else if(choice === '2') out = text.toLowerCase(); else if(choice === '3') out = text.toLowerCase().replace(/\b([a-z])/g, (m, c) => c.toUpperCase()); else if(choice === '4'){ out = text.toLowerCase().replace(/(^|[.!?]\s+)([a-z])/g, (m, p, c) => p + c.toUpperCase()); } else return; document.execCommand('insertText', false, out); markDirty(); } function applyLineSpacing(){ const el = document.getElementById('lineSpacing'); if(!el || !el.value) return; const v = el.value; getSelectedBlocks().forEach(b => { b.style.lineHeight = v; }); el.selectedIndex = 0; markDirty(); } function colorsEqual(a, b){ if(!a || !b) return false; const ma = ('' + a).match(/\d+/g); const mb = ('' + b).match(/\d+/g); if(!ma || !mb) return false; return ma.slice(0,3).join(',') === mb.slice(0,3).join(','); } function hexToRgb(hex){ const m = hex.replace('#',''); return 'rgb(' + parseInt(m.slice(0,2),16) + ', ' + parseInt(m.slice(2,4),16) + ', ' + parseInt(m.slice(4,6),16) + ')'; } function applyShade(){ const c = document.getElementById('paraShade').value; const target = hexToRgb(c); const blocks = getSelectedBlocks(); if(!blocks.length) return; const allHave = blocks.every(b => colorsEqual(b.style.backgroundColor, target)); blocks.forEach(b => { if(allHave){ b.style.backgroundColor = ''; if(!/pb-/.test(b.className)) b.style.padding = ''; } else { b.style.backgroundColor = c; if(!b.style.padding) b.style.padding = '4px 8px'; } }); markDirty(); } function applyBorder(){ const el = document.getElementById('borderSelect'); if(!el || !el.value) return; const which = el.value; const blocks = getSelectedBlocks(); el.selectedIndex = 0; if(!blocks.length) return; if(which === 'none'){ blocks.forEach(b => b.classList.remove('pb-all','pb-top','pb-bottom','pb-left','pb-right')); } else { const cls = 'pb-' + which; const allHave = blocks.every(b => b.classList.contains(cls)); blocks.forEach(b => { if(allHave){ b.classList.remove(cls); } else { b.classList.remove('pb-all','pb-top','pb-bottom','pb-left','pb-right'); b.classList.add(cls); } }); } markDirty(); } function applyListStyle(){ const el = document.getElementById('listStyleSelect'); if(!el || !el.value) return; const [tag, style] = el.value.split(':'); const cmdName = tag === 'ol' ? 'insertOrderedList' : 'insertUnorderedList'; document.execCommand(cmdName, false, null); const sel = window.getSelection(); if(sel && sel.rangeCount){ let n = sel.getRangeAt(0).startContainer; while(n && n !== doc){ if(n.nodeType === 1 && /^(UL|OL)$/.test(n.tagName)){ n.style.listStyleType = style; break; } n = n.parentNode; } } el.selectedIndex = 0; markDirty(); } /* FIND / REPLACE */ let findHits = []; let findIndex = -1; function clearFindHits(){ doc.querySelectorAll('.find-hit, .find-hit-current').forEach(el => { const parent = el.parentNode; while(el.firstChild) parent.insertBefore(el.firstChild, el); parent.removeChild(el); parent.normalize(); }); findHits = []; findIndex = -1; const cnt = document.getElementById('findCount'); if(cnt) cnt.textContent = ''; } function highlightAll(query){ clearFindHits(); if(!query) return; const re = new RegExp(query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi'); const walker = document.createTreeWalker(doc, NodeFilter.SHOW_TEXT, { acceptNode: n => n.parentElement && n.parentElement.closest('.find-hit') ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT }); const targets = []; let n; while((n = walker.nextNode())){ if(re.test(n.nodeValue)){ re.lastIndex = 0; targets.push(n); } } targets.forEach(node => { const text = node.nodeValue; const frag = document.createDocumentFragment(); let last = 0; let m; const re2 = new RegExp(query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi'); while((m = re2.exec(text))){ if(m.index > last) frag.appendChild(document.createTextNode(text.slice(last, m.index))); const span = document.createElement('span'); span.className = 'find-hit'; span.textContent = m[0]; frag.appendChild(span); findHits.push(span); last = m.index + m[0].length; if(m[0].length === 0) re2.lastIndex++; } if(last < text.length) frag.appendChild(document.createTextNode(text.slice(last))); node.parentNode.replaceChild(frag, node); }); const cnt = document.getElementById('findCount'); if(cnt) cnt.textContent = findHits.length ? (findHits.length + ' match' + (findHits.length === 1 ? '' : 'es')) : 'No matches'; if(findHits.length){ findIndex = 0; markCurrent(); } } function markCurrent(){ findHits.forEach((h, i) => h.className = (i === findIndex ? 'find-hit-current' : 'find-hit')); if(findHits[findIndex]) findHits[findIndex].scrollIntoView({block:'center', behavior:'smooth'}); const cnt = document.getElementById('findCount'); if(cnt && findHits.length) cnt.textContent = (findIndex+1) + ' / ' + findHits.length; } function findNext(){ if(!findHits.length) return; findIndex = (findIndex+1) % findHits.length; markCurrent(); } function findPrev(){ if(!findHits.length) return; findIndex = (findIndex-1+findHits.length) % findHits.length; markCurrent(); } function replaceOne(){ const r = document.getElementById('replaceInput').value; if(findIndex < 0 || !findHits[findIndex]) return; const hit = findHits[findIndex]; const txt = document.createTextNode(r); hit.parentNode.replaceChild(txt, hit); findHits.splice(findIndex, 1); if(findIndex >= findHits.length) findIndex = 0; markCurrent(); markDirty(); } function replaceAll(){ const r = document.getElementById('replaceInput').value; if(!findHits.length) return; findHits.forEach(h => { h.parentNode.replaceChild(document.createTextNode(r), h); }); const n = findHits.length; findHits = []; findIndex = -1; const cnt = document.getElementById('findCount'); if(cnt) cnt.textContent = 'Replaced ' + n; markDirty(); } function openFind(){ document.getElementById('findbar').style.display = 'flex'; const i = document.getElementById('findInput'); i.focus(); i.select(); } function openReplace(){ openFind(); document.getElementById('replaceInput').focus(); } function closeFind(){ clearFindHits(); document.getElementById('findbar').style.display = 'none'; doc.focus(); } /* JUMP-TO TOC */ function buildJumpTo(){ const sel = document.getElementById('jumpTo'); if(!sel) return; sel.innerHTML = ''; const heads = doc.querySelectorAll('h1, h2, h3'); let i = 0; heads.forEach(h => { if(!h.id) h.id = 'sec-' + (i++); const indent = h.tagName === 'H1' ? '' : (h.tagName === 'H2' ? '— ' : ' · '); const o = document.createElement('option'); o.value = h.id; o.textContent = indent + (h.textContent || '').trim().slice(0, 60); sel.appendChild(o); }); } buildJumpTo(); let tocTimer = null; doc.addEventListener('input', () => { clearTimeout(tocTimer); tocTimer = setTimeout(buildJumpTo, 1500); }); /* TOOLBAR WIRING */ document.querySelectorAll('.tb button, .findbar button').forEach(btn => { btn.addEventListener('mousedown', e => e.preventDefault()); }); let savedRange = null; function saveSelection(){ try{ const sel = window.getSelection(); if(sel && sel.rangeCount){ const r = sel.getRangeAt(0); if(doc.contains(r.commonAncestorContainer)) savedRange = r.cloneRange(); } }catch(e){} } function restoreSelection(){ if(!savedRange) return; try{ const sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(savedRange); }catch(e){} } doc.addEventListener('mouseup', saveSelection); doc.addEventListener('keyup', saveSelection); document.querySelectorAll('.tb select, .tb input[type=color]').forEach(el => { el.addEventListener('mousedown', saveSelection); el.addEventListener('focus', saveSelection); }); function wrapWithRestore(handler){ return function(e){ restoreSelection(); handler(e); }; } document.getElementById('styleSelect').addEventListener('change', wrapWithRestore(applyBlockStyle)); document.getElementById('lineSpacing').addEventListener('change', wrapWithRestore(applyLineSpacing)); document.getElementById('borderSelect').addEventListener('change', wrapWithRestore(applyBorder)); document.getElementById('listStyleSelect').addEventListener('change', wrapWithRestore(applyListStyle)); document.getElementById('jumpTo').addEventListener('change', e => { const id = e.target.value; if(!id) return; const target = document.getElementById(id); if(target){ target.scrollIntoView({behavior:'smooth', block:'start'}); } e.target.selectedIndex = 0; }); document.getElementById('findInput').addEventListener('input', e => highlightAll(e.target.value)); document.getElementById('findInput').addEventListener('keydown', e => { if(e.key === 'Enter'){ e.preventDefault(); e.shiftKey ? findPrev() : findNext(); } if(e.key === 'Escape'){ closeFind(); } }); document.getElementById('replaceInput').addEventListener('keydown', e => { if(e.key === 'Escape') closeFind(); }); /* HIGHLIGHT toggle */ function selectionHasHighlight(){ try{ const v = document.queryCommandValue('hiliteColor') || document.queryCommandValue('backColor'); if(!v) return false; const trimmed = ('' + v).trim(); if(!trimmed || trimmed === 'transparent' || /rgba?\(\s*0\s*,\s*0\s*,\s*0\s*,\s*0\s*\)/.test(trimmed)) return false; const m = trimmed.match(/rgba?\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)/); if(m && +m[1] >= 250 && +m[2] >= 250 && +m[3] >= 250) return false; return true; }catch(e){ return false; } } function unhighlight(){ try{ document.execCommand('hiliteColor', false, 'transparent'); const sel = window.getSelection(); if(sel && sel.rangeCount){ const range = sel.getRangeAt(0); const it = document.createTreeWalker(doc, NodeFilter.SHOW_ELEMENT, { acceptNode: n => range.intersectsNode(n) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP }); let n; while((n = it.nextNode())){ if(n.style && n.style.backgroundColor) n.style.backgroundColor = ''; } } doc.focus(); markDirty(); }catch(e){ console.warn('unhighlight failed', e); } } function toggleHighlight(){ if(selectionHasHighlight()){ unhighlight(); } else { const c = document.getElementById('bgColor').value; cmd('hiliteColor', c); } } /* CONFIGURABLE SHORTCUTS */ const ACTIONS = [ {id:'save', label:'Save now', def:'ctrl+s', run: () => saveNow()}, {id:'find', label:'Find', def:'ctrl+f', run: () => openFind()}, {id:'replace', label:'Find & Replace', def:'ctrl+h', run: () => openReplace()}, {id:'highlight', label:'Toggle highlight on selection', def:'ctrl+shift+h', run: () => toggleHighlight()}, {id:'unhighlight', label:'Remove highlight', def:'ctrl+shift+u', run: () => unhighlight()}, {id:'bold', label:'Bold', def:'ctrl+b', run: () => cmd('bold')}, {id:'italic', label:'Italic', def:'ctrl+i', run: () => cmd('italic')}, {id:'underline', label:'Underline', def:'ctrl+u', run: () => cmd('underline')}, {id:'strike', label:'Strikethrough', def:'', run: () => cmd('strikeThrough')}, {id:'super', label:'Superscript', def:'ctrl+shift+=', run: () => cmd('superscript')}, {id:'sub', label:'Subscript', def:'ctrl+=', run: () => cmd('subscript')}, {id:'selectAll', label:'Select all (in document)', def:'ctrl+a', run: () => { const range = document.createRange(); range.selectNodeContents(doc); const sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(range); }}, {id:'indent', label:'Increase indent', def:'tab', run: () => { document.execCommand('indent'); markDirty(); }}, {id:'outdent', label:'Decrease indent', def:'shift+tab', run: () => { document.execCommand('outdent'); markDirty(); }}, {id:'undo', label:'Undo', def:'ctrl+z', run: () => cmd('undo')}, {id:'redo', label:'Redo', def:'ctrl+y', run: () => cmd('redo')} ]; let bindings = {}; function loadShortcuts(){ try{ const saved = JSON.parse(localStorage.getItem(SHORTCUT_KEY) || '{}'); bindings = {}; ACTIONS.forEach(a => { bindings[a.id] = saved[a.id] != null ? saved[a.id] : a.def; }); }catch(e){ bindings = Object.fromEntries(ACTIONS.map(a => [a.id, a.def])); } } function saveShortcuts(){ try{ localStorage.setItem(SHORTCUT_KEY, JSON.stringify(bindings)); }catch(e){ console.warn('save shortcuts failed', e); } } function resetShortcuts(){ if(!confirm('Reset all shortcuts to defaults?')) return; bindings = {}; ACTIONS.forEach(a => { bindings[a.id] = a.def; }); saveShortcuts(); renderShortcuts(); } function comboFromEvent(e){ if(e.key === 'Tab'){ return (e.shiftKey ? 'shift+' : '') + 'tab'; } if(e.key === 'Escape' || e.key === 'Control' || e.key === 'Shift' || e.key === 'Alt' || e.key === 'Meta') return null; const parts = []; if(e.ctrlKey || e.metaKey) parts.push('ctrl'); if(e.altKey) parts.push('alt'); if(e.shiftKey) parts.push('shift'); parts.push(e.key.toLowerCase()); return parts.join('+'); } loadShortcuts(); function openShortcuts(){ document.getElementById('shortcutsModal').classList.add('show'); renderShortcuts(); } function closeShortcuts(){ document.getElementById('shortcutsModal').classList.remove('show'); } function renderShortcuts(){ const tbl = document.getElementById('shortcutsTable'); tbl.innerHTML = 'ActionShortcut'; ACTIONS.forEach(a => { const tr = document.createElement('tr'); tr.innerHTML = ''+a.label+'' + '' + ''; tbl.appendChild(tr); }); tbl.querySelectorAll('.kb-btn').forEach(btn => { btn.onclick = () => { btn.textContent = '… press a key combo …'; btn.style.background = '#fff4e2'; const handler = (ev) => { ev.preventDefault(); const combo = comboFromEvent(ev); if(combo == null) return; const conflict = Object.entries(bindings).find(([k,v]) => v === combo && k !== btn.dataset.act); if(conflict){ const ok = confirm('That combo is already bound to "'+ACTIONS.find(a=>a.id===conflict[0]).label+'". Reassign anyway?'); if(!ok){ btn.textContent = bindings[btn.dataset.act] || '— click to set —'; btn.style.background='#fff'; window.removeEventListener('keydown', handler, true); return; } bindings[conflict[0]] = ''; } bindings[btn.dataset.act] = combo; saveShortcuts(); window.removeEventListener('keydown', handler, true); renderShortcuts(); }; window.addEventListener('keydown', handler, true); }; }); tbl.querySelectorAll('button[data-clear]').forEach(btn => { btn.onclick = () => { bindings[btn.dataset.clear] = ''; saveShortcuts(); renderShortcuts(); }; }); } document.addEventListener('keydown', e => { if(document.getElementById('shortcutsModal').classList.contains('show')) return; const combo = comboFromEvent(e); if(!combo) return; for(const a of ACTIONS){ if(bindings[a.id] && bindings[a.id] === combo){ if((a.id === 'indent' || a.id === 'outdent') && !doc.contains(document.activeElement)) return; if(a.id === 'selectAll' && !doc.contains(document.activeElement)) return; e.preventDefault(); try{ a.run(); }catch(err){ console.warn('shortcut '+a.id+' failed:', err); } return; } } }); /* PUBLIC */ window.cmd = cmd; window.changeCase = changeCase; window.applyShade = applyShade; window.unhighlight = unhighlight; window.toggleHighlight = toggleHighlight; window.openShortcuts = openShortcuts; window.closeShortcuts = closeShortcuts; window.resetShortcuts = resetShortcuts; window.insertHorizontalRule = insertHorizontalRule; window.insertPageBreak = insertPageBreak; window.insertImageFromFile = insertImageFromFile; window.openFind = openFind; window.openReplace = openReplace; window.closeFind = closeFind; window.findNext = findNext; window.findPrev = findPrev; window.replaceOne = replaceOne; window.replaceAll = replaceAll; window.resetDoc = resetDoc; window.downloadHTML = downloadHTML; })();