Spaces:
Running
Build a Cross‑Platform Time Clock App (Multi‑Company, Employees, Geotagging, Reports)
Browse filesYou are Cursor, acting as a senior full‑stack engineer. Generate a production‑ready application with clean, modular code, strong typing, and tests. Use modern, boring tech that deploys easily.
Tech Stack (preferred)
Frontend: Next.js 14 (App Router), TypeScript, React Server Components where appropriate
UI: Tailwind CSS + shadcn/ui + lucide-react icons
State: React Query (TanStack Query) for data fetching; Zod for validation
Auth: NextAuth (Auth.js) or Supabase Auth; support email+password first (can add OAuth later)
Backend: Next.js API routes or Route Handlers; if using Supabase, leverage Database + RLS
DB: PostgreSQL (Supabase). Use Prisma or Supabase SQL directly. Provide both options in code where feasible.
Background jobs: Next.js Route Handlers / Edge Functions as needed
Storage: Supabase storage for avatars/files
Reports/Export: server-side CSV + PDF (e.g., pdf-lib or @react-pdf/renderer)
Maps/Geotag: HTML5 Geolocation API in browser; store lat/long/accuracy; optional reverse geocode later
PWA: Enable PWA behavior for phone/tablet/desktop (installable, offline cache for clocking while offline)
Testing: Vitest + Playwright (critical flows). ESLint + Prettier
Deployment: Vercel for web + Supabase for DB
Core Requirements
Multi‑tenant: Create multiple Companies. Each company has many Employees. Users may belong to one or multiple companies with roles.
Two roles:
Admin: manage companies, employees, timesheets, run reports, export CSV/PDF, view profiles.
Employee (user-level): clock in/out, view/edit own times (if allowed), download own timesheet report.
Profiles:
Company profile: name, legal name, time zone, pay period (weekly/biweekly/semimonthly/monthly), rounding rules (e.g., 5/10/15 min), break policies, overtime rules, geofence (optional radius), business address.
Employee profile: first/last, email, phone, status (active/inactive), hire date, employee ID, role title, base location, pay type (hourly/salary just as label), optional avatar.
User account links to employees (so a user can be an employee in multiple companies via a join table). Admins are users with company-scoped admin role.
Time Tracking:
Clock In/Out with one tap; capture timestamp, device info, and geotag (latitude, longitude, accuracy in meters, optional address if available later), note (optional), method (web/pwa)
GPS Prompting: ask permission; if declined, still allow clocking but label entry as no_location with reason
Offline mode: queue entries locally when offline; sync on reconnect
Prevent double punches; warn if already clocked in
Breaks: start/stop break, or deduct auto-break per company policy
Manual adjustments: Admins can edit entries with an audit log (who/when/what changed)
Timezone correctness: store timestamps in UTC, display in company timezone
Geotagging / Geofencing:
Store lat/long/accuracy per punch
If company has a geofence (center+radius meters), flag entries outside_geofence
Show map preview pin on entry detail (use a simple static map tile or library; keep vendor-agnostic)
Reports:
Employee Self‑Report: date range filter; totals by day/week; export CSV/PDF
Admin Reports: by company, by employee, by date range; include raw entries and rollups (regular hours, overtime, breaks, flags for outside_geofence or missing punches). Export CSV/PDF
Profiles export: company-wise employee profile export when needed
Access Control:
RBAC with company scope. Employees only see their own data. Admins see their company’s data. A Platform SuperAdmin (env‑gated) can see all for support.
If using Supabase: enforce RLS by company_id and user_id joins
Auditing & Compliance:
Every create/update/delete on time entries recorded in audit_log with before/after JSON
Keep immutable punch history; allow corrections via new revisions tied to original entry
UX:
Clock screen with big buttons (Clock In / Start Break / End Break / Clock Out)
Clear status: “You are clocked in since HH:MM” with running duration
Responsive, mobile-first; tablet/kiosk friendly (PIN or QR login optional later)
Data Model (Postgres)
Use snake_case. Provide Prisma schema and raw SQL migrations.
Tables
users (id, email, password_hash or external, name, created_at)
companies (id, name, legal_name, timezone, pay_period, rounding_rule_minutes, auto_break_minutes, overtime_policy_json, geofence_center_lat, geofence_center_lng, geofence_radius_m, address_json, created_at)
company_users (id, company_id, user_id, role ENUM('ADMIN','EMPLOYEE'), created_at)
employees (id, company_id, user_id NULLABLE, employee_code, first_name, last_name, email, phone, status ENUM('ACTIVE','INACTIVE'), hire_date, role_title, base_location_json, pay_type ENUM('HOURLY','SALARY'), avatar_url, created_at)
time_entries (id, company_id, employee_id, type ENUM('CLOCK_IN','CLOCK_OUT','BREAK_START','BREAK_END'), occurred_at_utc timestamptz, client_tz, lat NUMERIC(9,6) NULL, lng NUMERIC(9,6) NULL, accuracy_m NUMERIC NULL, location_status ENUM('ok','no_location','outside_geofence') DEFAULT 'ok', note TEXT NULL, device_fingerprint TEXT NULL, source ENUM('web','pwa'), linked_session_id UUID NULL)
time_sessions (id, company_id, employee_id, started_at_utc, ended_at_utc NULL, total_seconds INT NULL, flags JSONB)
audit_log (id, actor_user_id, company_id, entity_type, entity_id, action ENUM('CREATE','UPDATE','DELETE'), before JSONB NULL, after JSONB NULL, created_at)
invites (id, company_id, email, role, token, expires_at, accepted_at)
Notes
Sessions are optional but useful to compute totals efficiently. Each CLOCK_IN…CLOCK_OUT pair forms a session; breaks are sub-intervals.
Keep all timestamps UTC, display in company tz via server utilities.
API Contract (Route Handlers)
Create typed handlers with Zod validation. Return JSON: { data, error }. All endpoints require auth; RBAC enforced.
POST /api/auth/register
POST /api/auth/login
POST /api/companies // create company (admin or superadmin)
GET /api/companies // list for current user
GET /api/companies/:id // details
PATCH/DELETE /api/companies/:id
POST /api/companies/:id/employees
GET /api/companies/:id/employees
GET /api/employees/:id
PATCH/DELETE /api/employees/:id
POST /api/time/punch // { employeeId, type, note, geolocation: {lat,lng,accuracy}, clientTz }
POST /api/time/sync-offline // batch punch payload from PWA queue
GET /api/time/entries?companyId&employeeId&from&to
GET /api/time/sessions?companyId&employeeId&from&to
GET /api/reports/employee?employeeId&from&to&format=csv|pdf
GET /api/reports/company?companyId&from&to&format=csv|pdf
GET /api/export/employees?companyId&format=csv
Include pagination where lists can be large. For reports, stream CSV when possible.
Geotagging Details
Use navigator.geolocation.getCurrentPosition with { enableHighAccuracy: true, timeout: 8000, maximumAge: 0 }
Store lat, lng, accuracy_m and a location_status:
ok if coords present and (if geofence set) inside radius
outside_geofence if coords present but outside
no_location if user denied or timeout
Geofence check: haversine distance vs. geofence_radius_m
Show a tiny map snapshot on punch detail (defer provider selection; keep abstraction)
Timesheet Logic
Rounding (configurable): apply to session boundaries (in/out) per company rule: 5, 10, or 15 min to nearest (with half-up behavior). Preserve raw times in DB; compute rounded on report
Overtime: support daily (8+), weekly (40+), configurable in overtime_policy_json
Breaks: subtract explicit break intervals; optional auto-deduct; flag if missing break per policy
Edge cases: unpaired punches -> flagged, surfaced in admin review
Pages / Screens
Auth: login/register, accept invite
Employee:
Clock (primary): big buttons, current status, last punch, geotag prompt, offline queue status
My Timesheet: date range, list of sessions, totals, export CSV/PDF
Profile: edit personal info (limited)
Admin:
Dashboard: company selector, KPIs (active employees today, outside-geofence flags, missing punches)
Companies: create/edit company settings
Employees: CRUD, invite flow, import CSV
Timesheets: filters (company, employee, date range), review & approve edits, bulk export
Reports: employee report and company summary with exports
Audit Log: searchable table
Settings: roles, pay periods, rounding, geofence, overtime, break policies
File Structure (example)
app/
(auth)/login/page.tsx
(auth)/register/page.tsx
dashboard/page.tsx
companies/[companyId]/settings/page.tsx
companies/[companyId]/employees/page.tsx
employees/[employeeId]/page.tsx
clock/page.tsx
timesheets/page.tsx
reports/page.tsx
api/
auth/register/route.ts
auth/login/route.ts
companies/route.ts
companies/[id]/route.ts
companies/[id]/employees/route.ts
employees/[id]/route.ts
time/punch/route.ts
time/sync-offline/route.ts
time/entries/route.ts
time/sessions/route.ts
reports/employee/route.ts
reports/company/route.ts
export/employees/route.ts
components/
ClockWidget.tsx
GeoPrompt.tsx
MapPreview.tsx
DataTable.tsx
DateRangePicker.tsx
RoleGate.tsx
lib/
auth.ts
db.ts (Prisma) or supabase.ts
rls.ts (helpers for RLS policies)
geofence.ts (haversine)
timecalc.ts (rounding, overtime)
csv.ts, pdf.ts
zod-schemas.ts
prisma/
schema.prisma
migrations/
Prisma Schema (excerpt)
Provide a full schema.prisma covering all tables above, relations, and enums. Generate types and migrations.
SQL (Supabase) — Tables & RLS (excerpt)
SQL to create tables, indexes, and enums
RLS policies enforcing:
users can read their own user row
company_users controls access; employee rows restricted by company membership
time_entries restricted to employees in same company; employees can read their own entries; admins can read company entries
Validation & Error States
Zod schemas for all payloads
Graceful fallbacks when geolocation fails (surface reason on UI)
Prevent duplicate punches within N minutes
PWA & Offline
Service worker caches core routes
Local queue for punches when offline;
- README.md +8 -5
- components/footer.js +142 -0
- components/navbar.js +124 -0
- index.html +108 -19
- script.js +17 -0
- style.css +35 -18
|
@@ -1,10 +1,13 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: TimeKeeper Pro ⏱️
|
| 3 |
+
colorFrom: gray
|
| 4 |
+
colorTo: yellow
|
| 5 |
+
emoji: 🐳
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
| 8 |
+
tags:
|
| 9 |
+
- deepsite-v3
|
| 10 |
---
|
| 11 |
|
| 12 |
+
# Welcome to your new DeepSite project!
|
| 13 |
+
This project was created with [DeepSite](https://huggingface.co/deepsite).
|
|
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class CustomFooter extends HTMLElement {
|
| 2 |
+
connectedCallback() {
|
| 3 |
+
this.attachShadow({ mode: 'open' });
|
| 4 |
+
this.shadowRoot.innerHTML = `
|
| 5 |
+
<style>
|
| 6 |
+
footer {
|
| 7 |
+
background-color: var(--secondary-700);
|
| 8 |
+
color: white;
|
| 9 |
+
padding: 3rem 0;
|
| 10 |
+
}
|
| 11 |
+
.container {
|
| 12 |
+
max-width: 1200px;
|
| 13 |
+
margin: 0 auto;
|
| 14 |
+
padding: 0 1rem;
|
| 15 |
+
display: grid;
|
| 16 |
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
| 17 |
+
gap: 2rem;
|
| 18 |
+
}
|
| 19 |
+
.footer-logo {
|
| 20 |
+
font-size: 1.5rem;
|
| 21 |
+
font-weight: 700;
|
| 22 |
+
margin-bottom: 1rem;
|
| 23 |
+
display: flex;
|
| 24 |
+
align-items: center;
|
| 25 |
+
gap: 0.5rem;
|
| 26 |
+
}
|
| 27 |
+
.footer-description {
|
| 28 |
+
color: var(--secondary-200);
|
| 29 |
+
margin-bottom: 1.5rem;
|
| 30 |
+
}
|
| 31 |
+
.social-links {
|
| 32 |
+
display: flex;
|
| 33 |
+
gap: 1rem;
|
| 34 |
+
}
|
| 35 |
+
.social-link {
|
| 36 |
+
color: white;
|
| 37 |
+
width: 36px;
|
| 38 |
+
height: 36px;
|
| 39 |
+
border-radius: 50%;
|
| 40 |
+
background-color: rgba(255,255,255,0.1);
|
| 41 |
+
display: flex;
|
| 42 |
+
align-items: center;
|
| 43 |
+
justify-content: center;
|
| 44 |
+
transition: background-color 0.2s;
|
| 45 |
+
}
|
| 46 |
+
.social-link:hover {
|
| 47 |
+
background-color: var(--primary-500);
|
| 48 |
+
}
|
| 49 |
+
.footer-heading {
|
| 50 |
+
font-weight: 600;
|
| 51 |
+
margin-bottom: 1.25rem;
|
| 52 |
+
font-size: 1.125rem;
|
| 53 |
+
}
|
| 54 |
+
.footer-links {
|
| 55 |
+
display: flex;
|
| 56 |
+
flex-direction: column;
|
| 57 |
+
gap: 0.75rem;
|
| 58 |
+
}
|
| 59 |
+
.footer-link {
|
| 60 |
+
color: var(--secondary-200);
|
| 61 |
+
text-decoration: none;
|
| 62 |
+
transition: color 0.2s;
|
| 63 |
+
}
|
| 64 |
+
.footer-link:hover {
|
| 65 |
+
color: white;
|
| 66 |
+
}
|
| 67 |
+
.copyright {
|
| 68 |
+
margin-top: 3rem;
|
| 69 |
+
padding-top: 1.5rem;
|
| 70 |
+
border-top: 1px solid rgba(255,255,255,0.1);
|
| 71 |
+
text-align: center;
|
| 72 |
+
color: var(--secondary-300);
|
| 73 |
+
}
|
| 74 |
+
@media (max-width: 768px) {
|
| 75 |
+
.container {
|
| 76 |
+
grid-template-columns: 1fr 1fr;
|
| 77 |
+
}
|
| 78 |
+
}
|
| 79 |
+
</style>
|
| 80 |
+
<footer>
|
| 81 |
+
<div class="container">
|
| 82 |
+
<div class="footer-col">
|
| 83 |
+
<div class="footer-logo">
|
| 84 |
+
<i data-feather="clock"></i>
|
| 85 |
+
TimeKeeper Pro
|
| 86 |
+
</div>
|
| 87 |
+
<p class="footer-description">
|
| 88 |
+
Modern time tracking solution for businesses of all sizes with geotagging and reporting.
|
| 89 |
+
</p>
|
| 90 |
+
<div class="social-links">
|
| 91 |
+
<a href="#" class="social-link"><i data-feather="twitter"></i></a>
|
| 92 |
+
<a href="#" class="social-link"><i data-feather="facebook"></i></a>
|
| 93 |
+
<a href="#" class="social-link"><i data-feather="linkedin"></i></a>
|
| 94 |
+
<a href="#" class="social-link"><i data-feather="github"></i></a>
|
| 95 |
+
</div>
|
| 96 |
+
</div>
|
| 97 |
+
<div class="footer-col">
|
| 98 |
+
<h4 class="footer-heading">Product</h4>
|
| 99 |
+
<div class="footer-links">
|
| 100 |
+
<a href="/features" class="footer-link">Features</a>
|
| 101 |
+
<a href="/pricing" class="footer-link">Pricing</a>
|
| 102 |
+
<a href="/solutions" class="footer-link">Solutions</a>
|
| 103 |
+
<a href="/updates" class="footer-link">Updates</a>
|
| 104 |
+
</div>
|
| 105 |
+
</div>
|
| 106 |
+
<div class="footer-col">
|
| 107 |
+
<h4 class="footer-heading">Resources</h4>
|
| 108 |
+
<div class="footer-links">
|
| 109 |
+
<a href="/blog" class="footer-link">Blog</a>
|
| 110 |
+
<a href="/guides" class="footer-link">Guides</a>
|
| 111 |
+
<a href="/support" class="footer-link">Support</a>
|
| 112 |
+
<a href="/api" class="footer-link">API</a>
|
| 113 |
+
</div>
|
| 114 |
+
</div>
|
| 115 |
+
<div class="footer-col">
|
| 116 |
+
<h4 class="footer-heading">Company</h4>
|
| 117 |
+
<div class="footer-links">
|
| 118 |
+
<a href="/about" class="footer-link">About Us</a>
|
| 119 |
+
<a href="/careers" class="footer-link">Careers</a>
|
| 120 |
+
<a href="/contact" class="footer-link">Contact</a>
|
| 121 |
+
<a href="/legal" class="footer-link">Legal</a>
|
| 122 |
+
</div>
|
| 123 |
+
</div>
|
| 124 |
+
</div>
|
| 125 |
+
<div class="container">
|
| 126 |
+
<div class="copyright">
|
| 127 |
+
© ${new Date().getFullYear()} TimeKeeper Pro. All rights reserved.
|
| 128 |
+
</div>
|
| 129 |
+
</div>
|
| 130 |
+
</footer>
|
| 131 |
+
`;
|
| 132 |
+
|
| 133 |
+
// Replace Feather icons
|
| 134 |
+
setTimeout(() => {
|
| 135 |
+
if (window.feather) {
|
| 136 |
+
window.feather.replace({ class: 'feather-inline' });
|
| 137 |
+
}
|
| 138 |
+
}, 100);
|
| 139 |
+
}
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
customElements.define('custom-footer', CustomFooter);
|
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class CustomNavbar extends HTMLElement {
|
| 2 |
+
connectedCallback() {
|
| 3 |
+
this.attachShadow({ mode: 'open' });
|
| 4 |
+
this.shadowRoot.innerHTML = `
|
| 5 |
+
<style>
|
| 6 |
+
.navbar {
|
| 7 |
+
background-color: white;
|
| 8 |
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
| 9 |
+
padding: 1rem 0;
|
| 10 |
+
}
|
| 11 |
+
.container {
|
| 12 |
+
max-width: 1200px;
|
| 13 |
+
margin: 0 auto;
|
| 14 |
+
padding: 0 1rem;
|
| 15 |
+
display: flex;
|
| 16 |
+
justify-content: space-between;
|
| 17 |
+
align-items: center;
|
| 18 |
+
}
|
| 19 |
+
.logo {
|
| 20 |
+
font-size: 1.5rem;
|
| 21 |
+
font-weight: 700;
|
| 22 |
+
color: var(--primary-600);
|
| 23 |
+
text-decoration: none;
|
| 24 |
+
display: flex;
|
| 25 |
+
align-items: center;
|
| 26 |
+
gap: 0.5rem;
|
| 27 |
+
}
|
| 28 |
+
.nav-links {
|
| 29 |
+
display: flex;
|
| 30 |
+
gap: 1.5rem;
|
| 31 |
+
align-items: center;
|
| 32 |
+
}
|
| 33 |
+
.nav-link {
|
| 34 |
+
color: var(--secondary-600);
|
| 35 |
+
text-decoration: none;
|
| 36 |
+
font-weight: 500;
|
| 37 |
+
transition: color 0.2s;
|
| 38 |
+
}
|
| 39 |
+
.nav-link:hover {
|
| 40 |
+
color: var(--primary-600);
|
| 41 |
+
}
|
| 42 |
+
.nav-link.active {
|
| 43 |
+
color: var(--primary-600);
|
| 44 |
+
font-weight: 600;
|
| 45 |
+
}
|
| 46 |
+
.btn-primary {
|
| 47 |
+
background-color: var(--primary-600);
|
| 48 |
+
color: white;
|
| 49 |
+
padding: 0.5rem 1rem;
|
| 50 |
+
border-radius: 0.375rem;
|
| 51 |
+
font-weight: 500;
|
| 52 |
+
transition: background-color 0.2s;
|
| 53 |
+
}
|
| 54 |
+
.btn-primary:hover {
|
| 55 |
+
background-color: var(--primary-700);
|
| 56 |
+
}
|
| 57 |
+
.mobile-menu-btn {
|
| 58 |
+
display: none;
|
| 59 |
+
background: none;
|
| 60 |
+
border: none;
|
| 61 |
+
color: var(--secondary-600);
|
| 62 |
+
cursor: pointer;
|
| 63 |
+
}
|
| 64 |
+
@media (max-width: 768px) {
|
| 65 |
+
.nav-links {
|
| 66 |
+
display: none;
|
| 67 |
+
flex-direction: column;
|
| 68 |
+
position: absolute;
|
| 69 |
+
top: 70px;
|
| 70 |
+
left: 0;
|
| 71 |
+
right: 0;
|
| 72 |
+
background-color: white;
|
| 73 |
+
padding: 1rem;
|
| 74 |
+
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
| 75 |
+
}
|
| 76 |
+
.nav-links.open {
|
| 77 |
+
display: flex;
|
| 78 |
+
}
|
| 79 |
+
.mobile-menu-btn {
|
| 80 |
+
display: block;
|
| 81 |
+
}
|
| 82 |
+
}
|
| 83 |
+
</style>
|
| 84 |
+
<nav class="navbar">
|
| 85 |
+
<div class="container">
|
| 86 |
+
<a href="/" class="logo">
|
| 87 |
+
<i data-feather="clock"></i>
|
| 88 |
+
TimeKeeper Pro
|
| 89 |
+
</a>
|
| 90 |
+
|
| 91 |
+
<button class="mobile-menu-btn" id="mobileMenuBtn">
|
| 92 |
+
<i data-feather="menu"></i>
|
| 93 |
+
</button>
|
| 94 |
+
|
| 95 |
+
<div class="nav-links" id="navLinks">
|
| 96 |
+
<a href="/features" class="nav-link">Features</a>
|
| 97 |
+
<a href="/pricing" class="nav-link">Pricing</a>
|
| 98 |
+
<a href="/about" class="nav-link">About</a>
|
| 99 |
+
<a href="/contact" class="nav-link">Contact</a>
|
| 100 |
+
<a href="/login" class="nav-link">Login</a>
|
| 101 |
+
<a href="/register" class="btn-primary">Get Started</a>
|
| 102 |
+
</div>
|
| 103 |
+
</div>
|
| 104 |
+
</nav>
|
| 105 |
+
`;
|
| 106 |
+
|
| 107 |
+
// Initialize mobile menu toggle
|
| 108 |
+
const mobileMenuBtn = this.shadowRoot.getElementById('mobileMenuBtn');
|
| 109 |
+
const navLinks = this.shadowRoot.getElementById('navLinks');
|
| 110 |
+
|
| 111 |
+
mobileMenuBtn.addEventListener('click', () => {
|
| 112 |
+
navLinks.classList.toggle('open');
|
| 113 |
+
});
|
| 114 |
+
|
| 115 |
+
// Replace Feather icons
|
| 116 |
+
setTimeout(() => {
|
| 117 |
+
if (window.feather) {
|
| 118 |
+
window.feather.replace({ class: 'feather-inline' });
|
| 119 |
+
}
|
| 120 |
+
}, 100);
|
| 121 |
+
}
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
customElements.define('custom-navbar', CustomNavbar);
|
|
@@ -1,19 +1,108 @@
|
|
| 1 |
-
<!
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>TimeKeeper Pro | Modern Time Tracking</title>
|
| 7 |
+
<meta name="description" content="Cross-platform time clock app with geotagging and reporting">
|
| 8 |
+
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
|
| 9 |
+
<link rel="stylesheet" href="style.css">
|
| 10 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 11 |
+
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
|
| 12 |
+
<script src="https://unpkg.com/feather-icons"></script>
|
| 13 |
+
<script>
|
| 14 |
+
tailwind.config = {
|
| 15 |
+
theme: {
|
| 16 |
+
extend: {
|
| 17 |
+
colors: {
|
| 18 |
+
primary: {
|
| 19 |
+
50: '#f0f9ff',
|
| 20 |
+
100: '#e0f2fe',
|
| 21 |
+
500: '#3b82f6',
|
| 22 |
+
600: '#2563eb',
|
| 23 |
+
700: '#1d4ed8',
|
| 24 |
+
},
|
| 25 |
+
secondary: {
|
| 26 |
+
50: '#f8fafc',
|
| 27 |
+
100: '#f1f5f9',
|
| 28 |
+
500: '#64748b',
|
| 29 |
+
600: '#475569',
|
| 30 |
+
700: '#334155',
|
| 31 |
+
}
|
| 32 |
+
}
|
| 33 |
+
}
|
| 34 |
+
}
|
| 35 |
+
}
|
| 36 |
+
</script>
|
| 37 |
+
</head>
|
| 38 |
+
<body class="bg-gray-50 min-h-screen">
|
| 39 |
+
<custom-navbar></custom-navbar>
|
| 40 |
+
|
| 41 |
+
<main class="container mx-auto px-4 py-8">
|
| 42 |
+
<section class="hero bg-primary-50 rounded-xl p-8 mb-12">
|
| 43 |
+
<div class="flex flex-col md:flex-row items-center gap-8">
|
| 44 |
+
<div class="md:w-1/2">
|
| 45 |
+
<h1 class="text-4xl md:text-5xl font-bold text-primary-700 mb-4">Time Tracking Made Simple</h1>
|
| 46 |
+
<p class="text-lg text-secondary-600 mb-6">Geotagged clock-in/out, multi-company support, and powerful reporting for businesses of all sizes.</p>
|
| 47 |
+
<div class="flex gap-4">
|
| 48 |
+
<a href="/login" class="bg-primary-600 hover:bg-primary-700 text-white px-6 py-3 rounded-lg font-medium shadow-md transition-colors">
|
| 49 |
+
Get Started
|
| 50 |
+
</a>
|
| 51 |
+
<a href="/features" class="border border-primary-600 text-primary-600 hover:bg-primary-50 px-6 py-3 rounded-lg font-medium transition-colors">
|
| 52 |
+
Learn More
|
| 53 |
+
</a>
|
| 54 |
+
</div>
|
| 55 |
+
</div>
|
| 56 |
+
<div class="md:w-1/2">
|
| 57 |
+
<img src="http://static.photos/technology/1024x576/42" alt="Time tracking dashboard" class="rounded-lg shadow-xl">
|
| 58 |
+
</div>
|
| 59 |
+
</div>
|
| 60 |
+
</section>
|
| 61 |
+
|
| 62 |
+
<section class="features mb-16">
|
| 63 |
+
<h2 class="text-3xl font-bold text-center mb-12 text-secondary-700">Key Features</h2>
|
| 64 |
+
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
|
| 65 |
+
<div class="feature-card bg-white p-6 rounded-xl shadow-md hover:shadow-lg transition-shadow">
|
| 66 |
+
<div class="bg-primary-100 w-12 h-12 rounded-full flex items-center justify-center mb-4">
|
| 67 |
+
<i data-feather="clock" class="text-primary-600"></i>
|
| 68 |
+
</div>
|
| 69 |
+
<h3 class="text-xl font-semibold mb-2">One-Tap Clocking</h3>
|
| 70 |
+
<p class="text-secondary-600">Simple in/out interface with geotagging and offline support.</p>
|
| 71 |
+
</div>
|
| 72 |
+
<div class="feature-card bg-white p-6 rounded-xl shadow-md hover:shadow-lg transition-shadow">
|
| 73 |
+
<div class="bg-primary-100 w-12 h-12 rounded-full flex items-center justify-center mb-4">
|
| 74 |
+
<i data-feather="users" class="text-primary-600"></i>
|
| 75 |
+
</div>
|
| 76 |
+
<h3 class="text-xl font-semibold mb-2">Multi-Company</h3>
|
| 77 |
+
<p class="text-secondary-600">Manage multiple businesses with role-based access control.</p>
|
| 78 |
+
</div>
|
| 79 |
+
<div class="feature-card bg-white p-6 rounded-xl shadow-md hover:shadow-lg transition-shadow">
|
| 80 |
+
<div class="bg-primary-100 w-12 h-12 rounded-full flex items-center justify-center mb-4">
|
| 81 |
+
<i data-feather="bar-chart-2" class="text-primary-600"></i>
|
| 82 |
+
</div>
|
| 83 |
+
<h3 class="text-xl font-semibold mb-2">Advanced Reporting</h3>
|
| 84 |
+
<p class="text-secondary-600">Export CSV/PDF reports with overtime, breaks, and geofencing.</p>
|
| 85 |
+
</div>
|
| 86 |
+
</div>
|
| 87 |
+
</section>
|
| 88 |
+
|
| 89 |
+
<section class="cta bg-primary-600 text-white rounded-xl p-8 text-center">
|
| 90 |
+
<h2 class="text-3xl font-bold mb-4">Ready to Simplify Your Time Tracking?</h2>
|
| 91 |
+
<p class="text-primary-100 mb-6 max-w-2xl mx-auto">Join thousands of businesses using TimeKeeper Pro to manage their workforce efficiently.</p>
|
| 92 |
+
<a href="/register" class="bg-white text-primary-600 hover:bg-gray-100 px-8 py-3 rounded-lg font-semibold inline-block shadow-md transition-colors">
|
| 93 |
+
Start Free Trial
|
| 94 |
+
</a>
|
| 95 |
+
</section>
|
| 96 |
+
</main>
|
| 97 |
+
|
| 98 |
+
<custom-footer></custom-footer>
|
| 99 |
+
|
| 100 |
+
<script src="components/navbar.js"></script>
|
| 101 |
+
<script src="components/footer.js"></script>
|
| 102 |
+
<script src="script.js"></script>
|
| 103 |
+
<script>
|
| 104 |
+
feather.replace();
|
| 105 |
+
</script>
|
| 106 |
+
<script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
|
| 107 |
+
</body>
|
| 108 |
+
</html>
|
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 2 |
+
// Initialize any global functionality here
|
| 3 |
+
|
| 4 |
+
// Example: Mobile menu toggle would be handled in the navbar component
|
| 5 |
+
|
| 6 |
+
// Smooth scrolling for anchor links
|
| 7 |
+
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
| 8 |
+
anchor.addEventListener('click', function(e) {
|
| 9 |
+
e.preventDefault();
|
| 10 |
+
document.querySelector(this.getAttribute('href')).scrollIntoView({
|
| 11 |
+
behavior: 'smooth'
|
| 12 |
+
});
|
| 13 |
+
});
|
| 14 |
+
});
|
| 15 |
+
|
| 16 |
+
// Initialize any global event listeners
|
| 17 |
+
});
|
|
@@ -1,28 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
body {
|
| 2 |
-
|
| 3 |
-
|
| 4 |
}
|
| 5 |
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
margin-top: 0;
|
| 9 |
}
|
| 10 |
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
margin-bottom: 10px;
|
| 15 |
-
margin-top: 5px;
|
| 16 |
}
|
| 17 |
|
| 18 |
-
.
|
| 19 |
-
|
| 20 |
-
margin: 0 auto;
|
| 21 |
-
padding: 16px;
|
| 22 |
-
border: 1px solid lightgray;
|
| 23 |
-
border-radius: 16px;
|
| 24 |
}
|
| 25 |
|
| 26 |
-
|
| 27 |
-
|
|
|
|
|
|
|
|
|
|
| 28 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
|
| 2 |
+
|
| 3 |
+
:root {
|
| 4 |
+
--primary-50: #f0f9ff;
|
| 5 |
+
--primary-100: #e0f2fe;
|
| 6 |
+
--primary-500: #3b82f6;
|
| 7 |
+
--primary-600: #2563eb;
|
| 8 |
+
--primary-700: #1d4ed8;
|
| 9 |
+
--secondary-50: #f8fafc;
|
| 10 |
+
--secondary-100: #f1f5f9;
|
| 11 |
+
--secondary-500: #64748b;
|
| 12 |
+
--secondary-600: #475569;
|
| 13 |
+
--secondary-700: #334155;
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
body {
|
| 17 |
+
font-family: 'Inter', sans-serif;
|
| 18 |
+
line-height: 1.6;
|
| 19 |
}
|
| 20 |
|
| 21 |
+
.hero {
|
| 22 |
+
background: linear-gradient(135deg, var(--primary-50) 0%, rgba(255,255,255,1) 100%);
|
|
|
|
| 23 |
}
|
| 24 |
|
| 25 |
+
.feature-card:hover {
|
| 26 |
+
transform: translateY(-4px);
|
| 27 |
+
transition: transform 0.2s ease;
|
|
|
|
|
|
|
| 28 |
}
|
| 29 |
|
| 30 |
+
.cta {
|
| 31 |
+
background: linear-gradient(135deg, var(--primary-500) 0%, var(--primary-700) 100%);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
}
|
| 33 |
|
| 34 |
+
/* Responsive typography */
|
| 35 |
+
@media (min-width: 768px) {
|
| 36 |
+
html {
|
| 37 |
+
font-size: 16px;
|
| 38 |
+
}
|
| 39 |
}
|
| 40 |
+
|
| 41 |
+
@media (max-width: 767px) {
|
| 42 |
+
html {
|
| 43 |
+
font-size: 14px;
|
| 44 |
+
}
|
| 45 |
+
}
|