Files
E-Vote/resources/assets/js/components/demo/voter/home/index.vue
T
2026-05-13 16:53:11 +08:00

1407 lines
34 KiB
Vue

<template>
<div class="voter-home-page container-fluid">
<div class="row home-hero-row">
<div class="col-xs-12">
<div class="home-section-container home-hero-section">
<section class="home-hero-wrap">
<div class="hero-banner home-hero-banner">
<img :src="bannerUrl" alt="Banner" class="hero-banner-image">
<div class="home-hero-banner-shine" aria-hidden="true"></div>
</div>
<div class="hero-banner-caption home-hero-caption">
<h1 class="hero-banner-title">Portal Pengundian</h1>
<p class="hero-banner-subtitle">
Gunakan <strong class="home-hero-strong">pautan pantas</strong> di bawah untuk navigasi pantas ke
halaman utama portal.
</p>
<div class="home-hero-badges">
<span class="home-badge home-badge--location">
<i class="fa fa-map-marker"></i> {{ kehadiranLabel }}
</span>
<span class="home-badge" :class="hasVoted() ? 'home-badge--ok' : 'home-badge--pending'">
<i class="fa" :class="hasVoted() ? 'fa-check-circle' : 'fa-clock-o'"></i>
{{ hasVoted() ? 'Sudah mengundi' : 'Belum mengundi' }}
</span>
</div>
</div>
</section>
</div>
</div>
</div>
<!-- Butiran Keahlian and Coupon Claim Code -->
<div class="row home-content-row">
<div class="col-xs-12 col-md-6 home-column home-column--primary">
<!-- Member Information Card -->
<div class="home-section-container">
<h2 class="home-section-heading">
<span class="home-section-heading-icon" aria-hidden="true"><i class="fa fa-list-alt"></i></span>
Maklumat Anggota
</h2>
<div v-if="showRegisterCta" class="home-member-cta">
<div class="home-member-cta-hint home-member-cta-hint--warning" role="alert">
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
<span>Sila semak butiran keahlian anda sebelum mendaftar kehadiran.</span>
</div>
<div class="home-member-cta-btn-container">
<router-link :to="{ name: 'Attendance' }" class="btn btn-primary btn-block home-member-cta-btn">
<i class="fa fa-pencil-square-o" aria-hidden="true"></i>
Daftar MAT Sekarang
</router-link>
</div>
</div>
<div class="member-info-card home-member-card home-member-card--in-section">
<div v-for="row in memberRows" :key="row.label" class="member-field">
<span class="member-field-icon" aria-hidden="true"><i class="fa" :class="row.icon"></i></span>
<div class="member-field-body">
<span class="member-field-label">{{ row.label }}</span>
<span class="member-field-value">{{ row.value }}</span>
</div>
</div>
</div>
</div>
</div>
<!-- Coupon Claim Code Card -->
<div class="col-xs-12 col-md-6 home-column home-column--secondary">
<div class="home-claim-row">
<div class="home-section-container home-section-container--claim">
<h2 class="home-section-heading">
<span class="home-section-heading-icon home-section-heading-icon--teal" aria-hidden="true"><i
class="fa fa-ticket"></i></span>
Kod Tuntutan Elaun
</h2>
<div class="home-claim-card">
<div v-if="!canGenerateClaimCode" class="home-claim-locked">
<div class="home-claim-locked-icon"><i class="fa fa-lock"></i></div>
<p class="home-claim-locked-text">
Kod tuntutan hanya tersedia untuk kehadiran <strong>Fizikal</strong> selepas anda
<strong>selesai mengundi</strong>.
</p>
</div>
<div v-else class="home-claim-active">
<p class="home-claim-hint">
Tunjukkan kod ini kepada petugas di kaunter untuk pengesahan bayaran tunai.
</p>
<div v-if="claimError" class="alert alert-danger home-claim-alert">
{{ claimError }}
</div>
<div v-if="claimCode" class="home-claim-code-box">
<div class="home-claim-code">{{ claimCode }}</div>
<div class="home-claim-expiry">
Tamat tempoh: <strong>{{ claimExpiresAt || '—' }}</strong>
</div>
</div>
<button type="button" class="btn btn-primary btn-block home-claim-btn" :disabled="claimLoading"
@click="generateClaimCode()">
<i class="fa fa-refresh" :class="{ 'fa-spin': claimLoading }"></i>
{{ claimLoading ? ' Menjana...' : (claimCode ? ' Jana Semula Kod' : ' Jana Kod Tuntutan') }}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Pautan Pantas -->
<div class="row home-quick-links-row">
<div class="col-xs-12">
<div class="home-section-container">
<h2 class="home-section-heading">
<span class="home-section-heading-icon" aria-hidden="true"><i class="fa fa-th-large"></i></span>
Pautan Pantas
</h2>
<div class="home-quick-links-grid">
<div v-for="link in quickLinks" :key="link.key" class="home-quick-link-wrap">
<a v-if="link.external" :href="link.href" class="home-cta-link" :class="link.variant" target="_blank"
rel="noopener noreferrer">
<span class="home-cta-icon"><i class="fa" :class="link.icon"></i></span>
<span class="home-cta-body">
<span class="home-cta-title">{{ link.title }}</span>
<span class="home-cta-desc">{{ link.desc }}</span>
</span>
<span class="home-cta-chevron" aria-hidden="true"><i class="fa fa-external-link"></i></span>
</a>
<router-link v-else :to="link.route" class="home-cta-link" :class="link.variant">
<span class="home-cta-icon"><i class="fa" :class="link.icon"></i></span>
<span class="home-cta-body">
<span class="home-cta-title">{{ link.title }}</span>
<span class="home-cta-desc">{{ link.desc }}</span>
</span>
<span class="home-cta-chevron" aria-hidden="true"><i class="fa fa-angle-right"></i></span>
</router-link>
</div>
</div>
</div>
</div>
</div>
<!-- Kad Ahli Digital -->
<!-- <div class="col-xs-12">
<div class="home-section-container home-section-container--kad">
<h2 class="home-section-heading">
<span class="home-section-heading-icon" aria-hidden="true"><i class="fa fa-credit-card"></i></span>
Kad Ahli Digital
</h2>
<div class="home-kad-wrap">
<div class="kad-keahlian">
<div class="member-card-vertical">MEMBER CARD</div>
<div class="kad-logo">
<img src="/images/logo_kad.png" alt="Logo" class="kad-logo-img">
</div>
<div class="kad-content">
<img src="images/KOPKB_Final.png" class="watermark" alt="">
<div class="member-details">
<div class="detail-item">
<span class="detail-label">NAMA:</span>
<span class="detail-value">{{ data.user.name }}</span>
</div>
<div class="detail-item">
<span class="detail-label">NO KAD PENGENALAN:</span>
<span class="detail-value">{{ data.user.no_kp }}</span>
</div>
<div class="detail-item">
<span class="detail-label">NO ANGGOTA:</span>
<span class="detail-value">{{ data.user.no_anggota }}</span>
</div>
<div class="detail-item">
<span class="detail-label">UNIT:</span>
<span class="detail-value">{{ data.user.unit }}</span>
</div>
<div class="detail-item detail-item--barcode">
<svg id="barcode" class="detail-value">{{ data.user.no_kp }}</svg>
</div>
</div>
</div>
</div>
</div>
</div>
</div> -->
<!-- Saham Vote Blocked Message -->
<div v-if="showSahamVoteBlockedMessage" class="row home-saham-block-row">
<div class="col-xs-12">
<div class="alert alert-warning home-saham-block-alert" role="alert">
<span class="home-saham-block-icon" aria-hidden="true"><i class="fa fa-exclamation-triangle"></i></span>
<span>{{ sahamVoteBlockedMessage }}</span>
</div>
</div>
</div>
</div>
</template>
<script>
import JsBarcode from "jsbarcode";
export default {
data() {
return {
noKp: "",
claimLoading: false,
claimCode: "",
claimExpiresAt: "",
claimError: "",
matLinkUrl: "https://linktr.ee/kopkb",
portalSettings: null,
};
},
computed: {
etikaUrl: function () {
var url = this.portalSettings && this.portalSettings.attendance_etika_url
? String(this.portalSettings.attendance_etika_url).trim()
: '';
return url;
},
bannerUrl() {
const p = this.portalSettings && this.portalSettings.home_banner_image_path
? this.portalSettings.home_banner_image_path
: "/images/banner.jpeg";
const v = this.portalSettings && this.portalSettings.updated_at
? encodeURIComponent(this.portalSettings.updated_at)
: "";
if (!v) return p;
return p.includes("?") ? `${p}&v=${v}` : `${p}?v=${v}`;
},
/**
* Maya: boleh mengundi selepas daftar kehadiran. Fizikal: perlu disahkan pentadbir dahulu.
*/
canProceedToVote() {
const u = this.data?.user;
if (!u || Number(u.kehadiran) === 0) return false;
if (Number(u.kehadiran) !== 1) return true;
return Boolean(u.fizikal_registration_verified_at);
},
/** Saham (RM) must be at least 500 to vote — see Voter model `saham`. */
canVoteBySaham() {
const s = Number(this.data?.user?.saham);
return !Number.isNaN(s) && s >= 500;
},
showSahamVoteBlockedMessage() {
const election = this.data?.election || {};
return (
Number(election.status) === 2 &&
!this.hasVoted() &&
this.canProceedToVote &&
!this.canVoteBySaham
);
},
sahamVoteBlockedMessage() {
return "Undian tidak tersedia: jumlah saham anda mestilah sekurang-kurangnya RM 500.";
},
quickLinks() {
const user = this.data?.user || {};
const election = this.data?.election || {};
const noAnggota = user.no_anggota;
const matLabel = (this.portalSettings && this.portalSettings.home_mat_label)
? this.portalSettings.home_mat_label
: "MAT KE-29";
const links = [
{
key: "attendance",
title: "Daftar MAT",
desc: "Daftar kehadiran fizikal atau maya",
icon: "fa-pencil-square-o",
route: { name: "Attendance" },
variant: "home-cta-link--teal",
external: false
},
{
key: "calon",
title: "Maklumat Calon",
desc: "Senarai calon dan profil penuh mengikut jawatan",
icon: "fa-users",
route: { name: "Voter Calon" },
variant: "home-cta-link--indigo",
external: false
},
{
key: "mat28",
title: matLabel,
desc: "Pautan luar ke maklumat mesyuarat dan sumber berkaitan",
icon: "fa-globe",
href: this.matLinkUrl,
variant: "home-cta-link--violet",
external: true
},
// Muat Turun Etika Mesyuarat Agung
{
key: "etika-mesyuarat",
title: "Etika Mesyuarat Agung",
desc: "Muat Turun etika mesyuarat agung",
icon: "fa-file-pdf-o",
href: this.etikaUrl,
variant: "home-cta-link--grey",
external: true
},
];
if (
Number(election.status) === 2 &&
!this.hasVoted() &&
this.canProceedToVote &&
this.canVoteBySaham
) {
links.push({
key: "vote",
title: "Undian",
desc: "Undi calon pilihan anda",
icon: "fa-check-square-o",
route: { name: "Vote" },
variant: "home-cta-link--rose",
external: false
});
}
if (Number(user.kehadiran) !== 0) {
links.push({
key: "zoom",
title: "Pautan Mesyuarat",
desc: "Sambung ke mesyuarat dalam talian",
icon: "fa-video-camera",
route: { name: "Zoom" },
variant: "home-cta-link--cyan",
external: false
});
}
links.push({
key: "penyata",
title: "Penyata Ahli",
desc: "Lihat dan kemaskini penyata ahli",
icon: "fa-file-text-o",
route: { name: "penyata", params: { no_anggota: noAnggota } },
variant: "home-cta-link--amber",
external: false
});
return links;
},
/**
* Tunai: kehadiran Fizikal, dan sama ada sudah mengundi atau tidak layak mengundi
* (saham < RM500) tetapi kehadiran/tetapan mesyuarat sudah memenuhi syarat — masih boleh tuntut elaun.
*/
canGenerateClaimCode() {
if (Number(this.data?.user?.kehadiran) !== 1) return false;
if (this.hasVoted()) return true;
return this.canProceedToVote && !this.canVoteBySaham;
},
kehadiranLabel() {
const k = Number(this.data?.user?.kehadiran);
if (k === 1) return "Kehadiran Fizikal";
if (k === 2) return "Kehadiran Maya";
return "Belum daftar kehadiran";
},
showRegisterCta() {
// Only show the registration CTA when user hasn't registered attendance yet.
return Number(this.data?.user?.kehadiran) === 0;
},
memberRows() {
const u = this.data?.user || {};
const fmt = (n) => {
if (n === null || n === undefined || n === "") return "—";
return String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
};
return [
{ icon: "fa-user", label: "Nama", value: u.name || "—" },
{ icon: "fa-credit-card", label: "No. Kad Pengenalan", value: u.no_kp || "—" },
{ icon: "fa-barcode", label: "No. Anggota", value: u.no_anggota || "—" },
{ icon: "fa-building", label: "Unit", value: u.unit || "—" },
{ icon: "fa-line-chart", label: "Saham", value: "RM " + fmt(u.saham) },
{ icon: "fa-money", label: "Yuran", value: "RM " + fmt(u.yuran) },
{ icon: "fa-percent", label: "Dividen Simpanan Khas (Tabung 1) Tahun 2025", value: "RM " + fmt(u.pelaburan) }
];
}
},
mounted() {
this.syncBarcodeFromUser();
this.fetchPortalSettings();
},
watch: {
"data.user.no_kp": function () {
this.syncBarcodeFromUser();
}
},
methods: {
async fetchPortalSettings() {
try {
this.util.setAuthorization();
const res = await axios.get(config.API + "portal-settings");
if (res && res.data && res.data.status === "success") {
this.portalSettings = res.data.data || null;
}
} catch (e) {
// Non-critical — fall back to defaults.
}
},
syncBarcodeFromUser() {
if (this.data?.user?.no_kp) {
this.noKp = this.data.user.no_kp;
this.$nextTick(() => this.generateBarcode());
}
},
generateBarcode() {
if (!this.noKp) return;
JsBarcode("#barcode", this.noKp, {
format: "CODE128",
width: 1.3,
height: 30,
displayValue: false
});
},
hasVoted() {
return Array.isArray(this.data.result) && this.data.result.length > 0;
},
async generateClaimCode() {
if (this.claimLoading) return;
this.claimError = "";
this.claimLoading = true;
try {
this.util.setAuthorization();
const response = await axios.post(config.API + "allowance/code", { method: "cash" });
if (response && response.data && response.data.status === "success") {
this.claimCode = response.data.code || "";
this.claimExpiresAt = response.data.expires_at || "";
this.util.notify(response.data.message || "Kod dijana.", "success");
} else {
this.claimError = (response && response.data && response.data.message) ? response.data.message : "Gagal menjana kod.";
this.util.notify(this.claimError, "error");
}
} catch (error) {
this.claimError =
(error && error.response && error.response.data && error.response.data.message)
? error.response.data.message
: (error && error.message ? error.message : "Gagal menjana kod.");
if (error && error.response) this.util.showResult(error, "error");
else this.util.notify(this.claimError, "error");
} finally {
this.claimLoading = false;
}
}
}
};
</script>
<style>
.voter-home-page {
margin-top: 16px;
margin-bottom: 48px;
padding-left: 0;
padding-right: 0;
}
.home-hero-row {
margin-left: 0;
margin-right: 0;
}
.home-content-row {
margin-left: 0;
margin-right: 0;
}
.home-quick-links-row {
margin-left: 0;
margin-right: 0;
margin-top: 18px;
}
.home-claim-row {
margin-left: 0;
margin-right: 0;
margin-top: 18px;
}
.home-content-row .home-column--secondary .home-claim-row {
margin-top: 0;
}
.home-column--primary {
padding-top: 4px;
}
.home-column--secondary {
padding-top: 4px;
}
@media (max-width: 767px) {
.home-column--secondary {
margin-top: 8px;
padding-top: 0;
}
.home-content-row .home-column--secondary .home-claim-row {
margin-top: 18px;
}
}
@media (min-width: 992px) {
.home-column--primary {
padding-right: 20px;
}
.home-column--secondary {
padding-left: 20px;
}
}
/* —— Hero —— */
.home-hero-wrap {
margin-bottom: 0;
}
.home-hero-section {
padding: 16px 18px 20px;
}
.home-hero-section .hero-banner {
margin-bottom: 16px;
border-radius: 18px;
}
.home-hero-section .hero-banner-caption {
box-shadow: 0 8px 28px rgba(35, 64, 97, 0.08);
}
.hero-banner {
position: relative;
margin-bottom: 20px;
border-radius: 24px;
overflow: hidden;
box-shadow:
0 20px 50px rgba(11, 39, 79, 0.2),
0 0 0 1px rgba(255, 255, 255, 0.06) inset;
background: linear-gradient(145deg, #e8f0ff 0%, #f4f8fd 50%, #eef5ff 100%);
}
.home-hero-banner-shine {
pointer-events: none;
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 42%;
background: linear-gradient(to top,
rgba(13, 110, 253, 0.07) 0%,
transparent 100%);
}
.hero-banner-image {
width: 100%;
max-height: 360px;
object-fit: contain;
object-position: center;
display: block;
}
.hero-banner-caption {
margin-top: 0;
margin-bottom: 6px;
padding: 22px 24px;
border-radius: 20px;
background: #fff;
border: 1px solid rgba(13, 110, 253, 0.12);
box-shadow:
0 16px 40px rgba(35, 64, 97, 0.1),
0 1px 0 rgba(255, 255, 255, 0.8) inset;
}
.hero-banner-title {
margin: 0 0 10px;
color: #0f172a;
font-size: 2.85rem;
font-weight: 800;
line-height: 1.15;
letter-spacing: -0.02em;
}
.hero-banner-subtitle {
max-width: 720px;
margin: 0 0 16px;
color: #475569;
font-size: 1.45rem;
line-height: 1.65;
}
.home-hero-strong {
color: #1d4ed8;
font-weight: 700;
}
.home-hero-badges {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 4px;
}
.home-hero-actions {
margin: 14px 0 8px;
display: flex;
flex-direction: column;
gap: 8px;
max-width: 520px;
}
.home-hero-register-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 10px;
padding: 12px 16px;
border-radius: 12px;
font-weight: 800;
font-size: 1.35rem;
box-shadow: 0 10px 24px rgba(13, 110, 253, 0.35);
}
.home-hero-register-hint {
color: #64748b;
font-size: 1.2rem;
line-height: 1.35;
}
.home-member-cta {
margin: 0 0 14px;
}
.home-member-cta-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 10px;
padding: 12px 16px;
border-radius: 12px;
font-weight: 800;
font-size: 1.35rem;
box-shadow: 0 10px 24px rgba(13, 110, 253, 0.35);
}
.home-member-cta-hint {
margin-top: 8px;
color: #64748b;
font-size: 1.2rem;
line-height: 1.35;
}
.home-member-cta-hint--warning {
margin-top: 0;
margin-bottom: 10px;
display: flex;
gap: 10px;
align-items: flex-start;
padding: 10px 12px;
border-radius: 12px;
background: #fffbeb;
border: 1px solid rgba(245, 158, 11, 0.45);
color: #92400e;
}
.home-member-cta-hint--warning .fa {
flex-shrink: 0;
margin-top: 2px;
}
.home-badge {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 14px;
border-radius: 999px;
font-size: 1.25rem;
font-weight: 600;
border: 1px solid transparent;
}
.home-badge .fa {
opacity: 0.9;
}
.home-badge--location {
background: #f1f5f9;
color: #334155;
border-color: #e2e8f0;
}
.home-badge--ok {
background: linear-gradient(135deg, #ecfdf5, #d1fae5);
color: #047857;
border-color: rgba(16, 185, 129, 0.35);
}
.home-badge--pending {
background: linear-gradient(135deg, #fffbeb, #fef3c7);
color: #b45309;
border-color: rgba(245, 158, 11, 0.4);
}
/* —— Section containers (hero, quick links, panels) —— */
.home-section-container {
background: #fff;
border: 1px solid #e5ebf3;
border-radius: 18px;
box-shadow: 0 12px 32px rgba(35, 64, 97, 0.08);
padding: 20px 22px 24px;
margin-bottom: 28px;
}
.home-content-row .home-section-container {
margin-bottom: 0;
}
.home-column--secondary .home-section-container:not(:last-child) {
margin-bottom: 24px;
}
.home-section-heading {
display: flex;
align-items: center;
gap: 12px;
margin: 0 0 18px;
padding-bottom: 16px;
border-bottom: 1px solid #eef2f7;
font-size: 1.75rem;
font-weight: 800;
color: #0f172a;
letter-spacing: -0.02em;
}
.home-section-heading-icon {
display: flex;
align-items: center;
justify-content: center;
width: 44px;
height: 44px;
border-radius: 12px;
background: linear-gradient(135deg, #0d6efd, #3b82f6);
color: #fff;
font-size: 1.65rem;
box-shadow: 0 8px 20px rgba(13, 110, 253, 0.28);
flex-shrink: 0;
}
.home-section-heading-icon--teal {
background: linear-gradient(135deg, #0f766e, #14b8a6);
box-shadow: 0 8px 20px rgba(20, 184, 166, 0.35);
}
.home-member-card--in-section {
background: #f8fafc;
border: 1px solid #eef2f7;
box-shadow: none;
border-radius: 14px;
}
.home-section-container--kad .home-kad-wrap {
padding-top: 4px;
}
.home-saham-block-row {
margin-left: 0;
margin-right: 0;
margin-bottom: 8px;
}
.home-saham-block-alert {
margin-bottom: 0;
border-radius: 14px;
display: flex;
align-items: flex-start;
gap: 12px;
font-size: 1.3rem;
line-height: 1.5;
}
.home-saham-block-icon {
flex-shrink: 0;
font-size: 1.5rem;
padding-top: 2px;
}
.home-quick-links-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 16px;
margin-bottom: 0;
}
.home-quick-link-wrap {
min-width: 0;
display: flex;
}
.home-quick-link-wrap .home-cta-link {
flex: 1;
width: 100%;
min-height: 100%;
margin-bottom: 0;
}
/* —— CTA tiles —— */
.home-cta-link {
display: flex;
align-items: center;
gap: 16px;
padding: 18px 20px;
border-radius: 16px;
text-decoration: none !important;
color: #fff !important;
border: 1px solid rgba(255, 255, 255, 0.2);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.home-cta-link:hover {
transform: translateY(-2px);
color: #fff !important;
}
.home-cta-link--indigo {
background: linear-gradient(135deg, #1e3a8a 0%, #2563eb 48%, #1d4ed8 100%);
box-shadow: 0 14px 36px rgba(37, 99, 235, 0.35);
}
.home-cta-link--indigo:hover {
box-shadow: 0 18px 44px rgba(37, 99, 235, 0.45);
}
.home-cta-link--violet {
background: linear-gradient(135deg, #5b21b6 0%, #7c3aed 50%, #6d28d9 100%);
box-shadow: 0 14px 36px rgba(124, 58, 237, 0.35);
}
.home-cta-link--violet:hover {
box-shadow: 0 18px 44px rgba(124, 58, 237, 0.48);
}
.home-cta-link--teal {
background: linear-gradient(135deg, #0f766e 0%, #14b8a6 50%, #0d9488 100%);
box-shadow: 0 14px 36px rgba(20, 184, 166, 0.32);
}
.home-cta-link--teal:hover {
box-shadow: 0 18px 44px rgba(20, 184, 166, 0.45);
}
.home-cta-link--rose {
background: linear-gradient(135deg, #9f1239 0%, #e11d48 50%, #be123c 100%);
box-shadow: 0 14px 36px rgba(225, 29, 72, 0.32);
}
.home-cta-link--rose:hover {
box-shadow: 0 18px 44px rgba(225, 29, 72, 0.45);
}
.home-cta-link--cyan {
background: linear-gradient(135deg, #0e7490 0%, #06b6d4 50%, #0891b2 100%);
box-shadow: 0 14px 36px rgba(6, 182, 212, 0.3);
}
.home-cta-link--cyan:hover {
box-shadow: 0 18px 44px rgba(6, 182, 212, 0.42);
}
.home-cta-link--amber {
background: linear-gradient(135deg, #b45309 0%, #f59e0b 50%, #d97706 100%);
box-shadow: 0 14px 36px rgba(245, 158, 11, 0.32);
}
.home-cta-link--amber:hover {
box-shadow: 0 18px 44px rgba(245, 158, 11, 0.45);
}
.home-cta-link--grey {
background: linear-gradient(135deg, #64748b 0%, #94a3b8 50%, #64748b 100%);
box-shadow: 0 14px 36px rgba(100, 116, 139, 0.3);
}
.home-cta-link--grey:hover {
box-shadow: 0 18px 44px rgba(100, 116, 139, 0.42);
}
.home-cta-icon {
flex-shrink: 0;
width: 52px;
height: 52px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 14px;
background: rgba(255, 255, 255, 0.15);
font-size: 2.2rem;
}
.home-cta-body {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 4px;
}
.home-cta-title {
font-size: 1.65rem;
font-weight: 800;
letter-spacing: 0.02em;
}
.home-cta-desc {
font-size: 1.25rem;
opacity: 0.92;
line-height: 1.4;
}
.home-cta-chevron {
flex-shrink: 0;
font-size: 2.4rem;
opacity: 0.85;
}
.member-info-card {
margin-bottom: 0;
padding: 0;
border-radius: 18px;
background: #fff;
border: 1px solid #e5ebf3;
box-shadow: 0 12px 32px rgba(35, 64, 97, 0.08);
overflow: hidden;
}
.home-member-card {
padding: 6px 0;
}
.member-field {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 14px 16px;
border-bottom: 1px solid #eef2f7;
transition: background 0.15s ease;
}
.member-field:last-child {
border-bottom: 0;
}
.member-field:hover {
background: #f8fafc;
}
.member-field-icon {
flex-shrink: 0;
width: 36px;
height: 36px;
margin-top: 2px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 10px;
background: #eff6ff;
color: #2563eb;
font-size: 1.45rem;
}
.member-field-body {
flex: 1;
min-width: 0;
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: space-between;
gap: 12px 18px;
}
.member-field-label {
flex: 0 0 38%;
max-width: 200px;
font-size: 1.2rem;
font-weight: 700;
color: #64748b;
text-transform: uppercase;
letter-spacing: 0.04em;
padding-top: 4px;
line-height: 1.35;
}
.member-field-value {
flex: 1;
text-align: right;
font-size: 1.45rem;
font-weight: 700;
color: #0f172a;
line-height: 1.4;
word-break: break-word;
}
/* —— Claim —— */
.home-claim-card {
padding: 20px 22px;
}
.home-claim-locked {
display: flex;
gap: 16px;
align-items: flex-start;
}
.home-claim-locked-icon {
flex-shrink: 0;
width: 48px;
height: 48px;
border-radius: 12px;
background: #f1f5f9;
color: #64748b;
display: flex;
align-items: center;
justify-content: center;
font-size: 2rem;
}
.home-claim-locked-text {
margin: 0;
font-size: 1.35rem;
line-height: 1.55;
color: #64748b;
}
.home-claim-hint {
margin: 0 0 14px;
font-size: 1.3rem;
line-height: 1.5;
color: #475569;
}
.home-claim-alert {
margin-bottom: 12px;
border-radius: 12px;
font-size: 1.25rem;
}
.home-claim-code-box {
margin-bottom: 16px;
padding: 18px 16px;
border-radius: 14px;
background: linear-gradient(180deg, #f0fdf4 0%, #ecfdf5 100%);
border: 2px dashed rgba(16, 185, 129, 0.45);
text-align: center;
}
.home-claim-code {
font-weight: 800;
font-size: 2.8rem;
letter-spacing: 0.22em;
color: #047857;
font-family: ui-monospace, "Cascadia Code", Menlo, monospace;
line-height: 1.2;
}
.home-claim-expiry {
margin-top: 10px;
font-size: 1.2rem;
color: #64748b;
}
.home-claim-btn {
padding-top: 12px;
padding-bottom: 12px;
font-size: 1.35rem;
font-weight: 700;
border-radius: 12px;
border: 0;
box-shadow: 0 8px 24px rgba(13, 110, 253, 0.35);
}
.home-claim-btn .fa {
margin-right: 4px;
}
/* —— Kad —— */
.home-kad-wrap {
display: flex;
justify-content: center;
padding: 8px 0 12px;
}
.watermark {
position: absolute;
top: 50%;
left: 50%;
width: 60mm;
opacity: 0.09;
transform: translate(-50%, -50%);
z-index: 5;
}
.kad-keahlian {
width: 100%;
max-width: 420px;
aspect-ratio: 1.3 / 1;
max-height: 220px;
overflow: hidden;
padding: 20px;
font-size: 0.8rem;
border-radius: 16px;
border: 1px solid rgba(255, 255, 255, 0.12);
box-shadow:
0 20px 50px rgba(0, 0, 0, 0.25),
0 0 0 1px rgba(0, 0, 0, 0.06);
background: #2d3748;
position: relative;
font-family: Arial, sans-serif;
box-sizing: border-box;
font-weight: bold;
color: #f8fafc;
cursor: default;
transition: transform 0.25s ease, box-shadow 0.25s ease;
background-image: url("/images/kad_bg.png");
background-size: cover;
background-position: center;
}
.kad-keahlian:hover {
transform: translateY(-3px) scale(1.01);
box-shadow:
0 24px 56px rgba(0, 0, 0, 0.3),
0 0 0 1px rgba(255, 255, 255, 0.08);
}
@media (max-height: 600px) {
.kad-keahlian {
max-height: 200px;
padding: 12px;
font-size: 0.75rem;
}
}
.kad-logo {
position: absolute;
top: 10px;
right: 10px;
text-align: right;
}
.kad-logo-img {
width: 124px;
height: auto;
filter: drop-shadow(2px 2px 4px rgba(0, 0, 0, 0.5));
}
.member-card-vertical {
position: absolute;
top: 18px;
left: 0;
writing-mode: vertical-rl;
text-orientation: mixed;
font-weight: bold;
font-size: 1.4rem;
letter-spacing: 2px;
padding: 5px;
color: #fff;
text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.7);
}
.kad-keahlian::before {
content: "";
position: absolute;
background-size: 100%;
opacity: 0.06;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #000;
pointer-events: none;
}
.member-details {
font-family: Arial, sans-serif;
font-weight: normal;
font-size: 1.05rem;
line-height: 1.3;
display: flex;
flex-direction: column;
gap: 5px;
margin-left: 38px;
}
.kad-content {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 8px;
position: relative;
z-index: 5;
}
.detail-item {
position: relative;
text-align: left;
}
.detail-item--barcode {
margin-top: 4px;
}
#barcode {
transform: scaleX(1.45);
transform-origin: left;
max-width: 100%;
}
.detail-label {
text-align: left;
}
.detail-value {
text-align: right;
}
.no-anggota {
position: absolute;
top: 5px;
right: 8px;
text-align: right;
z-index: 10;
}
.contact-info {
text-align: right;
font-size: 9px;
line-height: 1.2;
color: rgb(12, 12, 12);
}
.contact-info i {
margin-right: 3px;
font-size: 8px;
font-weight: normal;
}
@media (max-width: 991px) {
.voter-home-page {
margin-top: 16px;
}
.hero-banner-image {
max-height: 300px;
}
.hero-banner-title {
font-size: 2.35rem;
}
.member-field-label {
flex: 0 0 42%;
max-width: 220px;
}
}
@media (max-width: 767px) {
.hero-banner {
border-radius: 18px;
}
.hero-banner-image {
max-height: 240px;
}
.hero-banner-caption {
padding: 18px;
border-radius: 16px;
}
.hero-banner-title {
font-size: 1.95rem;
}
.hero-banner-subtitle {
font-size: 1.25rem;
}
.home-section-container {
padding: 16px 14px 18px;
border-radius: 16px;
}
.home-hero-section {
padding: 12px 12px 16px;
}
.home-section-heading {
font-size: 1.45rem;
gap: 10px;
padding-bottom: 14px;
margin-bottom: 14px;
}
.home-section-heading-icon {
width: 40px;
height: 40px;
font-size: 1.45rem;
}
.home-cta-link {
flex-wrap: wrap;
padding: 16px;
}
.home-cta-title {
font-size: 1.45rem;
}
.home-cta-desc {
font-size: 1.15rem;
}
/* Butiran Keahlian: icon + stacked label/value — shorter rows, full width text */
.home-member-card--in-section {
border-radius: 12px;
}
.home-member-card--in-section .member-field {
padding: 12px 12px;
gap: 10px;
align-items: flex-start;
}
.home-member-card--in-section .member-field-icon {
width: 40px;
height: 40px;
margin-top: 0;
font-size: 1.35rem;
border-radius: 11px;
}
.home-member-card--in-section .member-field-body {
flex-direction: column;
align-items: stretch;
gap: 5px;
justify-content: flex-start;
}
.home-member-card--in-section .member-field-label {
flex: none;
max-width: none;
width: 100%;
padding-top: 0;
margin: 0;
font-size: 1.15rem;
font-weight: 700;
letter-spacing: 0.06em;
color: #475569;
text-transform: uppercase;
line-height: 1.25;
}
.home-member-card--in-section .member-field-value {
flex: none;
width: 100%;
text-align: left;
font-size: 1.42rem;
font-weight: 700;
color: #0f172a;
line-height: 1.35;
word-break: break-word;
overflow-wrap: anywhere;
}
.home-claim-code {
font-size: 2.2rem;
letter-spacing: 0.14em;
}
}
</style>