# Project export: MakerSafe

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: CruzHacks 2026
- Tagline: MakerSafe aims to make makerspaces safer and more productive for hobbyists by detecting safety violations and providing a dashboard to track machine availability. Build confidently with MakerSafe.
- Devpost: https://devpost.com/software/makersafe
- GitHub: https://github.com/justinsiek/Maker-Safe
- Video: https://www.youtube.com/embed/fLAjZmzKzr8?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner ([Sponsor - Viam] Best Use of Viam; Best Hardware Hack – Powered by ROBOTIS, Joby Aviation, OpenMV, PCBWay)
- Team: 3 GitHub contributor(s) — thinhtn3 (33 commits), Yash Khot (6 commits), Pranav Popuri (2 commits)

## Devpost submission (written by the team)

### Overview

Problem Statement Every year, thousands of eager tinkerers and enthusiasts enter makerspaces to gain access to powerful tools and machinery. Unfortunately, these tools are as dangerous as they are powerful, so it is imperative safety standards are respected in order to maintain a safe makerspace. Additionally, time is often of the essence for tinkerers on a project deadline, so we sought to create a informative and safety-first dashboard and monitoring system for makerspaces. Project Summary MakerSafe is an intelligent safety monitoring platform that brings computer vision to the makerspace, creating a seamless bridge between human creativity and machine vigilance. At its core, MakerSafe tackles a critical challenge faced by makerspaces worldwide: ensuring safety compliance without disrupting the creative flow. Using a network of cameras powered by real-time face recognition and object detection, the system automatically identifies makers as they enter the space, tracks their activity across workstations, and continuously monitors for safety violations, all without requiring manual check-ins or intrusive oversight. Built to run efficiently on a single Raspberry Pi with multiple camera inputs, MakerSafe demonstrates that advanced safety infrastructure doesn't require enterprise-level hardware. The architecture is designed for scalability, supporting additional stations and cameras as makerspaces grow. Breakdown of Components MakerSafe has 3 main UI components: a map of the entire makerspace, which displays who is using each machine, a list of the current actively working Makers and their status (idle, active, or currently violating safety code), and a list of the most recent violations of safety across all Makers belonging to the makerspace. For hardware, MakerSafe uses a Raspberry Pi 4 8GB RAM and Logitech Brio 101 Webcams. How We Built It Backend: Built with Python Flask, the backend served as the central orchestration layer for MakerSafe, handling communication between both hardware and software clients. It processed HTTP requests from VIAM’s platform on the hardware side and interacted with PostgreSQL via Supabase to manage application state, including maker records and station availability. Additionally, the backend maintained a WebSocket connection with the software client, enabling the server to push real-time updates for live UI rendering with low latency. Frontend: Built with React and Tailwind CSS, the frontend leveraged shadcn/ui as the core component system, with Framer Motion used for custom animations. Visual effect components were selectively incorporated from Magic UI and React Bits to enhance interactivity. v0 and Gemini were used early in development to generate wireframes and design inspiration, accelerating initial prototyping. Challenges We Ran Into Raspberry pi (flashing it, wifi, getting hot, handling processing 4 CV models on it) Custom CV model training Maintaining readable code Accomplishments That We're Proud Of First hack involving hardware for all team members. Real-time integration between hardware and software using WebSockets. Using Viam's Python SDK to manage hardware. What We Learned Networking/WebSockets Data flows Planning/prompt engineering What's Next For MakerSafe Optimizing to run on Raspberry Pi (upgrading to Pi 5, efficiently processing images) Supporting multiple stations Improve system latency (networking) Improve CV model quality

## README (from the GitHub repository)

# MakerSafe

Every year, thousands of eager tinkerers and enthusiasts enter makerspaces to gain access to powerful tools and machinery. Unfortunately, these tools are as dangerous as they are powerful, so it is imperative safety standards are respected in order to maintain a safe makerspace. Additionally, time is often of the essence for tinkerers on a project deadline, so we sought to create a informative and safety-first dashboard and monitoring system for makerspaces.

