Compare commits

...

2 Commits

Author SHA1 Message Date
456eafe9a9 finished demo video all done 2026-06-17 05:44:58 +09:00
ef7013ddf7 documentation almost done 2026-06-17 00:13:47 +09:00
9 changed files with 273 additions and 55 deletions

View File

@@ -1,25 +1,25 @@
robots.txt,1780149196204,c42e958aa717ecf11e70c927ebe348b7cfdeb317d164463dde44a6245edc25bf
index.html,1781534986659,c4e04f5484da1c956597345e2f98d096d8ae250aaac041cb1579ab8eddd2dad0
_app/env.js,1781534985969,3ad3b33040ca005034c918e78bb90257fafb951a2ac1dcb9063c8b3c8af2f638
_app/version.json,1781534985254,51f3700601405b891bb6acf7e8f8713fa82a5f942794780bbc05872e3ae795a9
_app/immutable/nodes/4.GA9nhugj.js,1781534985239,c7f1ca87a6c0ebdf966bf3c6f725599436d9f99fcafa37281a918d93ca846347
_app/immutable/nodes/1.lvAGEFLt.js,1781534985237,306b95c5c05905777dcae6eb606e86bb1aabd7f15978e89c11f697ea28e32aee
_app/immutable/nodes/3.Br_mUUSD.js,1781534985239,5d583a20725b7a1325d927c5303dd574b23e358fe897c91522320acc86aa6a8d
_app/immutable/nodes/0.ro2cMSuj.js,1781534985236,3eeddd736b4b5e5ef39c08228d107cb65e24bbc160df89788bc5190ee05c886a
_app/immutable/entry/start.BstLhiV9.js,1781534985236,b8a32b87469c068bd61d051f8a8ea67ae3074460be50dbd7ac7dd1feb7f579fc
_app/immutable/entry/app.DTPvPvz-.js,1781534985234,5250ef8be22692c66cb99eee468949620bcd962566596dcd753d007d909a02db
_app/immutable/chunks/dYw4s8FK.js,1781534985248,92bce86c6a19bb7e0519d637325d6b82f8da811e6574b4255feabff9bcdb90cb
_app/immutable/chunks/xihTtKlq.js,1781534985249,6dc2927621fce4ab1f29651cb0fc092849d26bcf1b4e7c03ca68912347351a5e
_app/immutable/chunks/Clt4wA-V.js,1781534985243,c7aa951803761d5d9911ddea6cc7b4043eee049800a8fae1dca2eb75b7ecf54f
_app/immutable/chunks/BmXl2Dnh.js,1781534985241,acee0daed1317076016cfe5cdefa61436f9f4e6138eb63980898acf623f19243
_app/immutable/chunks/kNaey6uv.js,1781534985249,b2e326f189779bca5f499cc8b1019822fec2ee950002f2208b82d7574d863610
_app/immutable/chunks/DOAwxdoU.js,1781534985244,58e7771df8a303c0ec3ad5bf6ccb0c1e5c16447516820a1d2e05873fc4bb63dc
_app/immutable/assets/3.RINX6wbk.css,1781534985253,8f9a6eebf728dfc5907cb76bd37c871d2d1f334aca214eee962e83896b47578d
_app/immutable/chunks/BXRdFrf6.js,1781534985240,b5ad6763a83bf8db5fa75951cfef48c9bd596a3b411a24b624cf1a4ffddd2a7a
_app/immutable/assets/4.DNmkdUsl.css,1781534985254,bf20a1cecff78d2c2566cb760593b117110eeacc701b4c79ee9b897bbdcc2ea5
_app/immutable/chunks/BqvGMMFS.js,1781534985242,2c51af2452bfec64b3f17ba6d670db004801349cf42953bd1f81c8b40f258999
_app/immutable/assets/0.DB_w-Orf.css,1781534985251,65816fda749a1c0cca0e0d336ab5ce2e8072226d1011391e0fa3577e8f8d7cd2
_app/immutable/assets/2.BAt0MrfH.css,1781534985252,7b585e41d74917eac70caacaddcfe19f9760dba8b270cb860b0ca31e485ebb11
_app/immutable/chunks/z5owE3AB.js,1781534985250,a3cbca6624018231c584f76781735d69f12b229ce8858cbcd219da801737e265
_app/immutable/nodes/2.g303636_.js,1781534985237,4ab58e678b87cbac73cf937dc61fe562a45db4f74a9ca74a687746d4371a4166
_app/immutable/chunks/DdlKGuOI.js,1781534985246,03899c735f5705ef92c0abb1351af5ff6a86455552bda7df4907b3cfc3b87228
_app/version.json,1781597180494,6853c009f37c9a6a3302f90db12c20e34d6b7862ad1c315714eb1044fbd7afe0
index.html,1781597182000,eb5a215dbb0a7539d036698b300e289b4291f76b78f497c0da6c17d844196b73
_app/env.js,1781597181277,3ad3b33040ca005034c918e78bb90257fafb951a2ac1dcb9063c8b3c8af2f638
_app/immutable/nodes/3.Da4ilo9V.js,1781597180475,8b7f71618cbb9481eae50bcab3557e1a18579ca05397504926155dcd34b273c5
_app/immutable/nodes/4.sF5B4qoA.js,1781597180476,0a444727ed27e504edd46e8b7e936eaac7e790d274ffe5f58ad7aceaad76a971
_app/immutable/nodes/0.CF0HerLL.js,1781597180473,92cfe635ac64a808680b6dbe6bb1ce4a3abae984f22c2a7896c27bc0a7476ead
_app/immutable/nodes/1.9IAeRu3w.js,1781597180474,7cbbfc75a88d77614bbdef3fc48e034cdee3958e893959707398dab9ed1d4c93
_app/immutable/entry/start.CqQaJc09.js,1781597180472,a84dddebd82a60e91841e49f5312f68fda4ac1cbf93cc66883c2b8a991c4e8a4
_app/immutable/chunks/kNaey6uv.js,1781597180482,b2e326f189779bca5f499cc8b1019822fec2ee950002f2208b82d7574d863610
_app/immutable/chunks/dYw4s8FK.js,1781597180481,92bce86c6a19bb7e0519d637325d6b82f8da811e6574b4255feabff9bcdb90cb
_app/immutable/chunks/dYDITFvm.js,1781597180480,5625d4bf7cca09d6005c0f12c974e1c33550d5704bcc8fc9651fd68207b3c241
_app/immutable/chunks/BXRdFrf6.js,1781597180477,b5ad6763a83bf8db5fa75951cfef48c9bd596a3b411a24b624cf1a4ffddd2a7a
_app/immutable/chunks/BT5Sv--u.js,1781597180477,1d71f3354e874a35e79c8aeda2248dd684ce1654178bcfa35dff0851a0d15a33
_app/immutable/chunks/FljZ3G7a.js,1781597180479,7e10d1e9a617bdf3f461d4061dbf4b00049c48e43c7b66a819ee0775cdaa2898
_app/immutable/assets/4.DNmkdUsl.css,1781597180492,bf20a1cecff78d2c2566cb760593b117110eeacc701b4c79ee9b897bbdcc2ea5
_app/immutable/chunks/BqvGMMFS.js,1781597180478,2c51af2452bfec64b3f17ba6d670db004801349cf42953bd1f81c8b40f258999
_app/immutable/chunks/xihTtKlq.js,1781597180483,6dc2927621fce4ab1f29651cb0fc092849d26bcf1b4e7c03ca68912347351a5e
_app/immutable/assets/2.BAt0MrfH.css,1781597180489,7b585e41d74917eac70caacaddcfe19f9760dba8b270cb860b0ca31e485ebb11
_app/immutable/entry/app.Mng5z0iy.js,1781597180472,a3b058ffac49561b3c78b9fc7acc407483787e55bb45712059f5c5dfd5299f34
_app/immutable/assets/0.DB_w-Orf.css,1781597180487,65816fda749a1c0cca0e0d336ab5ce2e8072226d1011391e0fa3577e8f8d7cd2
_app/immutable/assets/3.RINX6wbk.css,1781597180491,8f9a6eebf728dfc5907cb76bd37c871d2d1f334aca214eee962e83896b47578d
_app/immutable/chunks/z5owE3AB.js,1781597180484,a3cbca6624018231c584f76781735d69f12b229ce8858cbcd219da801737e265
_app/immutable/nodes/2.Bah3jasp.js,1781597180474,46e17d937aad72f59d8818941abee3cc7d6d02df294692e5850f1dbe2e0abe0b
_app/immutable/chunks/CGbwVvmT.js,1781597180478,f6685558a62443dfe58d97ae38ba1c9c4a9da6db8d2629f634e64a164cb164d3

