diff --git a/PhotoShotListPlanner/cobie1818/README.md b/PhotoShotListPlanner/cobie1818/README.md
new file mode 100644
index 000000000..0a14ea9fa
--- /dev/null
+++ b/PhotoShotListPlanner/cobie1818/README.md
@@ -0,0 +1,80 @@
+# Photo Shot List Planner
+
+A responsive photography planning app built with HTML, CSS, and vanilla
+JavaScript. Photographers can organize shot ideas before a trip or photo
+session and track which shots they have completed.
+
+## Features
+
+- Add shots with a title, category, and priority.
+- Reject empty and whitespace-only titles.
+- Mark shots as completed or pending.
+- Filter by all, pending, or completed shots.
+- Delete individual shots.
+- Display a completed-shot counter.
+- Save the list in localStorage between page visits.
+- Adapt the layout to desktop and mobile screens.
+
+## Technologies
+
+- HTML5 for page structure and form controls
+- CSS3 for styling and responsive layouts
+- Vanilla JavaScript for interactions and list management
+- Browser localStorage for persistence
+
+No framework, package installation, API key, or database is required.
+VS Code's Live Server extension is used for local development.
+
+## Run Locally
+
+1. Clone or download the repository.
+2. Open the repository folder in VS Code.
+3. Install the Live Server extension if it is not already installed.
+4. Navigate to PhotoShotListPlanner/cobie1818.
+5. Right-click index.html and select "Open with Live Server."
+
+## How to Use
+
+1. Enter a shot idea.
+2. Select its category and priority.
+3. Click "Add shot."
+4. Use a shot's checkbox to toggle its completion status.
+5. Use "Show shots" to filter the list.
+6. Click "Delete" to remove an unwanted shot.
+
+## Data Storage
+
+The list is saved in the current browser on the current device.
+It does not synchronize between browsers or devices. Clearing browser
+site data removes the saved list.
+
+The app displays a message if stored data cannot be loaded or changes
+cannot be saved. User-entered titles are displayed as text rather
+than interpreted as HTML.
+
+## Manual Testing
+
+The following checks were performed in Microsoft Edge:
+
+- Adding shots with different categories and priorities
+- Completing shots and returning them to pending
+- Updating the completion counter
+- Filtering completed and pending shots
+- Retaining the list and completion status after refresh
+- Rejecting empty and whitespace-only titles
+- Adding and deleting a temporary shot
+- Inspecting the responsive layout at a 375-pixel viewport width
+
+No automated tests are included with this contribution.
+
+## Screenshot
+
+
+
+## Related Issue
+
+https://github.com/thinkswell/javascript-mini-projects/issues/1245
+
+## Author
+
+Jacobie Jackson (cobie1818)
\ No newline at end of file
diff --git a/PhotoShotListPlanner/cobie1818/index.html b/PhotoShotListPlanner/cobie1818/index.html
new file mode 100644
index 000000000..84e285782
--- /dev/null
+++ b/PhotoShotListPlanner/cobie1818/index.html
@@ -0,0 +1,86 @@
+
+
+
+
+
+ Photo Shot List Planner
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Show shots
+
+ All shots
+ Pending
+ Completed
+
+
+
+ Your list is empty. Add your first shot above.
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PhotoShotListPlanner/cobie1818/screenshot.png b/PhotoShotListPlanner/cobie1818/screenshot.png
new file mode 100644
index 000000000..0a033367c
Binary files /dev/null and b/PhotoShotListPlanner/cobie1818/screenshot.png differ
diff --git a/PhotoShotListPlanner/cobie1818/script.js b/PhotoShotListPlanner/cobie1818/script.js
new file mode 100644
index 000000000..c2e151d21
--- /dev/null
+++ b/PhotoShotListPlanner/cobie1818/script.js
@@ -0,0 +1,233 @@
+"use strict";
+
+const STORAGE_KEY = "photo-shot-list-planner-cobie1818";
+
+const form = document.getElementById("shot-form");
+const titleInput = document.getElementById("shot-title");
+const categoryInput = document.getElementById("shot-category");
+const priorityInput = document.getElementById("shot-priority");
+const filterInput = document.getElementById("shot-filter");
+const shotList = document.getElementById("shot-list");
+const shotCount = document.getElementById("shot-count");
+const emptyMessage = document.getElementById("empty-message");
+const feedback = document.getElementById("feedback");
+
+const categories = Array.from(categoryInput.options, option => option.value);
+const priorities = Array.from(priorityInput.options, option => option.value);
+
+let storageWarning = "";
+let shots = loadShots();
+
+function showFeedback(message) {
+ feedback.textContent = [message, storageWarning].filter(Boolean).join(" ");
+}
+
+function loadShots() {
+ try {
+ const saved = localStorage.getItem(STORAGE_KEY);
+
+ if (saved === null) {
+ return [];
+ }
+
+ const parsed = JSON.parse(saved);
+ const ids = new Set();
+
+ // Validate stored data before using it in the interface.
+ if (!Array.isArray(parsed) || !parsed.every(shot => {
+ const valid =
+ shot !== null &&
+ typeof shot === "object" &&
+ typeof shot.id === "string" &&
+ shot.id.length > 0 &&
+ !ids.has(shot.id) &&
+ typeof shot.title === "string" &&
+ shot.title.trim().length > 0 &&
+ shot.title.length <= 120 &&
+ categories.includes(shot.category) &&
+ priorities.includes(shot.priority) &&
+ typeof shot.completed === "boolean";
+
+ if (valid) {
+ ids.add(shot.id);
+ }
+
+ return valid;
+ })) {
+ throw new Error("Invalid saved shot list");
+ }
+
+ return parsed;
+ } catch {
+ storageWarning =
+ "The saved list could not be loaded. An empty list is shown.";
+ return [];
+ }
+}
+
+function saveShots() {
+ try {
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(shots));
+ storageWarning = "";
+ } catch {
+ storageWarning =
+ "Changes could not be saved. Keep this page open to retain this list.";
+ }
+}
+
+function createId() {
+ let id;
+
+ do {
+ id = `shot-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
+ } while (shots.some(shot => shot.id === id));
+
+ return id;
+}
+
+function createBadge(text, extraClass = "") {
+ const badge = document.createElement("span");
+ badge.className = `badge ${extraClass}`.trim();
+ badge.textContent = text;
+ return badge;
+}
+
+function createShotCard(shot) {
+ const card = document.createElement("li");
+ card.className = shot.completed ? "shot-card completed" : "shot-card";
+
+ const checkbox = document.createElement("input");
+ checkbox.type = "checkbox";
+ checkbox.className = "shot-checkbox";
+ checkbox.checked = shot.completed;
+ checkbox.id = shot.id;
+
+ const details = document.createElement("div");
+ details.className = "shot-details";
+
+ // textContent displays user input as text, never as HTML.
+ const title = document.createElement("label");
+ title.className = "shot-title";
+ title.htmlFor = checkbox.id;
+ title.textContent = shot.title;
+
+ const meta = document.createElement("div");
+ meta.className = "shot-meta";
+ meta.append(
+ createBadge(shot.category),
+ createBadge(
+ `${shot.priority} priority`,
+ `priority-${shot.priority.toLowerCase()}`
+ )
+ );
+
+ details.append(title, meta);
+
+ const deleteButton = document.createElement("button");
+ deleteButton.type = "button";
+ deleteButton.className = "delete-button";
+ deleteButton.textContent = "Delete";
+ deleteButton.setAttribute("aria-label", `Delete shot: ${shot.title}`);
+
+ checkbox.addEventListener("change", () => {
+ shot.completed = checkbox.checked;
+ saveShots();
+ renderShots();
+
+ // Restore keyboard focus after rebuilding the list.
+ const updatedCheckbox = document.getElementById(shot.id);
+
+ if (updatedCheckbox) {
+ updatedCheckbox.focus();
+ } else {
+ filterInput.focus();
+ }
+
+ showFeedback(
+ `"${shot.title}" marked ${shot.completed ? "completed" : "pending"}.`
+ );
+ });
+
+ deleteButton.addEventListener("click", () => {
+ shots = shots.filter(item => item.id !== shot.id);
+ saveShots();
+ renderShots();
+ filterInput.focus();
+ showFeedback(`Deleted "${shot.title}".`);
+ });
+
+ card.append(checkbox, details, deleteButton);
+ return card;
+}
+
+function renderShots() {
+ const selectedFilter = filterInput.value;
+ const visibleShots = shots.filter(shot => {
+ if (selectedFilter === "pending") {
+ return !shot.completed;
+ }
+
+ if (selectedFilter === "completed") {
+ return shot.completed;
+ }
+
+ return true;
+ });
+
+ shotList.replaceChildren();
+
+ visibleShots.forEach(shot => {
+ shotList.appendChild(createShotCard(shot));
+ });
+
+ const completedCount = shots.filter(shot => shot.completed).length;
+ shotCount.textContent =
+ `${completedCount} of ${shots.length} shots completed`;
+
+ emptyMessage.hidden = visibleShots.length > 0;
+
+ if (shots.length === 0) {
+ emptyMessage.textContent =
+ "Your list is empty. Add your first shot above.";
+ } else {
+ emptyMessage.textContent = `No ${selectedFilter} shots to show.`;
+ }
+}
+
+titleInput.addEventListener("input", () => {
+ titleInput.setCustomValidity("");
+});
+
+form.addEventListener("submit", event => {
+ event.preventDefault();
+
+ const title = titleInput.value.trim();
+
+ if (!title) {
+ titleInput.setCustomValidity("Enter a shot idea, not just spaces.");
+ titleInput.reportValidity();
+ return;
+ }
+
+ shots.push({
+ id: createId(),
+ title,
+ category: categoryInput.value,
+ priority: priorityInput.value,
+ completed: false
+ });
+
+ saveShots();
+ form.reset();
+
+ // Show the new shot even if the completed filter was selected.
+ filterInput.value = "all";
+ renderShots();
+ titleInput.focus();
+ showFeedback(`Added "${title}".`);
+});
+
+filterInput.addEventListener("change", renderShots);
+
+renderShots();
+showFeedback("");
\ No newline at end of file
diff --git a/PhotoShotListPlanner/cobie1818/style.css b/PhotoShotListPlanner/cobie1818/style.css
new file mode 100644
index 000000000..301e78d4e
--- /dev/null
+++ b/PhotoShotListPlanner/cobie1818/style.css
@@ -0,0 +1,274 @@
+:root {
+ color-scheme: light;
+ font-family: Arial, Helvetica, sans-serif;
+ color: #20312d;
+ background: #f3f5f1;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+body {
+ margin: 0;
+ line-height: 1.6;
+}
+
+.container {
+ width: min(900px, 92%);
+ margin: 0 auto;
+ padding: 48px 0;
+}
+
+header {
+ margin-bottom: 32px;
+}
+
+.eyebrow {
+ color: #406650;
+ font-size: 0.8rem;
+ font-weight: bold;
+ letter-spacing: 0.12em;
+}
+
+h1 {
+ margin: 8px 0;
+ font-size: clamp(2rem, 5vw, 3rem);
+ line-height: 1.2;
+}
+
+h2 {
+ margin: 0 0 20px;
+ font-size: 1.3rem;
+}
+
+header > p:last-child,
+footer,
+#shot-count {
+ color: #53635b;
+}
+
+.panel {
+ margin-bottom: 24px;
+ padding: 28px;
+ background: #fff;
+ border: 1px solid #dce3da;
+ border-radius: 16px;
+ box-shadow: 0 8px 24px rgb(32 49 45 / 5%);
+}
+
+.field {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ margin-bottom: 18px;
+}
+
+label {
+ font-weight: bold;
+}
+
+input,
+select,
+button {
+ font: inherit;
+}
+
+input[type="text"],
+select {
+ width: 100%;
+ padding: 12px;
+ color: #20312d;
+ background: #fff;
+ border: 1px solid #86978b;
+ border-radius: 8px;
+}
+
+input::placeholder {
+ color: #657369;
+}
+
+.form-row {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 20px;
+}
+
+button {
+ min-height: 44px;
+ padding: 10px 18px;
+ border: 1px solid transparent;
+ border-radius: 8px;
+ cursor: pointer;
+}
+
+.primary-button {
+ color: #fff;
+ background: #315d46;
+ font-weight: bold;
+}
+
+.primary-button:hover {
+ background: #244734;
+}
+
+:focus-visible {
+ outline: 3px solid #2868b2;
+ outline-offset: 3px;
+}
+
+#feedback {
+ margin: 14px 0 0;
+ color: #315d46;
+}
+
+#feedback:empty {
+ display: none;
+}
+
+.list-header {
+ display: flex;
+ align-items: baseline;
+ justify-content: space-between;
+ flex-wrap: wrap;
+ gap: 8px 20px;
+ margin-bottom: 18px;
+}
+
+.list-header h2,
+#shot-count {
+ margin: 0;
+}
+
+.filter-field {
+ max-width: 240px;
+}
+
+#empty-message {
+ padding: 24px 12px;
+ color: #53635b;
+ text-align: center;
+ background: #f5f7f3;
+ border-radius: 8px;
+}
+
+.shot-list {
+ display: grid;
+ gap: 14px;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+
+.shot-card {
+ display: flex;
+ align-items: center;
+ gap: 14px;
+ padding: 18px;
+ border: 1px solid #dce3da;
+ border-radius: 10px;
+}
+
+.shot-checkbox {
+ flex-shrink: 0;
+ width: 22px;
+ height: 22px;
+ margin: 0;
+ accent-color: #315d46;
+ cursor: pointer;
+}
+
+.shot-details {
+ flex: 1;
+ min-width: 0;
+}
+
+.shot-title {
+ display: block;
+ margin: 0 0 6px;
+ overflow-wrap: anywhere;
+ font-weight: bold;
+}
+
+.shot-meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ font-size: 0.85rem;
+}
+
+.badge {
+ padding: 3px 10px;
+ color: #384b40;
+ background: #edf1eb;
+ border-radius: 20px;
+}
+
+.priority-high {
+ color: #842b21;
+ background: #fde8e4;
+}
+
+.priority-medium {
+ color: #705008;
+ background: #fff2cc;
+}
+
+.priority-low {
+ color: #24573c;
+ background: #e5f2e9;
+}
+
+.shot-card.completed {
+ background: #f5f7f3;
+}
+
+.completed .shot-title {
+ color: #59665d;
+ text-decoration: line-through;
+}
+
+.delete-button {
+ flex-shrink: 0;
+ color: #842b21;
+ background: #fff;
+ border-color: #d9ada7;
+}
+
+.delete-button:hover {
+ background: #fde8e4;
+}
+
+footer {
+ text-align: center;
+ font-size: 0.85rem;
+}
+
+@media (max-width: 600px) {
+ .container {
+ padding: 28px 0;
+ }
+
+ .panel {
+ padding: 20px;
+ }
+
+ .form-row {
+ grid-template-columns: 1fr;
+ gap: 0;
+ }
+
+ .primary-button,
+ .filter-field {
+ width: 100%;
+ max-width: none;
+ }
+
+ .shot-card {
+ flex-wrap: wrap;
+ }
+
+ .delete-button {
+ margin-left: auto;
+ }
+}
\ No newline at end of file