Compare commits
2 Commits
84b5a498e6
...
2e32d52133
| Author | SHA1 | Date | |
|---|---|---|---|
| 2e32d52133 | |||
| df1efb4881 |
@@ -3,3 +3,10 @@
|
|||||||
|
|
||||||
# Production Payload CMS URL (for SSR fetch)
|
# Production Payload CMS URL (for SSR fetch)
|
||||||
PAYLOAD_CMS_URL=https://enchun-admin.anlstudio.cc
|
PAYLOAD_CMS_URL=https://enchun-admin.anlstudio.cc
|
||||||
|
|
||||||
|
# Cloudflare Turnstile
|
||||||
|
CF_TURNSTILE_SITE_KEY=0x4AAAAAACkOUZK2u7Fo8IZ-
|
||||||
|
CF_TURNSTILE_SECRET_KEY=0x4AAAAAACkOUV9TDUOwVdcdLUakEVxJjww
|
||||||
|
|
||||||
|
# n8n webhook
|
||||||
|
N8N_WEBHOOK_URL=https://n8n.anlstudio.cc/webhook/contact
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
---
|
---
|
||||||
|
|
||||||
<section
|
<section
|
||||||
class="py-20 px-5 bg-white text-center mx-auto lg:py-[60px] lg:px-5 md:py-10"
|
class="py-15 px-5 md:py-20 bg-white text-center mx-auto"
|
||||||
aria-labelledby="cta-heading"
|
aria-labelledby="cta-heading"
|
||||||
>
|
>
|
||||||
<div class="max-w-3xl mx-auto">
|
<div class="max-w-3xl mx-auto">
|
||||||
|
|||||||
@@ -42,8 +42,8 @@ try {
|
|||||||
const currentYear = new Date().getFullYear();
|
const currentYear = new Date().getFullYear();
|
||||||
---
|
---
|
||||||
|
|
||||||
<footer class="bg-[var(--color-tropical-blue)] pt-10 mt-auto relative">
|
<footer class="bg-(--color-tropical-blue) pt-10 mt-auto">
|
||||||
<div class="max-w-4xl mx-auto">
|
<div class="max-w-4xl mx-auto px-12">
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-8 mb-16">
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-8 mb-16">
|
||||||
<div class="col-span-2">
|
<div class="col-span-2">
|
||||||
<Image
|
<Image
|
||||||
@@ -56,16 +56,14 @@ const currentYear = new Date().getFullYear();
|
|||||||
decoding="async"
|
decoding="async"
|
||||||
/>
|
/>
|
||||||
<p
|
<p
|
||||||
class="text-[var(--color-st-tropaz)] text-sm pr-8 font-light leading-relaxed"
|
class="text-(--color-st-tropaz) text-sm pr-0 md:pr-8 font-light leading-relaxed"
|
||||||
>
|
>
|
||||||
恩群數位累積多年廣告行銷操作經驗,擁有全方位行銷人才,讓我們可以為客戶精準的規劃每一分廣告預算,讓你的品牌深入人心。更重要的是恩群的存在,為了成為每家公司最佳數位夥伴,作為彼此最堅強的後盾,你會知道有我們的陪伴
|
恩群數位累積多年廣告行銷操作經驗,擁有全方位行銷人才,讓我們可以為客戶精準的規劃每一分廣告預算,讓你的品牌深入人心。更重要的是恩群的存在,為了成為每家公司最佳數位夥伴,作為彼此最堅強的後盾,你會知道有我們的陪伴
|
||||||
你並不孤單。
|
你並不孤單。
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3
|
<h3 class="text-lg font-bold text-(--color-st-tropaz) mb-4">
|
||||||
class="text-lg font-bold text-[var(--color-st-tropaz)] mb-4"
|
|
||||||
>
|
|
||||||
聯絡我們
|
聯絡我們
|
||||||
</h3>
|
</h3>
|
||||||
<a
|
<a
|
||||||
@@ -84,7 +82,7 @@ const currentYear = new Date().getFullYear();
|
|||||||
decoding="async"
|
decoding="async"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
<p class="text-sm text-[var(--color-st-tropaz)] mb-2">
|
<p class="text-sm text-(--color-st-tropaz) mb-2">
|
||||||
諮詢電話:<br /> 02 5570 0527
|
諮詢電話:<br /> 02 5570 0527
|
||||||
</p>
|
</p>
|
||||||
<a
|
<a
|
||||||
@@ -94,9 +92,7 @@ const currentYear = new Date().getFullYear();
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3
|
<h3 class="text-lg font-bold text-(--color-st-tropaz) mb-4">
|
||||||
class="text-lg font-bold text-[var(--color-st-tropaz)] mb-4"
|
|
||||||
>
|
|
||||||
行銷方案
|
行銷方案
|
||||||
</h3>
|
</h3>
|
||||||
<ul
|
<ul
|
||||||
@@ -125,9 +121,7 @@ const currentYear = new Date().getFullYear();
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3
|
<h3 class="text-lg font-bold text-(--color-st-tropaz) mb-4">
|
||||||
class="text-lg font-bold text-[var(--color-st-tropaz)] mb-4"
|
|
||||||
>
|
|
||||||
行銷放大鏡
|
行銷放大鏡
|
||||||
</h3>
|
</h3>
|
||||||
<ul class="text-sm font-thin space-y-2" id="marketing-articles">
|
<ul class="text-sm font-thin space-y-2" id="marketing-articles">
|
||||||
@@ -155,7 +149,7 @@ const currentYear = new Date().getFullYear();
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="w-screen bg-[var(--color-amber)] font-['Quicksand'] text-[var(--color-tarawera)] py-2 text-xs text-center"
|
class="w-screen bg-(--color-amber) font-['Quicksand'] text-(--color-tarawera) py-2 text-xs text-center"
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
copyright © Enchun digital 2018 - {currentYear}
|
copyright © Enchun digital 2018 - {currentYear}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const {
|
|||||||
<section class:list={classNameBg}>
|
<section class:list={classNameBg}>
|
||||||
<div
|
<div
|
||||||
class:list={[
|
class:list={[
|
||||||
"flex flex-rows sm:max-w-lg px-8 md:px-0 md:max-w-xl lg:max-w-3xl items-center justify-center gap-4 py-12 mx-auto",
|
"flex flex-rows sm:max-w-lg px-8 md:px-0 md:max-w-xl lg:max-w-3xl items-center justify-center gap-4 pt-2 pb-10 mx-auto",
|
||||||
className,
|
className,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -28,12 +28,6 @@ const description =
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Section Header -->
|
|
||||||
<SectionHeader
|
|
||||||
title="關於恩群"
|
|
||||||
subtitle="About Enchun"
|
|
||||||
sectionBg="bg-white"
|
|
||||||
/>
|
|
||||||
<!-- Service Features Section -->
|
<!-- Service Features Section -->
|
||||||
<FeatureSection />
|
<FeatureSection />
|
||||||
|
|
||||||
|
|||||||
82
apps/frontend/src/pages/api/contact.ts
Normal file
82
apps/frontend/src/pages/api/contact.ts
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import type { APIRoute } from "astro";
|
||||||
|
|
||||||
|
// Your Cloudflare Turnstile Secret Key
|
||||||
|
// In production, this should be set in environment variables
|
||||||
|
const TURNSTILE_SECRET_KEY = import.meta.env.CF_TURNSTILE_SECRET_KEY || import.meta.env.TURNSTILE_SECRET_KEY;
|
||||||
|
// The n8n webhook URL
|
||||||
|
const WEBHOOK_URL = import.meta.env.N8N_WEBHOOK_URL || "https://n8n.anlstudio.cc/webhook/contact";
|
||||||
|
|
||||||
|
export const POST: APIRoute = async ({ request }) => {
|
||||||
|
try {
|
||||||
|
const body = await request.json();
|
||||||
|
const { name, phone, email, message, "cf-turnstile-response": token } = body;
|
||||||
|
|
||||||
|
// Basic validation
|
||||||
|
if (!name || !phone || !email || !message) {
|
||||||
|
return new Response(JSON.stringify({ error: "Missing required fields" }), {
|
||||||
|
status: 400,
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
return new Response(JSON.stringify({ error: "[Turnstile] No token provided" }), {
|
||||||
|
status: 400,
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify Turnstile token
|
||||||
|
if (TURNSTILE_SECRET_KEY) {
|
||||||
|
const turnstileVerify = await fetch(
|
||||||
|
"https://challenges.cloudflare.com/turnstile/v0/siteverify",
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
},
|
||||||
|
body: new URLSearchParams({
|
||||||
|
secret: TURNSTILE_SECRET_KEY,
|
||||||
|
response: token,
|
||||||
|
}).toString(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const turnstileResult = await turnstileVerify.json();
|
||||||
|
|
||||||
|
if (!turnstileResult.success) {
|
||||||
|
return new Response(JSON.stringify({ error: "[Turnstile] Token verification failed" }), {
|
||||||
|
status: 400,
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn("TURNSTILE_SECRET_KEY is not defined. Skipping verification.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// After successful verification, send data to n8n
|
||||||
|
const n8nResponse = await fetch(WEBHOOK_URL, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ name, phone, email, message }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!n8nResponse.ok) {
|
||||||
|
throw new Error(`n8n webhook failed with status ${n8nResponse.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(JSON.stringify({ success: true }), {
|
||||||
|
status: 200,
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("API proxy error:", error);
|
||||||
|
return new Response(JSON.stringify({ error: "Internal server error" }), {
|
||||||
|
status: 500,
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
* Includes form validation, submission handling, and responsive layout
|
* Includes form validation, submission handling, and responsive layout
|
||||||
*/
|
*/
|
||||||
import Layout from "../layouts/Layout.astro";
|
import Layout from "../layouts/Layout.astro";
|
||||||
|
import HeaderBg from "../components/HeaderBg.astro";
|
||||||
|
|
||||||
// Metadata for SEO
|
// Metadata for SEO
|
||||||
const title = "聯絡我們 | 恩群數位行銷";
|
const title = "聯絡我們 | 恩群數位行銷";
|
||||||
@@ -13,6 +14,7 @@ const description =
|
|||||||
---
|
---
|
||||||
|
|
||||||
<Layout title={title} description={description}>
|
<Layout title={title} description={description}>
|
||||||
|
<HeaderBg />
|
||||||
<section class="py-20 bg-[#f4f5f7] scroll-mt-20 lg:py-10" id="contact">
|
<section class="py-20 bg-[#f4f5f7] scroll-mt-20 lg:py-10" id="contact">
|
||||||
<div
|
<div
|
||||||
class="grid grid-cols-2 gap-16 items-start max-w-3xl mx-auto px-5 lg:grid-cols-2 lg:gap-10"
|
class="grid grid-cols-2 gap-16 items-start max-w-3xl mx-auto px-5 lg:grid-cols-2 lg:gap-10"
|
||||||
@@ -144,6 +146,20 @@ const description =
|
|||||||
id="Message-error"></span>
|
id="Message-error"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Turnstile Widget -->
|
||||||
|
<div class="flex flex-col mt-2">
|
||||||
|
<div
|
||||||
|
class="cf-turnstile"
|
||||||
|
data-sitekey={import.meta.env.PUBLIC_TURNSTILE_SITE_KEY ||
|
||||||
|
"0x4AAAAAACkOUZK2u7Fo8IZ-"}
|
||||||
|
data-theme="light"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
class="error-message text-[#dc3545] text-sm mt-1 hidden"
|
||||||
|
id="turnstile-error"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Submit Button -->
|
<!-- Submit Button -->
|
||||||
<div class="flex justify-end mt-2">
|
<div class="flex justify-end mt-2">
|
||||||
<button
|
<button
|
||||||
@@ -163,6 +179,8 @@ const description =
|
|||||||
</section>
|
</section>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
|
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer
|
||||||
|
></script>
|
||||||
<script>
|
<script>
|
||||||
// Form validation and submission handler
|
// Form validation and submission handler
|
||||||
function initContactForm() {
|
function initContactForm() {
|
||||||
@@ -172,6 +190,9 @@ const description =
|
|||||||
) as HTMLButtonElement;
|
) as HTMLButtonElement;
|
||||||
const successMsg = document.getElementById("form-success") as HTMLElement;
|
const successMsg = document.getElementById("form-success") as HTMLElement;
|
||||||
const errorMsg = document.getElementById("form-error") as HTMLElement;
|
const errorMsg = document.getElementById("form-error") as HTMLElement;
|
||||||
|
const turnstileError = document.getElementById(
|
||||||
|
"turnstile-error",
|
||||||
|
) as HTMLElement;
|
||||||
|
|
||||||
if (!form) return;
|
if (!form) return;
|
||||||
|
|
||||||
@@ -281,10 +302,28 @@ const description =
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Collect form data to check turnstile token
|
||||||
|
const formData = new FormData(form);
|
||||||
|
const turnstileToken = formData.get("cf-turnstile-response");
|
||||||
|
|
||||||
|
// Validate Turnstile
|
||||||
|
if (!turnstileToken) {
|
||||||
|
isFormValid = false;
|
||||||
|
if (turnstileError) {
|
||||||
|
turnstileError.textContent = "請完成驗證";
|
||||||
|
turnstileError.style.display = "block";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (turnstileError) {
|
||||||
|
turnstileError.textContent = "";
|
||||||
|
turnstileError.style.display = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!isFormValid) {
|
if (!isFormValid) {
|
||||||
// Scroll to first error
|
// Scroll to first error
|
||||||
const firstError = form.querySelector(
|
const firstError = form.querySelector(
|
||||||
".input_field.error",
|
".input_field.error, #turnstile-error[style*='display: block']",
|
||||||
) as HTMLElement;
|
) as HTMLElement;
|
||||||
if (firstError) {
|
if (firstError) {
|
||||||
firstError.scrollIntoView({ behavior: "smooth", block: "center" });
|
firstError.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||||
@@ -305,17 +344,16 @@ const description =
|
|||||||
successMsg.style.display = "none";
|
successMsg.style.display = "none";
|
||||||
errorMsg.style.display = "none";
|
errorMsg.style.display = "none";
|
||||||
|
|
||||||
// Collect form data
|
|
||||||
const formData = new FormData(form);
|
|
||||||
const data = {
|
const data = {
|
||||||
name: formData.get("Name"),
|
name: formData.get("Name"),
|
||||||
phone: formData.get("Phone"),
|
phone: formData.get("Phone"),
|
||||||
email: formData.get("Email"),
|
email: formData.get("Email"),
|
||||||
message: formData.get("Message"),
|
message: formData.get("Message"),
|
||||||
|
"cf-turnstile-response": turnstileToken,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Submit to backend (via API proxy)
|
// Submit to API proxy
|
||||||
const response = await fetch("/api/contact", {
|
const response = await fetch("/api/contact", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -4,24 +4,28 @@
|
|||||||
* Pixel-perfect implementation based on Webflow design
|
* Pixel-perfect implementation based on Webflow design
|
||||||
* Data fetched from Payload CMS Pages Collection API
|
* Data fetched from Payload CMS Pages Collection API
|
||||||
*/
|
*/
|
||||||
import Layout from '../layouts/Layout.astro'
|
import Layout from "../layouts/Layout.astro";
|
||||||
import SolutionsHero from '../sections/SolutionsHero.astro'
|
import SolutionsHero from "../sections/SolutionsHero.astro";
|
||||||
import ServicesList from '../sections/ServicesList.astro'
|
import ServicesList from "../sections/ServicesList.astro";
|
||||||
import { getMarketingSolutionsPage } from '../lib/api/marketing-solution'
|
import { getMarketingSolutionsPage } from "../lib/api/marketing-solution";
|
||||||
import SectionHeader from '../components/SectionHeader.astro'
|
import SectionHeader from "../components/SectionHeader.astro";
|
||||||
|
import CtaSection from "../components/CtaSection.astro";
|
||||||
|
|
||||||
// Fetch page data from CMS
|
// Fetch page data from CMS
|
||||||
const pageData = await getMarketingSolutionsPage()
|
const pageData = await getMarketingSolutionsPage();
|
||||||
|
|
||||||
// Use CMS data or fallback to defaults
|
// Use CMS data or fallback to defaults
|
||||||
const heroTitle = pageData?.heroTitle || '行銷解決方案'
|
const heroTitle = pageData?.heroTitle || "行銷解決方案";
|
||||||
const heroSubtitle = pageData?.heroSubtitle || '提供全方位的數位行銷服務,協助您的品牌在數位時代脫穎而出'
|
const heroSubtitle =
|
||||||
const heroImage = pageData?.heroImage
|
pageData?.heroSubtitle ||
|
||||||
const services = pageData?.services || []
|
"提供全方位的數位行銷服務,協助您的品牌在數位時代脫穎而出";
|
||||||
|
const heroImage = pageData?.heroImage;
|
||||||
|
const services = pageData?.services || [];
|
||||||
|
|
||||||
// Metadata for SEO
|
// Metadata for SEO
|
||||||
const title = '行銷解決方案 | 恩群數位行銷'
|
const title = "行銷解決方案 | 恩群數位行銷";
|
||||||
const description = '恩群數位行銷提供全方位的數位行銷服務,包括 Google Ads、社群代操、論壇行銷、網紅行銷、網站設計等,協助您的品牌在數位時代脫穎而出。'
|
const description =
|
||||||
|
"恩群數位行銷提供全方位的數位行銷服務,包括 Google Ads、社群代操、論壇行銷、網紅行銷、網站設計等,協助您的品牌在數位時代脫穎而出。";
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title={title} description={description}>
|
<Layout title={title} description={description}>
|
||||||
@@ -39,4 +43,5 @@ const description = '恩群數位行銷提供全方位的數位行銷服務,
|
|||||||
/>
|
/>
|
||||||
<!-- Services List -->
|
<!-- Services List -->
|
||||||
<ServicesList services={services} />
|
<ServicesList services={services} />
|
||||||
|
<CtaSection />
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ import TeamsHero from "../sections/TeamsHero.astro";
|
|||||||
import EnvironmentSlider from "../sections/EnvironmentSlider.astro";
|
import EnvironmentSlider from "../sections/EnvironmentSlider.astro";
|
||||||
import CompanyStory from "../sections/CompanyStory.astro";
|
import CompanyStory from "../sections/CompanyStory.astro";
|
||||||
import BenefitsSection from "../sections/BenefitsSection.astro";
|
import BenefitsSection from "../sections/BenefitsSection.astro";
|
||||||
|
import SectionHeader from "../components/SectionHeader.astro";
|
||||||
|
import CtaHrCompoents from "../sections/Cta-Hr-compoents.astro";
|
||||||
// Metadata for SEO
|
// Metadata for SEO
|
||||||
const title = "恩群大本營 | 恩群數位行銷";
|
const title = "恩群大本營 | 恩群數位行銷";
|
||||||
const description =
|
const description =
|
||||||
@@ -28,50 +29,23 @@ const description =
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Environment Slider Section -->
|
<!-- Environment Slider Section -->
|
||||||
|
|
||||||
<EnvironmentSlider />
|
<EnvironmentSlider />
|
||||||
|
|
||||||
<!-- Company Story Section -->
|
<!-- Company Story Section -->
|
||||||
<CompanyStory />
|
<CompanyStory />
|
||||||
|
|
||||||
<!-- Benefits Section -->
|
<!-- Benefits Section -->
|
||||||
|
|
||||||
<BenefitsSection />
|
<BenefitsSection />
|
||||||
|
|
||||||
<!-- CTA Section -->
|
<!-- CTA Section -->
|
||||||
<section
|
<CtaHrCompoents
|
||||||
class="py-20 px-5 bg-slate-100 text-center lg:py-[60px] lg:px-4 md:py-10"
|
title="以人的成長為優先<br />創造人的最大價值"
|
||||||
aria-labelledby="cta-heading"
|
description="在恩群數位裡我們重視個人的特質能夠完全發揮,只要你樂於學習、善於跟人建立關係,並且重要的是你有一個善良的心,恩群數位歡迎你的加入"
|
||||||
>
|
image={{
|
||||||
<div class="max-w-[1200px] mx-auto">
|
url: "https://enchun-cms.anlstudio.cc/api/media/file/61f24aa108528b4723942d01_工作環境-銘言底圖-1400x659.jpg",
|
||||||
<div
|
alt: "工作環境",
|
||||||
class="grid grid-cols-[1fr_auto] gap-8 items-center relative lg:grid-cols-1 lg:text-center"
|
}}
|
||||||
>
|
/>
|
||||||
<div class="text-left lg:text-center">
|
|
||||||
<h3
|
|
||||||
id="cta-heading"
|
|
||||||
class="text-[1.75rem] font-semibold text-[#23608c] mb-4 leading-snug lg:text-[1.5rem] md:text-[1.5rem]"
|
|
||||||
>
|
|
||||||
以人的成長為優先<br />
|
|
||||||
創造人的最大價值
|
|
||||||
</h3>
|
|
||||||
<p
|
|
||||||
class="text-base text-slate-600 leading-relaxed max-w-[500px] lg:max-w-full md:text-[0.95rem]"
|
|
||||||
>
|
|
||||||
在恩群數位裡我們重視個人的特質能夠完全發揮,只要你樂於學習、善於跟人建立關係,並且重要的是你有一個善良的心,恩群數位歡迎你的加入
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<a
|
|
||||||
href="https://www.104.com.tw/company/1a2x6bkoaj?jobsource=joblist_r_cust"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
class="inline-flex items-center justify-center bg-link-hover text-white px-8 py-4 rounded-md font-semibold text-base transition-all duration-200 whitespace-nowrap hover:-translate-y-0.5 hover:shadow-md hover:bg-[#1a4d6e] lg:w-full lg:max-w-[300px] md:py-[14px] md:px-6 md:text-[0.95rem]"
|
|
||||||
>
|
|
||||||
立刻申請面試
|
|
||||||
</a>
|
|
||||||
<div
|
|
||||||
class="absolute inset-0 bg-gradient-to-br from-[rgba(35,96,140,0.05)] to-[rgba(35,96,140,0.02)] rounded-lg -z-10 lg:hidden"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|||||||
@@ -6,304 +6,93 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
interface BenefitItem {
|
interface BenefitItem {
|
||||||
title: string
|
title: string;
|
||||||
icon: string
|
img: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
benefits?: BenefitItem[]
|
benefits?: BenefitItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultBenefits: BenefitItem[] = [
|
const defaultBenefits: BenefitItem[] = [
|
||||||
{
|
{
|
||||||
title: '高績效、高獎金\n新人開張獎金',
|
title: "高績效、高獎金\n新人開張獎金",
|
||||||
icon: 'bonus',
|
img: "/api/media/file/61f24aa108528b79b2942d05_Make%20it%20rain-bro-%E6%96%B0%E4%BA%BA%E9%96%8B%E5%BC%B5%E7%8D%8E%E9%87%91.svg",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '生日慶生、電影日\n員工下午茶',
|
title: "生日慶生、電影日\n員工下午茶",
|
||||||
icon: 'birthday',
|
img: "/api/media/file/61f24aa108528be590942d06_Blowing%20out%20Birthday%20candles-bro-%E7%94%9F%E6%97%A5%E6%85%B6%E7%94%9F.svg",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '教育訓練補助',
|
title: "教育訓練補助",
|
||||||
icon: 'education',
|
img: "/api/media/file/61f24aa108528be22a942d03_Online%20learning-bro-%E6%95%99%E8%82%B2%E8%A8%93%E7%B7%B4%E8%A3%9C%E5%8A%A9.svg",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '寬敞的工作空間',
|
title: "寬敞的工作空間",
|
||||||
icon: 'workspace',
|
img: "/api/media/file/61f24aa108528be064942d08_Shared%20workspace-bro-%E5%AF%AC%E6%95%9E%E7%9A%84%E5%B7%A5%E4%BD%9C%E7%A9%BA%E9%96%93.svg",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '員工國內外旅遊\n部門聚餐、年終活動',
|
title: "員工國內外旅遊\n部門聚餐、年終活動",
|
||||||
icon: 'travel',
|
img: "/api/media/file/61f24aa108528b0960942d04_Flight%20Booking-bro-%E5%93%A1%E5%B7%A5%E6%97%85%E9%81%8A.svg",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '入職培訓及團隊建設',
|
title: "入職培訓及團隊建設",
|
||||||
icon: 'training',
|
img: "/api/media/file/61f24aa108528bf90b942d02_Brainstorming-bro-%E5%85%A5%E8%81%B7%E5%9F%B9%E8%A8%93.svg",
|
||||||
},
|
},
|
||||||
]
|
];
|
||||||
|
|
||||||
const benefits = Astro.props.benefits || defaultBenefits
|
const benefits = Astro.props.benefits || defaultBenefits;
|
||||||
|
import SectionHeader from "../components/SectionHeader.astro";
|
||||||
// Icon SVG components (placeholder for now, replace with actual SVG)
|
|
||||||
const getIconSVG = (iconType: string) => {
|
|
||||||
const icons: Record<string, string> = {
|
|
||||||
bonus: `<svg viewBox="0 0 200 200" class="benefit-icon-svg">
|
|
||||||
<circle cx="100" cy="70" r="40" fill="#23608c" opacity="0.2"/>
|
|
||||||
<rect x="60" y="100" width="80" height="60" rx="8" fill="#23608c"/>
|
|
||||||
<text x="100" y="140" text-anchor="middle" fill="white" font-size="36">💰</text>
|
|
||||||
</svg>`,
|
|
||||||
birthday: `<svg viewBox="0 0 200 200" class="benefit-icon-svg">
|
|
||||||
<circle cx="100" cy="70" r="40" fill="#23608c" opacity="0.2"/>
|
|
||||||
<rect x="60" y="100" width="80" height="60" rx="8" fill="#23608c"/>
|
|
||||||
<text x="100" y="140" text-anchor="middle" fill="white" font-size="36">🎂</text>
|
|
||||||
</svg>`,
|
|
||||||
education: `<svg viewBox="0 0 200 200" class="benefit-icon-svg">
|
|
||||||
<circle cx="100" cy="70" r="40" fill="#23608c" opacity="0.2"/>
|
|
||||||
<rect x="60" y="100" width="80" height="60" rx="8" fill="#23608c"/>
|
|
||||||
<text x="100" y="140" text-anchor="middle" fill="white" font-size="36">📚</text>
|
|
||||||
</svg>`,
|
|
||||||
workspace: `<svg viewBox="0 0 200 200" class="benefit-icon-svg">
|
|
||||||
<circle cx="100" cy="70" r="40" fill="#23608c" opacity="0.2"/>
|
|
||||||
<rect x="60" y="100" width="80" height="60" rx="8" fill="#23608c"/>
|
|
||||||
<text x="100" y="140" text-anchor="middle" fill="white" font-size="36">🏢</text>
|
|
||||||
</svg>`,
|
|
||||||
travel: `<svg viewBox="0 0 200 200" class="benefit-icon-svg">
|
|
||||||
<circle cx="100" cy="70" r="40" fill="#23608c" opacity="0.2"/>
|
|
||||||
<rect x="60" y="100" width="80" height="60" rx="8" fill="#23608c"/>
|
|
||||||
<text x="100" y="140" text-anchor="middle" fill="white" font-size="36">✈️</text>
|
|
||||||
</svg>`,
|
|
||||||
training: `<svg viewBox="0 0 200 200" class="benefit-icon-svg">
|
|
||||||
<circle cx="100" cy="70" r="40" fill="#23608c" opacity="0.2"/>
|
|
||||||
<rect x="60" y="100" width="80" height="60" rx="8" fill="#23608c"/>
|
|
||||||
<text x="100" y="140" text-anchor="middle" fill="white" font-size="36">🤝</text>
|
|
||||||
</svg>`,
|
|
||||||
}
|
|
||||||
return icons[iconType] || icons.bonus
|
|
||||||
}
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<section class="section-benefit" aria-labelledby="benefits-heading">
|
<section
|
||||||
<div class="container w-container">
|
class="py-15 px-5 bg-white md:py-10 md:px-4"
|
||||||
|
aria-labelledby="benefits-heading"
|
||||||
|
>
|
||||||
|
<div class="max-w-6xl mx-auto">
|
||||||
<!-- Section Header -->
|
<!-- Section Header -->
|
||||||
<div class="section_header_w_line">
|
<SectionHeader
|
||||||
<div class="divider_line"></div>
|
title="工作福利"
|
||||||
<div class="header_subtitle">
|
subtitle="Benefit Packages"
|
||||||
<h2 id="benefits-heading" class="header_subtitle_head">工作福利</h2>
|
sectionBg="bg-white"
|
||||||
<p class="header_subtitle_paragraph">Benefit Package</p>
|
/>
|
||||||
</div>
|
|
||||||
<div class="divider_line"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Benefits Grid -->
|
<!-- Benefits Grid: 2 cards per row -->
|
||||||
<div class="benefit-grid-wrapper">
|
<div
|
||||||
|
class="max-w-md md:max-w-3xl mx-auto grid grid-cols-1 gap-x-2 gap-y-2 md:gap-y-6 md:grid-cols-2 py-18"
|
||||||
|
>
|
||||||
{
|
{
|
||||||
benefits.map((benefit, index) => (
|
benefits.map((benefit, index) => {
|
||||||
<div class={`benefit-card ${index % 2 === 0 ? 'benefit-card' : 'benefit-card-opposite'}`}>
|
const isLeft = index % 2 === 0;
|
||||||
<!-- Odd: Icon on right, Even: Icon on left -->
|
return (
|
||||||
{
|
<div class="grid grid-cols-2 gap-2 items-center">
|
||||||
index % 2 === 0 ? (
|
<div
|
||||||
<>
|
class:list={[
|
||||||
<div class="benefit-content">
|
isLeft ? "order-1 text-right" : "order-2 text-left",
|
||||||
<h3 class="benefit-title-text">{benefit.title}</h3>
|
]}
|
||||||
</div>
|
>
|
||||||
<div class="benefit-image-right" set:html={getIconSVG(benefit.icon)} />
|
<h3 class="text-xl md:text-base lg:text-xl font-semibold text-(--color-enchunblue) whitespace-pre-wrap leading-snug">
|
||||||
</>
|
{benefit.title}
|
||||||
) : (
|
</h3>
|
||||||
<>
|
</div>
|
||||||
<div class="benefit-image-left" set:html={getIconSVG(benefit.icon)} />
|
<div
|
||||||
<div class="benefit-content">
|
class:list={[
|
||||||
<h3 class="benefit-title-text">{benefit.title}</h3>
|
"flex items-center",
|
||||||
</div>
|
isLeft ? "order-2 justify-start" : "order-1 justify-end",
|
||||||
</>
|
]}
|
||||||
)
|
>
|
||||||
}
|
<img
|
||||||
</div>
|
src={`https://enchun-cms.anlstudio.cc${benefit.img}`}
|
||||||
))
|
alt={benefit.title}
|
||||||
|
class="size-35 object-contain"
|
||||||
|
loading="lazy"
|
||||||
|
decoding="async"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<style>
|
|
||||||
/* Benefits Section Styles - Pixel-perfect from Webflow */
|
|
||||||
.section-benefit {
|
|
||||||
padding: 60px 20px;
|
|
||||||
background-color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.w-container {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Section Header */
|
|
||||||
.section_header_w_line {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 16px;
|
|
||||||
margin-bottom: 48px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header_subtitle {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header_subtitle_head {
|
|
||||||
color: var(--color-enchunblue);
|
|
||||||
font-family: "Noto Sans TC", sans-serif;
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 2rem;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header_subtitle_paragraph {
|
|
||||||
color: var(--color-gray-600);
|
|
||||||
font-family: "Quicksand", sans-serif;
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.divider_line {
|
|
||||||
width: 40px;
|
|
||||||
height: 2px;
|
|
||||||
background-color: var(--color-enchunblue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Benefits Grid */
|
|
||||||
.benefit-grid-wrapper {
|
|
||||||
max-width: 1000px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Benefit Card */
|
|
||||||
.benefit-card,
|
|
||||||
.benefit-card-opposite {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 40px;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Odd cards: icon on right */
|
|
||||||
.benefit-card {
|
|
||||||
grid-template-areas: "content image";
|
|
||||||
}
|
|
||||||
|
|
||||||
.benefit-card .benefit-content {
|
|
||||||
grid-area: content;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.benefit-card .benefit-image-right {
|
|
||||||
grid-area: image;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Even cards: icon on left */
|
|
||||||
.benefit-card-opposite {
|
|
||||||
grid-template-areas: "image content";
|
|
||||||
}
|
|
||||||
|
|
||||||
.benefit-card-opposite .benefit-content {
|
|
||||||
grid-area: content;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.benefit-card-opposite .benefit-image-left {
|
|
||||||
grid-area: image;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Benefit Title */
|
|
||||||
.benefit-title-text {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--color-tarawera, #23608c);
|
|
||||||
white-space: pre-line;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Benefit Icon */
|
|
||||||
.benefit-icon-svg {
|
|
||||||
width: 120px;
|
|
||||||
height: 120px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.benefit-image-right,
|
|
||||||
.benefit-image-left {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.benefit-image-right svg,
|
|
||||||
.benefit-image-left svg {
|
|
||||||
width: 120px;
|
|
||||||
height: 120px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive Adjustments */
|
|
||||||
@media (max-width: 991px) {
|
|
||||||
.benefit-card,
|
|
||||||
.benefit-card-opposite {
|
|
||||||
gap: 24px;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.benefit-title-text {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 767px) {
|
|
||||||
.section-benefit {
|
|
||||||
padding: 40px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section_header_w_line {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.benefit-card,
|
|
||||||
.benefit-card-opposite {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
grid-template-areas: "image" "content" !important;
|
|
||||||
gap: 24px;
|
|
||||||
margin-bottom: 48px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.benefit-content {
|
|
||||||
text-align: center !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.benefit-image-right,
|
|
||||||
.benefit-image-left {
|
|
||||||
order: -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.benefit-title-text {
|
|
||||||
font-size: 1.125rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.benefit-icon-svg {
|
|
||||||
width: 100px;
|
|
||||||
height: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.benefit-image-right svg,
|
|
||||||
.benefit-image-left svg {
|
|
||||||
width: 100px;
|
|
||||||
height: 100px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -4,128 +4,36 @@
|
|||||||
* Pixel-perfect implementation based on Webflow design
|
* Pixel-perfect implementation based on Webflow design
|
||||||
*/
|
*/
|
||||||
interface Props {
|
interface Props {
|
||||||
title?: string
|
title?: string;
|
||||||
subtitle?: string
|
subtitle?: string;
|
||||||
content?: string
|
content?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
title = '恩群數位的故事',
|
title = "恩群數位的故事",
|
||||||
subtitle = 'Something About Enchun Digital',
|
subtitle = "Something About Enchun Digital",
|
||||||
content = '恩群數位是由一群年輕果斷、敢冒險的年輕人聚在一起,共同為台灣在地經營努力不懈的商家老闆們建立品牌的知名度。而商家的經營本身就不是一件容易的事情,早在恩群成立之前,我們便一直聚焦在與不同行業的老闆們建立起信賴可靠的關係,從一家又一家的合作關係當中,聚集了在行銷領域裡的頂尖好手,培育了許多優秀行銷顧問。讓每個辛苦經營的商家老闆可以獲得最佳的服務,讓每次的行銷需求可以透過有效的互動與聆聽,達到彼此心目中的預期目標。數字的確會說話,但是每一個有溫度的服務才是在恩群裡最重視的地方。',
|
content = "恩群數位是由一群年輕果斷、敢冒險的年輕人聚在一起,共同為台灣在地經營努力不懈的商家老闆們建立品牌的知名度。而商家的經營本身就不是一件容易的事情,早在恩群成立之前,我們便一直聚焦在與不同行業的老闆們建立起信賴可靠的關係,從一家又一家的合作關係當中,聚集了在行銷領域裡的頂尖好手,培育了許多優秀行銷顧問。讓每個辛苦經營的商家老闆可以獲得最佳的服務,讓每次的行銷需求可以透過有效的互動與聆聽,達到彼此心目中的預期目標。數字的確會說話,但是每一個有溫度的服務才是在恩群裡最重視的地方。",
|
||||||
} = Astro.props
|
} = Astro.props;
|
||||||
|
import SectionHeader from "../components/SectionHeader.astro";
|
||||||
---
|
---
|
||||||
|
|
||||||
<section class="section-story" aria-labelledby="story-heading">
|
<section
|
||||||
<div class="container w-container">
|
class="section-story py-20 md:py-[60px] md:px-4 max-md:py-10 max-md:px-4 text-center bg-white"
|
||||||
|
aria-labelledby="story-heading"
|
||||||
|
>
|
||||||
|
<div class="container w-container max-w-4xl mx-auto">
|
||||||
<!-- Section Header -->
|
<!-- Section Header -->
|
||||||
<div class="section_header_w_line">
|
<SectionHeader
|
||||||
<div class="divider_line"></div>
|
title="恩群數位的故事"
|
||||||
<div class="header_subtitle">
|
subtitle="Something About Enchun Digital"
|
||||||
<h2 id="story-heading" class="header_subtitle_head">{title}</h2>
|
sectionBg="bg-white"
|
||||||
<p class="header_subtitle_paragraph">{subtitle}</p>
|
/>
|
||||||
</div>
|
|
||||||
<div class="divider_line"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Story Content -->
|
<!-- Story Content -->
|
||||||
<p class="story-paragraph">{content}</p>
|
<p
|
||||||
|
class="story-paragraph max-w-3xl mx-auto text-base md:text-lg font-thin leading-[1.8] max-md:leading-[1.7] text-(--color-text-secondary)/80 font-[Noto_Sans_TC,sans-serif]"
|
||||||
|
>
|
||||||
|
{content}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<style>
|
|
||||||
/* Company Story Styles - Pixel-perfect from Webflow */
|
|
||||||
.section-story {
|
|
||||||
padding: 80px 20px;
|
|
||||||
text-align: center;
|
|
||||||
background-color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.w-container {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Section Header */
|
|
||||||
.section_header_w_line {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 16px;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header_subtitle {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header_subtitle_head {
|
|
||||||
color: var(--color-enchunblue);
|
|
||||||
font-family: "Noto Sans TC", sans-serif;
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 2rem;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header_subtitle_paragraph {
|
|
||||||
color: var(--color-gray-600);
|
|
||||||
font-family: "Quicksand", sans-serif;
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.divider_line {
|
|
||||||
width: 40px;
|
|
||||||
height: 2px;
|
|
||||||
background-color: var(--color-enchunblue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Story Content */
|
|
||||||
.story-paragraph {
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 0 auto;
|
|
||||||
font-size: 1.125rem;
|
|
||||||
line-height: 1.8;
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
font-family: "Noto Sans TC", sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive Adjustments */
|
|
||||||
@media (max-width: 991px) {
|
|
||||||
.section-story {
|
|
||||||
padding: 60px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header_subtitle_head {
|
|
||||||
font-size: 1.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.story-paragraph {
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 767px) {
|
|
||||||
.section-story {
|
|
||||||
padding: 40px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section_header_w_line {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header_subtitle_head {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.story-paragraph {
|
|
||||||
font-size: 0.95rem;
|
|
||||||
line-height: 1.7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,274 +1,139 @@
|
|||||||
---
|
---
|
||||||
|
import SectionHeader from "@/components/SectionHeader.astro";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ComparisonSection - 恩群數位 vs 其他行銷公司 對比表格
|
* ComparisonSection - 恩群數位 vs 其他行銷公司 對比表格
|
||||||
* Pixel-perfect implementation based on Webflow design
|
* Custom layout based on new design specs with Tailwind CSS
|
||||||
*/
|
*/
|
||||||
interface ComparisonItem {
|
|
||||||
feature: string
|
|
||||||
enchun: string
|
|
||||||
others: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const comparisonItems: ComparisonItem[] = [
|
const otherCompanyItems = [
|
||||||
{
|
"缺乏經驗",
|
||||||
feature: '服務範圍',
|
"沒有成效保證",
|
||||||
enchun: '全方位數位行銷服務,從策略到執行一條龍',
|
"售後無服務",
|
||||||
others: '單一服務項目,缺乏整合性',
|
"沒有策略",
|
||||||
},
|
"不了解客戶需求",
|
||||||
{
|
"沒有接受客戶反饋",
|
||||||
feature: '數據分析',
|
];
|
||||||
enchun: '專業數據分析團隊,精準追蹤 ROI',
|
|
||||||
others: '基礎報告,缺乏深度分析',
|
const enchunItems = [
|
||||||
},
|
"實際執行經驗豐富",
|
||||||
{
|
"實際成效",
|
||||||
feature: '在地化經驗',
|
"售後服務架構完善",
|
||||||
enchun: '深耕台灣市場,了解本地消費者習性',
|
"行銷策略有方",
|
||||||
others: '通用策略,缺乏在地化調整',
|
"熟悉客戶需求",
|
||||||
},
|
"最多客戶回饋",
|
||||||
{
|
];
|
||||||
feature: '客戶服務',
|
|
||||||
enchun: '一對一專人服務,快速響應',
|
|
||||||
others: '標準化流程,回應較慢',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
feature: '價格透明',
|
|
||||||
enchun: '明確報價,無隱藏費用',
|
|
||||||
others: '複雜收費結構,容易超支',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<section class="section-comparison" aria-labelledby="comparison-heading">
|
<section
|
||||||
<div class="w-container">
|
class="bg-[#f8f9fa] py-10 px-3 md:py-[60px] md:px-4 lg:py-20 lg:px-5"
|
||||||
|
aria-labelledby="comparison-heading"
|
||||||
|
>
|
||||||
|
<div class="max-w-[1000px] mx-auto">
|
||||||
<!-- Section Header -->
|
<!-- Section Header -->
|
||||||
<div class="section-header-w-line">
|
<SectionHeader
|
||||||
<h2 id="comparison-heading" class="header-subtitle-head">
|
title="恩群與其他公司有什麼不同"
|
||||||
為什麼選擇恩群數位
|
subtitle="What make us different from others"
|
||||||
</h2>
|
/>
|
||||||
<div class="divider-line"></div>
|
|
||||||
|
<!-- Comparison Cards Layout -->
|
||||||
|
<div
|
||||||
|
class="relative bg-white rounded-2xl shadow-xl overflow-hidden mt-10 md:mt-12 max-w-4xl mx-auto border border-gray-100"
|
||||||
|
>
|
||||||
|
<!-- Winning Medal (Absolute Positioned) -->
|
||||||
|
<div
|
||||||
|
class="absolute -top-2 right-6 md:right-12 w-16 h-16 md:w-20 md:h-20 z-10 drop-shadow-lg"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="https://cdn.prod.website-files.com/61f24aa108528b1962942c95/61f24aa108528b7eab942cd9_winning%20medal.svg"
|
||||||
|
loading="lazy"
|
||||||
|
alt="Winning Medal"
|
||||||
|
class="w-full h-full object-contain"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2">
|
||||||
|
<!-- Left Column: Other Companies -->
|
||||||
|
<div
|
||||||
|
class="p-8 md:p-12 border-b md:border-b-0 md:border-r border-gray-200 bg-gray-50 flex flex-col gap-6"
|
||||||
|
>
|
||||||
|
<h3
|
||||||
|
class="text-2xl md:text-3xl font-bold text-gray-800 tracking-tight font-['Noto_Sans_TC',sans-serif]"
|
||||||
|
>
|
||||||
|
其他行銷公司
|
||||||
|
</h3>
|
||||||
|
<p
|
||||||
|
class="text-gray-500 text-sm md:text-base leading-relaxed h-[60px]"
|
||||||
|
>
|
||||||
|
市場上每間行銷公司想要讓自己的公司服務可以被看見,有時候客戶只能成為待宰羔羊
|
||||||
|
</p>
|
||||||
|
<div class="h-px w-full bg-gray-200 my-2"></div>
|
||||||
|
<ul role="list" class="flex flex-col gap-4 md:gap-5 mt-2">
|
||||||
|
{
|
||||||
|
otherCompanyItems.map((item) => (
|
||||||
|
<li class="flex items-center gap-3">
|
||||||
|
<span class="text-gray-700 font-medium text-base md:text-lg flex-1">
|
||||||
|
{item}
|
||||||
|
</span>
|
||||||
|
<div class="flex-shrink-0 flex items-center justify-center w-6 h-6 rounded-full bg-red-50 relative top-1">
|
||||||
|
<img
|
||||||
|
src="https://cdn.prod.website-files.com/61f24aa108528b1962942c95/61f24aa108528b9634942cdb_wrong.svg"
|
||||||
|
loading="lazy"
|
||||||
|
alt="Cross Icon"
|
||||||
|
class="w-3 h-3"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right Column: Enchun Digital -->
|
||||||
|
<div class="p-8 md:p-12 flex flex-col gap-6 relative bg-white">
|
||||||
|
<h3
|
||||||
|
class="text-2xl md:text-3xl font-bold text-[var(--color-enchunblue)] tracking-tight font-['Noto_Sans_TC',sans-serif]"
|
||||||
|
>
|
||||||
|
恩群數位
|
||||||
|
</h3>
|
||||||
|
<p
|
||||||
|
class="text-gray-600 text-sm md:text-base leading-relaxed h-[60px]"
|
||||||
|
>
|
||||||
|
在恩群數位每個客戶都是我們最重視的拍檔,不論合作的項目大小,我們珍惜與客戶的合作關係
|
||||||
|
</p>
|
||||||
|
<div class="h-px w-full bg-blue-100 my-2"></div>
|
||||||
|
<ul role="list" class="flex flex-col gap-4 md:gap-5 mt-2">
|
||||||
|
{
|
||||||
|
enchunItems.map((item) => (
|
||||||
|
<li class="flex items-center gap-3">
|
||||||
|
<span class="text-gray-800 font-medium text-base md:text-lg flex-1">
|
||||||
|
{item}
|
||||||
|
</span>
|
||||||
|
<div class="flex-shrink-0 flex items-center justify-center w-6 h-6 rounded-full bg-green-50 relative top-1">
|
||||||
|
<img
|
||||||
|
src="https://cdn.prod.website-files.com/61f24aa108528b1962942c95/61f24aa108528b7a79942cda_checked.svg"
|
||||||
|
loading="lazy"
|
||||||
|
alt="Check Icon"
|
||||||
|
class="w-4 h-4"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Comparison Table -->
|
<!-- CTA Button -->
|
||||||
<div class="comparison-table-wrapper">
|
<div class="mt-12 flex justify-center">
|
||||||
<table class="comparison-table">
|
<a
|
||||||
<thead>
|
href="https://heyform.itslouis.cc/form/7mYtUNjA"
|
||||||
<tr>
|
target="_blank"
|
||||||
<th class="th-feature">比較項目</th>
|
class="inline-flex items-center justify-center bg-[var(--color-enchunblue)] text-white font-bold text-lg px-10 py-4 rounded-full shadow-[0_8px_20px_-6px_rgba(43,83,186,0.5)] hover:shadow-[0_12px_24px_-8px_rgba(43,83,186,0.6)] hover:opacity-95 transition-all duration-300 transform hover:-translate-y-1"
|
||||||
<th class="th-enchun">
|
>
|
||||||
<span class="enchun-badge">恩群數位</span>
|
<div class="tracking-wide">跟行銷顧問聊聊</div>
|
||||||
</th>
|
</a>
|
||||||
<th class="th-others">其他行銷公司</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{
|
|
||||||
comparisonItems.map((item, index) => (
|
|
||||||
<tr class={index % 2 === 0 ? 'row-even' : 'row-odd'}>
|
|
||||||
<td class="td-feature">{item.feature}</td>
|
|
||||||
<td class="td-enchun">
|
|
||||||
<span class="enchun-icon">✓</span>
|
|
||||||
{item.enchun}
|
|
||||||
</td>
|
|
||||||
<td class="td-others">{item.others}</td>
|
|
||||||
</tr>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- CTA Note -->
|
|
||||||
<div class="comparison-note">
|
|
||||||
<p class="note-text">
|
|
||||||
選擇恩群數位,讓您的品牌在數位時代脫穎而出!
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<style>
|
|
||||||
/* Comparison Section Styles - Pixel-perfect from Webflow */
|
|
||||||
.section-comparison {
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
padding: 80px 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.w-container {
|
|
||||||
max-width: 1000px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Section Header */
|
|
||||||
.section-header-w-line {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 48px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-subtitle-head {
|
|
||||||
color: var(--color-enchunblue);
|
|
||||||
font-family: "Noto Sans TC", "Quicksand", sans-serif;
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 2rem;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.divider-line {
|
|
||||||
background-color: var(--color-enchunblue);
|
|
||||||
height: 2px;
|
|
||||||
width: 60px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Table Wrapper */
|
|
||||||
.comparison-table-wrapper {
|
|
||||||
background: white;
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
box-shadow: var(--shadow-md);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Comparison Table */
|
|
||||||
.comparison-table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Table Header */
|
|
||||||
.comparison-table thead {
|
|
||||||
background-color: var(--color-enchunblue);
|
|
||||||
}
|
|
||||||
|
|
||||||
.comparison-table th {
|
|
||||||
color: white;
|
|
||||||
font-family: "Noto Sans TC", sans-serif;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 1.125rem;
|
|
||||||
padding: 20px;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.th-feature {
|
|
||||||
width: 20%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.th-enchun {
|
|
||||||
width: 45%;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.th-others {
|
|
||||||
width: 35%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Enchun Badge */
|
|
||||||
.enchun-badge {
|
|
||||||
display: inline-block;
|
|
||||||
background-color: white;
|
|
||||||
color: var(--color-enchunblue);
|
|
||||||
padding: 4px 16px;
|
|
||||||
border-radius: 20px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Table Body */
|
|
||||||
.comparison-table tbody tr {
|
|
||||||
border-bottom: 1px solid var(--color-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.comparison-table tbody tr:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row-even {
|
|
||||||
background-color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row-odd {
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comparison-table td {
|
|
||||||
padding: 20px;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
.td-feature {
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--color-text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.td-enchun {
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.enchun-icon {
|
|
||||||
display: inline-block;
|
|
||||||
color: #22c55e;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.td-others {
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Comparison Note */
|
|
||||||
.comparison-note {
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-text {
|
|
||||||
font-size: 1.125rem;
|
|
||||||
color: var(--color-enchunblue);
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive Adjustments */
|
|
||||||
@media (max-width: 991px) {
|
|
||||||
.section-comparison {
|
|
||||||
padding: 60px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comparison-table th,
|
|
||||||
.comparison-table td {
|
|
||||||
padding: 16px 12px;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-subtitle-head {
|
|
||||||
font-size: 1.75rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 767px) {
|
|
||||||
/* Convert to card layout on mobile */
|
|
||||||
.comparison-table-wrapper {
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comparison-table {
|
|
||||||
min-width: 600px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-comparison {
|
|
||||||
padding: 40px 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-subtitle-head {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comparison-table th,
|
|
||||||
.comparison-table td {
|
|
||||||
padding: 12px 8px;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.enchun-badge {
|
|
||||||
font-size: 0.875rem;
|
|
||||||
padding: 2px 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
62
apps/frontend/src/sections/Cta-Hr-compoents.astro
Normal file
62
apps/frontend/src/sections/Cta-Hr-compoents.astro
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
---
|
||||||
|
/**
|
||||||
|
* Cta-Hr-compoents - HR CTA Section
|
||||||
|
* 招募行動呼籲區塊,引導求職者申請面試
|
||||||
|
*/
|
||||||
|
interface Props {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
image?: { url: string; alt: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
const { title, description, image } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<section
|
||||||
|
class="relative py-20 px-5 bg-slate-100 bg-cover bg-center overflow-hidden"
|
||||||
|
aria-labelledby="cta-heading"
|
||||||
|
style={image ? `background-image: url('${image.url}')` : undefined}
|
||||||
|
>
|
||||||
|
{/* Dark overlay when image is present */}
|
||||||
|
{
|
||||||
|
image && (
|
||||||
|
<div
|
||||||
|
class="absolute inset-0 bg-linear-to-b from-transparent to-black/70 z-0"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<div class="relative z-10 max-w-3xl md:max-w-3xl mx-auto">
|
||||||
|
<div
|
||||||
|
class="grid grid-rows-[1fr_auto] gap-8 items-center lg:grid-cols-[1fr_auto]"
|
||||||
|
>
|
||||||
|
<div class="text-center md:text-left">
|
||||||
|
<h3
|
||||||
|
id="cta-heading"
|
||||||
|
class:list={[
|
||||||
|
"text-4xl md:text-4xl font-semibold mb-4 leading-snug",
|
||||||
|
image ? "text-white" : "text-[#23608c]",
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Fragment set:html={title} />
|
||||||
|
</h3>
|
||||||
|
<p
|
||||||
|
class:list={[
|
||||||
|
"text-sm md:text-base leading-relaxed text-balance max-w-xl lg:max-w-full",
|
||||||
|
image ? "text-white/80" : "text-slate-600",
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<a
|
||||||
|
href="https://www.104.com.tw/company/1a2x6bkoaj?jobsource=joblist_r_cust"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="inline-flex items-center justify-center mx-auto bg-(--color-pale-purple) text-white px-8 py-4 rounded-md font-semibold text-xl transition-all duration-200 whitespace-nowrap hover:-translate-y-0.5 hover:shadow-md hover:bg-(--color-pale-purple)/90 w-full max-w-3xs md:w-full lg:max-w-[300px]"
|
||||||
|
>
|
||||||
|
立刻申請面試
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
@@ -6,54 +6,88 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
interface SlideImage {
|
interface SlideImage {
|
||||||
src: string
|
src: string;
|
||||||
alt: string
|
alt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
slides?: SlideImage[]
|
slides?: SlideImage[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultSlides: SlideImage[] = [
|
const defaultSlides: SlideImage[] = [
|
||||||
{ src: '/placeholder-environment-1.jpg', alt: '恩群環境照片 1' },
|
{
|
||||||
{ src: '/placeholder-environment-2.jpg', alt: '恩群環境照片 2' },
|
src: "https://enchun-cms.anlstudio.cc/api/media/file/61f76b4962117e2d84363174_%E6%81%A9%E7%BE%A4%E7%92%B0%E5%A2%83%20%E7%85%A7%E7%89%871.jpg",
|
||||||
{ src: '/placeholder-environment-3.jpg', alt: '恩群環境照片 3' },
|
alt: "恩群環境照片 1",
|
||||||
{ src: '/placeholder-environment-4.jpg', alt: '恩群環境照片 4' },
|
},
|
||||||
{ src: '/placeholder-environment-5.jpg', alt: '恩群環境照片 5' },
|
{
|
||||||
{ src: '/placeholder-environment-6.jpg', alt: '恩群環境照片 6' },
|
src: "https://enchun-cms.anlstudio.cc/api/media/file/61f76b49558b7e5b0e81de8f_%E6%81%A9%E7%BE%A4%E7%92%B0%E5%A2%83%20%E7%85%A7%E7%89%871-7.jpg",
|
||||||
{ src: '/placeholder-environment-7.jpg', alt: '恩群環境照片 7' },
|
alt: "恩群環境照片 2",
|
||||||
{ src: '/placeholder-environment-8.jpg', alt: '恩群環境照片 8' },
|
},
|
||||||
]
|
{
|
||||||
|
src: "https://enchun-cms.anlstudio.cc/api/media/file/61f76b4962117e2d84363174_%E6%81%A9%E7%BE%A4%E7%92%B0%E5%A2%83%20%E7%85%A7%E7%89%871-1.jpg",
|
||||||
|
alt: "恩群環境照片 3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "https://enchun-cms.anlstudio.cc/api/media/file/61f76c511895ed028da1c7f0_%E6%81%A9%E7%BE%A4%E7%92%B0%E5%A2%83%20%E7%85%A7%E7%89%871-3.jpg",
|
||||||
|
alt: "恩群環境照片 4",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "https://enchun-cms.anlstudio.cc/api/media/file/61f76b48558b7e072a81de8e_%E6%81%A9%E7%BE%A4%E7%92%B0%E5%A2%83%20%E7%85%A7%E7%89%871-4.jpg",
|
||||||
|
alt: "恩群環境照片 5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "https://enchun-cms.anlstudio.cc/api/media/file/61f76b483504a25babe537ef_%E6%81%A9%E7%BE%A4%E7%92%B0%E5%A2%83%20%E7%85%A7%E7%89%871-1.jpg",
|
||||||
|
alt: "恩群環境照片 6",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "https://enchun-cms.anlstudio.cc/api/media/file/61f76b48ef5754e17a8c1676_%E6%81%A9%E7%BE%A4%E7%92%B0%E5%A2%83%20%E7%85%A7%E7%89%871-6.jpg",
|
||||||
|
alt: "恩群環境照片 7",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "https://enchun-cms.anlstudio.cc/api/media/file/620639d7a68fef44f6569b23_%E6%81%A9%E7%BE%A4%E7%92%B0%E5%A2%83%20%E7%85%A7%E7%89%879.jpg",
|
||||||
|
alt: "恩群環境照片 8",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const slides = Astro.props.slides || defaultSlides
|
const slides = Astro.props.slides || defaultSlides;
|
||||||
|
|
||||||
import SectionHeader from '../components/SectionHeader.astro'
|
import SectionHeader from "../components/SectionHeader.astro";
|
||||||
---
|
---
|
||||||
|
|
||||||
<section class="section-video" aria-label="工作環境照片">
|
<section
|
||||||
<div class="container spacer8 w-container">
|
class="section-video py-10 md:py-20 md:px-5 px-4 bg-white"
|
||||||
|
aria-label="工作環境照片"
|
||||||
|
>
|
||||||
|
<div class="container w-container max-w-3xl mx-auto">
|
||||||
<!-- Section Header -->
|
<!-- Section Header -->
|
||||||
<SectionHeader
|
<SectionHeader
|
||||||
title="在恩群工作的環境"
|
title="在恩群工作的環境"
|
||||||
subtitle="Working Enviroment"
|
subtitle="Working Enviroment"
|
||||||
|
sectionBg="bg-white"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Environment Slider -->
|
<!-- Environment Slider -->
|
||||||
<div
|
<div
|
||||||
class="environment-slider"
|
class="environment-slider relative w-full max-w-4xl mx-auto"
|
||||||
id="env-slider-{Math.random().toString(36).slice(2, 8)}"
|
id="env-slider-{Math.random().toString(36).slice(2, 8)}"
|
||||||
>
|
>
|
||||||
<!-- Slides Container -->
|
<!-- Slides Container -->
|
||||||
<div class="slides-container">
|
<div
|
||||||
|
class="slides-container flex overflow-x-auto snap-x snap-mandatory scroll-smooth overscroll-x-contain [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden cursor-grab"
|
||||||
|
>
|
||||||
{
|
{
|
||||||
slides.map((slide, index) => (
|
slides.map((slide, index) => (
|
||||||
<div class="environment-slide" data-index={index}>
|
<div
|
||||||
|
class="environment-slide flex-none w-full snap-start aspect-video overflow-hidden rounded-xl"
|
||||||
|
data-index={index}
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
src={slide.src}
|
src={slide.src}
|
||||||
alt={slide.alt}
|
alt={slide.alt}
|
||||||
loading={index === 0 ? 'eager' : 'lazy'}
|
loading={index === 0 ? "eager" : "lazy"}
|
||||||
width="800"
|
width="800"
|
||||||
height="450"
|
height="450"
|
||||||
|
class="w-full h-full object-cover"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
@@ -61,23 +95,33 @@ import SectionHeader from '../components/SectionHeader.astro'
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Arrow Navigation -->
|
<!-- Arrow Navigation -->
|
||||||
<button class="slider-arrow slider-arrow-left" aria-label="上一張">
|
<button
|
||||||
|
class="slider-arrow slider-arrow-left absolute top-1/2 -translate-y-1/2 w-12 h-12 md:w-12 md:h-12 w-10 h-10 bg-white/80 rounded-full flex items-center justify-center cursor-pointer transition-all duration-200 ease-in-out z-10 hover:bg-white hover:scale-110 left-4 md:left-4 left-2"
|
||||||
|
aria-label="上一張"
|
||||||
|
>
|
||||||
<svg viewBox="0 0 24 24" width="24" height="24">
|
<svg viewBox="0 0 24 24" width="24" height="24">
|
||||||
<path fill="currentColor" d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/>
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<button class="slider-arrow slider-arrow-right" aria-label="下一張">
|
<button
|
||||||
|
class="slider-arrow slider-arrow-right absolute top-1/2 -translate-y-1/2 w-12 h-12 md:w-12 md:h-12 w-10 h-10 bg-white/80 rounded-full flex items-center justify-center cursor-pointer transition-all duration-200 ease-in-out z-10 hover:bg-white hover:scale-110 right-4 md:right-4 right-2"
|
||||||
|
aria-label="下一張"
|
||||||
|
>
|
||||||
<svg viewBox="0 0 24 24" width="24" height="24">
|
<svg viewBox="0 0 24 24" width="24" height="24">
|
||||||
<path fill="currentColor" d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/>
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Dot Navigation -->
|
<!-- Dot Navigation -->
|
||||||
<div class="slider-dots">
|
<div class="slider-dots flex justify-center gap-2 mt-4">
|
||||||
{
|
{
|
||||||
slides.map((_, index) => (
|
slides.map((_, index) => (
|
||||||
<button
|
<button
|
||||||
class={`slider-dot ${index === 0 ? 'active' : ''}`}
|
class={`slider-dot w-3 h-3 rounded-full cursor-pointer transition-all duration-200 ease-in-out ${index === 0 ? "active bg-(--color-enchunblue) w-8 rounded-[6px]" : "bg-(--color-enchunblue)/30"}`}
|
||||||
data-index={index}
|
data-index={index}
|
||||||
aria-label={`顯示第 ${index + 1} 張照片`}
|
aria-label={`顯示第 ${index + 1} 張照片`}
|
||||||
/>
|
/>
|
||||||
@@ -88,313 +132,183 @@ import SectionHeader from '../components/SectionHeader.astro'
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<style>
|
|
||||||
/* Environment Slider Styles - Pixel-perfect from Webflow */
|
|
||||||
.section-video {
|
|
||||||
padding: 80px 20px;
|
|
||||||
background-color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.w-container {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spacer8 {
|
|
||||||
padding-top: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Environment Slider */
|
|
||||||
.environment-slider {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slides-container {
|
|
||||||
display: flex;
|
|
||||||
overflow-x: auto;
|
|
||||||
scroll-snap-type: x mandatory;
|
|
||||||
scroll-behavior: smooth;
|
|
||||||
-webkit-overflow-scrolling: touch;
|
|
||||||
scrollbar-width: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slides-container::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.environment-slide {
|
|
||||||
flex: 0 0 100%;
|
|
||||||
scroll-snap-align: start;
|
|
||||||
aspect-ratio: 16/9;
|
|
||||||
overflow: hidden;
|
|
||||||
border-radius: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.environment-slide img {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Arrow Navigation */
|
|
||||||
.slider-arrow {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
background-color: rgba(255, 255, 255, 0.8);
|
|
||||||
border: none;
|
|
||||||
border-radius: 50%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 200ms ease;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider-arrow:hover {
|
|
||||||
background-color: white;
|
|
||||||
transform: translateY(-50%) scale(1.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider-arrow-left {
|
|
||||||
left: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider-arrow-right {
|
|
||||||
right: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dot Navigation */
|
|
||||||
.slider-dots {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 8px;
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider-dot {
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: rgba(35, 96, 140, 0.3);
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 200ms ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider-dot.active {
|
|
||||||
background-color: var(--color-enchunblue);
|
|
||||||
width: 32px;
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive Adjustments */
|
|
||||||
@media (min-width: 992px) {
|
|
||||||
.environment-slider {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 991px) {
|
|
||||||
.environment-slider {
|
|
||||||
max-width: 550px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 767px) {
|
|
||||||
.section-video {
|
|
||||||
padding: 60px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.environment-slider {
|
|
||||||
max-width: 90vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider-arrow {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider-arrow-left {
|
|
||||||
left: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider-arrow-right {
|
|
||||||
right: 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Environment Slider functionality
|
// Environment Slider functionality
|
||||||
function initEnvironmentSlider() {
|
function initEnvironmentSlider() {
|
||||||
const sliders = document.querySelectorAll('.environment-slider')
|
const sliders = document.querySelectorAll(".environment-slider");
|
||||||
|
|
||||||
sliders.forEach((slider) => {
|
sliders.forEach((slider) => {
|
||||||
const container = slider.querySelector('.slides-container') as HTMLElement
|
const container = slider.querySelector(
|
||||||
const slides = slider.querySelectorAll('.environment-slide')
|
".slides-container",
|
||||||
const dots = slider.querySelectorAll('.slider-dot')
|
) as HTMLElement;
|
||||||
const prevBtn = slider.querySelector('.slider-arrow-left') as HTMLButtonElement
|
const slides = slider.querySelectorAll(".environment-slide");
|
||||||
const nextBtn = slider.querySelector('.slider-arrow-right') as HTMLButtonElement
|
const dots = slider.querySelectorAll(".slider-dot");
|
||||||
|
const prevBtn = slider.querySelector(
|
||||||
|
".slider-arrow-left",
|
||||||
|
) as HTMLButtonElement;
|
||||||
|
const nextBtn = slider.querySelector(
|
||||||
|
".slider-arrow-right",
|
||||||
|
) as HTMLButtonElement;
|
||||||
|
|
||||||
if (!container || slides.length === 0) return
|
if (!container || slides.length === 0) return;
|
||||||
|
|
||||||
let currentIndex = 0
|
let currentIndex = 0;
|
||||||
const totalSlides = slides.length
|
const totalSlides = slides.length;
|
||||||
let isDragging = false
|
let isDragging = false;
|
||||||
let startPos = 0
|
let startPos = 0;
|
||||||
let currentTranslate = 0
|
let currentTranslate = 0;
|
||||||
let prevTranslate = 0
|
let prevTranslate = 0;
|
||||||
let animationID: number
|
let animationID: number;
|
||||||
|
|
||||||
// Update slider position
|
// Update slider position
|
||||||
const updateSlider = () => {
|
const updateSlider = () => {
|
||||||
container.scrollTo({
|
container.scrollTo({
|
||||||
left: currentIndex * container.offsetWidth,
|
left: currentIndex * container.offsetWidth,
|
||||||
behavior: 'smooth'
|
behavior: "smooth",
|
||||||
})
|
});
|
||||||
updateDots()
|
updateDots();
|
||||||
}
|
};
|
||||||
|
|
||||||
// Update dots
|
// Update dots
|
||||||
const updateDots = () => {
|
const updateDots = () => {
|
||||||
dots.forEach((dot, index) => {
|
dots.forEach((dot, index) => {
|
||||||
if (index === currentIndex) {
|
if (index === currentIndex) {
|
||||||
dot.classList.add('active')
|
dot.classList.add(
|
||||||
|
"active",
|
||||||
|
"w-8",
|
||||||
|
"rounded-[6px]",
|
||||||
|
"bg-(--color-enchunblue)",
|
||||||
|
);
|
||||||
|
dot.classList.remove("w-3", "bg-(--color-enchunblue)/30");
|
||||||
} else {
|
} else {
|
||||||
dot.classList.remove('active')
|
dot.classList.remove(
|
||||||
|
"active",
|
||||||
|
"w-8",
|
||||||
|
"rounded-[6px]",
|
||||||
|
"bg-(--color-enchunblue)",
|
||||||
|
);
|
||||||
|
dot.classList.add("w-3", "bg-(--color-enchunblue)/30");
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
// Go to specific slide
|
// Go to specific slide
|
||||||
const goToSlide = (index: number) => {
|
const goToSlide = (index: number) => {
|
||||||
if (index < 0) index = totalSlides - 1
|
if (index < 0) index = totalSlides - 1;
|
||||||
if (index >= totalSlides) index = 0
|
if (index >= totalSlides) index = 0;
|
||||||
currentIndex = index
|
currentIndex = index;
|
||||||
updateSlider()
|
updateSlider();
|
||||||
}
|
};
|
||||||
|
|
||||||
// Previous slide
|
// Previous slide
|
||||||
prevBtn?.addEventListener('click', () => goToSlide(currentIndex - 1))
|
prevBtn?.addEventListener("click", () => goToSlide(currentIndex - 1));
|
||||||
|
|
||||||
// Next slide
|
// Next slide
|
||||||
nextBtn?.addEventListener('click', () => goToSlide(currentIndex + 1))
|
nextBtn?.addEventListener("click", () => goToSlide(currentIndex + 1));
|
||||||
|
|
||||||
// Dot navigation
|
// Dot navigation
|
||||||
dots.forEach((dot, index) => {
|
dots.forEach((dot, index) => {
|
||||||
dot.addEventListener('click', () => goToSlide(index))
|
dot.addEventListener("click", () => goToSlide(index));
|
||||||
})
|
});
|
||||||
|
|
||||||
// Scroll snap detection
|
// Scroll snap detection
|
||||||
container.addEventListener('scroll', () => {
|
container.addEventListener("scroll", () => {
|
||||||
const slideIndex = Math.round(container.scrollLeft / container.offsetWidth)
|
const slideIndex = Math.round(
|
||||||
|
container.scrollLeft / container.offsetWidth,
|
||||||
|
);
|
||||||
if (slideIndex !== currentIndex) {
|
if (slideIndex !== currentIndex) {
|
||||||
currentIndex = slideIndex
|
currentIndex = slideIndex;
|
||||||
updateDots()
|
updateDots();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
// Touch/swipe support
|
// Touch/swipe support
|
||||||
const touchStart = (_index: number) => {
|
const touchStart = (_index: number) => {
|
||||||
return function(event: TouchEvent) {
|
return function (event: TouchEvent) {
|
||||||
isDragging = true
|
isDragging = true;
|
||||||
startPos = event.touches[0].clientX
|
startPos = event.touches[0].clientX;
|
||||||
animationID = requestAnimationFrame(animation)
|
animationID = requestAnimationFrame(animation);
|
||||||
container.style.cursor = 'grabbing'
|
container.style.cursor = "grabbing";
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const touchEnd = () => {
|
const touchEnd = () => {
|
||||||
isDragging = false
|
isDragging = false;
|
||||||
cancelAnimationFrame(animationID)
|
cancelAnimationFrame(animationID);
|
||||||
container.style.cursor = 'grab'
|
container.style.cursor = "grab";
|
||||||
|
|
||||||
const movedBy = currentTranslate - prevTranslate
|
const movedBy = currentTranslate - prevTranslate;
|
||||||
|
|
||||||
if (movedBy < -50 && currentIndex < totalSlides - 1) {
|
if (movedBy < -50 && currentIndex < totalSlides - 1) {
|
||||||
currentIndex += 1
|
currentIndex += 1;
|
||||||
} else if (movedBy > 50 && currentIndex > 0) {
|
} else if (movedBy > 50 && currentIndex > 0) {
|
||||||
currentIndex -= 1
|
currentIndex -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
goToSlide(currentIndex)
|
goToSlide(currentIndex);
|
||||||
}
|
};
|
||||||
|
|
||||||
const touchMove = (event: TouchEvent) => {
|
const touchMove = (event: TouchEvent) => {
|
||||||
if (isDragging) {
|
if (isDragging) {
|
||||||
const currentPosition = event.touches[0].clientX
|
const currentPosition = event.touches[0].clientX;
|
||||||
currentTranslate = prevTranslate + currentPosition - startPos
|
currentTranslate = prevTranslate + currentPosition - startPos;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const animation = () => {
|
const animation = () => {
|
||||||
if (isDragging) requestAnimationFrame(animation)
|
if (isDragging) requestAnimationFrame(animation);
|
||||||
}
|
};
|
||||||
|
|
||||||
// Mouse events for desktop
|
// Mouse events for desktop
|
||||||
let mouseStartPos = 0
|
let mouseStartPos = 0;
|
||||||
let isMouseDown = false
|
let isMouseDown = false;
|
||||||
|
|
||||||
container.addEventListener('mousedown', (e: MouseEvent) => {
|
container.addEventListener("mousedown", (e: MouseEvent) => {
|
||||||
isMouseDown = true
|
isMouseDown = true;
|
||||||
mouseStartPos = e.clientX
|
mouseStartPos = e.clientX;
|
||||||
container.style.cursor = 'grabbing'
|
container.style.cursor = "grabbing";
|
||||||
})
|
});
|
||||||
|
|
||||||
container.addEventListener('mouseup', (e: MouseEvent) => {
|
container.addEventListener("mouseup", (e: MouseEvent) => {
|
||||||
if (!isMouseDown) return
|
if (!isMouseDown) return;
|
||||||
isMouseDown = false
|
isMouseDown = false;
|
||||||
container.style.cursor = 'grab'
|
container.style.cursor = "grab";
|
||||||
|
|
||||||
const movedBy = e.clientX - mouseStartPos
|
const movedBy = e.clientX - mouseStartPos;
|
||||||
if (movedBy < -50 && currentIndex < totalSlides - 1) {
|
if (movedBy < -50 && currentIndex < totalSlides - 1) {
|
||||||
currentIndex += 1
|
currentIndex += 1;
|
||||||
} else if (movedBy > 50 && currentIndex > 0) {
|
} else if (movedBy > 50 && currentIndex > 0) {
|
||||||
currentIndex -= 1
|
currentIndex -= 1;
|
||||||
}
|
}
|
||||||
goToSlide(currentIndex)
|
goToSlide(currentIndex);
|
||||||
})
|
});
|
||||||
|
|
||||||
container.addEventListener('mouseleave', () => {
|
container.addEventListener("mouseleave", () => {
|
||||||
isMouseDown = false
|
isMouseDown = false;
|
||||||
container.style.cursor = 'grab'
|
container.style.cursor = "grab";
|
||||||
})
|
});
|
||||||
|
|
||||||
// Touch events
|
// Touch events
|
||||||
container.addEventListener('touchstart', touchStart(currentIndex))
|
container.addEventListener("touchstart", touchStart(currentIndex));
|
||||||
container.addEventListener('touchend', touchEnd)
|
container.addEventListener("touchend", touchEnd);
|
||||||
container.addEventListener('touchmove', touchMove)
|
container.addEventListener("touchmove", touchMove);
|
||||||
|
|
||||||
// Keyboard navigation
|
// Keyboard navigation
|
||||||
slider.addEventListener('keydown', (e) => {
|
slider.addEventListener("keydown", (e) => {
|
||||||
if (e instanceof KeyboardEvent) {
|
if (e instanceof KeyboardEvent) {
|
||||||
if (e.key === 'ArrowLeft') goToSlide(currentIndex - 1)
|
if (e.key === "ArrowLeft") goToSlide(currentIndex - 1);
|
||||||
if (e.key === 'ArrowRight') goToSlide(currentIndex + 1)
|
if (e.key === "ArrowRight") goToSlide(currentIndex + 1);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
// Set initial state
|
// Set initial state
|
||||||
container.style.cursor = 'grab'
|
container.style.cursor = "grab";
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize when DOM is ready
|
// Initialize when DOM is ready
|
||||||
document.addEventListener('DOMContentLoaded', initEnvironmentSlider)
|
document.addEventListener("DOMContentLoaded", initEnvironmentSlider);
|
||||||
if (document.readyState !== 'loading') {
|
if (document.readyState !== "loading") {
|
||||||
initEnvironmentSlider()
|
initEnvironmentSlider();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -36,38 +36,35 @@ const features = [
|
|||||||
description: '除了幫您拓展網路上的知名度,我們更是每家公司最專業的數位夥伴,你會知道有恩群的存在,事業路上你並不孤單。',
|
description: '除了幫您拓展網路上的知名度,我們更是每家公司最專業的數位夥伴,你會知道有恩群的存在,事業路上你並不孤單。',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
import SectionHeader from '../components/SectionHeader.astro'
|
||||||
---
|
---
|
||||||
|
|
||||||
<section class="section_feature bg-white px-4 py-[60px] md:px-5 md:py-[80px]" aria-labelledby="feature-heading">
|
<section class="section_feature bg-white px-4 py-16 md:px-5 md:py-20" aria-labelledby="feature-heading">
|
||||||
<div class="w-container mx-auto max-w-4xl">
|
<div class="w-container mx-auto max-w-4xl">
|
||||||
<!-- Section Header -->
|
<!-- Section Header -->
|
||||||
<div class="section_header_w_line mb-[60px] text-center">
|
<SectionHeader
|
||||||
<h2 id="feature-heading" class="header_subtitle_head my-4 text-[1.75rem] font-bold leading-[1.2] text-[var(--color-enchunblue)] md:text-[2.25rem]">
|
title="恩群服務特色"
|
||||||
{title}
|
subtitle="Why you can trust us"
|
||||||
</h2>
|
sectionBg="bg-white"
|
||||||
<p class="header_subtitle_paragraph mt-2 text-base font-normal text-[#666666]">
|
/>
|
||||||
{subtitle}
|
|
||||||
</p>
|
|
||||||
<div class="divider_line mx-auto h-[2px] w-[100px] bg-[var(--color-enchunblue)]"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Features Grid -->
|
<!-- Features Grid -->
|
||||||
<div class="grid grid-cols-1 gap-1 md:grid-cols-2 md:gap-2 lg:gap-6">
|
<div class="grid grid-cols-2 gap-1 md:grid-cols-2 md:gap-2 lg:gap-6">
|
||||||
{
|
{
|
||||||
features.map((feature) => (
|
features.map((feature) => (
|
||||||
<div class="feature_card bg-white p-5 transition-all duration-[var(--transition-base)] hover:-translate-y-1 md:p-6 lg:p-8">
|
<div class="feature_card bg-white p-5 transition-all duration-[var(--transition-base)] hover:-translate-y-1 md:p-6 lg:p-8">
|
||||||
<div class="grid grid-cols-[120px_1fr] items-start gap-4 md:grid-cols-[110px_1fr] lg:grid-cols-[190px_1fr] lg:gap-6">
|
<div class="flex flex-col items-center gap-4 text-center md:flex-row md:items-start md:text-left lg:gap-6">
|
||||||
<!-- Icon -->
|
<!-- Icon -->
|
||||||
<div class="flex w-full items-center justify-center">
|
<div class="flex w-full max-w-[100px] shrink-0 items-center justify-center md:max-w-[110px] lg:max-w-[190px]">
|
||||||
<img src={feature.img} alt={feature.title} class="h-auto w-full object-contain" />
|
<img src={feature.img} alt={feature.title} class="h-auto w-full object-contain" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<!-- Title -->
|
<!-- Title -->
|
||||||
<h3 class="font-['Noto_Sans_TC'] lg:text-3xl text-2xl font-semibold text-(--color-dark-blue)">{feature.title}</h3>
|
<h3 class="font-['Noto_Sans_TC'] text-2xl font-semibold text-(--color-dark-blue) lg:text-3xl">{feature.title}</h3>
|
||||||
|
|
||||||
<!-- Description -->
|
<!-- Description -->
|
||||||
<p class="font-['Noto_Sans_TC'] lg:text-base text-sm font-thin leading-[1.6] text-grey-2">{feature.description}</p>
|
<p class="font-['Noto_Sans_TC'] text-sm font-thin leading-[1.6] text-grey-2 lg:text-base">{feature.description}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ const painpoints = [
|
|||||||
{
|
{
|
||||||
id: 'burning',
|
id: 'burning',
|
||||||
title: '廣告行銷像燒錢',
|
title: '廣告行銷像燒錢',
|
||||||
icon: '💸',
|
img: '💸',
|
||||||
description: '廣告預算投入很多,但看不到實際效果?感覺像在燒錢?',
|
description: '廣告預算投入很多,但看不到實際效果?感覺像在燒錢?',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ const renderIcon = (service: ServicesListItem): string | null => {
|
|||||||
---
|
---
|
||||||
|
|
||||||
<section class="bg-white py-[60px] px-5 md:py-[60px] md:px-5 lg:py-[60px] lg:px-5" aria-labelledby="services-heading">
|
<section class="bg-white py-[60px] px-5 md:py-[60px] md:px-5 lg:py-[60px] lg:px-5" aria-labelledby="services-heading">
|
||||||
<div class="max-w-3xl mx-auto">
|
<div class="max-w-3xl mx-auto flex flex-col gap-6">
|
||||||
{
|
{
|
||||||
servicesList.map((service, index) => {
|
servicesList.map((service, index) => {
|
||||||
const isOdd = index % 2 === 0
|
const isOdd = index % 2 === 0
|
||||||
@@ -177,14 +177,14 @@ const renderIcon = (service: ServicesListItem): string | null => {
|
|||||||
<!-- Content Side -->
|
<!-- Content Side -->
|
||||||
<div class={`service-item-content order-2 ${isOdd ? 'md:order-1' : 'md:order-2'}`}>
|
<div class={`service-item-content order-2 ${isOdd ? 'md:order-1' : 'md:order-2'}`}>
|
||||||
<!-- Category Tag with Icon -->
|
<!-- Category Tag with Icon -->
|
||||||
<div class="flex items-center gap-2 mb-4">
|
<div class="flex flex-col-reverse items-start gap-2 mb-4">
|
||||||
<span class="inline-block text-(--color-enchunblue) text-3xl font-light">
|
<span class="inline-block text-(--color-enchunblue) text-3xl font-light">
|
||||||
{service.category}
|
{service.category}
|
||||||
</span>
|
</span>
|
||||||
{/* Icon displayed next to category */}
|
{/* Icon displayed next to category */}
|
||||||
{
|
{
|
||||||
iconHtml && (
|
iconHtml && (
|
||||||
<span class="inline-flex items-center justify-center w-8 h-8 text-(--color-enchunblue)" set:html={iconHtml} />
|
<span class="inline-flex items-center justify-start w-8 h-8 ml-2 scale-150 text-(--color-enchunblue)" set:html={iconHtml} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ const bgImageUrl = backgroundImage?.url || "";
|
|||||||
// Background color fallback
|
// Background color fallback
|
||||||
!hasBackgroundImage && "bg-(--color-dark-blue)",
|
!hasBackgroundImage && "bg-(--color-dark-blue)",
|
||||||
// Background image styles
|
// Background image styles
|
||||||
hasBackgroundImage && "bg-size-[120vw] bg-center bg-no-repeat",
|
hasBackgroundImage && "bg-cover bg-center bg-no-repeat",
|
||||||
// Pull up to counteract layout's pt-20 padding (80px)
|
// Pull up to counteract layout's pt-20 padding (80px)
|
||||||
"-mt-20",
|
"-mt-20",
|
||||||
// Full viewport height
|
// Full viewport height
|
||||||
|
|||||||
@@ -136,6 +136,8 @@
|
|||||||
/* Amber - CTA/強調 */
|
/* Amber - CTA/強調 */
|
||||||
--color-tarawera: #2d3748;
|
--color-tarawera: #2d3748;
|
||||||
/* Tarawera - 深色文字 */
|
/* Tarawera - 深色文字 */
|
||||||
|
--color-pale-purple: oklch(62.664% 0.15547 290.298);
|
||||||
|
/* Pale Purple - CTA/強調 */
|
||||||
--color-nav-link: var(--color-gray-200);
|
--color-nav-link: var(--color-gray-200);
|
||||||
/* Navigation Link - 使用灰色系 */
|
/* Navigation Link - 使用灰色系 */
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user