278
README.md
View File

@@ -1,42 +1,260 @@
# sv
**Name:** Samantha Lopez
Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
**ID:** 20266142
## Creating a project
**Email:** samantha@kaist.ac.kr
If you're seeing this, you've probably already done this step. Congrats!
**Gittea Repo:** [https://git.prototyping.id/20266142/Overheard.git](https://git.prototyping.id/20266142/Overheard.git)
```sh
# create a new project
npx sv create my-app
**Video Demo:** [https://youtu.be/pI6eU5PSqRY](https://youtu.be/pI6eU5PSqRY)
**Live site URL** [https://overheard-d7396.web.app](https://overheard-d7396.web.app)
# App Description
Overheard is a location based message board. Its premise is simple and intentionally constrained, you are only able to read what others have left at a place if you are physically in the same location. You can only leave a message if you are standing there too. Overheard functions on anonymity It strays away from becoming a "social media" by eliminating profiles, feeds, and online identities. The app utilizes firebase's anonymous authenticatioin to store activity on devices, enough to link interactions across sessions but not enough to create anythings that ties your messages back to you.
Overheard works like this; upon opening, the app gets your location and translates coordinates into a geohash, a short string representative of your geogrpahical location. It then takes that string to look for messages whose geohash contain an identical prefix thus returning messages left around you. The messages appear as pins on the map. You can tap on any pin to read what was left there. When reding a message you are presented with two choices: to echo it which restarts its 30 day life span and thus extends its visibility on the map, or let it go, which simply closes the read view of the messages. You can also leave your own messages compromised of text and optionally an image for others to read within the next 30 days, you can even choose a display pin color for your message(optional).
## Echoing messages
one of my favorite features of the entire app is the 30 day decay feature that each message lives under. This makes it so messages don't accumulate forever, they face, a message which goes unechoed dissapears within 30 days, not because it gets deleted from the database but because it was either undiscovered or because no one made the decision to extend its lifespan. Echoing a message resets its lifespan it signifies that someone cared enough to keep it alive. This interaction allows messages to become curated over time, messages that survive are those who kept being encountered by people who wanted those messages to survive. This si what makes the app interesting. Individual messages are fleeting, echoing is what makes something as close to permanent as you can get within this app and physical presence is required for all of these interactions.
## Features
**Geoloacation based message visiibility:** Overheard uses the browser Geolocation API to get user coordinates then turns them into a geohash (using `ngeohash`). The geohash is then used to look for messages in firebase within the desired area (location scope is calculated based on geohash prefix similarity, currently the prefix is 7 characters which is about a city blocks worth of distance). This feature makes physical presence a requirement to interact with the app in any meaningful way.
**Message pins on the map:** Each nearby message is visible as a pastel colored circle on the map. This is done via Google Maps legacy Marker API with the icon being handled by `pins.js`. Pin color can be chosen at the time of leaving a message otherwise a randomly generated color will be used. Opted for this view over a feed to reinforce spacial memory, messages live in the locations where they were left not in a social media like feed.
**30-day Decay:** Messages had a `lastEchoAt` timestamp. Messages whose last echo was more than 30 days ago get filtered out and not rendered thus making them dissapear entirely from user view.
**Echo:** Increments the messages `echoCount` and sets the messages `lastEchoAt` to the current timestamp, reseting their 30 day decay clock. Plays a small arpeggio like sound as feeback when pressed.
**Let go:** Pressing this simpley closes the detailed message view. The alternative to echoing somethings should be equally as effortless and framed as letting a memory go.
**Composing a message:** `ComposeSheet.svelte` calls `addMessage` with the text, image, and pin color. Maximum 240 characters are accepted. Images are analyzed to ensure that no selfies are uploaded to preserve anonymity.
**Share with link and QR code:** `SharePopover.svelte` creates a URL and a QR code (via a qrcode package). `+page.svelte` is able to read the the `?message` parameter on the url once it opens and shows that message (if it is still alive). I decided to add this feature as it brings some of the sharing and allows for even more physical interactions. For instance the QR code can be posted in places both sharing a message and inviting people to share their own.
**location trail:** Every time the app loads, `addTrailPoint` writes the coordinates to localStorage. In `MapView.svelte`, the full history is drawn as a polyline on the map (toggleable with the small button in the top-left). A visualizer of all the places where you have shared and received memories within Overheard.
**Archive page:** Shows a list of every message that has been left on the device and shows their decay status.`getMyMessages()` looks for messages in Firestore by authorId matching the anonymous UID. This allows you to be able to see what you've left behind and whether it's still alive.
**Stamps:** Every time a message is posted from a new geohash-4 region (4 char prefix location precision), `checkAndAwardStamp` reverse-geocodes the coordinates and writes a stamp to `users/{uid}/stamps/{geohash4}`. The stamp page shows all the earned stamps like a sticker-album layout. Each stamp is a circle with a predetermined icon and the place name. This serves as a track of all the places you've been in and in which you choose to partake in the sharing of anonymous memories.
**Face detection for photo uploads:** When you select a photo `hadFace(file)` from `faceDetection.js` block the upload of a picture if any face is detected. The check occurs before the image even gets to be previwed for upload. Since overheard is anonymous its pictures should also be.
## App summary; TLDR
Overheard was not built ot be a social app, it purposely erased the ability to create a public identity, you cannot follow anyone, see anyones profile, or even know who left a message in the first place. Its has no guarantee of permanence, participating is accepting that the memories you share and receive might fade. It reinforces physical presence, you cannot sit and scroll through messages but you can only experience the memories of others by crating some yourself in the same physical location. Overheard is designed to feel like you are reading something somene left for you in a particular place, not scrolling for content.
# Code Organization
## File Organization
Overheard is an application built using SvelteKit and which was deployed to firebase for hosting. The file structure it functions under is as illustrated below.
![File organization diagram](src/lib/assets/File_organization_overheard.png)
## Data/Application Flows
**App opens**
![App opens diagram](src/lib/assets/open%20app.png)
**Message pins render**
![Message pins render diagram](src/lib/assets/Message%20pins%20load.png)
**Tapping a pin**
![Tapping a pin diagram](src/lib/assets/Tapping%20a%20pin.png)
**Leaving a message**
![Leaving a message diagram](src/lib/assets/posting%20message.png)
**New message is added during active session**
![New Message added during active session diagram](src/lib/assets/New%20pin%20dropped.png)
**View the diagrams on Figma with the following link:**
[https://www.figma.com/board/zJCrMvicQFHpyX9eqeCY2W/Final-Project-Diagrams?node-id=2008-3984&t=NF572Ti6bMLwkJB1-1](https://www.figma.com/board/zJCrMvicQFHpyX9eqeCY2W/Final-Project-Diagrams?node-id=2008-3984&t=NF572Ti6bMLwkJB1-1)
## Svelte stores (shared states)
**messagesStore.js**
|Variable/Function|Purpose|
|-----------------|-------|
|`messageStore`|array of all the nearby active messages|
|`livePinIdsStore`| set of message ID's that arrive bia the real time listener|
|`setMessages(newMessages)`|setter that compares against previous list and plays `playNewPinchime()` if any new IDs appear|
**mapStore.js**
|Variable/Function|Purpose|
|-----------------|-------|
|`mapStore`| help figure out what to render|
|`selectedMessage`|the message tapped on by the user (null if none)|
|`composing`| `true` when the compose sheet is open|
**userStore.js**
|Variable/Function|Purpose|
|-----------------|-------|
|`userStore`|holds the anonymous Firebase identity for the device|
|`uid`|the anonymous Firebase UID (needed before stamps or archive can load)|
|`ready`|true once auth has resolved|
|`initAuth()`|calls signInAnonymously and subscribes to onAuthStateChanged to keep the store in sync|
**stampStore.js**
|Variable/Function|Purpose|
|-----------------|-------|
|`stampsStore`| array of earned passport stamps (populated on load, appended to live when a new stamp is earned)|
**globalCountStore.js**
|Variable/Function|Purpose|
|-----------------|-------|
|`globalCount`| total messages ever posted worldwide|
|`refreshGlobalCount()`|fetches from meta/stats in Firestore and updates the store|
## How different data is stored in Firestore
**Messages**
```
authorId: "T9R85su4FvUII22QMUUljK0jyy33" //(string)
createdAt: May 17, 2026 at 7:34:03PM UTC+9 //(timestamp)
echoCount: 0 //(int64)
geohash: "wy6wfhcqd" //(string)
imageUrl: "" //(string)
lastEchoAt: May 17, 2026 at 7:34:03PM UTC+9 //(timestamp)
lat: 36.370018 //(double)
lng: 127.355324 //(double)
moodColor: "hsl(40, 60%, 72%)" //(string)
text: "cocumentation example" //(string)
```
To recreate this project with the same configuration:
```sh
# recreate this project
npx sv@0.15.3 create --template minimal --no-types --install npm overheard
**Meta Stats**
```
id: "" //(string)
totalMessagesEverPosted: 0 //(int64)
```
**Users**
```
uid: "" //(string)
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```sh
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
**Stamps**
```
## Building
To create a production version of your app:
```sh
npm run build
geohash4: "precision-4 geohash" //(string)
city: "" //(string)
country: "" //(string)
iconId: "" //(string)
color: "hsl(...)" //(string)
earnedAt: May 17, 2026 at 7:34:03PM UTC+9 //(timestamp)
```
## Whats in the .env file
You can preview the production build with `npm run preview`.
| Variable | Description | Where I found |
| --- | --- | --- |
| `PUBLIC_FIREBASE_API_KEY` | Firebase Web API key | Firebase Console → Project Settings → Your apps |
| `PUBLIC_FIREBASE_AUTH_DOMAIN` | Firebase Auth domain | Same — `{projectId}.firebaseapp.com` |
| `PUBLIC_FIREBASE_PROJECT_ID` | Firebase project ID | Same |
| `PUBLIC_FIREBASE_STORAGE_BUCKET` | Firebase Storage bucket | Same — `{projectId}.appspot.com` |
| `PUBLIC_FIREBASE_MESSAGING_SENDER_ID` | Firebase messaging sender ID | Same |
| `PUBLIC_FIREBASE_APP_ID` | Firebase app ID | Same |
| `PUBLIC_MAPS_KEY` | Google Maps JavaScript API key (also used for the Geocoding REST API) | Google Cloud Console → APIs & Services → Credentials |
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
# Opportunities for improvement
- The current decay logic works by simply not rendering the messages which are past the 30 day threshold after their last echo. However, these messaages never get deleted from the Firestore which means they are never truly gone.
- The map does not update as the user is moving. Currently the app only calculates messages and location on open or on refresh. Making it so the app updates location as the user is moving could add to the interacton significantly.
- The current face detection was utilized using a model trained on close range faces and works best on selfies, thus currently some mid to far range pictures with faces can bypass this check.
# Resource auknoledgement
Below is a comprhensive list of everything utilized to being Overheard to life (including links and usage notes)
**Google Maps JavaScript API**
[https://developers.google.com/maps/documentation/javascript/overview](https://developers.google.com/maps/documentation/javascript/overview)
renders the interactive map, places custom pins (via `google.maps.Marker` with SVG icons), handles click interactions, and applies the custom desaturated/parchment visual styling via the `styles` array.
**Google Maps Geocoding API**
[https://developers.google.com/maps/documentation/geocoding/overview]([https://developers.google.com/maps/documentation/geocoding/overview])
Used for the stamps feature, converts a message's lat/lng coordinates into a city and country name when a user posts from a new region.
**@googlemaps/js-api-loader**
[https://www.npmjs.com/package/@googlemaps/js-api-loader]([https://www.npmjs.com/package/@googlemaps/js-api-loader])
A library used to cleanly load the Google Maps JavaScript API script asynchronously inside Svelte's `onMount`.
**Firebase Firestore**
[https://firebase.google.com/docs/firestore](https://firebase.google.com/docs/firestore)
Stores all message documents (text, coordinates, geohash, timestamps, echo data, mood colors), the global message counter, and per-user stamp collections.
**Firebase Storage**
[https://firebase.google.com/docs/storage](https://firebase.google.com/docs/storage)
Stores uploaded images attached to messages. Files are uploaded here and the resulting download URLs are saved in Firestore message documents.
**Firebase Hosting**
[https://firebase.google.com/docs/hosting]([https://firebase.google.com/docs/hosting])
Hosts the built static SvelteKit app, providing the live `.web.app` deployment URL.
**Firebase Authentication (Anonymous)**
[https://firebase.google.com/docs/auth/web/anonymous-auth](https://firebase.google.com/docs/auth/web/anonymous-auth)
Gives every device a persistent, anonymous user ID without requiring registration. Used to link messages to a device for the archive page and stamps, preserving the app's anonymous design.
**ngeohash**
[https://www.npmjs.com/package/ngeohash](https://www.npmjs.com/package/ngeohash)
Converts coordinates to geohash strings and back (`encode`, `decode_bbox`). This powers the proximity-based "nearby messages" queries.
**@mediapipe/tasks-vision (BlazeFace)**
[https://ai.google.dev/edge/mediapipe/solutions/vision/face_detector/web_js](https://ai.google.dev/edge/mediapipe/solutions/vision/face_detector/web_js)
Google's face detection model. Used to scan images before upload and block any containing faces.
**lucide-react**
[https://lucide.dev/](https://lucide.dev/)
Outline icon set used throughout the UI( navigation bar icons, stamp icons, and other interface elements).
**qrcode**
[https://www.npmjs.com/package/qrcode](https://www.npmjs.com/package/qrcode)
Generates QR codes for the share feature, allowing any message to be turned into a scannable code that links back to that specific message.
**SvelteKit / Svelte 5**
[https://svelte.dev/docs/kit/introduction](https://svelte.dev/docs/kit/introduction)
The application framework, Svelte 5's runes mode (`$state`, `$derived`, `$effect`, `$props`) is used throughout for reactive state management, and SvelteKit provides routing (map page, archive page) and the build pipeline.
**@sveltejs/adapter-static**
[https://www.npmjs.com/package/@sveltejs/adapter-static](https://www.npmjs.com/package/@sveltejs/adapter-static)
Configures SvelteKit to build as a fully static site, which is what allows the app to be deployed to Firebase Hosting.
- Claude (coding assistance, debugging, feature implementation)
- Copilot (coding assistance)
- Further documentation on AI Prompts along with additional work documentation can be found in the following notion link [https://quilted-recorder-3b4.notion.site/Final-Project-ongoing-documentation-370f941b5e518013a8a7dd703d7dbe22?source=copy_link](https://quilted-recorder-3b4.notion.site/Final-Project-ongoing-documentation-370f941b5e518013a8a7dd703d7dbe22?source=copy_link)
- Course notes (ID 30011)
- Referenced code and materials from my own Individual project from earlier in the semester

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

BIN
src/lib/assets/open app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 409 KiB

View File

@@ -16,7 +16,7 @@ export function encode(lat, lng) {
// other way around (a prefix LONGER than the stored geohash's 9 characters
// would never match anything - same failure mode as before).
export function getQueryPrefix(lat, lng) {
return ngeohash.encode(lat, lng, 6);
return ngeohash.encode(lat, lng, 7);
}
// --- geohash cell-boundary problem ----------------------------------------