← Guides EVOLution
Loaded

Exam 2 · Adaptations / Sex / Social Study Guide

BIOL 4230 · Evolution · Exam 2 · Adaptations / Sex / Social — Final exam Mon May 4, 2026 · 5–7 PM · Dr. Travis Robbins
Lectures: L07 · L08 · L09 · L11 · L12 · L13

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.

L07 · Empirical Studies of Natural Selection (Ch 8)

How biologists actually MEASURE natural selection in the wild — landmark long-term studies that move 'selection' from theory to observation. Grants' finches, peppered moths, and similar systems are the empirical canon.

§A — How selection is measured in nature

To demonstrate natural selection in the field, you need to (1) measure heritable variation in a trait, (2) measure differential survival or reproduction associated with that variation, and (3) ideally see allele-frequency change across generations.

Key points

Key terms

Exam traps
  • Selection in the field can produce TINY per-generation changes that nonetheless add up over decades. Don't dismiss small year-on-year shifts.
  • Without heritability data, observed differential reproduction is selection but doesn't predict evolution.

§B — Grants' Galápagos finches

Peter and Rosemary Grant's decades-long study of medium ground finches (Geospiza fortis) on Daphne Major is the gold standard of natural selection in action.

Key points

Key terms

Exam traps
  • The classic finch result is one-direction selection (deeper beaks) followed by reversal in different conditions. Selection can flip direction.
  • The Grants observed evolution over a few generations — not 'gradualism takes millions of years' as caricatured. Natural selection can act rapidly given strong pressure.

§C — Peppered moths and industrial melanism

Peppered moths (Biston betularia) shifted from light to dark forms during the Industrial Revolution and back again with pollution control. The textbook case of rapid microevolution under human-caused selection.

Key points

Key terms

Exam traps
  • The peppered moth case is sometimes attacked as bad evidence; modern replication of Kettlewell's experiments confirms differential predation does drive the morph frequencies.
  • The selective agent is bird predation on resting moths — not direct toxicity of pollutants.

§D — Other documented examples — flu, antibiotics, domestication

Beyond classic natural systems, human activities provide many real-time tests of selection: pathogens evolving resistance, domesticated species evolving under artificial selection.

Key points

Key terms

Exam traps
  • Antibiotic resistance is NOT caused by the antibiotic mutating bacteria. The mutations occur randomly; the antibiotic selects existing variants.
  • Artificial selection follows the same logic as natural selection — the only difference is who/what is doing the selecting.

L08 · Complex Adaptations (Ch 10)

How complex traits — eyes, wings, body plans — evolve through stepwise mutations, regulatory changes, and gene-network conservation. This lecture is the EVO-DEVO chapter, integrating molecular biology with classical evolutionary thinking.

§A — Adaptation as both trait and process

The word 'adaptation' has two meanings: a trait that has been shaped by past selection, AND the process by which selection produces such traits. Complex adaptations build incrementally — they do not appear in their finished form.

Key points

Key terms

Exam traps
  • The classic 'half an eye is useless' objection ignores that early eyes are not failed full eyes — they are simpler light-detecting structures with their own value.
  • Selection works on what's available. A trait can be 'good enough' rather than perfect — historical contingency leaves limits.

§B — Evolution of the vertebrate eye — gradual stepwise model

The vertebrate eye is the textbook example of complex adaptation evolving through functional intermediates. Each step (light-sensitive patch → cup → pinhole → lensed eye) is itself useful.

Key points

Key terms

Exam traps
  • The vertebrate camera eye and the cephalopod camera eye look superficially similar but evolved independently — convergent, not homologous.
  • Each eye-evolution stage is documented in a living organism today (flatworm patches, Nautilus pinhole, etc.). The intermediates are not hypothetical.

§C — Regulatory networks and gene duplication

A lot of evolutionary novelty comes from RE-WIRING existing genes rather than inventing new ones. Mutations in regulatory regions, gene duplication followed by sub- or neofunctionalization, and changes in expression timing/location all reshape phenotype without changing the underlying protein-coding repertoire.