See more at: https://devpost.com/software/makersafe


## Detected evidence (automated analysis)

Indexed codebase: 51 recognized source files, 210 KB.
- CSS (language) — detected in the code
- Flask (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Supabase (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- PostgreSQL (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (60 of 60)

```
.DS_Store
client/.gitignore
client/.stylelintrc.json
client/components.json
client/eslint.config.js
client/index.html
client/jsconfig.json
client/package.json
client/README.md
client/src/App.css
client/src/App.jsx
client/src/assets/turtle
client/src/components/Clock.jsx
client/src/components/DecryptedText.jsx
client/src/components/LiveAlert.jsx
client/src/components/Makers.jsx
client/src/components/MapComponent.jsx
client/src/components/MapLayout.jsx
client/src/components/Nav.jsx
client/src/components/Shuffle.jsx
client/src/components/ui/accordion.jsx
client/src/components/ui/animated-list.jsx
client/src/components/ui/button.jsx
client/src/components/ui/card-hover-effect.jsx
client/src/components/ui/card.jsx
client/src/components/ui/container-scroll-animation.jsx
client/src/components/ui/pointer-highlight.jsx
client/src/components/ui/safari.jsx
client/src/components/ui/sticky-banner.jsx
client/src/components/ui/text-animate.jsx
client/src/components/ui/typing-animation.jsx
client/src/components/Violation.jsx
client/src/index.css
client/src/lib/utils.js
client/src/main.jsx
client/src/page/Dashboard.jsx
client/src/page/Home.jsx
client/src/page/Login.jsx
client/tailwind.config.js
client/vite.config.js
README.md
server/__init__.py
server/.gitignore
server/config.py
server/login/__init__.py
server/login/routes.py
server/logout/__init__.py
server/logout/routes.py
server/requirements.txt
server/server.py
server/station/__init__.py
server/station/routes.py
server/test_websocket.html
server/violation/__init__.py
server/violation/routes.py
specs/dbschema.md
specs/prd.md
viam/combined.py
viam/loginreq.py
viam/station.py
```

### Dependencies

- client/package.json: @eslint/js@^9.39.1, @gsap/react@^2.1.2, @radix-ui/react-accordion@^1.2.12, @radix-ui/react-slot@^1.2.4, @tailwindcss/vite@^4.1.18, @types/react@^19.2.5, @types/react-dom@^19.2.3, @vitejs/plugin-react@^5.1.1, aceternity@^1.0.1, autoprefixer@^10.4.23, class-variance-authority@^0.7.1, clsx@^2.1.1, eslint@^9.39.1, eslint-plugin-react-hooks@^7.0.1, eslint-plugin-react-refresh@^0.4.24, framer-motion@^12.26.2, globals@^16.5.0, gsap@^3.14.2, lucide-react@^0.562.0, motion@^12.26.2, postcss@^8.5.6, react@^19.2.0, react-bits@^1.0.5, react-dom@^19.2.0, react-router-dom@^7.12.0, shadcn-ui@^0.9.5, socket.io-client@^4.8.3, tailwind-merge@^3.4.0, tailwindcss@^4.1.18, tailwindcss-animate@^1.0.7, vite@^7.2.4
- server/requirements.txt: flask, flask-cors, flask-socketio, python-dotenv, supabase

### Recent commits (newest first)

- Update README.md
- Merge branch 'main' of https://github.com/justinsiek/Maker-Safe
- finis
- Added project title to README.md
- Added README.md
- added cooldown after leave
- updated some frontend component and idk
- fixed station_enter service
- updated padding of footer
- added function to prevent users from entering station without checking in
- updated login route to toggle login/leave
- updated navbar
- fix cooldown
- fixed cooldown
- fds
- login page + dashpage component
- asdf
- added logo to tab and updated nav title
- logo on login
- added logo to navbar

## Key source files (fetched from GitHub, selected and truncated for size)

### specs/dbschema.md

```markdown
-- -------------------------------------------------------------------
-- Makers
-- -------------------------------------------------------------------
create table if not exists public.makers (
  id uuid primary key default gen_random_uuid(),
  display_name text not null,
  external_label text not null unique,
  created_at timestamptz not null default now()
);

-- -------------------------------------------------------------------
-- Stations
-- -------------------------------------------------------------------
create table if not exists public.stations (
  id uuid primary key default gen_random_uuid(),
  name text not null,
  created_at timestamptz not null default now()
);

-- -------------------------------------------------------------------
-- Cameras (maps multiple webcams on the single Raspberry Pi)
-- -------------------------------------------------------------------
create table if not exists public.cameras (
  id uuid primary key default gen_random_uuid(),
  camera_key text not null unique,          -- e.g. 'login_cam_1', 'station_cam_1'
  role text not null,                       -- 'login' or 'station'
  station_id uuid null references public.stations(id) on delete set null,
  device_path text null,                    -- optional: '/dev/video0'
  created_at timestamptz not null default now(),
  constraint cameras_role_check check (role in ('login', 'station'))
);

-- -------------------------------------------------------------------
-- Maker Status (LIVE state; if a maker is offline, no row exists)
-- -------------------------------------------------------------------
create table if not exists public.maker_status (
  maker_id uuid primary key references public.makers(id) on delete cascade,
  status text not null,                     -- 'idle' | 'active' | 'violation'
  station_id uuid null references public.stations(id) on delete set null,
  updated_at timestamptz not null default now(),
  constraint maker_status_status_check check (status in ('idle', 'active', 'violation'))
);

-- -------------------------------------------------------------------
-- Station Status (LIVE state)
-- -------------------------------------------------------------------
create table if not exists public.station_status (
  station_id uuid primary key references public.stations(id) on delete cascade,
  in_use bool not null,
  active_maker_id uuid null references public.makers(id) on delete set null,
  updated_at timestamptz not null default now(),
  constraint station_status_status_check check (status in ('idle', 'in_use', 'violation'))
);

-- -------------------------------------------------------------------
-- Violations (HISTORY)
-- -------------------------------------------------------------------
create table if not exists public.violations (
  id uuid primary key default gen_random_uuid(),
  maker_id uuid not null references public.makers(id) on delete cascade,
  station_id uuid not null references public.stations(id) on delete cascade,
  camera
[truncated — 762 more characters]
```

### specs/prd.md

```markdown

# MakerSafe PRD (Hackathon MVP)

## Problem Statement

Every year, thousands of students, hobbyists, and tinkerers use makerspaces. They’re a great resource for people to get their hands dirty in the world of woodworking, metalworking, and robotics. Unfortunately, most makerspaces currently have two big problems that slow productivity and put makers at risk of serious injury:

1. **Limited safety monitoring/enforcement** — staff can’t watch every station at all times.
2. **Unclear machine availability** — it’s not always obvious which tools/stations are currently in use.

---

## Project Summary

**MakerSafe** is a real-time makerspace safety dashboard that uses computer vision to (1) identify makers as they enter the space and (2) monitor tool/station usage for safety violations.

* When a maker walks past the **login camera**, the system recognizes their face and adds them to the live dashboard as **Idle**.
* When they step into the **station camera** frame, the station is marked **In Use**, the maker is marked **Active**, and the system continuously checks for required safety gear (MVP: **goggles worn properly**).
* If goggles are not detected, the maker and station flip to a **Violation** state and a violation record is logged with a snapshot image.

The makerspace has **one shared admin dashboard screen** (single-page UI). There is no real authentication; the dashboard uses a simple shared “fake auth” login (**username == username** and **password == password**). The system is designed for **1 station now**, but the DB schema and APIs support adding more stations later.

**Hardware note:** Multiple webcams (login + station) are connected to a **single Raspberry Pi** (one Pi total). **Face recognition runs on both the login camera and the station camera.**

---

## Goals and Non-Goals

### Goals

* Show a live, single-screen admin view of:

  * Makers currently present (**Idle / Active / Violation**)
  * Station status (**Idle / In Use / Violation**)
  * A violations list with **expandable image preview**
* Face-recognition “check-in” via login camera
* Station camera performs **face recognition** and enforces goggles rule immediately
* Real-time updates via **WebSockets** (no refresh required)
* Store violation images in **Supabase Storage** and reference them in the DB
* SenseCAP indicator reflects station state (gray/green/flashing red)

### Non-Goals (MVP)

* Real authentication / roles / per-user accounts
* Multi-person per station (assume **one person**)
* Maker certifications / gating access to tools
* Session tracking and analytics
* Notifications (SMS/email), audit exports, retention policies
* Device authentication / rate limiting / hardening
* Privacy/compliance workflows (consent, retention, etc.)

---

## Tech Stack

### Frontend

* **Vite + React**
* Single dashboard screen:

  * Makers list (cards with status color)
  * Stations list/map (1 now, N later)
  * Violations feed (expand to view snapshot image)

### Backend

* **Fla
[truncated — 4167 more characters]
```

### server/requirements.txt

```
flask
flask-cors
flask-socketio
supabase
python-dotenv

```

### client/package.json

```
{
  "name": "client",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "lint": "eslint .",
    "preview": "vite preview"
  },
  "dependencies": {
    "@gsap/react": "^2.1.2",
    "@radix-ui/react-accordion": "^1.2.12",
    "@radix-ui/react-slot": "^1.2.4",
    "@tailwindcss/vite": "^4.1.18",
    "aceternity": "^1.0.1",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "framer-motion": "^12.26.2",
    "gsap": "^3.14.2",
    "lucide-react": "^0.562.0",
    "motion": "^12.26.2",
    "react": "^19.2.0",
    "react-bits": "^1.0.5",
    "react-dom": "^19.2.0",
    "react-router-dom": "^7.12.0",
    "shadcn-ui": "^0.9.5",
    "socket.io-client": "^4.8.3",
    "tailwind-merge": "^3.4.0",
    "tailwindcss": "^4.1.18",
    "tailwindcss-animate": "^1.0.7"
  },
  "devDependencies": {
    "@eslint/js": "^9.39.1",
    "@types/react": "^19.2.5",
    "@types/react-dom": "^19.2.3",
    "@vitejs/plugin-react": "^5.1.1",
    "autoprefixer": "^10.4.23",
    "eslint": "^9.39.1",
    "eslint-plugin-react-hooks": "^7.0.1",
    "eslint-plugin-react-refresh": "^0.4.24",
    "globals": "^16.5.0",
    "postcss": "^8.5.6",
    "vite": "^7.2.4"
  }
}

```

### server/server.py

```python
from config import create_app, supabase
from login.routes import login_bp, set_socketio as set_login_socketio
from station.routes import station_bp, set_socketio as set_station_socketio
from violation.routes import violation_bp, set_socketio as set_violation_socketio
from logout.routes import logout_bp, set_socketio as set_logout_socketio
import os
from flask import jsonify
from flask_socketio import SocketIO

app = create_app()

# Initialize SocketIO with CORS support
socketio = SocketIO(app, cors_allowed_origins="*")

# Pass socketio instance to route modules
set_login_socketio(socketio)
set_station_socketio(socketio)
set_violation_socketio(socketio)
set_logout_socketio(socketio)

app.register_blueprint(login_bp)
app.register_blueprint(station_bp)
app.register_blueprint(violation_bp)
app.register_blueprint(logout_bp)

@app.route('/')
def index():
    """ A simple index route to confirm the server is running. """
    return "Flask server is running!"


@app.route('/state')
def get_state():
    """
    Get the current state of the makerspace.
    Returns all present makers, station statuses, and active violations.
    """
    if not supabase:
        return jsonify({"error": "Database connection not available"}), 500
    
    try:
        # Get all present makers (those with a maker_status entry)
        maker_status_response = supabase.table('maker_status').select(
            '*, makers(*)'
        ).execute()
        
        makers = []
        for ms in maker_status_response.data or []:
            maker_info = ms.get('makers', {})
            makers.append({
                "id": ms['maker_id'],
                "display_name": maker_info.get('display_name'),
                "external_label": maker_info.get('external_label'),
                "status": ms['status'],
                "station_id": ms.get('station_id'),
                "updated_at": ms['updated_at']
            })
        
        # Get all station statuses
        station_status_response = supabase.table('station_status').select(
            '*, stations(*)'
        ).execute()
        
        stations = []
        for ss in station_status_response.data or []:
            station_info = ss.get('stations', {})
            stations.append({
                "id": ss['station_id'],
                "name": station_info.get('name'),
                
                "active_maker_id": ss.get('active_maker_id'),
                "updated_at": ss['updated_at']
            })
        
        # Get all active (unresolved) violations
        violations_response = supabase.table('violations').select(
            '*, makers(*), stations(*)'
        ).is_('resolved_at', 'null').order('created_at', desc=True).execute()
        
        violations = []
        for v in violations_response.data or []:
            maker_info = v.get('makers', {})
            station_info = v.get('stations', {})
            violations.append({
                "id": v['id'],
                "maker_id": v['maker_id'],
                "maker_name": maker_info.get('display_name'),
                "station_id": v['station_id'],
                "station_name": station_info.get('name'),
                "violation_type": v['violation_type'],
                "image_url": v.get('image_url'),
                "created_at": v['created_at']
            })
        
        return jsonify({
            "makers": makers,
            "stations": stations,
            "violations": violations
        }), 200
        
    except Exception as e:
        return jsonify({"error": str(e)}), 500

@socketio.on('connect')
def handle_connect():
    print('Client connected')

@socketio.on('disconnect')
def handle_disconnect():
    print('Client disconnected')
 
if __name__ == '__main__':
    port = int(os.environ.get("PORT", 8080))
    print(f"Starting Flask server with WebSocket on port {port}...")
    socketio.run(app, debug=os.getenv('FLASK_DEBUG', 'True').lower() == 'true', host='0.0.0.0', port=port)
```

### client/src/main.jsx

```javascript
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <App />
  </StrictMode>,
)
```

### client/src/App.jsx

```javascript
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import Home from './page/Home'
import Dashboard from './page/Dashboard'
import Login from './page/Login'

export default function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/login" element={<Login />} />
      </Routes>
    </BrowserRouter>
  )
}
```

### client/vite.config.js

```javascript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
import path from 'path'

// https://vite.dev/config/
export default defineConfig({
  plugins: [react(), tailwindcss()],
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
})
```

### client/tailwind.config.js

```javascript
/** @type {import('tailwindcss').Config} */
export default {
  content: ["./index.html", "./src/**/*.{js,jsx,ts,tsx}"],
  theme: {
    extend: {
      fontFamily: {
        sans: ["Inter", "sans-serif"]
      },
      borderRadius: {
        sm: "4px",
        md: "8px",
        lg: "12px",
        xl: "16px"
      },
      boxShadow: {
        elev: "0 4px 12px rgba(0,0,0,0.08)"
      },
      transitionDuration: {
        fast: "150ms",
        normal: "250ms"
      }
    }
  },
  plugins: []
};
```

### client/index.html

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/src/assets/logo.png" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>MakerSafe</title>
    <!-- Import Inter from Google Fonts -->
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>
```

[35 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]