polish: fix padding and spacing across all sections to match Pencil design

- FeatureCard: px-7 pt-8 pb-8 (design: padding [32,28]), h-full for equal heights
- HeroSection: py-20 on mobile, gap-20 between columns, screenshot 380px
- PrivacyBanner: py-14, gap-20, stat items min-w-[100px]
- SeeItLiveSection: py-24, max-w-[640px] header, block img
- FeaturesSection: gap-[72px] section, flex wrapper for equal-height cards
- ScreenshotsSection: lg:h-[340px] images, px-6 py-5 info, gap-2
- HowItWorksSection: gap-20 columns, gap-10 steps, divide-y billing items, p-10 card
- SupportSection: py-24, gap-16, flex wrappers for equal-height cards
- ContactSection: py-20, gap-20, p-9 form card, gap-5 fields
- AppFooter: pt-14 pb-7, border-b [#1E293B], pt-5 bottom bar
This commit is contained in:
kevin-asprec
2026-03-17 04:31:21 +08:00
parent 2340ae58c6
commit 6199f78cfb
11 changed files with 139 additions and 136 deletions

View File

@@ -1327,7 +1327,7 @@
"fill": { "fill": {
"type": "image", "type": "image",
"enabled": true, "enabled": true,
"url": "public/screenshots/rj2JV.png", "url": "Reports.jpg",
"mode": "fill" "mode": "fill"
}, },
"cornerRadius": [ "cornerRadius": [

View File

@@ -1,16 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
// Design: padding [56, 100, 28, 100], footerTop gap 64, fBrand width 260, fDiv #1E293B
const productLinks = [ const productLinks = [
{ label: 'Features', href: '#features' }, { label: 'Features', href: '#features' },
{ label: 'How It Works', href: '#how-it-works' }, { label: 'How It Works', href: '#how-it-works' },
{ label: 'Services', href: '#support' }, { label: 'Services', href: '#support' },
] ]
const companyLinks = [ const companyLinks = [
{ label: 'About Us', href: '#' }, { label: 'About Us', href: '#' },
{ label: 'Contact', href: '#contact' }, { label: 'Contact', href: '#contact' },
{ label: 'Blog', href: '#' }, { label: 'Blog', href: '#' },
] ]
const legalLinks = [ const legalLinks = [
{ label: 'Privacy Policy', href: '#' }, { label: 'Privacy Policy', href: '#' },
{ label: 'Terms of Service', href: '#' }, { label: 'Terms of Service', href: '#' },
@@ -18,20 +17,22 @@ const legalLinks = [
</script> </script>
<template> <template>
<footer class="bg-[#020617] text-white"> <footer class="bg-[#020617]">
<div class="page-container py-14 pb-7"> <div class="page-container pt-14 pb-7">
<!-- Top row: brand + nav columns --> <!-- Top row: fBrand 260 + spacer + 3 nav columns, gap 64 -->
<div class="flex flex-col md:flex-row gap-12 md:gap-16 pb-10 border-b border-[#1E293B]"> <div class="flex flex-col md:flex-row gap-16 pb-10 border-b border-[#1E293B]">
<!-- Brand column --> <!-- Brand column: gap 14, width 260 -->
<div class="flex flex-col gap-3.5 w-full md:w-[260px] shrink-0"> <div class="flex flex-col gap-3.5 w-full md:w-[260px] shrink-0">
<!-- Logo row: gap 8 -->
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div class="w-7 h-7 rounded-[6px] bg-[#1D4ED8] flex items-center justify-center shrink-0"> <div class="w-7 h-7 rounded-[6px] bg-[#1D4ED8] flex items-center justify-center shrink-0">
<span class="text-white font-bold text-sm leading-none">T</span> <span class="text-white font-bold text-sm leading-none">T</span>
</div> </div>
<span class="text-white font-bold text-base">TapTrack</span> <span class="text-white font-bold text-base">TapTrack</span>
</div> </div>
<!-- Tagline: 13px, lineHeight 1.65, max-w 220 -->
<p class="text-[#475569] text-[13px] leading-[1.65] max-w-[220px]"> <p class="text-[#475569] text-[13px] leading-[1.65] max-w-[220px]">
On-premises NFC attendance for Philippine schools. Student data stays where it belongs with you. On-premises NFC attendance for Philippine schools. Student data stays where it belongs with you.
</p> </p>
@@ -40,10 +41,9 @@ const legalLinks = [
<!-- Spacer --> <!-- Spacer -->
<div class="hidden md:block flex-1" /> <div class="hidden md:block flex-1" />
<!-- Nav columns --> <!-- Nav columns: gap 64 between columns, gap 12 inside each -->
<div class="grid grid-cols-2 sm:grid-cols-3 gap-8 md:gap-16"> <div class="grid grid-cols-2 sm:grid-cols-3 gap-8 md:gap-16">
<!-- Product -->
<div class="flex flex-col gap-3"> <div class="flex flex-col gap-3">
<span class="text-[#D1D5DB] font-semibold text-[13px]">Product</span> <span class="text-[#D1D5DB] font-semibold text-[13px]">Product</span>
<a <a
@@ -56,7 +56,6 @@ const legalLinks = [
</a> </a>
</div> </div>
<!-- Company -->
<div class="flex flex-col gap-3"> <div class="flex flex-col gap-3">
<span class="text-[#D1D5DB] font-semibold text-[13px]">Company</span> <span class="text-[#D1D5DB] font-semibold text-[13px]">Company</span>
<a <a
@@ -69,7 +68,6 @@ const legalLinks = [
</a> </a>
</div> </div>
<!-- Legal -->
<div class="flex flex-col gap-3"> <div class="flex flex-col gap-3">
<span class="text-[#D1D5DB] font-semibold text-[13px]">Legal</span> <span class="text-[#D1D5DB] font-semibold text-[13px]">Legal</span>
<a <a
@@ -81,10 +79,11 @@ const legalLinks = [
{{ link.label }} {{ link.label }}
</a> </a>
</div> </div>
</div> </div>
</div> </div>
<!-- Bottom bar --> <!-- Bottom bar: padding [20, 0, 0, 0] -->
<div class="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-2 pt-5"> <div class="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-2 pt-5">
<p class="text-[#374151] text-xs"> <p class="text-[#374151] text-xs">
© 2025 TapTrack. All rights reserved. Built in the Philippines. © 2025 TapTrack. All rights reserved. Built in the Philippines.

View File

@@ -1,12 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref } from 'vue'
const form = ref({ // Design: padding [80, 100], gap 80, alignItems center
school: '', const form = ref({ school: '', contact: '', phone: '' })
contact: '',
phone: '',
})
const submitted = ref(false) const submitted = ref(false)
const submitting = ref(false) const submitting = ref(false)
const error = ref('') const error = ref('')
@@ -17,7 +13,6 @@ const bulletPoints = [
'✓ Demo of the live system before you commit', '✓ Demo of the live system before you commit',
] ]
// Replace with your actual Formspree form ID: https://formspree.io
const FORMSPREE_URL = 'https://formspree.io/f/YOUR_FORM_ID' const FORMSPREE_URL = 'https://formspree.io/f/YOUR_FORM_ID'
async function handleSubmit() { async function handleSubmit() {
@@ -31,11 +26,7 @@ async function handleSubmit() {
const res = await fetch(FORMSPREE_URL, { const res = await fetch(FORMSPREE_URL, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
body: JSON.stringify({ body: JSON.stringify({ school: form.value.school, contact: form.value.contact, phone: form.value.phone }),
school: form.value.school,
contact: form.value.contact,
phone: form.value.phone,
}),
}) })
if (res.ok) { if (res.ok) {
submitted.value = true submitted.value = true
@@ -52,9 +43,9 @@ async function handleSubmit() {
<template> <template>
<section id="contact" class="bg-[#0F172A]"> <section id="contact" class="bg-[#0F172A]">
<div class="page-container flex flex-col lg:flex-row items-start lg:items-center gap-16 xl:gap-20 py-20"> <div class="page-container flex flex-col lg:flex-row items-start lg:items-center gap-20 py-20">
<!-- Left: Copy --> <!-- Left: gap 20 -->
<div v-reveal class="flex-1 flex flex-col gap-5 min-w-0"> <div v-reveal class="flex-1 flex flex-col gap-5 min-w-0">
<span class="text-[#60A5FA] font-semibold text-[11px] tracking-[2px] uppercase"> <span class="text-[#60A5FA] font-semibold text-[11px] tracking-[2px] uppercase">
Get in Touch Get in Touch
@@ -65,6 +56,7 @@ async function handleSubmit() {
<p class="text-[#94A3B8] text-[16px] leading-[1.65] max-w-[520px]"> <p class="text-[#94A3B8] text-[16px] leading-[1.65] max-w-[520px]">
Tell us about your school and we'll get back to you with a tailored proposal — including implementation scope, NFC hardware, and SMS credit packages that fit your needs. Tell us about your school and we'll get back to you with a tailored proposal — including implementation scope, NFC hardware, and SMS credit packages that fit your needs.
</p> </p>
<!-- Bullet points: gap 10 -->
<div class="flex flex-col gap-2.5 mt-1"> <div class="flex flex-col gap-2.5 mt-1">
<span <span
v-for="point in bulletPoints" v-for="point in bulletPoints"
@@ -76,17 +68,17 @@ async function handleSubmit() {
</div> </div>
</div> </div>
<!-- Right: Form card --> <!-- Right: Form card, width 480, padding 36, gap 20, cornerRadius 16, fill #1E293B -->
<div v-reveal="{ delay: 150 }" class="w-full lg:w-[480px] shrink-0"> <div v-reveal="{ delay: 150 }" class="w-full lg:w-[480px] shrink-0">
<div class="bg-[#1E293B] rounded-2xl p-8 lg:p-9 flex flex-col gap-5"> <div class="bg-[#1E293B] rounded-2xl p-9 flex flex-col gap-5">
<div> <div class="flex flex-col gap-1">
<h3 class="text-white font-bold text-xl">Request a Demo</h3> <h3 class="text-white font-bold text-xl">Request a Demo</h3>
<p class="text-[#64748B] text-[13px] mt-1">Fill this out and we'll reach you within 24 hours.</p> <p class="text-[#64748B] text-[13px]">Fill this out and we'll reach you within 24 hours.</p>
</div> </div>
<!-- Success state --> <!-- Success state -->
<div v-if="submitted" class="flex flex-col items-center gap-3 py-6 text-center"> <div v-if="submitted" class="flex flex-col items-center gap-3 py-8 text-center">
<div class="w-12 h-12 rounded-full bg-[#059669] flex items-center justify-center"> <div class="w-12 h-12 rounded-full bg-[#059669] flex items-center justify-center">
<span class="text-white font-bold text-xl"></span> <span class="text-white font-bold text-xl"></span>
</div> </div>
@@ -94,7 +86,7 @@ async function handleSubmit() {
<p class="text-[#94A3B8] text-sm">We'll get back to you within 24 hours.</p> <p class="text-[#94A3B8] text-sm">We'll get back to you within 24 hours.</p>
</div> </div>
<!-- Form --> <!-- Form: gap 16 between fields -->
<form v-else class="flex flex-col gap-4" @submit.prevent="handleSubmit"> <form v-else class="flex flex-col gap-4" @submit.prevent="handleSubmit">
<div class="flex flex-col gap-1.5"> <div class="flex flex-col gap-1.5">
@@ -127,8 +119,9 @@ async function handleSubmit() {
/> />
</div> </div>
<p v-if="error" class="text-red-400 text-[13px]">{{ error }}</p> <p v-if="error" class="text-red-400 text-[13px] -mt-1">{{ error }}</p>
<!-- Submit button: padding [13, 0], width fill -->
<button <button
type="submit" type="submit"
:disabled="submitting" :disabled="submitting"

View File

@@ -11,19 +11,18 @@ defineProps<{
</script> </script>
<template> <template>
<div class="flex flex-col gap-3.5 bg-[#F8FAFC] border border-[#E2E8F0] rounded-[14px] p-7 pt-8"> <!-- Design: cornerRadius 14, padding [32, 28], gap 14 -->
<!-- Icon box --> <div class="flex flex-col gap-3.5 bg-[#F8FAFC] border border-[#E2E8F0] rounded-2xl px-7 pt-8 pb-8 h-full">
<!-- Icon box: 44×44, cornerRadius 10 -->
<div <div
class="w-11 h-11 rounded-[10px] flex items-center justify-center shrink-0" class="w-11 h-11 rounded-[10px] flex items-center justify-center shrink-0"
:style="{ backgroundColor: iconBg }" :style="{ backgroundColor: iconBg }"
> >
<component :is="icon" :size="22" :stroke-width="1.75" :style="{ color: iconColor }" /> <component :is="icon" :size="22" :stroke-width="1.75" :style="{ color: iconColor }" />
</div> </div>
<!-- Title: 16px semibold -->
<!-- Title -->
<h3 class="text-[#0F172A] font-semibold text-base leading-snug">{{ title }}</h3> <h3 class="text-[#0F172A] font-semibold text-base leading-snug">{{ title }}</h3>
<!-- Description: 13px, lineHeight 1.65 -->
<!-- Description -->
<p class="text-[#64748B] text-[13px] leading-[1.65]">{{ description }}</p> <p class="text-[#64748B] text-[13px] leading-[1.65]">{{ description }}</p>
</div> </div>
</template> </template>

View File

@@ -15,87 +15,86 @@ const features = [
iconColor: '#1D4ED8', iconColor: '#1D4ED8',
iconBg: '#EFF6FF', iconBg: '#EFF6FF',
title: 'NFC Tap-In Attendance', title: 'NFC Tap-In Attendance',
description: description: 'Students tap their NFC ID card on the reader. Attendance is recorded instantly — no queuing, no manual entry, no errors.',
'Students tap their NFC ID card on the reader. Attendance is recorded instantly — no queuing, no manual entry, no errors.',
}, },
{ {
icon: MessageSquare, icon: MessageSquare,
iconColor: '#059669', iconColor: '#059669',
iconBg: '#ECFDF5', iconBg: '#ECFDF5',
title: 'Instant Parent SMS Alerts', title: 'Instant Parent SMS Alerts',
description: description: 'Parents receive an automatic SMS the moment their child arrives or is marked absent. No app required on their end.',
'Parents receive an automatic SMS the moment their child arrives or is marked absent. No app required on their end.',
}, },
{ {
icon: LayoutDashboard, icon: LayoutDashboard,
iconColor: '#CA8A04', iconColor: '#CA8A04',
iconBg: '#FEF9C3', iconBg: '#FEF9C3',
title: 'Live Attendance Dashboard', title: 'Live Attendance Dashboard',
description: description: 'Principals and advisers see real-time attendance, late counts, and absent students — all from a single screen.',
'Principals and advisers see real-time attendance, late counts, and absent students — all from a single screen.',
}, },
{ {
icon: BookOpen, icon: BookOpen,
iconColor: '#7C3AED', iconColor: '#7C3AED',
iconBg: '#F3E8FF', iconBg: '#F3E8FF',
title: 'Automated Attendance Reports', title: 'Automated Attendance Reports',
description: description: 'Monthly attendance summaries are generated and sent automatically to administrators — with per-student and per-class breakdowns.',
'Monthly attendance summaries are generated and sent automatically to administrators — with per-student and per-class breakdowns.',
}, },
{ {
icon: Users, icon: Users,
iconColor: '#E11D48', iconColor: '#E11D48',
iconBg: '#FFF1F2', iconBg: '#FFF1F2',
title: 'Student & Section Management', title: 'Student & Section Management',
description: description: "Manage your full student roster, assign sections and advisers, and link each student to their parent's contact number.",
"Manage your full student roster, assign sections and advisers, and link each student to their parent's contact number.",
}, },
{ {
icon: Activity, icon: Activity,
iconColor: '#0284C7', iconColor: '#0284C7',
iconBg: '#E0F2FE', iconBg: '#E0F2FE',
title: 'SMS Credit Monitoring', title: 'SMS Credit Monitoring',
description: description: 'Track your prepaid SMS credit balance in real time. Low-credit alerts let you top up before you run out — no interruptions to parent notifications.',
'Track your prepaid SMS credit balance in real time. Low-credit alerts let you top up before you run out — no interruptions to parent notifications.',
}, },
] ]
</script> </script>
<template> <template>
<!-- Design: padding [96, 100], gap 72 between header and grid -->
<section id="features" class="bg-white"> <section id="features" class="bg-white">
<div class="page-container flex flex-col gap-[72px] py-24"> <div class="page-container flex flex-col gap-[72px] py-24">
<!-- Section header --> <!-- Header: gap 10, alignItems center -->
<div v-reveal class="flex flex-col items-center gap-2.5"> <div v-reveal class="flex flex-col items-center gap-2.5 text-center">
<span class="text-[#1D4ED8] font-semibold text-[11px] tracking-[2px] uppercase"> <span class="text-[#1D4ED8] font-semibold text-[11px] tracking-[2px] uppercase">
What TapTrack Does What TapTrack Does
</span> </span>
<h2 class="text-[#0F172A] font-bold text-[clamp(28px,3vw,40px)] leading-[1.15] text-center max-w-[560px]"> <h2 class="text-[#0F172A] font-bold text-[clamp(28px,3vw,40px)] leading-[1.15] max-w-[560px]">
Every feature your school<br class="hidden sm:block" /> actually needs. Every feature your school<br class="hidden sm:block" /> actually needs.
</h2> </h2>
<p class="text-[#64748B] text-[17px] leading-[1.6] text-center max-w-[560px]"> <p class="text-[#64748B] text-[17px] leading-[1.6] max-w-[560px]">
Designed around the real workflow of Filipino schools from homeroom attendance to parent communication. Designed around the real workflow of Filipino schools from homeroom attendance to parent communication.
</p> </p>
</div> </div>
<!-- Cards grid: 2 rows × 3 cols --> <!-- 2 rows × 3 cols, gap 24 between cards, gap 24 between rows -->
<div class="flex flex-col gap-6"> <div class="flex flex-col gap-6">
<!-- Row 1 -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6"> <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
<div <div
v-for="(feature, i) in features.slice(0, 3)" v-for="(f, i) in features.slice(0, 3)"
:key="feature.title" :key="f.title"
v-reveal="{ delay: i * 80 }" v-reveal="{ delay: i * 80 }"
class="flex"
> >
<FeatureCard v-bind="feature" /> <FeatureCard v-bind="f" />
</div> </div>
</div> </div>
<!-- Row 2 -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6"> <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
<div <div
v-for="(feature, i) in features.slice(3)" v-for="(f, i) in features.slice(3)"
:key="feature.title" :key="f.title"
v-reveal="{ delay: i * 80 }" v-reveal="{ delay: i * 80 }"
class="flex"
> >
<FeatureCard v-bind="feature" /> <FeatureCard v-bind="f" />
</div> </div>
</div> </div>
</div> </div>

View File

@@ -3,29 +3,30 @@
</script> </script>
<template> <template>
<section class="bg-[#F8FAFF] min-h-[640px] flex items-center"> <!-- Design: height 640, padding [0, 100], gap 80 between columns -->
<div class="page-container w-full flex flex-col lg:flex-row items-center gap-16 xl:gap-20 py-16 lg:py-0"> <section class="bg-[#F8FAFF]">
<div class="page-container w-full flex flex-col lg:flex-row items-center gap-20 min-h-[640px] py-20 lg:py-16">
<!-- Left Column --> <!-- Left Column -->
<div v-reveal class="flex-1 flex flex-col gap-7 min-w-0"> <div v-reveal class="flex-1 flex flex-col gap-7 min-w-0">
<!-- Pill badge --> <!-- Pill badge: fill #EFF6FF, cornerRadius 20, padding [5,12], gap 6 -->
<div class="inline-flex items-center gap-1.5 bg-[#EFF6FF] rounded-full px-3 py-[5px] w-fit"> <div class="inline-flex items-center gap-1.5 bg-[#EFF6FF] rounded-full px-3 py-[5px] w-fit">
<span class="w-1.5 h-1.5 rounded-full bg-[#1D4ED8] shrink-0"></span> <span class="w-1.5 h-1.5 rounded-full bg-[#1D4ED8] shrink-0"></span>
<span class="text-[#1D4ED8] font-semibold text-xs">Built for Philippine Schools</span> <span class="text-[#1D4ED8] font-semibold text-xs leading-none">Built for Philippine Schools</span>
</div> </div>
<!-- H1 --> <!-- H1: 58px bold, lineHeight 1.1 -->
<h1 class="text-[#0F172A] font-bold text-[clamp(38px,4.5vw,58px)] leading-[1.1] max-w-[560px]"> <h1 class="text-[#0F172A] font-bold text-[clamp(38px,4.5vw,58px)] leading-[1.1] max-w-[560px]">
Attendance That<br>Runs Itself. Attendance That<br>Runs Itself.
</h1> </h1>
<!-- Subtitle --> <!-- Subtitle: 17px, lineHeight 1.7 -->
<p class="text-[#475569] text-[17px] leading-[1.7] max-w-[520px]"> <p class="text-[#475569] text-[17px] leading-[1.7] max-w-[520px]">
TapTrack is an on-premises NFC attendance system students tap their ID card, parents get an SMS, and your records are updated automatically. No manual registers. No paper trails. TapTrack is an on-premises NFC attendance system students tap their ID card, parents get an SMS, and your records are updated automatically. No manual registers. No paper trails.
</p> </p>
<!-- CTA Buttons --> <!-- CTA Buttons: gap 12, padding [13, 26] -->
<div class="flex flex-wrap items-center gap-3"> <div class="flex flex-wrap items-center gap-3">
<a <a
href="#contact" href="#contact"
@@ -41,18 +42,18 @@
</a> </a>
</div> </div>
<!-- Trust row --> <!-- Trust row: gap 20 -->
<div class="flex flex-wrap items-center gap-5"> <div class="flex flex-wrap items-center gap-5">
<span class="text-[#64748B] text-[13px]">&nbsp; Student data stays on your servers</span> <span class="text-[#64748B] text-[13px]">&nbsp; Student data stays on your servers</span>
<span class="text-[#64748B] text-[13px]">&nbsp; No cloud lock-in</span> <span class="text-[#64748B] text-[13px]">&nbsp; No cloud lock-in</span>
</div> </div>
</div> </div>
<!-- Right Column --> <!-- Right Column: width 580, gap 16 -->
<div v-reveal="{ delay: 150 }" class="w-full lg:w-[580px] shrink-0 flex flex-col gap-4 items-center"> <div v-reveal="{ delay: 150 }" class="w-full lg:w-[580px] shrink-0 flex flex-col gap-4 items-center">
<!-- Screenshot frame --> <!-- Screenshot: 580×380, cornerRadius 16, border 2px -->
<div class="w-full max-w-[580px] h-[300px] sm:h-[380px] rounded-2xl overflow-hidden border-2 border-[#E2E8F0] shadow-xl"> <div class="w-full max-w-[580px] h-[320px] sm:h-[380px] rounded-2xl overflow-hidden border-2 border-[#E2E8F0] shadow-xl">
<img <img
src="/screenshots/58IBI.png" src="/screenshots/58IBI.png"
alt="TapTrack live attendance dashboard" alt="TapTrack live attendance dashboard"
@@ -60,7 +61,7 @@
/> />
</div> </div>
<!-- Caption --> <!-- Caption: 12px, textAlign center -->
<p class="text-[#94A3B8] text-xs text-center max-w-[520px] leading-relaxed"> <p class="text-[#94A3B8] text-xs text-center max-w-[520px] leading-relaxed">
Live dashboard running on your school's own server data never leaves your premises. Live dashboard running on your school's own server data never leaves your premises.
</p> </p>

View File

@@ -1,24 +1,22 @@
<script setup lang="ts"> <script setup lang="ts">
import { Wrench, Zap, CircleCheck } from 'lucide-vue-next' import { Wrench, Zap, CircleCheck } from 'lucide-vue-next'
// Design: padding [96, 100], gap 80, layout horizontal
const steps = [ const steps = [
{ {
num: '1', num: '1',
title: "We install TapTrack on your school's hardware", title: "We install TapTrack on your school's hardware",
description: description: 'Our team sets up the on-premises software and NFC reader. You pay a one-time implementation fee and you own the setup.',
'Our team sets up the on-premises software and NFC reader. You pay a one-time implementation fee and you own the setup.',
}, },
{ {
num: '2', num: '2',
title: 'We enrol your students and train your staff', title: 'We enrol your students and train your staff',
description: description: 'We import your student list, assign NFC ID cards, add parent numbers, and run a full walkthrough with your administrators.',
'We import your student list, assign NFC ID cards, add parent numbers, and run a full walkthrough with your administrators.',
}, },
{ {
num: '3', num: '3',
title: 'Top up SMS credits monthly as needed', title: 'Top up SMS credits monthly as needed',
description: description: 'No recurring subscription. You purchase prepaid SMS credits when you need them. The system keeps sending alerts as long as your credits last.',
'No recurring subscription. You purchase prepaid SMS credits when you need them. The system keeps sending alerts as long as your credits last.',
}, },
] ]
@@ -42,10 +40,12 @@ const billingItems = [
<template> <template>
<section id="how-it-works" class="bg-[#F8FAFF]"> <section id="how-it-works" class="bg-[#F8FAFF]">
<div class="page-container flex flex-col lg:flex-row gap-16 xl:gap-20 py-24"> <div class="page-container flex flex-col lg:flex-row gap-20 py-24">
<!-- Left: Steps --> <!-- Left column: gap 40 -->
<div v-reveal class="flex-1 flex flex-col gap-10 min-w-0"> <div v-reveal class="flex-1 flex flex-col gap-10 min-w-0">
<!-- Header: gap 10 -->
<div class="flex flex-col gap-2.5"> <div class="flex flex-col gap-2.5">
<span class="text-[#1D4ED8] font-semibold text-[11px] tracking-[2px] uppercase"> <span class="text-[#1D4ED8] font-semibold text-[11px] tracking-[2px] uppercase">
How It Works How It Works
@@ -58,16 +58,19 @@ const billingItems = [
</p> </p>
</div> </div>
<div class="flex flex-col gap-8"> <!-- Steps: gap 40, each step gap 20 -->
<div class="flex flex-col gap-10">
<div <div
v-for="(step, i) in steps" v-for="(step, i) in steps"
:key="step.num" :key="step.num"
v-reveal="{ delay: i * 100 }" v-reveal="{ delay: i * 100 }"
class="flex items-start gap-5" class="flex items-start gap-5"
> >
<!-- Number bubble: 36×36, cornerRadius 18 -->
<div class="w-9 h-9 rounded-full bg-[#1D4ED8] flex items-center justify-center shrink-0 mt-0.5"> <div class="w-9 h-9 rounded-full bg-[#1D4ED8] flex items-center justify-center shrink-0 mt-0.5">
<span class="text-white font-bold text-[15px] leading-none">{{ step.num }}</span> <span class="text-white font-bold text-[15px] leading-none">{{ step.num }}</span>
</div> </div>
<!-- Step content: gap 6 -->
<div class="flex flex-col gap-1.5 min-w-0"> <div class="flex flex-col gap-1.5 min-w-0">
<h3 class="text-[#0F172A] font-semibold text-[15px] leading-snug">{{ step.title }}</h3> <h3 class="text-[#0F172A] font-semibold text-[15px] leading-snug">{{ step.title }}</h3>
<p class="text-[#64748B] text-[13px] leading-[1.6]">{{ step.description }}</p> <p class="text-[#64748B] text-[13px] leading-[1.6]">{{ step.description }}</p>
@@ -76,20 +79,22 @@ const billingItems = [
</div> </div>
</div> </div>
<!-- Right: Billing card --> <!-- Right: Billing card, width 460, padding 40, gap 24, cornerRadius 20 -->
<div v-reveal="{ delay: 150 }" class="w-full lg:w-[460px] shrink-0"> <div v-reveal="{ delay: 150 }" class="w-full lg:w-[460px] shrink-0">
<div class="bg-white border border-[#E2E8F0] rounded-[20px] p-8 lg:p-10 flex flex-col gap-6"> <div class="bg-white border border-[#E2E8F0] rounded-[20px] p-10 flex flex-col gap-6 h-full">
<h3 class="text-[#0F172A] font-bold text-xl">How billing works</h3> <div class="flex flex-col gap-2">
<p class="text-[#64748B] text-[14px] leading-[1.6]"> <h3 class="text-[#0F172A] font-bold text-xl">How billing works</h3>
Transparent, no surprises. You only pay for what you actually use. <p class="text-[#64748B] text-[14px] leading-[1.6]">
</p> Transparent, no surprises. You only pay for what you actually use.
</p>
</div>
<div class="flex flex-col"> <!-- Billing items -->
<div class="flex flex-col divide-y divide-[#F1F5F9]">
<div <div
v-for="(item, i) in billingItems" v-for="item in billingItems"
:key="item.title" :key="item.title"
class="flex items-start gap-3.5 py-4" class="flex items-start gap-3.5 py-4 first:pt-0 last:pb-0"
:class="i === 0 ? 'border-b border-[#F1F5F9]' : ''"
> >
<div <div
class="w-9 h-9 rounded-lg flex items-center justify-center shrink-0" class="w-9 h-9 rounded-lg flex items-center justify-center shrink-0"
@@ -97,13 +102,14 @@ const billingItems = [
> >
<component :is="item.icon" :size="18" :stroke-width="1.75" :style="{ color: item.iconColor }" /> <component :is="item.icon" :size="18" :stroke-width="1.75" :style="{ color: item.iconColor }" />
</div> </div>
<div class="flex flex-col gap-0.5 min-w-0"> <div class="flex flex-col gap-1 min-w-0">
<span class="text-[#0F172A] font-semibold text-[14px]">{{ item.title }}</span> <span class="text-[#0F172A] font-semibold text-[14px]">{{ item.title }}</span>
<span class="text-[#64748B] text-[13px] leading-[1.5]">{{ item.description }}</span> <span class="text-[#64748B] text-[13px] leading-[1.5]">{{ item.description }}</span>
</div> </div>
</div> </div>
</div> </div>
<!-- Note: fill #F0FDF4, cornerRadius 8, padding [12,14], gap 10 -->
<div class="flex items-start gap-2.5 bg-[#F0FDF4] rounded-lg px-3.5 py-3"> <div class="flex items-start gap-2.5 bg-[#F0FDF4] rounded-lg px-3.5 py-3">
<CircleCheck :size="16" :stroke-width="2" class="text-[#16A34A] shrink-0 mt-0.5" /> <CircleCheck :size="16" :stroke-width="2" class="text-[#16A34A] shrink-0 mt-0.5" />
<p class="text-[#15803D] text-[13px] leading-[1.5]"> <p class="text-[#15803D] text-[13px] leading-[1.5]">

View File

@@ -9,31 +9,35 @@ const stats = [
</script> </script>
<template> <template>
<!-- Design: padding [56, 100], gap 80, alignItems center -->
<section class="bg-[#0F172A]"> <section class="bg-[#0F172A]">
<div class="page-container flex flex-col lg:flex-row items-start lg:items-center gap-12 lg:gap-20 py-14"> <div class="page-container flex flex-col lg:flex-row items-start lg:items-center gap-20 py-14">
<!-- Left: Text content --> <!-- Left: gap 12 -->
<div v-reveal class="flex-1 flex flex-col gap-3 min-w-0"> <div v-reveal class="flex-1 flex flex-col gap-3 min-w-0">
<!-- Label: fill #1E3A5F, cornerRadius 4, padding [4,10] -->
<div class="w-fit bg-[#1E3A5F] rounded px-[10px] py-1"> <div class="w-fit bg-[#1E3A5F] rounded px-[10px] py-1">
<span class="text-[#60A5FA] font-semibold text-[11px] tracking-[1px] uppercase"> <span class="text-[#60A5FA] font-semibold text-[11px] tracking-[1px] uppercase">
Data Privacy Data Privacy
</span> </span>
</div> </div>
<!-- Title: 28px bold, lineHeight 1.2, max-w 500 -->
<h2 class="text-white font-bold text-[28px] leading-[1.2] max-w-[500px]"> <h2 class="text-white font-bold text-[28px] leading-[1.2] max-w-[500px]">
Student data never leaves your school. Student data never leaves your school.
</h2> </h2>
<!-- Sub: 15px, lineHeight 1.6, max-w 500 -->
<p class="text-[#94A3B8] text-[15px] leading-[1.6] max-w-[500px]"> <p class="text-[#94A3B8] text-[15px] leading-[1.6] max-w-[500px]">
TapTrack runs entirely on your own server. No student records, attendance logs, or personal data are ever uploaded to a third-party cloud. TapTrack runs entirely on your own server. No student records, attendance logs, or personal data are ever uploaded to a third-party cloud.
</p> </p>
</div> </div>
<!-- Right: Stat items --> <!-- Right: gap 40, stat items centered -->
<div class="flex flex-row flex-wrap gap-10 shrink-0"> <div class="flex flex-row flex-wrap gap-10 shrink-0">
<div <div
v-for="(stat, i) in stats" v-for="(stat, i) in stats"
:key="stat.label" :key="stat.label"
v-reveal="{ delay: i * 100 }" v-reveal="{ delay: i * 100 }"
class="flex flex-col items-center gap-1.5 min-w-[90px]" class="flex flex-col items-center gap-1.5 min-w-[100px]"
> >
<component :is="stat.icon" class="text-[#60A5FA]" :size="28" :stroke-width="1.75" /> <component :is="stat.icon" class="text-[#60A5FA]" :size="28" :stroke-width="1.75" />
<span class="text-white font-semibold text-[13px]">{{ stat.label }}</span> <span class="text-white font-semibold text-[13px]">{{ stat.label }}</span>

View File

@@ -1,18 +1,17 @@
<script setup lang="ts"> <script setup lang="ts">
// Design: padding [96, 100], gap 56, screenshot cards cornerRadius 14, border #E2E8F0
const cards = [ const cards = [
{ {
img: '/screenshots/4frZA.png', img: '/screenshots/4frZA.png',
alt: 'TapTrack Student Directory screen', alt: 'TapTrack Student Directory screen',
title: 'Student Directory', title: 'Student Directory',
description: description: 'Manage your full roster — NFC ID assignments, parent numbers, section placement, and enrolment status all in one place.',
'Manage your full roster — NFC ID assignments, parent numbers, section placement, and enrolment status all in one place.',
}, },
{ {
img: '/screenshots/Reports.jpg', img: '/screenshots/Reports.jpg',
alt: 'TapTrack Reports & Analytics screen', alt: 'TapTrack Reports & Analytics screen',
title: 'Reports & Analytics', title: 'Reports & Analytics',
description: description: 'Generate attendance summaries by class, date range, and section. Export to PDF or Excel for DepEd compliance and parent meetings.',
'Generate attendance summaries by class, date range, and section. Export to PDF or Excel for DepEd compliance and parent meetings.',
}, },
] ]
</script> </script>
@@ -22,19 +21,19 @@ const cards = [
<div class="page-container flex flex-col gap-14 py-24"> <div class="page-container flex flex-col gap-14 py-24">
<!-- Header --> <!-- Header -->
<div v-reveal class="flex flex-col items-center gap-2.5"> <div v-reveal class="flex flex-col items-center gap-2.5 text-center">
<span class="text-[#1D4ED8] font-semibold text-[11px] tracking-[2px] uppercase"> <span class="text-[#1D4ED8] font-semibold text-[11px] tracking-[2px] uppercase">
Inside TapTrack Inside TapTrack
</span> </span>
<h2 class="text-[#0F172A] font-bold text-[clamp(26px,3vw,38px)] leading-[1.15] text-center max-w-[600px]"> <h2 class="text-[#0F172A] font-bold text-[clamp(26px,3vw,38px)] leading-[1.15] max-w-[600px]">
A system built for how your school actually works. A system built for how your school actually works.
</h2> </h2>
<p class="text-[#64748B] text-[16px] leading-[1.6] text-center max-w-[580px]"> <p class="text-[#64748B] text-[16px] leading-[1.6] max-w-[580px]">
Every screen is purpose-built for school administrators, advisers, and principals not generic enterprise software. Every screen is purpose-built for school administrators, advisers, and principals not generic enterprise software.
</p> </p>
</div> </div>
<!-- Screenshot cards grid --> <!-- 2-col card grid, gap 28 -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-7"> <div class="grid grid-cols-1 lg:grid-cols-2 gap-7">
<div <div
v-for="(card, i) in cards" v-for="(card, i) in cards"
@@ -42,14 +41,16 @@ const cards = [
v-reveal="{ delay: i * 120 }" v-reveal="{ delay: i * 120 }"
class="flex flex-col rounded-[14px] border border-[#E2E8F0] overflow-hidden" class="flex flex-col rounded-[14px] border border-[#E2E8F0] overflow-hidden"
> >
<div class="h-[240px] sm:h-[300px] lg:h-[340px] overflow-hidden"> <!-- Image: height 340, object-top -->
<div class="h-[260px] sm:h-[320px] lg:h-[340px] overflow-hidden">
<img <img
:src="card.img" :src="card.img"
:alt="card.alt" :alt="card.alt"
class="w-full h-full object-cover object-top" class="w-full h-full object-cover object-top block"
/> />
</div> </div>
<div class="flex flex-col gap-1.5 px-6 py-5"> <!-- Info: padding [20, 24], gap 6 -->
<div class="flex flex-col gap-2 px-6 py-5">
<h3 class="text-[#0F172A] font-semibold text-base">{{ card.title }}</h3> <h3 class="text-[#0F172A] font-semibold text-base">{{ card.title }}</h3>
<p class="text-[#64748B] text-[13px] leading-[1.6]">{{ card.description }}</p> <p class="text-[#64748B] text-[13px] leading-[1.6]">{{ card.description }}</p>
</div> </div>

View File

@@ -1,34 +1,37 @@
<script setup lang="ts"> <script setup lang="ts">
// Gate View kiosk screenshot section // Design: padding [96, 100], gap 40, layout vertical, alignItems center
</script> </script>
<template> <template>
<section class="bg-[#F8FAFF]"> <section class="bg-[#F8FAFF]">
<div class="page-container flex flex-col items-center gap-10 py-24"> <div class="page-container flex flex-col items-center gap-10 py-24">
<!-- Header --> <!-- Header: gap 10, alignItems center, width fill -->
<div v-reveal class="flex flex-col items-center gap-2.5 w-full"> <div v-reveal class="flex flex-col items-center gap-2.5 w-full max-w-[640px]">
<!-- Label: 11px semibold, tracking 2px -->
<span class="text-[#1D4ED8] font-semibold text-[11px] tracking-[2px] uppercase"> <span class="text-[#1D4ED8] font-semibold text-[11px] tracking-[2px] uppercase">
The System in Action The System in Action
</span> </span>
<h2 class="text-[#0F172A] font-bold text-[clamp(28px,3vw,38px)] leading-[1.15] text-center max-w-[600px]"> <!-- Title: 38px bold, lineHeight 1.15, textAlign center, width 600 -->
<h2 class="text-[#0F172A] font-bold text-[clamp(28px,3vw,38px)] leading-[1.15] text-center">
The gate display students see every morning. The gate display students see every morning.
</h2> </h2>
<p class="text-[#64748B] text-[16px] leading-[1.6] text-center max-w-[600px]"> <!-- Sub: 16px, lineHeight 1.6, textAlign center, width 600 -->
<p class="text-[#64748B] text-[16px] leading-[1.6] text-center">
A full-screen kiosk view runs at your school gate students tap their NFC ID card and instantly see their name, section, and status confirmed. A full-screen kiosk view runs at your school gate students tap their NFC ID card and instantly see their name, section, and status confirmed.
</p> </p>
</div> </div>
<!-- Gate View Screenshot --> <!-- Gate screenshot: width 960, height 540, cornerRadius 16, border #E2E8F0 -->
<div v-reveal="{ delay: 100 }" class="w-full max-w-[960px] rounded-2xl overflow-hidden border border-[#E2E8F0] shadow-lg"> <div v-reveal="{ delay: 100 }" class="w-full max-w-[960px] rounded-2xl overflow-hidden border border-[#E2E8F0] shadow-lg">
<img <img
src="/screenshots/gE861.png" src="/screenshots/gE861.png"
alt="TapTrack gate kiosk display" alt="TapTrack gate kiosk display"
class="w-full h-auto object-cover" class="w-full h-auto block"
/> />
</div> </div>
<!-- Caption --> <!-- Caption: 13px, textAlign center, width 700 -->
<p v-reveal="{ delay: 150 }" class="text-[#94A3B8] text-[13px] text-center max-w-[700px] leading-relaxed"> <p v-reveal="{ delay: 150 }" class="text-[#94A3B8] text-[13px] text-center max-w-[700px] leading-relaxed">
Full-screen gate display running on a monitor at your school entrance. No login required for students just tap and go. Full-screen gate display running on a monitor at your school entrance. No login required for students just tap and go.
</p> </p>

View File

@@ -8,46 +8,42 @@ import {
} from 'lucide-vue-next' } from 'lucide-vue-next'
import FeatureCard from './FeatureCard.vue' import FeatureCard from './FeatureCard.vue'
// Design: padding [96, 100], gap 64, card padding [36, 28]
const supportCards = [ const supportCards = [
{ {
icon: Headphones, icon: Headphones,
iconColor: '#1D4ED8', iconColor: '#1D4ED8',
iconBg: '#EFF6FF', iconBg: '#EFF6FF',
title: 'Dedicated Support Channel', title: 'Dedicated Support Channel',
description: description: 'Get direct access to our technical team via chat or phone. No ticket queues, no bots — real people who know your setup.',
'Get direct access to our technical team via chat or phone. No ticket queues, no bots — real people who know your setup.',
}, },
{ {
icon: Settings, icon: Settings,
iconColor: '#CA8A04', iconColor: '#CA8A04',
iconBg: '#FEF9C3', iconBg: '#FEF9C3',
title: 'System Updates & Maintenance', title: 'System Updates & Maintenance',
description: description: 'We push updates to fix bugs and add improvements. Your system stays current without you having to do anything.',
'We push updates to fix bugs and add improvements. Your system stays current without you having to do anything.',
}, },
{ {
icon: GraduationCap, icon: GraduationCap,
iconColor: '#059669', iconColor: '#059669',
iconBg: '#ECFDF5', iconBg: '#ECFDF5',
title: 'Staff Re-training Assistance', title: 'Staff Re-training Assistance',
description: description: "New school year, new staff? We'll get your team up to speed. Re-training sessions available on request.",
"New school year, new staff? We'll get your team up to speed. Re-training sessions available on request.",
}, },
{ {
icon: Monitor, icon: Monitor,
iconColor: '#7C3AED', iconColor: '#7C3AED',
iconBg: '#F3E8FF', iconBg: '#F3E8FF',
title: 'Remote Troubleshooting', title: 'Remote Troubleshooting',
description: description: 'Most issues can be resolved remotely. Our team can connect to your system and fix problems without an on-site visit.',
'Most issues can be resolved remotely. Our team can connect to your system and fix problems without an on-site visit.',
}, },
{ {
icon: DatabaseBackup, icon: DatabaseBackup,
iconColor: '#E11D48', iconColor: '#E11D48',
iconBg: '#FFF1F2', iconBg: '#FFF1F2',
title: 'Data Backup Assistance', title: 'Data Backup Assistance',
description: description: 'We help you set up regular attendance data backups so your records are always safe, even if hardware fails.',
'We help you set up regular attendance data backups so your records are always safe, even if hardware fails.',
}, },
] ]
</script> </script>
@@ -57,42 +53,44 @@ const supportCards = [
<div class="page-container flex flex-col gap-16 py-24"> <div class="page-container flex flex-col gap-16 py-24">
<!-- Header --> <!-- Header -->
<div v-reveal class="flex flex-col items-center gap-2.5"> <div v-reveal class="flex flex-col items-center gap-2.5 text-center">
<span class="text-[#1D4ED8] font-semibold text-[11px] tracking-[2px] uppercase"> <span class="text-[#1D4ED8] font-semibold text-[11px] tracking-[2px] uppercase">
After-Sales Support After-Sales Support
</span> </span>
<h2 class="text-[#0F172A] font-bold text-[clamp(28px,3vw,38px)] leading-[1.15] text-center max-w-[560px]"> <h2 class="text-[#0F172A] font-bold text-[clamp(28px,3vw,38px)] leading-[1.15] max-w-[560px]">
We don't just install and leave. We don't just install and leave.
</h2> </h2>
<p class="text-[#64748B] text-[17px] leading-[1.6] text-center max-w-[560px]"> <p class="text-[#64748B] text-[17px] leading-[1.6] max-w-[560px]">
Our team stays with you long after go-live. If something breaks or your staff needs help, we're one message away. Our team stays with you long after go-live. If something breaks or your staff needs help, we're one message away.
</p> </p>
</div> </div>
<!-- Row 1: 3 cards --> <!-- Row 1: 3 cards, gap 24 -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6"> <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
<div <div
v-for="(card, i) in supportCards.slice(0, 3)" v-for="(card, i) in supportCards.slice(0, 3)"
:key="card.title" :key="card.title"
v-reveal="{ delay: i * 80 }" v-reveal="{ delay: i * 80 }"
class="flex"
> >
<FeatureCard v-bind="card" /> <FeatureCard v-bind="card" />
</div> </div>
</div> </div>
<!-- Row 2: 2 cards + quote card --> <!-- Row 2: 2 support cards + quote card -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6"> <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
<div <div
v-for="(card, i) in supportCards.slice(3)" v-for="(card, i) in supportCards.slice(3)"
:key="card.title" :key="card.title"
v-reveal="{ delay: i * 80 }" v-reveal="{ delay: i * 80 }"
class="flex"
> >
<FeatureCard v-bind="card" /> <FeatureCard v-bind="card" />
</div> </div>
<!-- Quote card --> <!-- Quote card: fill #EFF6FF, border #BFDBFE, padding [36, 28], gap 16 -->
<div v-reveal="{ delay: 160 }" class="flex flex-col gap-4 bg-[#EFF6FF] border border-[#BFDBFE] rounded-[14px] p-7 pt-9"> <div v-reveal="{ delay: 160 }" class="flex flex-col gap-4 bg-[#EFF6FF] border border-[#BFDBFE] rounded-2xl px-7 pt-9 pb-8">
<p class="text-[#1E3A5F] font-medium text-base leading-[1.65]"> <p class="text-[#1E3A5F] font-medium text-base leading-[1.65] flex-1">
"Think of us as your school's IT partner for attendance — not just a vendor who sold you something." "Think of us as your school's IT partner for attendance — not just a vendor who sold you something."
</p> </p>
<div class="flex items-center gap-2.5"> <div class="flex items-center gap-2.5">