Key points

Key terms

Exam traps
  • Cis-regulatory evolution is a major route to morphological novelty — Hox-gene expression changes reshape body plans without changing the Hox proteins themselves.
  • Don't confuse subfunctionalization with neofunctionalization. Sub: each copy does part of the original. Neo: one copy gains a new function.

§D — Heterochrony — changes in developmental timing

Many morphological differences across species reflect changes in WHEN developmental processes occur, not WHAT they do. Speeding up, slowing down, or shifting the onset of a process produces dramatically different adult forms.

Key points

Key terms

Exam traps
  • Heterochrony explains a lot of variation among closely related species without invoking many new genes.
  • Don't conflate heterochrony with heterotopy (changes in WHERE in the body something develops). Different concept, often paired.

§E — Hox genes and conserved developmental networks

Hox genes encode transcription factors that pattern the anterior-posterior axis of bilaterian animals. They are deeply conserved across phyla — the same Hox toolkit patterns flies, mice, and humans.

Key points

Key terms

Exam traps
  • Hox-gene CONSERVATION across phyla is the headline. Differences in body plan come largely from differences in REGULATION, not from new Hox proteins.
  • Don't say Hox genes 'cause' segments. They specify segment IDENTITY — what each segment becomes — given an underlying segmentation process.

§F — Imperfect adaptation — limits and constraints

Selection produces traits that are 'good enough,' not optimal. Historical legacy, developmental constraints, and trade-offs leave fingerprints of imperfection — and those imperfections are often the strongest evidence of evolution.

Key points

Key terms

Exam traps
  • Imperfections are powerful evidence of evolution. A designed system would not have a blind spot; an evolved one inherits its 'mistakes.'
  • 'Good enough' is the right framing — selection lifts fitness, but is not an optimization algorithm with foresight.

L09 · Coevolution (Ch 15)

Coevolution is reciprocal evolutionary change in two interacting species — each lineage's adaptations are evolutionary responses to the other. It produces arms races, mutualisms, and the geographic mosaic of selection.

§A — Defining reciprocal coevolution

Coevolution is not just any pair of species coexisting — it requires that adaptations in one lineage drive adaptive responses in the other, in a feedback loop.

Key points

Key terms

Exam traps
  • Mere ecological association ≠ coevolution. The textbook bar is: changes in species A drive changes in species B, which drive further changes in A.
  • Convergent evolution (similar solutions in unrelated lineages) is NOT coevolution — there's no reciprocal feedback.

§B — Antagonistic arms races

When predator-prey or host-parasite interactions persist, each side evolves countermeasures, which selects for new adaptations in the other side, in an open-ended escalation.

Key points

Key terms

Exam traps
  • Arms races don't always escalate forever — fitness costs (e.g., snake speed cost of TTX resistance) can cap the escalation.
  • Variation across geography is the key signature of a true arms race; uniform 'one-size-fits-all' adaptations argue against ongoing coevolution.

§C — Mutualistic coevolution

Mutualisms are coevolutionary too: each partner's traits are adapted to the other, and selection on one drives change in the other in a cooperative direction.

Key points

Key terms

Exam traps
  • Mutualists often cheat — selection favors taking benefits without paying costs. Stable mutualisms typically have enforcement mechanisms.
  • Mutualism is not 'cooperation for the good of the species' — each partner is selected for individual fitness; mutual benefit is a coincidence of interests.

§D — Mimicry — Batesian vs. Müllerian

Mimicry is a coevolutionary product where one species' phenotype evolves to resemble another. The two flavors differ in who pays the cost.

Key points

Key terms

Exam traps
  • Batesian mimicry breaks down at high mimic-to-model ratios — predators encounter the harmless mimic too often and stop avoiding the warning signal.
  • Müllerian mimicry is technically convergent evolution AND coevolution — both species evolve toward each other's signal.

§E — Geographic Mosaic Theory of Coevolution

Thompson's framework: coevolutionary outcomes vary across geography because local ecological conditions tilt the cost/benefit balance differently in different places.

Key points

