Table of Contents
3 Month (Fall 2023)
Concept Prototyping
UI Design
Front-end Development
Back-end Development
Connecting pet owners with the pet community/industry
Advisor: Andrew Twigg
Team: Lucy Cao, Dhruvisha Mondhe

OVERVIEW
Web3.5 Social Web Application
PetSphere is a dynamic pet community social web application designed to connect pet owners with service providers, fostering a vibrant community for pet enthusiasts. The platform offers a comprehensive blog for sharing ideas and a service for pet walking and sitting, catering to the diverse needs of pet owners.
ROLE
UI Designer & Full-stack Developer
As the designer and full-stack developer, my role involved designing and developing out the user experience and interface of PetSphere. I focused on developing a responsive and intuitive design, integrating features like lazy signup, personalized recommendations, and a user dashboard.
1. Features: A Highly personalized Web 3.5 App

Post Creation
Users can easily create and share their pet stories or advice. This feature is designed to be intuitive, encouraging even the most novice users to contribute.

Tagging
Each post can be tagged with relevant keywords, making it easier for others to find content on specific topics like "dog grooming" or "cat health".

Upvoting & Notification
A feature that allows users to upvote posts they find useful or interesting, helping to surface quality content to the community. User gets a notification when someone interacted with their posts by upvoting/downvoting/tagging.

Searching & Bookmarking
User uses keywords to search content/other users. Tags are used to extend search results. If the user find certain posts helpful, they can be bookmarked on found in user dashboard for future reference.

Personalized Recommendation System
This system suggests posts based on individual user preferences and past interactions, enhancing user engagement with relevant content.
2. Responsive Design and Minimal UI
Responsive Design
The PetSphere platform is meticulously designed to offer a consistent and functional experience on various devices - from desktops to smartphones. This responsive design approach ensures that the website adjusts its layout, images, and functionalities to fit the screen size and resolutions of different devices.

User-Friendly Interface
The user interface of PetSphere is designed to be intuitive and easy to navigate, reducing the learning curve for new users and enhancing the overall user experience. Key information is presented clearly, and interactive elements are easily accessible, making the journey from discovering content to booking a service straightforward and enjoyable.

3. Working as a Full-stack Developer
PetSphere is built on a robust foundation of modern web technologies, ensuring a seamless and secure user experience. Our technology stack includes HTML, CSS, JavaScript, PHP, SQL, Ruby, and Node.js, each playing a vital role in the functionality and aesthetics of our platform.
Front-End Development
In the front-end development of PetSphere, interface is meticulously crafted to provide a seamless user experience. Utilizing HTML, CSS, and JavaScript. For example, I structured the signup form with HTML, ensuring each input field is clearly labeled and accessible. The form's design is enhanced with CSS, making it visually appealing and consistent with the overall aesthetic of PetSphere.JavaScript is used for front-end validation, ensuring the user inputs the required information correctly before submitting the form.
Connecting to the Back-End
The back-end of PetSphere plays a crucial role in powering the entire application, ensuring efficient data handling, security, and seamless interaction with the front-end. We employed a combination of technologies including PHP, Node.js, and SQL to create a robust and scalable back-end infrastructure. For example, the signup form is integrated with the back-end to handle user registration. Using PHP and SQL, we developed a secure and efficient process for storing user data. The back-end script in PHP handles the form submission. It includes validation to check if the email is already registered and encrypts the password before storing it in the database.

