diff --git a/index.html b/index.html
index aa199df..b56b91e 100644
--- a/index.html
+++ b/index.html
@@ -55,7 +55,7 @@
""
]
}
-
+
@@ -64,7 +64,8 @@
@@ -77,7 +78,6 @@
Kidnappings & Missing Persons in Uganda
Released
Missing
Fallen
-
@@ -88,6 +88,7 @@ Kidnappings & Missing Persons in Uganda
+
diff --git a/src/components/PersonCard.js b/src/components/PersonCard.js
new file mode 100644
index 0000000..dd8072e
--- /dev/null
+++ b/src/components/PersonCard.js
@@ -0,0 +1,154 @@
+const personTemplate = document.createElement('template');
+ personTemplate.innerHTML = `
+
+
+
+
+
+
+
Taken by
+
Time:
+
Last seen:
+
Gender:
+
+
Current Location:
+
+
+
+ `;
+
+class PersonCard extends HTMLElement {
+ constructor() {
+ super();
+ const shadow = this.attachShadow({ mode: 'open' });
+ shadow.append(personTemplate.content.cloneNode(true));
+ }
+
+ connectedCallback() {
+ this.render();
+ }
+
+ static get observedAttributes() {
+ return ['name', 'status', 'security-organ', 'time-taken', 'last-known-location', 'gender', 'twitter-handle', 'holding-location', 'photo-url', 'id'];
+ }
+
+ attributeChangedCallback(name, oldValue, newValue) {
+ if (oldValue !== newValue) {
+ this.render();
+ }
+ }
+
+ render() {
+ // Set non-slot content
+ this.shadowRoot.querySelector('.card-img').src = this.getAttribute('photo-url') || '';
+ this.shadowRoot.querySelector('.card-img').alt = this.getAttribute('name') || '';
+ this.shadowRoot.querySelector('.card__name').textContent = this.getAttribute('name') || '';
+
+ const status = this.getAttribute('status') || '';
+ const statusElement = this.shadowRoot.querySelector('.card-status');
+ statusElement.textContent = status;
+ statusElement.className = `card-status ${status.toLowerCase()}`;
+
+ const twitterHandle = this.getAttribute('twitter-handle') || '--';
+ const twitterLink = this.shadowRoot.querySelector('.card__twitter a');
+ twitterLink.href = `https://x.com/${twitterHandle}`;
+ twitterLink.textContent = twitterHandle;
+
+ this.shadowRoot.querySelector('.share-button').addEventListener('click', this.shareCard.bind(this));
+ }
+
+ shareCard() {
+ const name = this.getAttribute('name');
+ const status = this.getAttribute('status');
+ const lastKnownLocation = this.getAttribute('last-known-location');
+
+ let text;
+ if (status.toLowerCase() === "released") {
+ text = `GOOD NEWS🖐!!!! ${name}, who was previously missing, has been released. They were last held at ${lastKnownLocation || 'Unknown'}. #March2Parliament`;
+ } else {
+ text = `NOTICE! This is a missing person: ${name}, status: ${status}, last seen at ${lastKnownLocation || 'Unknown'}. #March2Parliament`;
+ }
+
+ const url = `https://x.com/intent/tweet?text=${encodeURIComponent(text)}`;
+ window.open(url, "_blank");
+ }
+}
+
+customElements.define('person-card', PersonCard);
\ No newline at end of file
diff --git a/src/css/styles.css b/src/css/styles.css
index 488c032..0d1f091 100644
--- a/src/css/styles.css
+++ b/src/css/styles.css
@@ -5,7 +5,7 @@
--blue: #007bff;
--green: #28a745;
--lightergrey: #444;
- --lightgrey: #333;
+ --lightgrey: #5e5d5d;
--white: #fff;
--black: #000000;
--yellow: #ffb224;
@@ -47,9 +47,9 @@ button:hover {
}
.header {
- background-color: var(--lightgrey);
- padding: 2rem;
- text-align: center;
+ background-color: var(--lightgrey);
+ padding: .5rem;
+ text-align: center;
}
.header h1 {
@@ -59,8 +59,9 @@ button:hover {
}
.header h2 {
- font-size: 1.3rem;
+ font-size: 1rem;
font-weight: 400;
+ font-style: italic;
}
.filters {
@@ -77,7 +78,7 @@ button:hover {
.content {
flex-grow: 1;
- max-width: 1400px;
+ max-width: 1440px;
width: 95%;
margin: 2rem auto;
padding: 1rem 0;
@@ -151,86 +152,10 @@ button:hover {
.persons {
display: grid;
- grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
- gap: 1.5rem;
+ grid-template-columns: repeat(auto-fill, minmax(225px, 1fr));
+ gap: 1rem;
justify-content: center;
}
-
-.card {
- background-color: var(--card-bg);
- border-radius: 15px;
- padding: 1.25rem;
- text-align: center;
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
- transition: background-color 0.3s ease, box-shadow 0.3s ease;
- display: flex;
- flex-direction: column;
- justify-content: space-between;
-}
-
-.card:hover {
- background-color: var(--card-hover-bg);
- box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
-}
-
-.card-img {
- width: 100px;
- height: 100px;
- border-radius: 50%;
- margin: 0 auto 1rem;
- object-fit: cover;
- border: 2px solid var(--white);
-}
-
-.card__name {
- font-size: 1.2rem;
- margin-bottom: 0.6rem;
- font-weight: 700;
-}
-
-.card-status {
- font-size: 0.8rem;
- margin-bottom: 1rem;
- color: var(--white);
- background-color: var(--purple);
- display: inline-block;
- border-radius: 12px;
- padding: 0.3rem 1rem;
-}
-
-.card-status.remanded { background-color: var(--yellow); color: var(--black); }
-.card-status.fallen { background-color: var(--red); }
-.card-status.missing { background-color: var(--blue); }
-.card-status.released { background-color: var(--green); }
-
-.card__info {
- margin-bottom: 1rem;
-}
-
-.card__office, .locations, .card__gender {
- margin-bottom: 0.6rem;
- font-size: 0.9rem;
-}
-
-.card__twitter {
- font-size: 0.9rem;
- margin-bottom: 1rem;
- word-break: break-all;
-}
-
-.card__currently {
- font-size: 0.9rem;
- margin-bottom: 0.6rem;
- margin-bottom: 1rem;
- font-weight: 500;
-}
-
-.card__currently-status {
- font-weight: normal;
- display: block;
- margin-top: 0.3rem;
-}
-
.share-button {
background-color: var(--blue);
border: none;
@@ -314,66 +239,21 @@ button:hover {
opacity: 0.8;
}
-@media screen and (max-width: 1400px) {
- .persons {
- grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
- }
-}
-
-@media screen and (max-width: 1200px) {
- .persons {
- grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
- }
-}
-
@media screen and (max-width: 1080px) {
.search-sort {
display: flex;
flex-flow: column;
}
-}
+}
@media screen and (max-width: 768px) {
- .persons {
- grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
- }
-
.header h1 {
font-size: 2rem;
}
}
-@media screen and (max-width: 480px) {
- .persons {
- grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
- }
-
- .card {
- padding: 1rem;
- }
-
- .card-img {
- width: 80px;
- height: 80px;
- }
-
- .card__name {
- font-size: 1rem;
- }
-
- .card-status, .card__office, .locations, .card__gender, .card__twitter, .card__currently, .share-button {
- font-size: 0.8rem;
- }
-
- .search-sort input,
- .content__buttons button {
- font-size: 0.9rem;
- padding: 0.6rem 1rem;
- }
-}
-
/* spinner */
.loading {
display: flex;
diff --git a/src/js/scripts.js b/src/js/scripts.js
index b669cf6..0851834 100644
--- a/src/js/scripts.js
+++ b/src/js/scripts.js
@@ -1,27 +1,6 @@
const API_URL = `https://dashboard.missingpersonsug.org/api/victims?per_page=1000`;
-
-// Define the shareCard function globally
- function shareCard(id) {
- fetch(API_URL)
- .then((response) => response.json())
- .then(responseBody => {
- const card = responseBody.data.find((item) => item.id === id);
- let text;
-
- if (card.status.toLowerCase() === "released") {
- text = `GOOD NEWS🖐!!!! ${card.name}, who was previously missing, has been released. They were last held at ${card.last_known_location || 'Unknown'}. #March2Parliament`;
- } else {
- text = `NOTICE! This is a missing person: ${card.name}, status: ${card.status}, last seen at ${card.last_known_location || 'Unknown'}. #March2Parliament`;
- }
-
- const url = `https://x.com/intent/tweet?text=${encodeURIComponent(text)}`;
- window.open(url, "_blank");
- })
- .catch((error) => console.error("Error fetching data:", error));
-}
-
function parseCustomDateFormat(dateString) {
const [time, date] = dateString.split(' ');
const [hours, minutes] = time.split(':').map(Number);
@@ -30,29 +9,6 @@ const API_URL = `https://dashboard.missingpersonsug.org/api/victims?per_page=100
return parsedDate;
}
- // Function to create the cards
- function createCard(card) {
- const takenTime = card.time_taken ? getRelativeTime(parseCustomDateFormat(card.time_taken)) : 'Unknown';
- const exactTime = card.time_taken_formatted ? parseCustomDateFormat(card.time_taken_formatted).toLocaleString('en-US', { month: 'long', day: 'numeric', year: 'numeric', hour: 'numeric', minute: 'numeric', hour12: true }) : 'Unknown';
-
- return `
-
-
-
-
${card.name}
-
${card.status}
-
Taken by ${card.security_organ}
-
Time: ${takenTime}
-
Last seen: ${card.last_known_location}
-
Gender: ${card.gender}
-
-
Currently: ${card.holding_location || "--"}
-
-
-
- `;
-}
-
/**
* Returns a relative time string based on the time a person was taken.
*
@@ -76,7 +32,28 @@ const API_URL = `https://dashboard.missingpersonsug.org/api/victims?per_page=100
if (months < 12) return `${months} months ago`;
return `${years} years ago`;
}
-
+ function createPersonElement(person) {
+ const personElement = document.createElement('person-card');
+ const takenTime = person.time_taken ? getRelativeTime(parseCustomDateFormat(person.time_taken)) : 'Unknown';
+
+ personElement.setAttribute('name', person.name);
+ personElement.setAttribute('status', person.status);
+ personElement.setAttribute('photo-url', person.photo_url);
+ personElement.setAttribute('twitter-handle', person.x_handle || '--');
+ personElement.setAttribute('id', person.id);
+
+ // Set slot content
+ personElement.innerHTML = `
+ ${person.security_organ || 'Police'}
+ ${takenTime}
+ ${person.last_known_location || 'Unknown'}
+ ${person.gender || 'Unknown'}
+ ${person.holding_location || '--'}
+ `;
+
+ return personElement;
+ }
+
document.addEventListener("DOMContentLoaded", function() {
// init pagination
initPaginate();
@@ -246,39 +223,6 @@ async function fetchData(url){
}
}
-/**
- * Returns a person's element object
- *
- * @param {person} object - data.json object
- */
-function createPersonElement(person){
- const personElement = document.createElement('div');
- personElement.classList.add('person');
-
- const takenTime = person.time_taken ? getRelativeTime(parseCustomDateFormat(person.time_taken)) : 'Unknown';
- const exactTime = person.time_taken_formatted ? person.time_taken_formatted : 'Unknown';
- const taken_by = person.security_organ? person.security_organ: 'Police';
-
-
- personElement.innerHTML = `
-
-
-
-
${person.name}
-
${person.status}
-
Taken by ${taken_by}
-
Time: ${takenTime}
-
Last seen: ${person.last_known_location}
-
Gender: ${person.gender}
-
-
Currently: ${person.holding_location || "--"}
-
-
-
- `;
- return personElement
-}
-
// load persons
function loadPersons(){
// if search query is empty, check if all persons are loaded, coz then we want infinte scroll otw we don't want it.