Key terms

Exam traps
  • Mosaic theory predicts trait MISMATCH in some areas — this is evidence FOR ongoing coevolution, not against it.
  • Don't confuse the geographic mosaic with simple local adaptation; mosaic theory specifically requires reciprocal feedback between species.

L11 · Sex and Sexual Selection (Ch 11)

Why does sex exist at all, given its costs? Once it exists, why do males and females differ so dramatically? This lecture covers the evolution of sexual reproduction, the origin of anisogamy, and the consequences — sexual selection, sexual conflict, sperm competition.

§A — The cost of sex and why sex evolved anyway

Sexual reproduction has obvious costs (the 'twofold cost of males,' searching for mates, recombination breaking up good gene combinations) — yet sex is widespread. The benefits must outweigh these costs.

Key points

Key terms

Exam traps
  • The 'twofold cost' is sometimes phrased as the cost of MALES, sometimes the cost of SEX. The cost is real either way — sexual mothers transmit half the gene copies per offspring vs. asexual.
  • Muller's ratchet specifically requires that recombination be ABSENT. Bacteria recombine via horizontal gene transfer and can purge mutations by other routes.

§B — Anisogamy — the foundation of male and female

ANISOGAMY = unequal gamete sizes. Females (by definition) produce few large gametes (eggs); males produce many small gametes (sperm). This asymmetry is the foundation of nearly all sex differences.

Key points

Key terms

Exam traps
  • Sex is defined by GAMETE SIZE, not by secondary sex characteristics, behavior, or chromosomes. Don't define 'female' as 'the one that nurtures' — that's downstream of anisogamy.
  • Anisogamy is not 'why' sex exists; it's a feature of HOW sex is organized once it exists.

§C — Sexual selection — Darwin's second mechanism

Sexual selection is selection on traits that increase MATING success rather than survival. It explains otherwise-puzzling traits (peacock tails, antlers) that reduce survival.

Key points

Key terms

Exam traps
  • Sexual selection IS a form of natural selection — Darwin distinguished them but they share the same logic of differential reproduction.
  • Sexually selected traits (peacock tails) often REDUCE survival. Their fitness gain comes from mating, which more than offsets the survival cost.

§D — Sexual conflict and sperm competition

Male and female evolutionary interests often diverge. Sexual CONFLICT — selection pressures pulling males and females in different directions — drives much of the elaborate biology of mating.

Key points

Key terms

Exam traps
  • Sexual conflict is INTRA-species coevolution — males and females are members of the same species and yet are evolutionary antagonists.
  • Sperm competition implies polyandry (multiple mating by females). In strictly monogamous species, sperm competition is weak.

L12 · Life History Evolution (Ch 12)

Life history is the schedule of an organism's birth, growth, reproduction, and death. Selection cannot maximize all these at once — trade-offs between traits force compromises, and the form those compromises take depends on the environment.

§A — Trade-offs in energy allocation

An organism has finite resources and time. Energy spent on growth cannot be spent on reproduction; reproduction now reduces resources available for survival and future reproduction.

Key points

Key terms

Exam traps
  • Trade-offs are physical/physiological — they are not optional. Selection works WITHIN trade-offs to optimize, not to escape them.
  • Don't expect organisms to maximize one fitness component (e.g., longevity). Selection maximizes lifetime reproductive success, integrated over all life-history traits.

§B — Extrinsic mortality and life-history strategies

Extrinsic mortality — the rate at which adults die from external causes (predation, disease) — is the single most important determinant of optimal life-history strategy.

Key points

Key terms

Exam traps
  • When the study guide asks about predation effects, the answer is usually: high predation → early/fast reproduction; low predation → delayed/slow reproduction.
  • Extrinsic mortality affects optimal age at maturity, even if intrinsic survival is high.

§C — Theories of senescence — why we age

Why doesn't selection prevent aging? Two main answers, both rooted in the fact that selection becomes weaker at older ages.

Key points

Key terms

