A single-page application (SPA) for the PCU International Office, showcasing inbound and outbound academic programs, campus facilities, news, and contact information for prospective international students and partners.
├── index.html # Main HTML file — pages injected at load time; admin modals and FAB included
├── CSS/
│ ├── styles.css # Custom styles, animations, page color themes, and PCU brand variables
│ ├── tailwind.src.css # Tailwind source (directives only — compiled to tailwind.build.css)
│ └── tailwind.build.css # Compiled and minified Tailwind output (committed; regenerated on build)
├── JS/
│ ├── main.js # Navigation (hash routing), Supabase client init, modals, visit tracking, trending list
│ ├── admin.js # Admin system — Supabase Auth login, article/OSE/internship CRUD
│ ├── admin-dashboard.js # Dedicated #admin dashboard logic (tabs, lists, Home content save)
│ ├── data/
│ │ └── news.js # Static news seed data and news page renderer (trending populated at runtime)
│ └── pages/ # One render function per page, grouped by nav section
│ ├── home.js
│ ├── news.js
│ ├── about/
│ │ ├── pcu-at-glance.js
│ │ ├── facilities.js
│ │ └── contact-us.js
│ ├── inbound/
│ │ ├── semester-exchange.js
│ │ ├── international-degree.js
│ │ ├── international-community-outreach-program.js
│ │ └── indonesian-spectrum.js
│ ├── outbound/
│ │ ├── outbound-semester-exchange.js
│ │ ├── joint-double-degree.js
│ │ └── internship.js
│ ├── partnership/
│ │ ├── international-partnership.js
│ │ ├── domestic-partnership.js
│ │ ├── consortium-association.js
│ │ └── meet-us.js
│ └── life-at-pcu/
│ ├── how-to-get-to-pcu.js
│ ├── accommodation.js
│ ├── preparation-arrival-guide.js
│ └── visa-immigration.js
├── Assets/
│ ├── Data/
│ │ ├── Meeting Request Form.docx # Offline version of the partnership meeting request form
│ │ └── News Form.docx # Offline template for submitting news article content
│ ├── Graphics/
│ │ ├── logo-UKP.svg
│ │ └── Petra Graphic Asset/
│ │ └── *.svg # Decorative background illustrations
│ └── Images/
│ ├── Accreditation/ # Accreditation certificate images
│ ├── Facilities/ # Campus facility photos
│ ├── Faculty/ # Faculty photos
│ ├── Flag/ # Country flag images
│ ├── Foto Rektorat/ # Rector office photos
│ ├── Gedung Petra/ # PCU building photos
│ ├── ICOP/ # ICOP program photos
│ ├── Industries/ # Industry partner logos (~71 logos)
│ ├── Logo/ # International partner university logos, organized by country
│ ├── Partnership/ # Partnership event photos
│ ├── Student Exchange/ # Student exchange program photos
│ └── Thumbnails/ # Thumbnail images
├── supabase/
│ ├── config.toml # Supabase CLI project config (project ref, exposed schemas, edge function)
│ ├── .env.example # Documents every Supabase/edge-function value; copy to .env
│ ├── migrations/
│ │ ├── 001_initial_schema.sql # Full schema: pcu_global tables + RLS policies + increment_article_visits RPC
│ │ └── 002_site_config.sql # site_config table (single-row store for editable Home content)
│ └── functions/
│ └── send-meeting-email/
│ └── index.ts # Deno Edge Function — triggered by DB webhook on meeting_requests INSERT; sends email via Resend
├── scripts/
│ └── fix_news.py # One-time dev utility — see note below (safe to ignore)
├── package.json # npm scripts: build (minify) and dev (watch) for Tailwind CSS
├── tailwind.config.js
├── postcss.config.js
├── vercel.json # Vercel deploy config — runs npm run build, serves repo root
├── .gitignore
└── README.md
| Tool | Purpose |
|---|---|
| HTML5 | Page structure and SPA page sections |
| Tailwind CSS v3.4 | Utility-first styling — built locally from CSS/tailwind.src.css |
| Lucide Icons v0.263 | Icon library (loaded via CDN) |
| DM Sans + Playfair Display | Typography (loaded via Google Fonts) |
| Vanilla JavaScript | Hash routing, animations, and dynamic page rendering |
| Supabase | PostgreSQL database, Auth (email+password), Row Level Security, Edge Functions |
| Resend | Transactional email for meeting request notifications |
| Deno | Runtime for the send-meeting-email Supabase Edge Function |
The frontend requires no runtime build step for development — open index.html directly. Run npm run build to regenerate the compiled Tailwind CSS before deploying. All other JS dependencies are loaded via CDN.
Navigation is handled client-side via navigateTo(pageId), which pushes #pageId to the browser history. Direct links (example.com/#semester-exchange) and browser back/forward both work via a hashchange listener. The following pages are available:
About
home— Hero carousel, stats counters, and program overviewpcu-at-glance— University facts and figuresfacilities— Campus facilitiesnews— International news carouselcontact-us— Staff directory and contact details
Inbound Programs
semester-exchange— Incoming exchange semester programintl-degree— International degree programcop— International Community Outreach Programindonesian-spectrum— Indonesian SPECTRUM program
Outbound Programs
outbound-semester-exchange— Outgoing exchange semester programjoint-double-degree— Joint/Double Degree programinternship— Internship opportunities
Partnership
international-partnership— International partner universitiesdomestic-partnership— Domestic/national partner institutionsconsortium-association— Consortium and association membershipspartnership-meet-us— Partnership contact and meeting information
Life at PCU
how-to-get— Directions and transport to PCUaccommodation— Student housing options (placeholder — content pending)preparation-arrival— Arrival guide for incoming studentsvisa-immigration— Visa and immigration information
Note for IT team: The
accommodationpage is still a placeholder. Do not link it from public-facing navigation until full content is provided.
- Hash Routing —
navigateTo(pageId)pushes#pageIdto history;hashchangehandles back/forward and direct deep-links - Modular JS Pages — Each page section lives in its own render function file under
JS/pages/, keepingmain.jsfocused on navigation and shared logic - Meeting Request Form — Multi-step modal form (institution details → meeting details → guest list) that writes directly to
pcu_global.meeting_requestsin Supabase via the JS client; a database webhook fires thesend-meeting-emailEdge Function to send an email via Resend - Admin News System — Role-based admin panel (Inbound / Outbound / Partnership / Head) for creating, editing, and deleting news articles; articles are persisted in
pcu_global.articlesin Supabase and merged with static seed data at runtime - Article Image Upload — Drag-and-drop or file-picker image upload in the article form; images stored as base64 data URLs in the
image_urlcolumn - Article Visit Tracking — Each article view calls the
increment_article_visitsSupabase RPC; the News page sidebar "Trending" list is sorted by visit count in real time - Dynamic Article Pages — Admin-published articles generate full detail pages on the fly (
renderAdminArticlePage) and are injected into#adminArticlePages; no page reload required - Hero Carousel — Auto-advancing slides with navigation arrows and dot indicators
- Scroll Reveal Animations — Sections fade in as they enter the viewport via
IntersectionObserver - Animated Stat Counters — Numbers count up when scrolled into view
- Flip Cards — Hover-to-flip program cards (CSS 3D transforms)
- Program Type Selector — Animated slide-in/out transitions for Joint vs Double Degree content
- OSE University Popup Cards — Each university card on the Outbound Semester Exchange page is clickable and opens a detail popup; data is fetched from
pcu_global.ose_programsand merged by university name with the base partner list - Admin OSE Manager — A "Manage Universities" button in the admin FAB opens
ose-manager-modal; admins can add custom university entries or edit/delete program details via Supabase - Internship Partner Filtering — The Internship page splits partners into International/Domestic tabs with a live search input, domestic sub-type filter, partner count label, and a paginated "Show More" button
- Internship Opportunities CMS — Admin-managed internship listings (position, company, apply link) stored in
pcu_global.internship_opportunities; displayed as cards below the partner grid on the Internship page - International Partnership Modal — Drill-down modal: continent → country → individual partner details, populated from
intlLogoFilesinmain.js - International Partner Logo Carousel — Auto-scrolling marquee of partner university logos
- Domestic Partnership Modal — Institution detail modal with categorized partner cards and toggleable sections
- Domestic Partner Logo Carousel — Auto-scrolling marquee of domestic partner logos (pauses on hover)
- Page Color Theming — Each section group applies its own accent color via CSS overrides: Inbound (Sky), Outbound (Orange), Partnership (Purple), Life at PCU (Green), About (Navy)
- Partner Maps — Interactive partner university maps with tooltips and popups
- News Carousel — Dynamically rendered news card carousel
- Mobile Menu — Slide-in drawer navigation for small screens
- Element SDK Integration — Supports runtime config (colors, fonts, headings) via
window.elementSdkif available - Floating Background Decorations — Animated SVG linework graphics in the background
Defined as CSS custom properties in styles.css and extended into Tailwind via tailwind.config.js:
| Variable | Hex |
|---|---|
--pcu-navy / --pcu-blue |
#1d446e |
--pcu-sky |
#30aeb4 |
--pcu-red |
#f7000d |
--pcu-orange |
#fa6632 |
--pcu-yellow / --pcu-gold |
#fdd600 |
--pcu-purple |
#8d4bb1 |
--pcu-green |
#52ac2d |
--pcu-magenta |
#fa207d |
--pcu-white |
#ebe6e5 |
No build step is required to run the site locally:
# Option 1: Open directly
open index.html
# Option 2: Serve locally (recommended to avoid CORS issues with SVG assets)
npx serve .
# or
python -m http.server 8080Note: SVG background assets are loaded as CSS
background-imageURLs. A local server is recommended so these resolve correctly.
To work on styles, rebuild the compiled Tailwind CSS:
npm install # first time only
npm run dev # watch mode — rebuilds CSS/tailwind.build.css on save
npm run build # one-shot minified build (required before deploying)All backend functionality (database, auth, and email) runs on Supabase. No local server is required.
1. Create a Supabase project
Go to supabase.com, create a new project, and note your Project URL and anon public key.
2. Run the database migrations
Either approach works:
-
CLI (recommended) — with the Supabase CLI installed, copy
supabase/.env.exampletosupabase/.env, then:supabase login supabase link --project-ref <your-project-ref> # project_id is preset in supabase/config.toml supabase db push # applies everything in supabase/migrations/
-
Dashboard — in Supabase Dashboard → SQL Editor, paste and run
supabase/migrations/001_initial_schema.sqlthen002_site_config.sql.
This creates the pcu_global schema, all tables, RLS policies, the increment_article_visits RPC, and the site_config store. config.toml also exposes the pcu_global schema to the API (schemas = [..., "pcu_global"]), matching the db: { schema: 'pcu_global' } option in the JS client.
3. Create admin users
In Supabase Dashboard → Authentication → Users, create one user per admin role:
| Intended role | |
|---|---|
admin.inbound@petra.ac.id (or any email) |
Inbound |
admin.outbound@petra.ac.id |
Outbound |
admin.partnership@petra.ac.id |
Partnership |
admin.head@petra.ac.id |
Head |
The role/tag mapping for each email is defined in the ADMIN_ROLES object at the top of JS/admin.js. Update that object if you use different email addresses.
4. Update the Supabase credentials in the frontend
At the top of JS/main.js, update:
const SUPABASE_URL = 'https://<your-project-ref>.supabase.co';
const SUPABASE_ANON_KEY = '<your-anon-key>';5. Deploy the send-meeting-email Edge Function
The meeting request email is sent by a Supabase Edge Function triggered via a database webhook.
# Install the Supabase CLI if needed: https://supabase.com/docs/guides/cli
supabase login
supabase link --project-ref <your-project-ref>
supabase functions deploy send-meeting-emailThen set the required secret in Supabase Dashboard → Edge Functions → Secrets:
| Secret | Value |
|---|---|
RESEND_API_KEY |
Your API key from resend.com |
RECIPIENT_EMAIL |
Who receives meeting request notifications (default: io@petra.ac.id) |
FROM_EMAIL |
Verified sender address in Resend (default: PCU Global <noreply@yourdomain.com>) |
6. Create the database webhook
In Supabase Dashboard → Database → Webhooks → Create Webhook:
- Table:
meeting_requests(in schemapcu_global) - Events:
INSERT - Type: Supabase Edge Functions
- Function:
send-meeting-email
The frontend is deployed on Vercel. The vercel.json config runs npm run build before serving the repo root as a static site.
To redeploy or self-host on Vercel:
- Connect the GitHub repository to your Vercel project.
- Vercel will automatically detect
vercel.jsonand runnpm run buildbefore each deploy. - No additional environment variables are needed on Vercel — the Supabase credentials are hardcoded in
JS/main.js(the anon key is safe to expose publicly; RLS enforces access control server-side).
To self-host on any static server instead:
- Run
npm run buildlocally to generateCSS/tailwind.build.css. - Copy the repository folder (excluding
node_modules/) to your web server's public directory. - No
.htaccessrewrite rules are needed — all navigation is handled client-side via JavaScript. - Verify that the
Assets/folder and all subfolders are accessible. Several folder names contain spaces (Assets/Graphics/Petra Graphic Asset/,Assets/Images/Foto Rektorat/, etc.) — confirm your server handles these correctly, or rename them and update all references inindex.html,styles.css, andmain.js.
No separate backend deployment is needed. The database, auth, and Edge Function all run on Supabase's managed infrastructure. See Getting Started → Backend above for setup steps.
The site includes a lightweight CMS for managing news articles, OSE programs, and internship opportunities. All data is persisted in Supabase (PostgreSQL + RLS), so changes are shared across all devices and browsers instantly.
Once logged in, a Dashboard button at the top of the floating action button stack opens a dedicated full-page admin view at the #admin route. It consolidates every dynamic section in one place via a left tab rail:
- Overview — record counts and quick-create actions.
- News Articles / OSE Programs / Internships — inline lists with add / edit / delete, reusing the existing CRUD modals.
- Home Content — edit the home hero title/subtitle, section headings, brand colors, and font. Saved to the
pcu_global.site_configtable (single row) and applied to the live site immediately. Runsupabase/migrations/002_site_config.sqlonce in the Supabase SQL Editor to provision this table; until then the dashboard reads/writes nothing and the site falls back todefaultConfiginJS/main.js.
The dashboard logic lives in JS/admin-dashboard.js; its markup is rendered by renderAdminDashboard() in JS/pages/admin-dashboard.js. The #admin route is gated — visiting it while logged out redirects home and opens the login modal.
| Email (example) | Role | Publishes to tag |
|---|---|---|
admin.inbound@… |
Inbound | #inboundstudents |
admin.outbound@… |
Outbound | #outboundstudents |
admin.partnership@… |
Partnership | #partnership |
admin.head@… |
Head | Any tag (unrestricted) |
The email-to-role mapping is defined in ADMIN_ROLES at the top of JS/admin.js. Authentication is handled by Supabase Auth (signInWithPassword); the Supabase session token is stored in sessionStorage. RLS policies on the pcu_global tables enforce write access to authenticated users only.
- Click the Admin button (bottom-right corner) to open the login modal.
admin.jscallssupabase.auth.signInWithPassword({ email, password }); on success, the Supabase session is stored insessionStorage.- A floating action button (FAB) appears with Dashboard, Add Article, Manage Universities (OSE), and Sign Out options.
- The article form collects: title, excerpt, body paragraphs, key highlights, contact info, tag, and an optional image (drag-and-drop or file picker — stored as a base64 data URL in the
image_urlcolumn). - On submit,
admin.jsupserts the article directly topcu_global.articlesvia the Supabase JS client.refreshNewsData()merges the updated data with the static seed articles fromJS/data/news.js. - Admins can edit or delete their own articles; the
Headrole can manage all articles. - OSE university management — Clicking "Manage Universities" opens
ose-manager-modal. The admin can create, edit, or delete entries inpcu_global.ose_programs. Custom entries (is_custom=true) appear as extra cards on the OSE page; base universities fromoseBasePartnerscan have program details attached by name-matching. - Internship opportunity management — On the Internship page, admins see an "Add Opportunity" button. Saved opportunities are fetched from
pcu_global.internship_opportunitiesand displayed as cards. - Every article view calls
supabase.rpc('increment_article_visits', { article_id })to increment the server-side counter. The News page "Trending" sidebar is sorted byvisitsdescending.
External image URLs (Unsplash)
Several news article pages (page-news-1 through page-news-6) load hero images from https://images.unsplash.com. These are placeholder images used during development. Before going live, replace them with actual PCU-owned images hosted locally or on a CDN.
CDN dependencies Lucide Icons and Google Fonts are loaded from CDN. If the site needs to work in an offline or intranet environment, these assets must be downloaded and self-hosted. Tailwind CSS is now built locally and committed — it does not require a CDN at runtime.
Supabase anon key
The Supabase anon key in JS/main.js is intentionally public — it is the client-side key designed for browser use. Database access is controlled entirely by Row Level Security policies defined in 001_initial_schema.sql. Do not replace it with the service_role key.
Admin sessions
Supabase Auth sessions are stored in sessionStorage and expire according to Supabase's JWT TTL (default 1 hour). Sessions are not shared across tabs. To extend session lifetime, update the JWT expiry in Supabase Dashboard → Authentication → Settings.
Partner logo paths
International partner logos are referenced as relative paths inside intlLogoFiles in main.js, resolved under Assets/Images/Logo/<Country>/. If logos are added, renamed, or reorganized, that array must be updated to match.
Page render functions
Each page is a standalone render*() function in JS/pages/<section>/<page>.js. When adding a new page, create the render file, add a <script src="..."> tag plus a matching mount-point call in index.html (see the existing entries near line 420–458), and register the page ID in main.js.
Assets/Images/ folder size
The Assets/Images/ directory contains a large number of images (logos, facilities, faculty, etc.). Ensure your web server or CDN is configured to serve these files efficiently, and verify that folder names with spaces (e.g. Foto Rektorat/, Petra Graphic Asset/) are handled correctly by your deployment pipeline.
This is a one-time developer utility that was used during development to replace a static banner in the News page with a dynamic carousel. It has already been applied — the result is baked into index.html.
The IT team does not need to run this script. It is kept in scripts/ for reference only and can be safely ignored or deleted.
If window.elementSdk is present on the page (e.g. when embedded in a CMS or page builder), the site supports live editing of:
- Hero title and subtitle
- Section headings (Stats, Study, News)
- Background, surface, text, primary, and secondary colors
- Font family and base font size
These are configured via defaultConfig at the top of main.js.
Zefanya Kharisma Nugroho, S.Hub.Int. Surabaya, Indonesia 📧 Personal: zefanya.kharisma@gmail.com 📧 Work: zefanya.kharisma@petra.ac.id
Social media: Instagram · LinkedIn
© 2025–2026 Zefanya Kharisma Nugroho — Petra Christian University. All rights reserved.