first commit

This commit is contained in:
Husni Ailatat 2025-12-04 15:07:59 +07:00
commit 140428b9bd
48 changed files with 6398 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules

17
eslint.config.js Normal file
View File

@ -0,0 +1,17 @@
import js from '@eslint/js'
import pluginVue from 'eslint-plugin-vue'
export default [
{
name: 'app/files-to-lint',
files: ['**/*.{js,mjs,jsx,vue}'],
},
{
name: 'app/files-to-ignore',
ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**'],
},
js.configs.recommended,
...pluginVue.configs['flat/essential'],
]

19
index.html Normal file
View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap"
rel="stylesheet"
/>
<title>Solnus | Legal Service Solutions</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

8
jsconfig.json Normal file
View File

@ -0,0 +1,8 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
},
"exclude": ["node_modules", "dist"]
}

4071
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

24
package.json Normal file
View File

@ -0,0 +1,24 @@
{
"name": "vue-project",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint . --fix"
},
"dependencies": {
"vue": "^3.5.13",
"vue-router": "^4.6.3"
},
"devDependencies": {
"@eslint/js": "^9.14.0",
"@vitejs/plugin-vue": "^5.2.1",
"eslint": "^9.14.0",
"eslint-plugin-vue": "^9.30.0",
"vite": "^6.0.5",
"vite-plugin-vue-devtools": "^7.6.8"
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

22
src/App.vue Normal file
View File

@ -0,0 +1,22 @@
<script setup>
import BackToTop from "./components/button/BackToTop.vue";
import Footer from "./components/footer/Footer.vue";
import Navigation from "./components/Navigation.vue";
</script>
<template>
<header>
<Navigation />
</header>
<router-view />
<footer id="kontak">
<Footer />
</footer>
<BackToTop />
</template>
<style scoped>
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

5
src/assets/base.css Normal file
View File

@ -0,0 +1,5 @@
body {
margin: 0;
padding: 0;
font-family: "Poppins", serif;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

BIN
src/assets/hero.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 KiB

BIN
src/assets/icon-check.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

BIN
src/assets/icon-pt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

BIN
src/assets/icon_check.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 929 B

BIN
src/assets/icon_law.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 686 B

BIN
src/assets/logo-solnus.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

1
src/assets/logo.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>

After

Width:  |  Height:  |  Size: 276 B

4
src/assets/main.css Normal file
View File

@ -0,0 +1,4 @@
@import './base.css';

BIN
src/assets/salad.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
src/assets/spaghetti.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

BIN
src/assets/steak.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

View File

@ -0,0 +1,315 @@
<template>
<header>
<nav class="navbar">
<div class="left">
<router-link to="/">
<img src="@/assets/logo-solnus.png" class="logo" />
</router-link>
</div>
<div class="right">
<ul class="nav-links" :class="{ active: isMenuOpen }">
<li><a href="#solusi" @click.prevent="scrollToSection('solusi')" :class="{ 'active-link': activeSection === 'solusi' }">Solusi</a></li>
<li class="dropdown" ref="dropdownRef">
<button type="button" class="dropdown-toggle" @click="toggleDropdown" :class="{ 'active-link': $route.path.startsWith('/packages') }">
Paket Layanan
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg" style="margin-left: 4px; transition: transform 0.2s;" :style="{ transform: showDropdown ? 'rotate(180deg)' : 'rotate(0deg)' }">
<path d="M3 4.5L6 7.5L9 4.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<div class="dropdown-menu" :class="{ show: showDropdown }">
<router-link to="/packages" @click="closeMenu">Semua Paket</router-link>
<router-link to="/packages/pendirian" @click="closeMenu">Paket Pendirian</router-link>
<router-link to="/packages/perubahan" @click="closeMenu">Paket Perubahan & Pembubaran</router-link>
<router-link to="/packages/property" @click="closeMenu">Paket Property</router-link>
<router-link to="/packages/khusus" @click="closeMenu">Paket Khusus</router-link>
<router-link to="/packages/umum" @click="closeMenu">Paket Umum</router-link>
</div>
</li>
<li><a href="#kontak" @click.prevent="scrollToSection('kontak')" :class="{ 'active-link': activeSection === 'kontak' }">Kontak Kami</a></li>
<li>
<a href="#konsultasi" @click.prevent="scrollToSection('konsultasi')" class="consult-btn" :class="{ 'active-link': activeSection === 'konsultasi' }">Konsultasi Gratis</a>
</li>
</ul>
<div class="hamburger" @click="toggleMenu">
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
</div>
</div>
</nav>
</header>
</template>
<script>
export default {
data() {
return {
isMenuOpen: false,
activeSection: '',
showDropdown: false,
};
},
methods: {
toggleMenu() {
this.isMenuOpen = !this.isMenuOpen;
},
closeMenu() {
this.isMenuOpen = false;
this.showDropdown = false;
},
toggleDropdown() {
this.showDropdown = !this.showDropdown;
},
scrollToSection(sectionId) {
this.closeMenu();
// If not on home page, navigate to home first
if (this.$route.path !== '/') {
this.$router.push('/').then(() => {
// Wait for DOM to be fully rendered
setTimeout(() => {
this.performScroll(sectionId);
}, 100);
});
} else {
this.performScroll(sectionId);
}
},
performScroll(sectionId) {
const element = document.getElementById(sectionId);
if (element) {
element.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
},
handleScroll() {
// Only track sections on home page
if (this.$route.path !== '/') {
this.activeSection = '';
return;
}
const sections = ['solusi', 'konsultasi', 'kontak'];
const scrollPosition = window.scrollY + 100;
for (const sectionId of sections) {
const element = document.getElementById(sectionId);
if (element) {
const offsetTop = element.offsetTop;
const offsetBottom = offsetTop + element.offsetHeight;
if (scrollPosition >= offsetTop && scrollPosition < offsetBottom) {
this.activeSection = sectionId;
return;
}
}
}
this.activeSection = '';
}
},
mounted() {
// Close menu when clicking on any nav link
const navLinks = this.$el.querySelectorAll('.nav-links a');
navLinks.forEach(link => {
link.addEventListener('click', () => {
this.closeMenu();
});
});
// Track scroll position for active section
window.addEventListener('scroll', this.handleScroll);
this.handleScroll();
},
beforeUnmount() {
window.removeEventListener('scroll', this.handleScroll);
}
};
</script>
<style scoped>
.navbar {
position: fixed;
top: 0;
width: 100%;
padding: 10px;
background: white;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
z-index: 1000;
overflow: visible;
}
.logo {
width: 140px;
cursor: pointer;
}
.right {
display: flex;
align-items: center;
gap: 48px;
overflow: visible;
}
.nav-links {
list-style: none;
display: flex;
gap: 50px;
align-items: center;
margin-right: 20px;
overflow: visible;
}
.nav-links li a {
text-decoration: none;
color: #333;
font-size: 16px;
font-weight: 500;
}
.nav-links li a:hover {
color: #FF6640;
}
.nav-links li a.router-link-active,
.nav-links li a.router-link-exact-active,
.nav-links li a.active-link {
color: #FF6640;
}
.dropdown {
position: relative;
}
.dropdown-toggle {
display: flex;
align-items: center;
cursor: pointer;
background: none;
border: none;
color: #333;
font-size: 16px;
font-weight: 500;
padding: 0;
}
.dropdown-menu {
display: none;
position: absolute;
top: 50px;
left: 0;
background: white;
border: 1px solid #e5e7eb;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
padding: 8px 0;
margin: 0;
min-width: 260px;
z-index: 99999;
}
.dropdown-menu.show {
display: block;
}
.dropdown-menu a {
display: block;
padding: 12px 20px;
color: #333;
text-decoration: none;
font-size: 14px;
font-weight: 500;
transition: background 0.2s, color 0.2s;
white-space: nowrap;
}
.dropdown-menu a:hover {
background: #f9fafb;
color: #FF6640;
}
.dropdown-menu a.router-link-active,
.dropdown-menu a.router-link-exact-active {
color: #FF6640;
background: #fff5f3;
}
.consult-btn {
padding: 10px 22px;
border: 2px solid #FF6640;
border-radius: 8px;
font-weight: bold;
color: #FF6640;
white-space: nowrap; /* prevent wrapping */
}
.consult-btn:hover {
background: #FF6640;
color: white !important;
}
.hamburger {
display: none;
flex-direction: column;
gap: 5px;
cursor: pointer;
margin-left: 20px;
}
.bar {
width: 28px;
height: 3px;
background: #333;
}
/* Mobile */
@media (max-width: 768px) {
.nav-links {
display: none;
flex-direction: column;
position: absolute;
top: 70px;
right: 0;
background: white;
width: 100%;
padding: 20px 0;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
gap: 0;
}
.nav-links.active {
display: flex;
}
.hamburger {
display: flex;
}
.dropdown {
width: 100%;
}
.dropdown-toggle {
padding: 12px 20px;
width: 100%;
justify-content: space-between;
}
.dropdown-menu {
position: static;
transform: none;
box-shadow: none;
border-left: 3px solid #FF6640;
background: #f9fafb;
margin: 0;
}
.nav-links > li > a {
display: block;
padding: 12px 20px;
}
}
</style>

View File

@ -0,0 +1,46 @@
<script setup>
import { ref, onMounted, onUnmounted } from "vue";
const showButton = ref(false);
const scrollToTop = () => {
window.scrollTo({ top: 0, behavior: "smooth" });
};
const handleScroll = () => {
showButton.value = window.scrollY > 0;
};
onMounted(() => {
window.addEventListener("scroll", handleScroll);
});
onUnmounted(() => {
window.removeEventListener("scroll", handleScroll);
});
</script>
<template>
<button v-if="showButton" @click="scrollToTop">&#x2191;</button>
</template>
<style scoped>
button {
position: fixed;
bottom: 20px;
right: 20px;
width: 50px;
height: 50px;
background-color: #f8b400;;
color: black;
border: none;
border-radius: 50%;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
cursor: pointer;
font-size: 20px;
}
button:hover {
background-color: #c39004;;
}
</style>

View File

@ -0,0 +1,83 @@
<template>
<footer class="footer">
<div class="footer-wrapper">
<div class="container">
<div class="grid">
<div class="col brand">
<div class="logo">
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="28" height="28" rx="6" fill="#FF6640"/>
<path d="M8 10h12M8 14h12M8 18h8" stroke="#fff" stroke-width="2" stroke-linecap="round"/>
</svg>
<span class="logo-text">Solnus</span>
</div>
<p class="tagline">Urus Legalitas Bisnis Tanpa Ribet.<br/>Dari pendirian PT, CV, hingga Yayasan,<br/>semua proses hukum kini lebih cepat,<br/>aman, dan transparan bersama tim<br/>profesional berpengalaman.</p>
<div class="contact">+62 821 xxxx xxxx</div>
<div class="social">
<a href="#" aria-label="LinkedIn">
<svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24"><path d="M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z"/></svg>
</a>
<a href="#" aria-label="Instagram">
<svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z"/></svg>
</a>
</div>
</div>
<div class="col">
<h3 class="title">Navigasi</h3>
<ul class="links">
<li><a href="#">Solusi</a></li>
<li><a href="#">Kenapa Solnus?</a></li>
<li><a href="#">Layanan Solnus</a></li>
<li><a href="#">Cara Kerja</a></li>
<li><a href="#">Testimoni</a></li>
</ul>
</div>
<div class="col">
<h3 class="title">Layanan</h3>
<ul class="links">
<li><a href="#">Pendirian PT (Perseroan Terbatas)</a></li>
<li><a href="#">Pendirian CV (Commanditaire Vennootschap)</a></li>
<li><a href="#">Pendirian Yayasan</a></li>
</ul>
</div>
</div>
</div>
</div>
<div class="bottom">
<div class="container">
<div class="bottom-content">
<span>© 2025 All Rights Reserved | PT Solusi Legal Nusantara</span>
<a href="#" class="privacy">Privacy Policy</a>
</div>
</div>
</div>
</footer>
</template>
<style scoped>
.footer { background: #fff; border-top: 1px solid #e5e7eb; }
.footer-wrapper { padding: 60px 20px 0; }
.container { max-width: 1200px; margin: 0 auto; }
.grid { display: grid; gap: 48px; grid-template-columns: 1fr; }
@media (min-width: 768px) { .grid { grid-template-columns: 1.5fr 1fr 1fr; } }
.col { }
.brand { }
.logo { display: flex; align-items: center; gap: 10px; margin-bottom: 16px; }
.logo-text { font-size: 24px; font-weight: 800; color: #FF6640; }
.tagline { font-size: 14px; line-height: 1.7; color: #6b7280; margin: 0 0 20px; }
.contact { font-size: 16px; font-weight: 600; color: #111827; margin-bottom: 16px; }
.social { display: flex; gap: 12px; }
.social a { width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; color: #6b7280; transition: color .2s; }
.social a:hover { color: #111827; }
.title { font-size: 16px; font-weight: 700; color: #111827; margin: 0 0 16px; }
.links { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 12px; }
.links a { color: #6b7280; text-decoration: none; font-size: 14px; transition: color .2s; }
.links a:hover { color: #111827; }
.bottom { background: #111827; margin-top: 48px; padding: 20px; }
.bottom-content { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px; color: #9ca3af; font-size: 14px; }
.privacy { color: #9ca3af; text-decoration: none; }
.privacy:hover { color: #fff; }
@media (max-width: 640px) { .bottom-content { flex-direction: column; text-align: center; } }
</style>

View File

@ -0,0 +1,39 @@
<script setup>
const props = defineProps({
title: { type: String, default: 'Jangan biarkan bisnis hanya Anda berdiri di atas dokumen yang lemah.' },
buttonText: { type: String, default: 'Konsultasi Gratis' }
})
</script>
<template>
<section class="cta">
<div class="overlay"></div>
<div class="container">
<h2 class="title">{{ title }}</h2>
<button class="btn">{{ buttonText }}</button>
</div>
</section>
</template>
<style scoped>
.cta {
padding: 80px 20px;
text-align: center;
position: relative;
background-image: url('@/assets/business-hero.png');
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
.overlay {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.65);
z-index: 1;
}
.container { max-width: 800px; margin: 0 auto; position: relative; z-index: 2; }
.title { color: #fff; font-size: 28px; font-weight: 700; line-height: 1.4; margin: 0 0 24px; }
.btn { padding: 14px 32px; background: #FF6640; color: #fff; border: none; border-radius: 6px; font-weight: 600; font-size: 16px; cursor: pointer; transition: background .2s; }
.btn:hover { background: #e85a30; }
@media (min-width: 768px) { .title { font-size: 36px; } }
</style>

View File

@ -0,0 +1,174 @@
<template>
<section class="contact">
<h2 class="contact-title">{{ heading }}</h2>
<p class="contact-description">
{{ subheading }}
</p>
<div class="contact-items">
<article
v-for="item in contactItems"
:key="item.id"
class="item"
>
<i aria-hidden="true">{{ item.icon }}</i>
<div class="details">
<h3>{{ item.title }}</h3>
<p class="detail-text">{{ item.detail }}</p>
<p
v-if="item.description"
class="detail-subtext"
>
{{ item.description }}
</p>
</div>
</article>
</div>
</section>
</template>
<script setup>
const heading = "Hubungi Solnus";
const subheading =
"Tim kami siap mendampingi legalitas bisnis Anda melalui jalur komunikasi yang paling nyaman.";
const contactItems = [
{
id: "phone",
icon: "📞",
title: "Telepon",
detail: "+62 811-2233-4455",
description: "Senin - Jumat, 09.00 - 18.00 WIB"
},
{
id: "email",
icon: "✉️",
title: "Email",
detail: "support@solnus.id",
description: "Kami membalas dalam 1x24 jam kerja"
},
{
id: "office",
icon: "📍",
title: "Kantor",
detail: "Green Office Park, BSD City",
description: "Silakan buat janji sebelum kunjungan"
}
];
</script>
<style scoped>
.contact {
padding: 80px 20px;
}
.contact-title {
text-align: center;
font-size: 2.2rem;
margin-bottom: 10px;
color: var(--color-heading);
}
.contact-description {
text-align: center;
max-width: 640px;
margin: 0 auto 40px;
color: var(--color-text);
line-height: 1.6;
}
.contact-items {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.item {
margin-top: 2rem;
display: flex;
position: relative;
}
.details {
flex: 1;
margin-left: 1rem;
}
i {
display: flex;
place-items: center;
place-content: center;
width: 32px;
height: 32px;
color: var(--color-text);
background: var(--color-background-mute);
border-radius: 50%;
}
h3 {
font-size: 1.2rem;
font-weight: 500;
margin-bottom: 0.4rem;
color: var(--color-heading);
}
.detail-text {
font-weight: 600;
color: var(--color-heading);
margin: 0;
}
.detail-subtext {
margin: 0.2rem 0 0;
color: var(--color-text);
}
@media (min-width: 1024px) {
.contact-items {
gap: 0;
}
.item {
margin-top: 0;
padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
}
i {
top: calc(50% - 25px);
left: -26px;
position: absolute;
border: 1px solid var(--color-border);
background: var(--color-background);
border-radius: 8px;
width: 50px;
height: 50px;
}
.item:before {
content: ' ';
border-left: 1px solid var(--color-border);
position: absolute;
left: 0;
bottom: calc(50% + 25px);
height: calc(50% - 25px);
}
.item:after {
content: ' ';
border-left: 1px solid var(--color-border);
position: absolute;
left: 0;
top: calc(50% + 25px);
height: calc(50% - 25px);
}
.item:first-of-type:before {
display: none;
}
.item:last-of-type:after {
display: none;
}
}
</style>

View File

@ -0,0 +1,209 @@
<template>
<section
class="hero"
:style="heroStyle"
>
<div class="overlay">
<div class="hero-content">
<h1 class="headline">{{ headline }}</h1>
<div class="badges">
<button
v-for="badge in badges"
:key="badge.id"
class="badge"
:class="badge.variant"
type="button"
>
<img
:src="badge.icon"
:alt="badge.alt"
class="icon"
/>
{{ badge.label }}
</button>
</div>
<p class="description">
{{ description }}
</p>
<div class="cta-buttons">
<button
v-for="button in ctaButtons"
:key="button.id"
class="cta-button"
:class="button.variant"
type="button"
>
{{ button.label }}
</button>
</div>
</div>
</div>
</section>
</template>
<script setup>
import heroBackground from "@/assets/background_hero.png";
import iconLaw from "@/assets/icon_law.png";
import iconCheck from "@/assets/icon_check.png";
const headline = "Urus Legalitas Bisnis Tanpa Ribet";
const description =
"Dari pendirian PT, CV, hingga Yayasan, semua proses hukum lebih cepat, aman, dan transparan bersama tim profesional berpengalaman Solnus secara online.";
const heroStyle = {
backgroundImage: `url(${heroBackground})`
};
const badges = [
{
id: "law-support",
label: "Didampingi Tim Hukum",
variant: "primary",
icon: iconLaw,
alt: "Ikon tim hukum"
},
{
id: "verified-partner",
label: "Legal Partner Terverifikasi",
variant: "secondary",
icon: iconCheck,
alt: "Ikon verifikasi legal"
}
];
const ctaButtons = [
{ id: "consult", label: "Jadwalkan Konsultasi Gratis", variant: "primary" },
{ id: "services", label: "Lihat Layanan Solnus", variant: "secondary" }
];
</script>
<style scoped>
.hero {
position: relative;
background-repeat: no-repeat;
background-position: center;
background-size: cover;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 0 40px;
color: white;
text-align: center;
}
.overlay {
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.3);
display: flex;
align-items: center;
justify-content: center;
}
.hero-content {
max-width: 900px;
margin-top: -40px;
}
.headline {
font-size: 4rem;
font-weight: 700;
line-height: 1.2;
margin-bottom: 30px;
}
.badges {
display: flex;
gap: 20px;
justify-content: center;
margin-bottom: 30px;
flex-wrap: wrap;
}
.badge {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 22px;
font-weight: 600;
border-radius: 8px;
border: none;
cursor: default;
font-size: 1rem;
}
.badge.primary {
background-color: #b71c1c;
}
.badge.secondary {
background-color: #5e0000;
}
.icon {
width: 20px;
height: 20px;
}
.description {
font-size: 1.25rem;
line-height: 1.6;
margin-bottom: 40px;
}
.cta-buttons {
display: flex;
gap: 20px;
justify-content: center;
flex-wrap: wrap;
}
.cta-button {
padding: 14px 28px;
font-size: 1.1rem;
border-radius: 10px;
font-weight: 700;
cursor: pointer;
border: 2px solid transparent;
}
.cta-button.primary {
background-color: #FF6640;
color: black;
border-color: #FF6640;
}
.cta-button.primary:hover {
background-color: #FF6640;
border-color: #FF6640;
}
.cta-button.secondary {
background-color: transparent;
color: white;
border-color: white;
}
.cta-button.secondary:hover {
background-color: white;
color: #222;
}
@media (max-width: 768px) {
.headline {
font-size: 2.4rem;
}
.description {
font-size: 1.05rem;
}
.hero-content {
margin-top: 0;
}
}
</style>

View File

@ -0,0 +1,170 @@
<template>
<section id="menu" class="featured">
<h2>Popular Dishes</h2>
<div class="dishes">
<div
v-for="dish in dishes"
:key="dish.id"
class="dish"
>
<img
:src="dish.image"
width="280"
height="280"
:alt="dish.alt"
/>
<h3>{{ dish.name }}</h3>
<p>{{ dish.description }}</p>
<div class="card-actions">
<a
:href="dish.whatsappLink"
class="whatsapp-button"
target="_blank"
rel="noopener"
>
WhatsApp
</a>
<button
class="detail-button"
type="button"
>
{{ dish.detailLabel }}
</button>
</div>
</div>
</div>
</section>
</template>
<script setup>
import spaghettiImg from "@/assets/spaghetti.jpg";
import steakImg from "@/assets/steak.jpg";
import saladImg from "@/assets/salad.jpg";
const whatsappLink = "https://wa.me/1234567890";
const dishes = [
{
id: "spaghetti",
name: "Spaghetti Carbonara",
description: "Classic Italian pasta with creamy sauce and pancetta.",
image: spaghettiImg,
alt: "Spaghetti Carbonara",
whatsappLink,
detailLabel: "Detail"
},
{
id: "steak",
name: "Grilled Steak",
description: "Perfectly grilled steak served with mashed potatoes.",
image: steakImg,
alt: "Grilled Steak",
whatsappLink,
detailLabel: "Detail"
},
{
id: "salad",
name: "Caesar Salad",
description: "Fresh greens tossed in Caesar dressing.",
image: saladImg,
alt: "Caesar Salad",
whatsappLink,
detailLabel: "Detail"
}
];
</script>
<style scoped>
#menu {
box-sizing: border-box;
padding: 100px 0;
}
h2 {
text-align: center;
margin-bottom: 50px;
font-size: 30px;
}
.dishes {
display: flex;
width: 100%;
box-sizing: border-box;
padding: 0 80px;
justify-content: space-evenly;
}
.dish {
background-color: white;
width: 280px;
height: 400px;
border-radius: 20px;
display: flex;
flex-direction: column;
align-items: center;
box-shadow: 0 0 13px 0 rgba(0, 0, 0, 0.3);
transition: 300ms;
padding: 0 0 20px;
}
.dish:hover {
margin-top: -10px;
box-shadow: 0 0 20px 1px rgba(0, 0, 0, 1);
}
img {
border-radius: 20px 20px 0 0;
height: 220px;
object-fit: cover;
}
p {
width: 230px;
}
.card-actions {
margin-top: auto;
display: flex;
justify-content: space-between;
width: 230px;
padding-top: 10px;
}
.detail-button {
background-color: #007bff;
color: white;
border: none;
padding: 6px 12px;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
}
.detail-button:hover {
background-color: #0056b3;
}
.whatsapp-button {
background-color: #25d366;
color: white;
text-decoration: none;
padding: 6px 12px;
border-radius: 6px;
font-size: 14px;
}
.whatsapp-button:hover {
background-color: #1ebe5d;
}
@media (max-width: 1024px) {
.dishes {
flex-direction: column;
align-items: center;
}
.dish {
margin-bottom: 20px;
}
}
</style>

View File

@ -0,0 +1,53 @@
<script setup>
const steps = [
{
number: '1.',
title: 'Konsultasi & Pemilihan Layanan',
description: 'Pilih layanan yang sesuai dengan kebutuhan usaha Anda.'
},
{
number: '2.',
title: 'Upload & Verifikasi Dokumen',
description: 'Kirim dokumen yang dibutuhkan, kami bantu cek kelengkapannya.'
},
{
number: '3.',
title: 'Proses oleh Tim Legal',
description: 'Dokumen Anda diurus oleh notaris & konsultan hukum terpercaya.'
},
{
number: '4.',
title: 'Dokumen Selesai & Dikirim',
description: 'Terima semua dokumen resmi dalam bentuk digital dan fisik.'
}
]
</script>
<template>
<section class="reasons">
<div class="container">
<h2 class="headline">Bagaimana Kami Membantu Anda</h2>
<div class="grid">
<article v-for="(step, i) in steps" :key="i" class="card">
<div class="number-badge">{{ step.number }}</div>
<h3 class="card-title">{{ step.title }}</h3>
<p class="card-desc">{{ step.description }}</p>
</article>
</div>
</div>
</section>
</template>
<style scoped>
.reasons { padding: 80px 20px; background: #f9fafb; }
.container { max-width: 1200px; margin: 0 auto; }
.headline { font-size: 36px; font-weight: 800; text-align: center; margin: 0 0 56px; color: #111827; }
.grid { display: grid; gap: 24px; grid-template-columns: 1fr; }
@media (min-width: 640px) { .grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1024px) { .grid { grid-template-columns: repeat(4, 1fr); } }
.card { background: #fff; padding: 32px 24px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); text-align: left; }
.number-badge { width: 48px; height: 48px; border-radius: 50%; border: 2px solid #e5e7eb; display: flex; align-items: center; justify-content: center; font-size: 18px; font-weight: 700; color: #6b7280; margin-bottom: 16px; }
.card-title { font-size: 18px; font-weight: 700; margin: 0 0 12px; color: #111827; line-height: 1.4; }
.card-desc { font-size: 14px; line-height: 1.6; color: #6b7280; margin: 0; }
@media (min-width: 768px) { .headline { font-size: 40px; } }
</style>

View File

@ -0,0 +1,192 @@
<template>
<div class="service-card">
<div class="diamond-wrapper">
<div class="diamond-bg"></div>
<img
:src="image"
:alt="title"
class="service-image"
/>
<div class="icon-badge">
<svg v-if="!primary" width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19 3H5C3.89543 3 3 3.89543 3 5V19C3 20.1046 3.89543 21 5 21H19C20.1046 21 21 20.1046 21 19V5C21 3.89543 20.1046 3 19 3Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9 9H15" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
<path d="M9 13H15" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
<path d="M9 17H13" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
<svg v-else width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21 15C21 15.5304 20.7893 16.0391 20.4142 16.4142C20.0391 16.7893 19.5304 17 19 17H7L3 21V5C3 4.46957 3.21071 3.96086 3.58579 3.58579C3.96086 3.21071 4.46957 3 5 3H19C19.5304 3 20.0391 3.21071 20.4142 3.58579C20.7893 3.96086 21 4.46957 21 5V15Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<!-- Tags -->
<div v-if="tags && tags.length > 0" class="tags">
<div v-for="(tag, i) in tags" :key="i" class="tag">
{{ tag }}
</div>
</div>
</div>
<h3 class="service-title">{{ title }}</h3>
<p class="service-desc">
{{ description }}
</p>
<button
class="service-btn"
:class="{ primary }"
type="button"
>
{{ buttonText }}
</button>
</div>
</template>
<script setup>
const { image, title, description, buttonText, primary, tags } = defineProps({
image: { type: String, required: true },
title: { type: String, required: true },
description: { type: String, required: true },
buttonText: { type: String, required: true },
primary: { type: Boolean, default: false },
tags: { type: Array, default: () => [] }
});
</script>
<style scoped>
.service-card {
background: #f9fafb;
padding: 32px 24px;
text-align: center;
transition: 0.2s ease-in-out;
cursor: default;
display: flex;
flex-direction: column;
align-items: center;
}
.service-card:hover {
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
/* Diamond wrapper */
.diamond-wrapper {
position: relative;
width: 260px;
height: 240px;
margin-bottom: 24px;
display: flex;
align-items: center;
justify-content: center;
}
.diamond-bg {
position: absolute;
width: 200px;
height: 200px;
background: linear-gradient(135deg, #FF6640 0%, #e85a30 100%);
transform: rotate(45deg);
z-index: 1;
border-radius: 0;
}
.service-image {
position: relative;
width: 180px;
height: 200px;
object-fit: cover;
object-position: center top;
z-index: 2;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
.icon-badge {
position: absolute;
top: 15px;
right: 20px;
width: 48px;
height: 48px;
background: #fff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 12px rgba(0,0,0,0.15);
z-index: 3;
color: #FF6640;
}
/* Tags */
.tags {
position: absolute;
bottom: -8px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 6px;
z-index: 3;
flex-wrap: wrap;
justify-content: center;
width: 100%;
max-width: 220px;
}
.tag {
background: rgba(75, 85, 99, 0.88);
color: #fff;
padding: 7px 14px;
font-size: 12px;
font-weight: 500;
white-space: nowrap;
box-shadow: 0 2px 8px rgba(0,0,0,0.25);
line-height: 1.2;
}
/* Title */
.service-title {
font-size: 22px;
font-weight: 700;
margin-bottom: 12px;
color: #111827;
}
/* Description */
.service-desc {
font-size: 14px;
color: #6b7280;
line-height: 1.6;
margin-bottom: 24px;
flex: 1;
}
/* Button */
.service-btn {
border: 2px solid #FF6640;
padding: 12px 28px;
color: #FF6640;
font-weight: 600;
background: white;
cursor: pointer;
transition: 0.2s;
font-size: 14px;
}
.service-btn:hover {
background: #FF6640;
color: white;
}
/* Primary style */
.service-btn.primary {
background: #FF6640;
color: white;
border-color: #FF6640;
}
.service-btn.primary:hover {
background: #e85a30;
border-color: #e85a30;
}
</style>

View File

@ -0,0 +1,118 @@
<template>
<section class="services-wrapper">
<div class="container">
<div class="eyebrow">SOLUSI LEGAL</div>
<h2 class="headline">Semua Urusan Legal Anda,<br/>Kami Bantu Selesaikan</h2>
<p class="description">
Solnus membantu individu dan perusahaan mengurusberbagai kebutuhan hukum, dari pendirian badan usaha, pengurusan izin, hingga konsultasi legal. Kami menghadirkan kemudahan, transparansi, dan kepastian dalam setiap proses legal yang Anda butuhkan.
</p>
<div class="services-container">
<Service
v-for="service in services"
:key="service.id"
:image="service.image"
:title="service.title"
:description="service.description"
:button-text="service.buttonText"
:primary="service.primary"
:tags="service.tags"
/>
</div>
</div>
</section>
</template>
<script setup>
import Service from "./Service.vue";
const services = [
{
id: "business-formation",
image: 'https://images.unsplash.com/photo-1560250097-0b93528c311a?w=400&h=400&fit=crop',
title: "Pendirian Badan Usaha",
description: "Buat PT, CV, atau Yayasan secara online, dengan dokumen resmi Kemenkumham.",
buttonText: "Lihat Detail Layanan",
primary: false,
tags: ['PT', 'CV', 'Yayasan']
},
{
id: "license-nib",
image: 'https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?w=400&h=400&fit=crop',
title: "Perizinan & NIB",
description: "Dapatkan Nomor Induk Berusaha (NIB) dan izin OSS dengan cepat & aman.",
buttonText: "Lihat Detail Layanan",
primary: false,
tags: []
},
{
id: "legal-consultation",
image: 'https://images.unsplash.com/photo-1519085360753-af0119f7cbe7?w=400&h=400&fit=crop',
title: "Konsultasi Hukum",
description: "Konsultasikan masalah hukum bisnis Anda dengan ahli berpengalaman.",
buttonText: "Jadwalkan Konsultasi Gratis",
primary: true,
tags: ['Lebih baik PT atau CV ya?', 'Ini masih bisa daftar NIB?']
}
];
</script>
<style scoped>
.services-wrapper {
padding: 80px 20px;
background: #fff;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.eyebrow {
text-align: center;
font-size: 14px;
font-weight: 700;
color: #FF6640;
letter-spacing: 1px;
margin-bottom: 16px;
}
.headline {
font-size: 36px;
font-weight: 800;
text-align: center;
color: #111827;
margin: 0 0 20px;
line-height: 1.3;
}
.description {
font-size: 15px;
line-height: 1.7;
color: #6b7280;
text-align: center;
max-width: 800px;
margin: 0 auto 56px;
}
.services-container {
display: grid;
gap: 32px;
grid-template-columns: 1fr;
}
@media (min-width: 768px) {
.services-container {
grid-template-columns: repeat(3, 1fr);
}
.headline {
font-size: 42px;
}
}
@media (max-width: 767px) {
.headline {
font-size: 28px;
}
}
</style>

View File

@ -0,0 +1,159 @@
<script setup>
const services = [
{
id: 'pendirian',
title: 'Paket Pendirian',
icon: '🏢',
image: 'https://images.unsplash.com/photo-1486406146926-c627a92ad1ab?w=400&h=300&fit=crop'
},
{
id: 'perubahan',
title: 'Paket Perubahan\n& Pembubaran',
icon: '📄',
image: 'https://images.unsplash.com/photo-1450101499163-c8848c66ca85?w=400&h=300&fit=crop'
},
{
id: 'property',
title: 'Paket Property',
icon: '🏠',
image: 'https://images.unsplash.com/photo-1560518883-ce09059eeffa?w=400&h=300&fit=crop'
},
{
id: 'khusus',
title: 'Paket Khusus',
icon: '⚖️',
image: 'https://images.unsplash.com/photo-1589829545856-d10d557cf95f?w=400&h=300&fit=crop'
},
{
id: 'umum',
title: 'Paket Umum',
icon: '🔨',
image: 'https://images.unsplash.com/photo-1505664194779-8beaceb93744?w=400&h=300&fit=crop'
}
]
</script>
<template>
<section class="services-menu">
<div class="overlay"></div>
<div class="container">
<h2 class="headline">Layanan Legal Solnus</h2>
<div class="cards">
<a
v-for="service in services"
:key="service.id"
:href="`#${service.id}`"
class="card"
>
<div class="card-icon">{{ service.icon }}</div>
<h3 class="card-title">{{ service.title }}</h3>
</a>
</div>
</div>
</section>
</template>
<style scoped>
.services-menu {
position: relative;
padding: 80px 20px;
background-image: url('@/assets/service_legal.jpg');
background-size: cover;
background-position: center;
background-repeat: no-repeat;
background-attachment: fixed;
}
.overlay {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.75);
z-index: 1;
}
.container {
position: relative;
z-index: 2;
max-width: 1200px;
margin: 0 auto;
}
.headline {
font-size: 36px;
font-weight: 800;
text-align: center;
margin: 0 0 48px;
color: #fff;
}
.cards {
display: grid;
gap: 20px;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
max-width: 1100px;
margin: 0 auto;
}
@media (min-width: 640px) {
.cards {
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 768px) {
.cards {
grid-template-columns: repeat(3, 1fr);
}
}
@media (min-width: 1024px) {
.cards {
grid-template-columns: repeat(5, 1fr);
}
}
.card {
background: #fff;
border-radius: 12px;
padding: 32px 20px;
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
text-decoration: none;
transition: transform .2s, box-shadow .2s;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
}
.card-icon {
font-size: 48px;
width: 80px;
height: 80px;
display: flex;
align-items: center;
justify-content: center;
background: #f9fafb;
border-radius: 12px;
}
.card-title {
margin: 0;
font-size: 16px;
font-weight: 700;
color: #111827;
text-align: center;
line-height: 1.4;
white-space: pre-line;
}
@media (min-width: 768px) {
.headline {
font-size: 42px;
}
}
</style>

View File

@ -0,0 +1,92 @@
<script setup>
import { ref } from 'vue'
const testimonials = [
{
name: 'Imam Santosa',
role: 'Owner Nona Craft Studio',
text: 'Saya sempat ragu bikin PT karena takut ribet dan mahal. Tapi ternyata lewat Solnus, semua prosesnya transparan, cepat, dan saya bisa pantau dari HP. Dalam 7 hari, SK dan NIB saya sudah keluar. Sekarang bisnis saya bisa kerja sama dengan brand besar.',
avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=200&h=200&fit=crop'
}
]
const current = ref(0)
function prev() {
current.value = (current.value - 1 + testimonials.length) % testimonials.length
}
function next() {
current.value = (current.value + 1) % testimonials.length
}
</script>
<template>
<section class="testimonial">
<div class="container">
<div class="eyebrow">1500+ Pengusaha Telah Buktikan</div>
<h2 class="headline">Sekarang Giliran Anda!</h2>
<div class="card">
<div class="author">
<img :src="testimonials[current].avatar" :alt="testimonials[current].name" class="avatar" />
<div class="meta">
<div class="name">{{ testimonials[current].name }}</div>
<div class="role">{{ testimonials[current].role }}</div>
</div>
</div>
<blockquote class="quote">
"{{ testimonials[current].text }}"
</blockquote>
<div class="stats">
<div class="stat">
<div class="stat-value">100%</div>
<div class="stat-label">Kepuasan pelanggan</div>
</div>
<div class="stat">
<div class="stat-value">4x</div>
<div class="stat-label">Lebih cepat dari proses manual</div>
</div>
</div>
<div class="nav">
<button @click="prev" class="nav-btn" aria-label="Previous">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.5 15L7.5 10L12.5 5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<button @click="next" class="nav-btn" aria-label="Next">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.5 15L12.5 10L7.5 5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</div>
</div>
</div>
</section>
</template>
<style scoped>
.testimonial { padding: 80px 20px; background: #fff; }
.container { max-width: 1200px; margin: 0 auto; }
.eyebrow { font-size: 16px; font-weight: 600; color: #FF6640; text-align: center; margin: 0 0 12px; letter-spacing: .5px; }
.headline { font-size: 40px; font-weight: 800; text-align: center; margin: 0 0 56px; color: #111827; line-height: 1.2; }
.card { padding: 48px; background: #f9fafb; border-radius: 16px; position: relative; max-width: 1000px; margin: 0 auto; }
.author { display: flex; align-items: center; gap: 16px; margin-bottom: 24px; }
.avatar { width: 64px; height: 64px; border-radius: 50%; object-fit: cover; }
.meta { display: flex; flex-direction: column; gap: 4px; }
.name { font-weight: 700; font-size: 18px; color: #111827; }
.role { font-size: 15px; color: #9ca3af; font-style: italic; }
.quote { margin: 0 0 32px; font-size: 20px; line-height: 1.7; color: #374151; font-weight: 400; }
.stats { display: flex; gap: 48px; margin-bottom: 0; }
.stat { }
.stat-value { font-size: 28px; font-weight: 800; color: #111827; margin-bottom: 4px; }
.stat-label { font-size: 14px; color: #6b7280; }
.nav { position: absolute; bottom: 48px; right: 48px; display: flex; gap: 12px; }
.nav-btn { width: 44px; height: 44px; border-radius: 50%; border: 1px solid #e5e7eb; background: #fff; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all .2s; color: #6b7280; }
.nav-btn:hover { background: #f3f4f6; border-color: #d1d5db; color: #111827; }
@media (min-width: 768px) { .headline { font-size: 48px; } }
@media (max-width: 768px) { .card { padding: 32px 24px; } .nav { position: static; margin-top: 24px; justify-content: center; } .stats { flex-direction: column; gap: 20px; } }
</style>

View File

@ -0,0 +1,67 @@
<script setup>
const features = [
{
icon: 'lightning',
title: 'Proses Cepat & Efisien',
description: 'Semua layanan dilakukan online dengan deadline jelas untuk efisiensi.'
},
{
icon: 'lock',
title: 'Aman & Terpercaya',
description: 'Dokumen diverifikasi langsung oleh notaris dan instansi resmi.'
},
{
icon: 'star',
title: 'Tim Profesional',
description: 'Didukung pengacara dan konsultan hukum berpengalaman.'
},
{
icon: 'message',
title: 'Proses Transparan',
description: 'Anda bisa pantau status dokumen kapan pun.'
}
]
</script>
<template>
<section class="why-choose">
<div class="container">
<h2 class="headline">Kenapa Bisnis Memilih Solusi Kami</h2>
<div class="grid">
<article v-for="(feature, i) in features" :key="i" class="card">
<div class="icon-wrapper">
<svg v-if="feature.icon === 'lightning'" width="32" height="32" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13 2L3 14H12L11 22L21 10H12L13 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<svg v-else-if="feature.icon === 'lock'" width="32" height="32" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="3" y="11" width="18" height="11" rx="2" stroke="currentColor" stroke-width="2"/>
<path d="M7 11V7C7 4.79086 8.79086 3 11 3H13C15.2091 3 17 4.79086 17 7V11" stroke="currentColor" stroke-width="2"/>
</svg>
<svg v-else-if="feature.icon === 'star'" width="32" height="32" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<svg v-else-if="feature.icon === 'message'" width="32" height="32" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21 11.5C21.0034 12.8199 20.6951 14.1219 20.1 15.3C19.3944 16.7118 18.3098 17.8992 16.9674 18.7293C15.6251 19.5594 14.0782 19.9994 12.5 20C11.1801 20.0035 9.87812 19.6951 8.7 19.1L3 21L4.9 15.3C4.30493 14.1219 3.99656 12.8199 4 11.5C4.00061 9.92179 4.44061 8.37488 5.27072 7.03258C6.10083 5.69028 7.28825 4.6056 8.7 3.90003C9.87812 3.30496 11.1801 2.99659 12.5 3.00003H13C15.0843 3.11502 17.053 3.99479 18.5291 5.47089C20.0052 6.94699 20.885 8.91568 21 11V11.5Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<h3 class="card-title">{{ feature.title }}</h3>
<p class="card-desc">{{ feature.description }}</p>
</article>
</div>
</div>
</section>
</template>
<style scoped>
.why-choose { padding: 80px 20px; background: #111827; }
.container { max-width: 1200px; margin: 0 auto; }
.headline { font-size: 36px; font-weight: 800; text-align: center; margin: 0 0 56px; color: #fff; line-height: 1.2; }
.grid { display: grid; gap: 32px; grid-template-columns: 1fr; }
@media (min-width: 640px) { .grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1024px) { .grid { grid-template-columns: repeat(4, 1fr); } }
.card { text-align: center; }
.icon-wrapper { width: 64px; height: 64px; margin: 0 auto 20px; display: flex; align-items: center; justify-content: center; border-radius: 50%; border: 2px solid rgba(255,255,255,0.2); color: #fff; }
.card-title { font-size: 18px; font-weight: 700; margin: 0 0 12px; color: #fff; }
.card-desc { font-size: 14px; line-height: 1.6; color: #9ca3af; margin: 0; }
@media (min-width: 768px) { .headline { font-size: 40px; } }
</style>

View File

@ -0,0 +1,60 @@
<script setup>
const props = defineProps({
title: { type: String, required: true },
price: { type: String, required: true },
subtitle: { type: String, default: '' },
image: { type: String, default: '' },
features: { type: Array, default: () => [] },
ctaText: { type: String, default: 'Jadwalkan Konsultasi Gratis' }
})
</script>
<template>
<article class="card">
<div class="media" v-if="image">
<img :src="image" :alt="title" />
<div class="overlay"></div>
<div class="media-text">
<h3>{{ title }}</h3>
<p>{{ subtitle }}</p>
</div>
</div>
<div class="body">
<div class="price">{{ price }}</div>
<ul class="features">
<li v-for="(f, i) in features" :key="i">
<svg class="icon" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="8" cy="8" r="8" fill="#10b981"/>
<path d="M11 6L7 10L5 8" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span>{{ f }}</span>
</li>
</ul>
<button class="cta">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="margin-right: 6px;">
<path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413Z" fill="currentColor"/>
</svg>
{{ ctaText }}
</button>
</div>
</article>
</template>
<style scoped>
.card { border: 1px solid #e5e7eb; border-radius: 12px; overflow: hidden; background: #fff; display: flex; flex-direction: column; height: 100%; box-shadow: 0 1px 3px rgba(0,0,0,.08); transition: box-shadow .2s; }
.card:hover { box-shadow: 0 4px 12px rgba(0,0,0,.12); }
.media { position: relative; height: 160px; overflow: hidden; }
.media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.overlay { position: absolute; inset: 0; background: linear-gradient(180deg, rgba(0,0,0,0.6) 0%, rgba(0,0,0,0.3) 100%); }
.media-text { position: absolute; bottom: 12px; left: 16px; right: 16px; color: #fff; }
.media-text h3 { margin: 0; font-size: 18px; font-weight: 700; }
.media-text p { margin: 4px 0 0; opacity: 0.95; font-size: 13px; }
.body { padding: 20px; display: flex; flex-direction: column; gap: 16px; flex: 1; }
.price { font-weight: 800; color: #EE3000; font-size: 24px; }
.features { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; flex: 1; }
.features li { display: flex; align-items: flex-start; gap: 10px; color: #374151; font-size: 14px; line-height: 1.5; }
.icon { flex-shrink: 0; margin-top: 2px; }
.cta { width: 100%; padding: 12px 16px; background: #111827; color: #fff; border: none; border-radius: 8px; cursor: pointer; font-weight: 600; font-size: 14px; display: flex; align-items: center; justify-content: center; transition: background .2s; }
.cta:hover { background: #000; }
</style>

View File

@ -0,0 +1,29 @@
<script setup>
import PackageCard from './PackageCard.vue'
const props = defineProps({
items: { type: Array, default: () => [] }
})
</script>
<template>
<div class="grid">
<PackageCard
v-for="(p, i) in items"
:key="i"
:title="p.title"
:subtitle="p.subtitle"
:price="p.price"
:image="p.image"
:features="p.features"
:cta-text="p.ctaText"
/>
</div>
</template>
<style scoped>
.grid { display: grid; grid-template-columns: repeat(12, 1fr); gap: 16px; }
@media (min-width: 0px) { .grid { grid-template-columns: 1fr; } }
@media (min-width: 640px) { .grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1024px) { .grid { grid-template-columns: repeat(3, 1fr); } }
</style>

View File

@ -0,0 +1,44 @@
<script setup>
import { TABS as tabs, DATA_BY_TAB as dataByTab } from './data.js'
import { computed } from 'vue'
import PackagesGrid from './PackagesGrid.vue'
const props = defineProps({
initialCategory: { type: String, default: null }
})
// If no category specified, show all packages
const currentCategory = computed(() => props.initialCategory || null)
const currentData = computed(() => {
if (!currentCategory.value) {
// Show all packages from all categories
return Object.values(dataByTab).flat()
}
return dataByTab[currentCategory.value] || []
})
const headline = computed(() => {
if (!currentCategory.value) {
return 'Semua Paket Layanan'
}
const tab = tabs.find(t => t.key === currentCategory.value)
return tab ? tab.label : 'Paket Layanan'
})
</script>
<template>
<section class="packages">
<div class="container">
<h2 class="headline">{{ headline }}</h2>
<PackagesGrid :items="currentData" />
</div>
</section>
</template>
<style scoped>
.packages { padding: 80px 0; background: #fff; margin-top: 60px; }
.container { max-width: 1200px; margin: 0 auto; padding: 0 20px; }
.headline { font-size: 32px; font-weight: 800; text-align: center; margin: 0 0 40px; color: #111827; }
@media (min-width: 768px) { .headline { font-size: 40px; margin-bottom: 48px; } }
</style>

View File

@ -0,0 +1,169 @@
export const TABS = [
{ key: 'pendirian', label: 'Paket Pendirian' },
{ key: 'perubahan', label: 'Paket Perubahan & Pembubaran' },
{ key: 'property', label: 'Paket Property' },
{ key: 'khusus', label: 'Paket Khusus' },
{ key: 'umum', label: 'Paket Umum' }
]
const commonFeatures = [
'Akta Pendirian',
'SK Pendirian AHU (Kemenkum)',
'NIB',
'Akun OSS',
'K3L',
'SPPL',
'Pernyataan Mandiri',
'Sertifikat Standar (untuk KBLI tertentu)',
'NPWP',
'Akun Coretax',
'SKT Pajak',
'Surat Penerbitan Akun Pajak',
'Bukti Penerimaan Surat Pendaftaran Wajib Pajak',
'Email Badan',
'Stempel/Cap Badan',
'Free Pengecekan Nama',
'Free Konsultasi'
]
const img = (q) => `https://images.unsplash.com/${q}&auto=format&fit=crop&w=1200&q=60`
export const DATA_BY_TAB = {
pendirian: [
{
title: 'Pendirian PT',
subtitle: 'Mulai Langkah Pertama Bisnis Resmi Anda',
price: 'Rp 3,850 K',
image: img('photo-1520975916090-3105956dac38?ixlib=rb-4.0.3'),
features: commonFeatures
},
{
title: 'Paket Level Up',
subtitle: 'Saatnya Naik Kelas & Siap Dapat Klien Besar',
price: 'Rp 3,200 K',
image: img('photo-1517245386807-bb43f82c33c4?ixlib=rb-4.0.3'),
features: commonFeatures
},
{
title: 'Pendirian Yayasan',
subtitle: 'Yayasan Resmi, Siap Operasional Penuh',
price: 'Rp 3,800 K',
image: img('photo-1515165562835-c3b8b2c9288b?ixlib=rb-4.0.3'),
features: commonFeatures
},
{
title: 'Pendirian Firma',
subtitle: 'Mulai Langkah Pertama Bisnis Resmi Anda',
price: 'Rp 3,500 K',
image: img('photo-1525182008055-f88b95ff7980?ixlib=rb-4.0.3'),
features: commonFeatures
},
{
title: 'Pendirian Perkumpulan',
subtitle: 'Mulai Langkah Pertama Bisnis Resmi Anda',
price: 'Rp 5,000 K',
image: img('photo-1521737604893-d14cc237f11d?ixlib=rb-4.0.3'),
features: commonFeatures
},
{
title: 'Pendirian Persekutuan Perdata',
subtitle: 'Mulai Langkah Pertama Bisnis Resmi Anda',
price: 'Rp 3,500 K',
image: img('photo-1538688423619-a81d3f23454b?ixlib=rb-4.0.3'),
features: commonFeatures
},
{
title: 'Pendirian Koperasi',
subtitle: 'Mulai Langkah Pertama Bisnis Resmi Anda',
price: 'Rp 8,500 K',
image: img('photo-1520974722070-6a669e3f0de7?ixlib=rb-4.0.3'),
features: commonFeatures
},
{
title: 'Pendirian PT Perseorangan',
subtitle: 'Mulai Langkah Pertama Bisnis Resmi Anda',
price: 'Rp 1,200 K',
image: img('photo-1519340241574-2cec6aef0c01?ixlib=rb-4.0.3'),
features: [
'Sertifikat Pendaftaran Pendirian Perseorangan (Kemenkum)',
'Pernyataan Pendirian Kemenkum',
'NIB', 'Akun OSS', 'K3L', 'SPPL', 'Pernyataan Mandiri',
'Sertifikat Standar (untuk KBLI tertentu)', 'NPWP', 'Akun Coretax', 'SKT Pajak',
'Surat Penerbitan Akun Pajak', 'Bukti Penerimaan Surat Pendaftaran Wajib Pajak',
'Email Badan', 'Stempel/Cap Badan', 'Free Pengecekan Nama', 'Free Konsultasi'
]
}
],
perubahan: [
{
title: 'Perubahan Data PT',
subtitle: 'Ubah nama, alamat, modal, pengurus',
price: 'Konsultasi Dulu',
image: img('photo-1522075469751-3a6694fb2f61?ixlib=rb-4.0.3'),
features: [
'Analisis kebutuhan perubahan',
'Penyusunan akta perubahan',
'Pengesahan AHU (jika perlu)',
'Update OSS dan perpajakan',
'Dokumentasi lengkap',
'Free Konsultasi'
]
},
{
title: 'Pembubaran Badan Usaha',
subtitle: 'Aman sesuai regulasi',
price: 'Konsultasi Dulu',
image: img('photo-1521791136064-7986c2920216?ixlib=rb-4.0.3'),
features: [
'Konsultasi dan strategi pembubaran',
'Penyusunan akta pembubaran',
'Pencabutan perizinan terkait',
'Penutupan perpajakan (jika perlu)',
'Dokumentasi lengkap',
'Free Konsultasi'
]
}
],
property: [
{
title: 'Legalitas Property Dasar',
subtitle: 'Cek sertifikat dan dokumen',
price: 'Konsultasi Dulu',
image: img('photo-1505693416388-ac5ce068fe85?ixlib=rb-4.0.3'),
features: [
'Review dokumen kepemilikan',
'Cek keabsahan sertifikat',
'Rekomendasi langkah lanjutan',
'Free Konsultasi'
]
}
],
khusus: [
{
title: 'Paket Khusus Tender',
subtitle: 'Lengkap untuk kebutuhan pengadaan',
price: 'Customize',
image: img('photo-1542744173-8e7e53415bb0?ixlib=rb-4.0.3'),
features: [
'Dokumen administrasi lengkap',
'Perizinan spesifik KBLI',
'Pendampingan kelengkapan dokumen',
'Free Konsultasi'
]
}
],
umum: [
{
title: 'Virtual Office + Legal',
subtitle: 'Hemat dan fleksibel',
price: 'Start from Rp 1 Jt',
image: img('photo-1487014679447-9f8336841d58?ixlib=rb-4.0.3'),
features: [
'Alamat legal bisnis',
'Forwarding surat',
'Konsultasi legal dasar',
'Free Konsultasi'
]
}
]
}

View File

@ -0,0 +1,72 @@
<script setup>
import { computed, ref, watch } from 'vue'
const props = defineProps({
tabs: { type: Array, required: true },
modelValue: { type: String, default: '' },
ariaLabel: { type: String, default: 'Sections' }
})
const emit = defineEmits(['update:modelValue', 'change'])
const internal = ref(props.modelValue || (props.tabs[0] && props.tabs[0].key) || '')
watch(() => props.modelValue, (v) => {
if (v && v !== internal.value) internal.value = v
})
watch(internal, (v) => {
emit('update:modelValue', v)
emit('change', v)
})
const activeIndex = computed(() => props.tabs.findIndex(t => t.key === internal.value))
function selectByIndex(idx) {
if (idx < 0 || idx >= props.tabs.length) return
internal.value = props.tabs[idx].key
}
function onKeydown(e) {
if (!['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(e.key)) return
e.preventDefault()
if (e.key === 'ArrowLeft') selectByIndex((activeIndex.value - 1 + props.tabs.length) % props.tabs.length)
if (e.key === 'ArrowRight') selectByIndex((activeIndex.value + 1) % props.tabs.length)
if (e.key === 'Home') selectByIndex(0)
if (e.key === 'End') selectByIndex(props.tabs.length - 1)
}
</script>
<template>
<div class="tabs">
<div class="tablist" role="tablist" :aria-label="ariaLabel" @keydown="onKeydown">
<button
v-for="(t, i) in tabs"
:key="t.key"
class="tab"
:class="{ active: t.key === internal }"
role="tab"
:id="`tab-${t.key}`"
:aria-selected="t.key === internal"
:tabindex="t.key === internal ? 0 : -1"
@click="internal = t.key"
>
{{ t.label }}
</button>
</div>
<div class="tabpanel">
<slot :active="internal"></slot>
</div>
</div>
</template>
<style scoped>
.tabs { width: 100%; }
.tablist { display: flex; justify-content: center; gap: 32px; border-bottom: 1px solid #e5e7eb; overflow-x: auto; }
.tab { appearance: none; background: transparent; border: none; padding: 14px 4px; cursor: pointer; color: #6b7280; font-weight: 600; font-size: 14px; letter-spacing: .25px; white-space: nowrap; border-bottom: 3px solid transparent; margin-bottom: -1px; transition: color .18s, border-color .18s; }
.tab.active { color: #111827; border-bottom-color: #FF6640; }
.tab:hover { color: #111827; }
.tabpanel { padding-top: 24px; }
</style>

7
src/main.js Normal file
View File

@ -0,0 +1,7 @@
import './assets/main.css'
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
createApp(App).use(router).mount('#app')

25
src/pages/Home.vue Normal file
View File

@ -0,0 +1,25 @@
<script setup>
import Hero from "@/components/main/Hero.vue";
import ServicesMenu from "@/components/main/ServicesMenu.vue";
import ServicesSection from "@/components/main/ServiceSection.vue";
import WhyChoose from "@/components/main/WhyChoose.vue";
import CallToAction from "@/components/main/CallToAction.vue";
import ReasonsSection from "@/components/main/ReasonsSection.vue";
import Testimonial from "@/components/main/Testimonial.vue";
</script>
<template>
<main>
<Hero />
<ServicesMenu id="solusi" />
<!-- FIX: use section, not single card -->
<ServicesSection />
<WhyChoose />
<CallToAction id="konsultasi" />
<ReasonsSection />
<Testimonial />
</main>
</template>
<style scoped>
</style>

17
src/pages/Packages.vue Normal file
View File

@ -0,0 +1,17 @@
<script setup>
import PackagesSection from "../components/packages/PackagesSection.vue";
import { useRoute } from 'vue-router';
import { computed } from 'vue';
const route = useRoute();
const category = computed(() => route.meta.category || null);
</script>
<template>
<main>
<PackagesSection id="paket" :initialCategory="category" />
</main>
</template>
<style scoped>
</style>

65
src/router/index.js Normal file
View File

@ -0,0 +1,65 @@
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/pages/Home.vue'
import Packages from '@/pages/Packages.vue'
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/packages',
name: 'Packages',
component: Packages
},
{
path: '/packages/pendirian',
name: 'PackagesPendirian',
component: Packages,
meta: { category: 'pendirian' }
},
{
path: '/packages/perubahan',
name: 'PackagesPerubahan',
component: Packages,
meta: { category: 'perubahan' }
},
{
path: '/packages/property',
name: 'PackagesProperty',
component: Packages,
meta: { category: 'property' }
},
{
path: '/packages/khusus',
name: 'PackagesKhusus',
component: Packages,
meta: { category: 'khusus' }
},
{
path: '/packages/umum',
name: 'PackagesUmum',
component: Packages,
meta: { category: 'umum' }
}
]
const router = createRouter({
history: createWebHistory(),
routes,
scrollBehavior(to, from, savedPosition) {
if (to.hash) {
return {
el: to.hash,
behavior: 'smooth'
}
} else if (savedPosition) {
return savedPosition
} else {
return { top: 0 }
}
}
})
export default router

18
vite.config.js Normal file
View File

@ -0,0 +1,18 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
vueDevTools(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
},
},
})