Exam traps
  • Antagonistic pleiotropy is a single-gene story (one gene, two effects at different ages). Mutation accumulation is a many-gene story (many late-acting mutations escape selection).
  • 'Why we age' has multiple non-exclusive answers — be ready to name and distinguish at least two of the three theories.

§D — Age at maturity and offspring size

When to start reproducing, and how to package each offspring, are central life-history decisions. Both depend on environmental risk and resource availability.

Key points

Key terms

Exam traps
  • r/K is a useful heuristic but not a strict dichotomy. Many species are intermediate. Modern life-history theory uses continuous variables.
  • Earlier maturity does NOT always mean smaller offspring; the trade-off is about TOTAL reproductive investment, not necessarily individual offspring size.

§E — Case study — Seychelles warblers

The Seychelles warbler (Acrocephalus sechellensis) population on Cousin Island provides one of the best documented life-history datasets, including sex-biased dispersal driven by territory quality.

Key points

Key terms

Exam traps
  • Sex-biased dispersal in Seychelles warblers is FEMALE-PHILOPATRIC (females stay on good territories, males disperse). The direction of bias is testable.
  • Helping is not pure altruism — helpers gain inclusive fitness through helping kin and may inherit territories later.

L13 · Evolution of Social Behavior (Ch 16)

How can selection favor cooperative or self-sacrificing behavior when those behaviors apparently lower the actor's fitness? The answer — kin selection, inclusive fitness, evolutionarily stable strategies — is a foundational result in modern evolutionary biology.

§A — Individual vs. group selection

An old idea: traits evolve 'for the good of the species' or 'good of the group.' Modern evolutionary biology rejects naïve group selection — selection acts predominantly at the level of individuals (or genes), and selfish individuals usually outcompete cooperative ones.

Key points

Key terms

Exam traps
  • 'Good of the species' is a non-explanation. Always ask: what's in it for the individual (or the gene)?
  • Group selection isn't strictly impossible, but it's overwhelmed by individual selection in nearly all real-world cases.

§B — Kin selection and inclusive fitness

Hamilton's insight: an altruistic gene can spread if its bearers help GENETIC RELATIVES, who share copies of the gene. The propagation of the gene matters, not the survival of the individual.

Key points

Key terms

Exam traps
  • Hamilton's rule is rB > C, NOT B > C. Forgetting the r is the classic error.
  • Helping random strangers does not satisfy Hamilton's rule (r = 0). Apparent altruism toward strangers usually involves reciprocity, reputation, or other mechanisms.

§C — Evolutionarily stable strategies (ESS)

An ESS is a behavioral strategy that, once common in a population, cannot be invaded by a rare alternative. ESS reasoning predicts which behaviors persist under frequency-dependent selection.

Key points

Key terms

Exam traps
  • An ESS is not necessarily the BEST strategy on average — it's the strategy that can't be displaced once common. Mixed ESS solutions are common.
  • Frequency-dependent selection can MAINTAIN diversity at equilibrium — different strategies coexist because each is favored when rare.

§D — Side-blotched lizards — rock-paper-scissors in nature

Male side-blotched lizards (Uta stansburiana) come in three throat-color morphs that play a rock-paper-scissors mating game — a real-world demonstration of frequency-dependent ESS dynamics.

Key points

Key terms

Exam traps
  • The 'rock-paper-scissors' framing: Orange > Blue, Blue > Yellow, Yellow > Orange. Cyclic dominance, not one winner.
  • This is a real demonstration of NEGATIVE frequency-dependent selection — each morph is favored when rare, disadvantaged when common.

§E — Cooperation among non-kin — reciprocity, reputation, byproducts

Cooperation among unrelated individuals does occur. The mechanisms are different from kin-selected altruism — reciprocity, reputation, partner choice, or mutual benefit.

Key points

Key terms

Exam traps
  • Reciprocity requires REPEATED INTERACTION — it doesn't work in one-shot games or anonymous encounters.
  • Cooperation among non-kin is often unstable; cheating invades unless mechanisms exist to detect and punish defectors.
'; const blob = new Blob([html], {type:'text/html'}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'Exam2_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; })();