<!-- Sample .html Code for Signup Interface Structure -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PetSphere | Sign-up</title>
<link rel="stylesheet" href="./style.css" />
<style>
body {
background-image: url("http://localhost/petsphere2/assets/images/backgrounds/bg_animal.jpeg");
background-size: cover;
background-repeat: no-repeat;
background-position: center;
background-attachment: fixed;
}
</style>
</head>
<body>
<div class="popup-container">
<div class="signup">
<div class="logo-container">
<a href="./index.php">
<img class="logo" src="http://localhost/petsphere2/assets/images/icons/logo.png" alt=""/>
</a>
</div>
<form action="signup.php" method="post">
<!-- user input username -->
<div class="input-container">
<input type="text" name="username" value="" required />
<label for="username">Username</label>
<div class="error-message"></div>
<div class="error-message"></div>
</div>
<div class="input-container">
<input type="email" name="email" value="" required />
<label for="email">Email</label>
<div class="error-message"></div>
<div class="error-message"></div>
</div>
<div class="input-container">
<input type="password" name="password" required />
<label for="password">Password</label>
<div class="error-message"></div>
<div class="error-message"></div>
</div>
<div class="input-container">
<input type="password" name="confirm_password" required />
<label for="confirm_password">Confirm Password</label>
<div class="error-message"></div>
</div>
<button class="popup-button" type="submit">Sign up</button>
</form>
<p>Have an account? <a href="login.php" class="popup-link">Login</a></p>
</div>
</div>
</body>
</html>
/* Sample .css Code for Login & Signup Interface */
.error-message {
font-size: 12px;
padding-top: 2px;
padding-left: 3px;
margin-bottom: 0;
padding-bottom: 0;
color: var(--MAIN);
}
.popup-container {
display: flex;
justify-content: center;
align-items: center;
height: auto;
}
.login,
.signup {
position: fixed;
top: 20vh;
background-co/lor: rgba(255, 255, 255, 0.9);
padding: 20px;
border: 1px solid #cccccc68;
border-radius: 18px;
width: 300px;
padding: 50px;
}
.popup-container h2 {
text-align: center;
padding-bottom: 0;
margin-bottom: 5px;
}
form .input-container {
position: relative;
margin-bottom: 20px;
width: 100%;
}
.popup-container input {
width: 100%;
padding: 10px;
padding-left: 10px;
padding-right: 32px;
border: 1px solid var(--GRAYLIGHT);
border-radius: 10px;
font-size: 1.05rem;
font-family: "Karla";
}
.popup-container .login input {
width: 85%;
}
.popup-container label {
position: absolute;
top: 22px;
left: 10px;
transform: translateY(-50%);
pointer-events: none;
color: var(--GRAY);
transition: all 0.2s ease-in-out;
}
.popup-container input:focus + label {
top: 0;
left: 0;
padding-bottom: 20px;
font-size: 12px;
color: var(--GRAY);
}
.popup-container input:valid + label {
top: 0;
left: 0;
padding-bottom: 20px;
font-size: 12px;
color: var(--GRAY);
}
.popup-container p {
text-align: center;
padding-top: 20px;
}
.popup-link {
color: var(--MAIN);
text-decoration: underline;
}
// Sample .php Code for Handling Signup
<?php
$username = "";
$email = "";
$password = "";
$confirm_password = "";
$signup_date = date("Y-m-d");
$profile_pic = "";
$error_array = array();
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$username = $_POST["username"];
$_SESSION["username"] = $username;
$email = $_POST["email"];
$_SESSION["email"] = $email;
$password = $_POST["password"];
$confirm_password = $_POST["confirm_password"];
// verify username length
if (strlen($username) > 50 || strlen($username) < 2) {
array_push($error_array, "Username must be between 2 and 50 characters<br>");
}
// check if username already exists
if ($stmt = $con->prepare("SELECT username FROM users WHERE username = ?")) {
$stmt->bind_param("s", $username);
$stmt->execute();
$result = $stmt->get_result();
$num_rows = $result->num_rows;
$stmt->close();
if ($num_rows > 0) {
array_push($error_array, "Username already exists<br>");
}
}
// verify email address
if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
$email = filter_var($email, FILTER_VALIDATE_EMAIL);
// prevent SQL injection
$stmt = $con->prepare("SELECT email FROM users WHERE email = ?");
$stmt->bind_param("s", $email);
$stmt->execute();
$result = $stmt->get_result();
$num_rows = $result->num_rows;
$stmt->close();
if ($num_rows > 0) {
array_push($error_array, "Email already in use<br>");
}
} else {
array_push($error_array, "Invalid email address<br>");
}
// verify password
$password_valid = true;
if (preg_match('/[^A-Za-z0-9]/', $password)) {
array_push($error_array, "Your password can only contain English letters or numbers<br>");
$password_valid = false;
}
if (strlen($password) > 30 || strlen($password) < 5) {
array_push($error_array, "Your password must be between 5 and 30 characters<br>");
$password_valid = false;
}
if ($password !== $confirm_password) {
array_push($error_array, "Passwords did not match<br>");
$password_valid = false;
}
// register new user if no errors
if (empty($error_array) && $password_valid) {
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
//profile picture assignment
$rand = rand(1,3);
if($rand == 1){
$profile_pic = "http://localhost/petsphere2/assets/images/profile_pics/momo.jpeg";
} else if ($rand == 2){
$profile_pic = "http://localhost/petsphere2/assets/images/profile_pics/momo1.jpg";
} else if ($rand == 3){
$profile_pic = "http://localhost/petsphere2/assets/images/profile_pics/momo2.jpg";
};
// write the data into database
$stmt = $con->prepare("INSERT INTO users (username, email, password, signup_date, profile_pic) VALUES (?, ?, ?, ?, ?)");
$stmt->bind_param("sssss", $username, $email, $hashed_password, $signup_date, $profile_pic);
if ($stmt->execute()) {
echo "Welcome to PetSphere!<br>";
$_SESSION["username"] = $username;
header("Location: index.php");
exit();
} else {
echo "Woops, Something went wrong: " . $con->error;
}
$stmt->close();
}
}
$_SESSION['error_array'] = $error_array;
?>
<script>
console.log($error_array)
</script>
4. Implementing Web Analytics
User Behavior Analysis
By tracking user interactions on our platform, such as tagging, likes, bookmarks, we gain valuable insights into what content resonates with our audience. This data could be used to informs our content strategy, ensuring the delivery of relevant and engaging material to the users.
Personalized Recommendations Algorithm
I used a external pragram to analyze user activity and to create user vector. This algorithm considers factors such as the posts a user has liked or bookmarked to rank the un-viewed posts and recommend them by relevance.
# Sample .rb Code for Calculating Keywords
require 'json'
# parses the JSON data passed to the script
input_json = ARGV[0]
posts_data = JSON.parse(input_json)
# initializes a hash table for keyword frequencies
keyword_frequencies = Hash.new(0)
# defines a method for basic text cleaning
def clean_text(text)
# basic cleaning: converts to lowercase and removes non-alphanumeric characters
text.downcase.gsub(/[^a-z0-9\s]/i, '')
end
# Extended list of stopwords
stopwords = %w[
the and to of a in that is was it for on with as by he she they at be this have from or one had
by word but not what all were we when your can said there use an each which she do how their if
will up other about out many then them these so some her would make like him into time has look
two more write go see number no way could people my than first water been call who oil its now
find long down day did get come made may part over new sound take only little work know place
year live me back give most very after thing our just name good sentence man think say great
where help through much before line right too mean old any same tell boy follow came want show
also around form three small set put end does another well large must big even such because turn
here why ask went men read need land different home us move try kind hand picture again change
off play spell air away animal house point page letter mother answer found study still learn
should america world high every near add food between own below country plant last school father
keep tree never start city earth eye light thought head under story saw left few while
along might close something seem next hard open example begin life always those both paper
together got group often run important until children side feet car mile night walk white sea
began grow took river four carry state once book hear stop without second late miss idea enough
eat face watch far indian real almost let above girl sometimes mountains cut young talk soon list
song being leave family
]
TITLE_TAG_WEIGHT = 100
# iterates over post data, extracting and processing text
posts_data.each do |post|
# combines title, body, and tags
combined_text = post['title'] + ' ' + post['body'] + ' ' + post['tags']
cleaned_text = clean_text(combined_text)
# splits the text into words and counts frequencies, skipping stopwords
cleaned_text.split.each do |word|
next if stopwords.include?(word)
keyword_frequencies[word] += 1
end
title_and_tags = post['title'] + ' ' + post['tags']
cleaned_title_and_tags = clean_text(title_and_tags)
cleaned_title_and_tags.split.each do |word|
next if stopwords.include?(word)
keyword_frequencies[word] += TITLE_TAG_WEIGHT
end
end
# outputs the keyword frequencies in JSON format
puts keyword_frequencies.to_json
// Sample .php Code for Creating User Vector and Getting Recommendation
<?php
function fetchPostsLikedByUser($userId, $con) {
$likedPostsData = [];
$likedPostsQuery = mysqli_query($con, "SELECT post_id FROM likes WHERE username = '" . mysqli_real_escape_string($con, $userId) . "'");
if (!$likedPostsQuery) {
die('Query failed: ' . mysqli_error($con));
}
while ($row = mysqli_fetch_assoc($likedPostsQuery)) {
$postId = $row['post_id'];
$postQuery = mysqli_query($con, "SELECT title, body, tags FROM posts WHERE id = '$postId'");
if ($postRow = mysqli_fetch_assoc($postQuery)) {
$likedPostsData[] = $postRow;
}
}
return $likedPostsData;
}
function getKeywordFrequencies($postsData) {
$rubyExecutable = '/usr/bin/ruby';
$scriptPath = __DIR__ . '/../assets/ruby/process.rb';
$command = $rubyExecutable . ' ' . escapeshellarg($scriptPath) . ' ' . escapeshellarg(json_encode($postsData)) . ' 2>&1';
$output = shell_exec($command);
if ($output === null) {
die("Error: Ruby script did not return any output");
}
$decodedOutput = json_decode($output, true);
if (json_last_error() !== JSON_ERROR_NONE) {
die("Error processing posts data: " . $output . "\nJSON Error: " . json_last_error_msg());
}
return $decodedOutput;
}
function getRecommendations($userId, $con) {
$likedPostsData = fetchPostsLikedByUser($userId, $con);
if (empty($likedPostsData)) {
$allPostsQuery = mysqli_query($con, "SELECT id FROM posts WHERE deleted='no' ORDER BY date_added DESC");
$allPostsData = mysqli_fetch_all($allPostsQuery, MYSQLI_ASSOC);
return array_column($allPostsData, 'id');
}
$keywordFrequencies = getKeywordFrequencies($likedPostsData);
arsort($keywordFrequencies);
$allPostsQuery = mysqli_query($con, "SELECT id, title, body, tags FROM posts WHERE deleted='no'");
$allPostsData = mysqli_fetch_all($allPostsQuery, MYSQLI_ASSOC);
usort($allPostsData, function ($post1, $post2) use ($keywordFrequencies, $userId, $con) {
$score1 = calculatePostScore($post1, $keywordFrequencies);
$score2 = calculatePostScore($post2, $keywordFrequencies);
$liked1 = isLikedByUser($post1['id'], $userId, $con);
$liked2 = isLikedByUser($post2['id'], $userId, $con);
if ($liked1 == $liked2) {
return $score2 <=> $score1;
}
return $liked1 ? 1 : -1;
});
return array_column($allPostsData, 'id');
}
function isLikedByUser($postId, $userId, $con) {
$likeQuery = mysqli_query($con, "SELECT * FROM likes WHERE post_id='$postId' AND username='" . mysqli_real_escape_string($con, $userId) . "'");
return mysqli_num_rows($likeQuery) > 0;
}
function calculatePostScore($post, $keywordFrequencies) {
$combinedText = $post['title'] . ' ' . $post['body'] . ' ' . $post['tags'];
$words = explode(' ', $combinedText);
$score = 0;
foreach ($words as $word) {
if (isset($keywordFrequencies[$word])) {
$score += $keywordFrequencies[$word];
}
}
return $score;
}
if (isset($userLoggedIn)) {
// get list of posts based on recommendation algorithm
$recommendations = getRecommendations($userLoggedIn, $con);
$postIds = implode(',', $recommendations);
$postQuery = "SELECT * FROM posts WHERE id IN ($postIds) ORDER BY FIELD(id, $postIds)"; // 使用 FIELD 函数确保按推荐顺序排序
} else {
$postQuery = "SELECT * FROM posts ORDER BY likes DESC, date_added DESC";
}
$postResult = mysqli_query($con, $postQuery);
if(mysqli_num_rows($postResult) > 0) {
while($post = mysqli_fetch_assoc($postResult)) {
$userQuery = "SELECT profile_pic FROM users WHERE username = '" . mysqli_real_escape_string($con, $post['added_by']) . "'";
$userResult = mysqli_query($con, $userQuery);
$postUser = mysqli_fetch_assoc($userResult);
date_default_timezone_set('America/New_York');
$dateAdded = new DateTime($post['date_added']);
$now = new DateTime("now");
$interval = $dateAdded->diff($now);
// format date based on the difference
if ($interval->h < 1) {
// less than 1 hour
if ($interval->i == 0) {
$timeString = 'Just now';
} else {
$timeString = $interval->i . 'min ago';
}
} elseif ($interval->d < 1) {
// less than 24 hours but more than 1 hour
$timeString = $interval->h . 'h ago';
} elseif ($interval->d < 7) {
// less than 7 days
$timeString = $interval->d . 'd ago';
} else {
// 7 days or more
$timeString = $dateAdded->format('M d, Y');
}
// check if the vote, flag, like has been clicked
$flag_query = mysqli_query($con, "SELECT * FROM flags WHERE post_id='{$post['id']}' AND username='{$userLoggedIn}'");
$flag_icon_class = (mysqli_num_rows($flag_query) > 0) ? "fa" : "far";
$like_query = mysqli_query($con, "SELECT * FROM likes WHERE post_id='{$post['id']}' AND username='{$userLoggedIn}'");
$like_icon_class = (mysqli_num_rows($like_query) > 0) ? "fa" : "far";
$vote_query = mysqli_query($con, "SELECT * FROM votes WHERE post_id='{$post['id']}' AND username='{$userLoggedIn}'");
$vote_icon_class = (mysqli_num_rows($vote_query) > 0) ? "fa" : "far";
echo '
<div class="card-container" id="post-' . htmlspecialchars($post['id']) . '">
<div class="card-header">
<h2>' . htmlspecialchars($post['title']) . '</h2>';
// display tags
if(!empty($post['tags'])) {
$tags = explode(',', $post['tags']);
foreach($tags as $tag) {
echo '<span class="post-tag">#' . htmlspecialchars(trim($tag)) . '</span> ';
}
}
echo '
<a href="#" class="card-userinfo">
<div class="user-icon">
<img src="' . htmlspecialchars($postUser['profile_pic']) . '" alt="" />
</div>
<span>' . htmlspecialchars($post['added_by']) . '</span>
<span> </span>
<span class="post-date" style="color:#b1a290">' . htmlspecialchars($timeString) . '</span>
<span> </span>
<span class="critter-type">' . htmlspecialchars($post['category']) . '</span>
</a>
</div>
<div class="card-content">
<img src="' . htmlspecialchars($post['imageName']) . '" alt="" class="card-image" />
<div>' . $post['body'] . '</div>
</div>
<div class="card-footer">
<div class="interaction-icons">
<i class="' . $vote_icon_class . ' fa-thumbs-up" id="vote-icon-' . $post['id'] . '" onclick="toggleVote(' . $post['id'] . ', \'' . $userLoggedIn . '\')"></i>
<span>' . htmlspecialchars($post['votes']) . ' votes</span>
<i class="' . $flag_icon_class . ' fa-thumbs-down" id="flag-icon-' . $post['id'] . '" onclick="toggleFlag(' . $post['id'] . ', \'' . $userLoggedIn . '\')"></i>
<span>' . htmlspecialchars($post['flags']) . ' flags</span>
<i class="' . $like_icon_class . ' fa-heart" id="like-icon-' . $post['id'] . '" onclick="toggleLike(' . $post['id'] . ', \'' . $userLoggedIn . '\')"></i>
<span>' . htmlspecialchars($post['likes']) . '</span>
</div>';
if (isset($userLoggedIn) && $userLoggedIn != $post['added_by']) {
echo '
<div class="tag-add-form">
<form action="/petsphere2/includes/form_handlers/tag_adder.php" method="POST">
<span>#</span>
<input type="hidden" name="post_id" value="' . htmlspecialchars($post['id']) . '">
<input type="text" name="tag" placeholder="Add a tag">
<button type="submit" name="add_tag">+ Tag</button>
</form>
</div>
';
}
echo '
</div>
</div>
';
}
} else {
echo 'No posts found.';
}
?>
MORE PROJECTS
PlanPal
PlanPal is an AI-powered mobile app incorporating 4 AI agents with different interaction styles that aims to help users set and achieve personalized goals through scaffolded guidance, personalized feedback, and motivational coaching.

YUPI
YUPI is a smart, centralized platform designed for urgent-care services that makes doctor-patient interactions clearer and more effective than ever before.

Bumble Vibely
The Vibely project transformed Bumble's user experience by introducing a map feature that facilitates connections through real-time, location-based interactions.

BIKELINK PRO
A semi-autonomous e-bike with interfaces that employs human-centered design methodologies and principles of typography, color, and composition to set paradigms for interactions.
