improved site header, added contact form, content card carousel, footer, and more photos
@@ -1,5 +1,11 @@
|
||||
export default function Page() {
|
||||
import ContactForm from '@/components/contact-form'
|
||||
|
||||
export default function ContactPage() {
|
||||
return (
|
||||
<h1>contact page</h1>
|
||||
<div className="flex min-h-screen flex-col bg-zinc-50 font-sans dark:bg-black">
|
||||
<main className="mx-auto flex w-full max-w-7xl flex-1 flex-col items-center px-8 py-16 sm:py-24">
|
||||
<ContactForm />
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -19,6 +19,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-padding-top: 100px;
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import Menubar from '../components/menubar';
|
||||
import SiteHeader from '../components/site-header';
|
||||
import Footer from "@/components/footer";
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
@@ -18,6 +19,11 @@ export const metadata: Metadata = {
|
||||
description: "Generated by create next app",
|
||||
};
|
||||
|
||||
export const viewport = {
|
||||
width: "device-width",
|
||||
initialScale: 1,
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
@@ -29,8 +35,9 @@ export default function RootLayout({
|
||||
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
|
||||
>
|
||||
<body className="min-h-full flex flex-col dark:bg-black">
|
||||
<Menubar />
|
||||
<SiteHeader />
|
||||
{children}
|
||||
<Footer />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
55
app/page.tsx
@@ -1,25 +1,60 @@
|
||||
import Image from "next/image";
|
||||
import HeroSection from "@/components/hero-section";
|
||||
import ContentCard from "@/components/ui/content-cards";
|
||||
import ContentCardCarousel from "@/components/content-card-carousel";
|
||||
import Footer from "@/components/footer";
|
||||
import PicWithText from "@/components/pic-with-text";
|
||||
|
||||
import pictureOne from "../assets/hero-picture-1.png"
|
||||
import pictureTwo from "../assets/hero-picture-2.png"
|
||||
import pictureThree from "../assets/hero-picture-3.png"
|
||||
import logo from "../assets/milaoktasafitri-astronomy-9746537_1920.png"
|
||||
import { title } from "process";
|
||||
import { reverse } from "dns";
|
||||
|
||||
export default function Home() {
|
||||
const contentCards = [
|
||||
{
|
||||
heading: "Michala Art Shop",
|
||||
card_text: "This case study was for a small business selling artwork",
|
||||
button_text: "click me",
|
||||
src: pictureOne.src
|
||||
},
|
||||
{
|
||||
heading: "Athlito",
|
||||
card_text: "This was an Ecommerce padel shop",
|
||||
button_text: "click me",
|
||||
src: pictureTwo.src
|
||||
},
|
||||
{
|
||||
heading: "Birchover Reclamation",
|
||||
card_text: "This case study was for a stone yard company",
|
||||
button_text: "click me",
|
||||
src: pictureThree.src
|
||||
},
|
||||
{
|
||||
heading: "Michala Art Shop",
|
||||
card_text: "This case study was for a small business selling artwork",
|
||||
button_text: "click me",
|
||||
src: pictureOne.src
|
||||
}
|
||||
]
|
||||
|
||||
const picWithText = {
|
||||
title: "Building a better web, one pixel at a time",
|
||||
text: "At Sculpted Arc, we are passionate about crafting exceptional web solutions that empower businesses to thrive in the digital landscape. With a team of skilled developers and designers, we specialize in creating stunning websites that not only captivate audiences but also drive results. Whether you're a startup looking to establish your online presence or an established company seeking to revamp your website, we have the expertise to bring your vision to life. Our commitment to quality, innovation, and customer satisfaction sets us apart as a trusted partner in your web development journey.",
|
||||
src: logo.src,
|
||||
reverse: false
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col flex-1 items-center justify-center bg-zinc-50 font-sans dark:bg-black">
|
||||
<main className="flex flex-1 w-full max-w-7xl flex-col items-center justify-between py-32 px-8 bg-white dark:bg-black sm:items-start">
|
||||
<div className="flex min-h-screen flex-col bg-zinc-50 font-sans dark:bg-black">
|
||||
<main className="mx-auto flex w-full max-w-7xl flex-1 flex-col items-center justify-between bg-white px-8 py-32 dark:bg-black sm:items-start">
|
||||
|
||||
<HeroSection />
|
||||
<div className="flex flex-row flex-wrap justify-center m-auto">
|
||||
<ContentCard heading="Michala Art Shop" card_text="This case study was for a small business selling artwork" button_text="click me" src={pictureOne.src}/>
|
||||
<ContentCard heading="Athlito" card_text="This was an Ecommerce padel shop" button_text="click me" src={pictureTwo.src}/>
|
||||
<ContentCard heading="Birchover Reclamation" card_text="This case study was for a stone yard company" button_text="click me" src={pictureThree.src}/>
|
||||
<ContentCard heading="Michala Art Shop" card_text="This case study was for a small business selling artwork" button_text="click me" src={pictureOne.src}/>
|
||||
<PicWithText {...picWithText} />
|
||||
|
||||
<div className="w-full m-auto">
|
||||
<ContentCardCarousel cards={contentCards} />
|
||||
</div>
|
||||
<PicWithText />
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
|
||||
BIN
assets/julientromeur-cartoon-477.gif
Normal file
|
After Width: | Height: | Size: 5.7 MiB |
BIN
assets/kuzu420-elder-26145.gif
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
assets/milaoktasafitri-astronomy-9746537_1920.png
Normal file
|
After Width: | Height: | Size: 124 KiB |
BIN
assets/mohamed_hassan-hands-8731277_1920.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
assets/mohamed_hassan-hiking-10223379_1920.png
Normal file
|
After Width: | Height: | Size: 162 KiB |
BIN
assets/mxj-files-planet-27066.gif
Normal file
|
After Width: | Height: | Size: 3.4 MiB |
BIN
assets/openclipart-vectors-shooting-star-2024127_1920.png
Normal file
|
After Width: | Height: | Size: 132 KiB |
BIN
assets/satheeshsankaran-rocket-158.gif
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
assets/saws.png
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
assets/scws-logo-no-bg.png
Normal file
|
After Width: | Height: | Size: 403 KiB |
BIN
assets/slimane-kadi-moon-9824889_1920.png
Normal file
|
After Width: | Height: | Size: 229 KiB |
BIN
assets/stux-watercolor-4116932_1920.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
assets/u_mey4kjj5ww-angry-2498.gif
Normal file
|
After Width: | Height: | Size: 90 KiB |
@@ -1,252 +0,0 @@
|
||||
/* app/Menubar.css */
|
||||
|
||||
.menubar {
|
||||
position: sticky;
|
||||
top: 20px;
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-self: center;
|
||||
width: calc(100% - 40px);
|
||||
min-width: 720px;
|
||||
max-width: 1000px;
|
||||
margin: 20px auto 0;
|
||||
border-radius: 30px;
|
||||
background-color: rgba(29, 29, 29, 0.9);
|
||||
backdrop-filter: blur(10px);
|
||||
padding: 10px 20px;
|
||||
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.2);
|
||||
border: 1px solid rgba(60, 60, 60, 0.3);
|
||||
isolation: isolate;
|
||||
transition: padding 0.35s ease;
|
||||
}
|
||||
|
||||
.menubar::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: -5px;
|
||||
background: linear-gradient(45deg, #ff00ff, #00ffff);
|
||||
filter: blur(25px);
|
||||
border-radius: 20px;
|
||||
z-index: 0;
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.menubar-content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
max-width: 960px;
|
||||
transition: flex-direction 0.35s ease;
|
||||
}
|
||||
|
||||
.desktop-menu {
|
||||
display: flex;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
color: #e0e0e0;
|
||||
text-decoration: none;
|
||||
font-size: 1.1em;
|
||||
padding: 5px 10px;
|
||||
transition: color 0.3s ease, transform 0.3s ease;
|
||||
}
|
||||
|
||||
.menu-item:hover {
|
||||
color: #ffffff;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.mobile-menu {
|
||||
display: none;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mobile-menu-checkbox {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.burger-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: none;
|
||||
border: none;
|
||||
color: #e0e0e0;
|
||||
font-size: 2em;
|
||||
line-height: 1;
|
||||
padding: 5px 10px;
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
touch-action: manipulation;
|
||||
width: 1.4em;
|
||||
height: 1.4em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.burger-icon-menu,
|
||||
.burger-icon-close {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.25s ease, transform 0.25s ease;
|
||||
}
|
||||
|
||||
.burger-icon-menu {
|
||||
opacity: 1;
|
||||
transform: scale(1) rotate(0deg);
|
||||
}
|
||||
|
||||
.burger-icon-close {
|
||||
opacity: 0;
|
||||
transform: scale(0.8) rotate(-90deg);
|
||||
}
|
||||
|
||||
.mobile-menu-checkbox:checked + .burger-icon .burger-icon-menu {
|
||||
opacity: 0;
|
||||
transform: scale(0.8) rotate(90deg);
|
||||
}
|
||||
|
||||
.mobile-menu-checkbox:checked + .burger-icon .burger-icon-close {
|
||||
opacity: 1;
|
||||
transform: scale(1) rotate(0deg);
|
||||
}
|
||||
|
||||
.mobile-dropdown {
|
||||
display: grid;
|
||||
grid-template-rows: 0fr;
|
||||
width: 100%;
|
||||
opacity: 0;
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
border-top: 1px solid transparent;
|
||||
pointer-events: none;
|
||||
transition:
|
||||
grid-template-rows 0.35s ease,
|
||||
opacity 0.35s ease,
|
||||
margin-top 0.35s ease,
|
||||
padding-top 0.35s ease,
|
||||
border-color 0.35s ease;
|
||||
}
|
||||
|
||||
.mobile-menu-checkbox:checked ~ .mobile-dropdown {
|
||||
grid-template-rows: 1fr;
|
||||
opacity: 1;
|
||||
margin-top: 8px;
|
||||
padding-top: 12px;
|
||||
border-top-color: rgba(60, 60, 60, 0.3);
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.mobile-dropdown-inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.mobile-dropdown-item {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 12px 10px;
|
||||
border-radius: 16px;
|
||||
text-align: left;
|
||||
opacity: 0;
|
||||
transform: translateY(-6px);
|
||||
transition:
|
||||
opacity 0.3s ease,
|
||||
transform 0.3s ease,
|
||||
background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.mobile-menu-checkbox:checked ~ .mobile-dropdown .mobile-dropdown-item {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.mobile-dropdown-item:hover {
|
||||
background-color: rgba(60, 60, 60, 0.4);
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 750px) {
|
||||
.menubar {
|
||||
min-width: 0;
|
||||
width: calc(100% - 24px);
|
||||
margin-top: 16px;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.desktop-menu {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobile-menu {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.menubar-content {
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.menubar:has(.mobile-menu-checkbox:checked) .menubar-content {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.mobile-menu-checkbox:checked ~ .mobile-dropdown .mobile-dropdown-item:nth-child(1) {
|
||||
transition-delay: 0.05s;
|
||||
}
|
||||
|
||||
.mobile-menu-checkbox:checked ~ .mobile-dropdown .mobile-dropdown-item:nth-child(2) {
|
||||
transition-delay: 0.1s;
|
||||
}
|
||||
|
||||
.mobile-menu-checkbox:checked ~ .mobile-dropdown .mobile-dropdown-item:nth-child(3) {
|
||||
transition-delay: 0.15s;
|
||||
}
|
||||
|
||||
.mobile-menu-checkbox:checked ~ .mobile-dropdown .mobile-dropdown-item:nth-child(4) {
|
||||
transition-delay: 0.2s;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.menubar,
|
||||
.menubar-content,
|
||||
.burger-icon-menu,
|
||||
.burger-icon-close,
|
||||
.mobile-dropdown,
|
||||
.mobile-dropdown-item {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.mobile-menu-checkbox:checked + .burger-icon .burger-icon-menu,
|
||||
.burger-icon-close {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.mobile-dropdown-item {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
167
components/contact-form.css
Normal file
@@ -0,0 +1,167 @@
|
||||
.contact-form-box {
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
width: 100%;
|
||||
max-width: 36rem;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
border-radius: 1.5rem;
|
||||
background-color: rgba(255, 255, 255, 0.95);
|
||||
box-shadow:
|
||||
0 4px 24px rgba(0, 0, 0, 0.08),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.contact-form-box::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
padding: 2px;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
rgba(56, 189, 248, 0.45),
|
||||
rgba(109, 40, 217, 0.35),
|
||||
rgba(60, 60, 60, 0.15)
|
||||
);
|
||||
-webkit-mask:
|
||||
linear-gradient(#fff 0 0) content-box,
|
||||
linear-gradient(#fff 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
mask:
|
||||
linear-gradient(#fff 0 0) content-box,
|
||||
linear-gradient(#fff 0 0);
|
||||
mask-composite: exclude;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.contact-form-inner {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.25rem;
|
||||
}
|
||||
|
||||
.contact-form-title {
|
||||
margin: 0 0 0.25rem;
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(90deg, #38bdf8, #6d28d9);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.contact-form-description {
|
||||
margin: 0 0 0.5rem;
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.5;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.contact-form-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
|
||||
.contact-form-label {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.contact-form-label-optional {
|
||||
font-weight: 400;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.contact-form-input,
|
||||
.contact-form-textarea {
|
||||
width: 100%;
|
||||
border-radius: 0.75rem;
|
||||
border: 1px solid rgba(60, 60, 60, 0.25);
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
padding: 0.75rem 1rem;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
color: #171717;
|
||||
transition:
|
||||
border-color 0.2s ease,
|
||||
box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.contact-form-input::placeholder,
|
||||
.contact-form-textarea::placeholder {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.contact-form-input:focus,
|
||||
.contact-form-textarea:focus {
|
||||
outline: none;
|
||||
border-color: #38bdf8;
|
||||
box-shadow: 0 0 0 3px rgba(56, 189, 248, 0.2);
|
||||
}
|
||||
|
||||
.contact-form-textarea {
|
||||
min-height: 10rem;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.contact-form-submit {
|
||||
align-self: flex-start;
|
||||
margin-top: 0.5rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.625rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 9999px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
background: linear-gradient(90deg, #38bdf8, #6d28d9);
|
||||
box-shadow: 0 2px 12px rgba(56, 189, 248, 0.25);
|
||||
cursor: pointer;
|
||||
transition:
|
||||
transform 0.25s ease,
|
||||
box-shadow 0.25s ease,
|
||||
filter 0.25s ease;
|
||||
}
|
||||
|
||||
.contact-form-submit:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 20px rgba(109, 40, 217, 0.35);
|
||||
filter: brightness(1.08);
|
||||
}
|
||||
|
||||
.contact-form-submit:focus-visible {
|
||||
outline: 2px solid #38bdf8;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.contact-form-box {
|
||||
background-color: rgba(24, 24, 27, 0.95);
|
||||
box-shadow:
|
||||
0 4px 24px rgba(0, 0, 0, 0.35),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
.contact-form-description {
|
||||
color: #a1a1aa;
|
||||
}
|
||||
|
||||
.contact-form-label {
|
||||
color: #e4e4e7;
|
||||
}
|
||||
|
||||
.contact-form-input,
|
||||
.contact-form-textarea {
|
||||
border-color: rgba(255, 255, 255, 0.15);
|
||||
background-color: rgba(0, 0, 0, 0.25);
|
||||
color: #fafafa;
|
||||
}
|
||||
}
|
||||
73
components/contact-form.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
'use client'
|
||||
|
||||
import { type FormEvent, useState } from 'react'
|
||||
import './contact-form.css'
|
||||
|
||||
export default function ContactForm() {
|
||||
const [submitted, setSubmitted] = useState(false)
|
||||
|
||||
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault()
|
||||
setSubmitted(true)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="contact-form-box">
|
||||
<form className="contact-form-inner" onSubmit={handleSubmit} noValidate>
|
||||
<div>
|
||||
<h2 className="contact-form-title">Get in touch</h2>
|
||||
<p className="contact-form-description">
|
||||
Send us a message and we'll get back to you as soon as we can.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="contact-form-field">
|
||||
<label htmlFor="contact-email" className="contact-form-label">
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
id="contact-email"
|
||||
name="email"
|
||||
type="email"
|
||||
required
|
||||
autoComplete="email"
|
||||
placeholder="you@example.com"
|
||||
className="contact-form-input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="contact-form-field">
|
||||
<label htmlFor="contact-phone" className="contact-form-label">
|
||||
Phone <span className="contact-form-label-optional">(optional)</span>
|
||||
</label>
|
||||
<input
|
||||
id="contact-phone"
|
||||
name="phone"
|
||||
type="tel"
|
||||
autoComplete="tel"
|
||||
placeholder="+44 7700 900000"
|
||||
className="contact-form-input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="contact-form-field">
|
||||
<label htmlFor="contact-message" className="contact-form-label">
|
||||
Message
|
||||
</label>
|
||||
<textarea
|
||||
id="contact-message"
|
||||
name="message"
|
||||
required
|
||||
rows={6}
|
||||
placeholder="Tell us about your project..."
|
||||
className="contact-form-textarea"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button type="submit" className="contact-form-submit">
|
||||
{submitted ? 'Message sent' : 'Send message'}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
62
components/content-card-carousel.css
Normal file
@@ -0,0 +1,62 @@
|
||||
/* Slide width is driven by CSS breakpoints (not JS innerWidth) so real phones match devtools */
|
||||
|
||||
.carousel-viewport {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.carousel-slide {
|
||||
flex: 0 0 100%;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.carousel-slide {
|
||||
flex: 0 0 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.carousel-slide {
|
||||
flex: 0 0 33.333%;
|
||||
}
|
||||
}
|
||||
|
||||
.carousel-controls {
|
||||
position: relative;
|
||||
z-index: 20;
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
.carousel-control-btn {
|
||||
display: inline-flex;
|
||||
min-height: 44px;
|
||||
min-width: 44px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
touch-action: manipulation;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
cursor: pointer;
|
||||
border-radius: 9999px;
|
||||
border: 1px solid var(--color-default-medium, rgba(60, 60, 60, 0.35));
|
||||
background-color: var(--color-neutral-secondary-medium, rgba(60, 60, 60, 0.25));
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 1.125rem;
|
||||
line-height: 1;
|
||||
color: inherit;
|
||||
transition: background-color 0.2s ease, color 0.2s ease;
|
||||
}
|
||||
|
||||
.carousel-control-btn:hover:not(:disabled) {
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.carousel-control-btn:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.carousel-control-btn:active:not(:disabled) {
|
||||
transform: scale(0.96);
|
||||
}
|
||||
126
components/content-card-carousel.tsx
Normal file
@@ -0,0 +1,126 @@
|
||||
'use client'
|
||||
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import ContentCard from './ui/content-cards'
|
||||
import './content-card-carousel.css'
|
||||
|
||||
interface ContentCardItem {
|
||||
src?: string
|
||||
button_link?: string
|
||||
heading?: string
|
||||
button_text?: string
|
||||
card_text?: string
|
||||
}
|
||||
|
||||
interface ContentCardCarouselProps {
|
||||
cards: ContentCardItem[]
|
||||
}
|
||||
|
||||
function getVisibleCardCount() {
|
||||
if (typeof window === 'undefined') return 1
|
||||
if (window.matchMedia('(min-width: 1024px)').matches) return 3
|
||||
if (window.matchMedia('(min-width: 640px)').matches) return 2
|
||||
return 1
|
||||
}
|
||||
|
||||
export default function ContentCardCarousel({ cards }: ContentCardCarouselProps) {
|
||||
const [visibleCards, setVisibleCards] = useState(1)
|
||||
const [currentIndex, setCurrentIndex] = useState(0)
|
||||
|
||||
const syncVisibleCards = useCallback(() => {
|
||||
setVisibleCards(getVisibleCardCount())
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
syncVisibleCards()
|
||||
|
||||
const mediaQueries = [
|
||||
window.matchMedia('(min-width: 640px)'),
|
||||
window.matchMedia('(min-width: 1024px)'),
|
||||
]
|
||||
|
||||
const onBreakpointChange = () => syncVisibleCards()
|
||||
|
||||
mediaQueries.forEach((mq) => mq.addEventListener('change', onBreakpointChange))
|
||||
window.addEventListener('resize', onBreakpointChange)
|
||||
|
||||
return () => {
|
||||
mediaQueries.forEach((mq) => mq.removeEventListener('change', onBreakpointChange))
|
||||
window.removeEventListener('resize', onBreakpointChange)
|
||||
}
|
||||
}, [syncVisibleCards])
|
||||
|
||||
const maxIndex = useMemo(() => Math.max(cards.length - visibleCards, 0), [cards.length, visibleCards])
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentIndex((previous) => Math.min(previous, maxIndex))
|
||||
}, [maxIndex])
|
||||
|
||||
// % of track width — matches flex slide sizes (100% / 50% / 33.333% per breakpoint)
|
||||
const trackOffsetPercent = cards.length > 0 ? (currentIndex * 100) / cards.length : 0
|
||||
|
||||
const next = () => {
|
||||
if (maxIndex === 0) return
|
||||
setCurrentIndex((previous) => (previous >= maxIndex ? 0 : previous + 1))
|
||||
}
|
||||
|
||||
const previous = () => {
|
||||
if (maxIndex === 0) return
|
||||
setCurrentIndex((index) => (index <= 0 ? maxIndex : index - 1))
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-full pb-30">
|
||||
<h1 className="mb-4 flex justify-center text-5xl font-bold">Our Case Studies</h1>
|
||||
|
||||
<div className="carousel-viewport relative overflow-hidden">
|
||||
<div
|
||||
className="carousel-track flex items-stretch transition-transform duration-500 ease-in-out"
|
||||
style={{ transform: `translate3d(-${trackOffsetPercent}%, 0, 0)` }}
|
||||
>
|
||||
{cards.map((card, index) => {
|
||||
const isEdgeCard =
|
||||
visibleCards > 1 && (index === currentIndex || index === currentIndex + visibleCards - 1)
|
||||
|
||||
return (
|
||||
<div
|
||||
key={`${card.heading ?? 'card'}-${index}`}
|
||||
className={`carousel-slide flex shrink-0 justify-center px-2 py-3 transition-opacity duration-500 ${
|
||||
isEdgeCard ? 'opacity-75' : 'opacity-100'
|
||||
}`}
|
||||
>
|
||||
<div className="flex h-full w-full max-w-sm">
|
||||
<ContentCard {...card} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="pointer-events-none absolute inset-y-0 left-0 w-12 bg-gradient-to-r from-white to-transparent dark:from-black" />
|
||||
<div className="pointer-events-none absolute inset-y-0 right-0 w-12 bg-gradient-to-l from-white to-transparent dark:from-black" />
|
||||
</div>
|
||||
|
||||
<div className="carousel-controls relative z-20 mt-6 flex justify-center gap-4">
|
||||
<button
|
||||
type="button"
|
||||
onClick={previous}
|
||||
disabled={maxIndex === 0}
|
||||
className="carousel-control-btn"
|
||||
aria-label="Show previous content cards"
|
||||
>
|
||||
←
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={next}
|
||||
disabled={maxIndex === 0}
|
||||
className="carousel-control-btn"
|
||||
aria-label="Show next content cards"
|
||||
>
|
||||
→
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
22
components/footer.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import saws from "../assets/saws.png"
|
||||
import SocialMediaLinks from "./social-media-links"
|
||||
|
||||
export default function Footer() {
|
||||
return (
|
||||
<footer className="w-full border-t border-neutral-300 bg-white dark:border-neutral-800 dark:bg-black">
|
||||
<div className="mx-auto flex w-full max-w-7xl flex-col items-center justify-between gap-4 px-8 py-6 text-sm text-body md:flex-row">
|
||||
<div className="flex items-center gap-3">
|
||||
<img src={saws.src} alt="Sculpted Arc logo" className="h-12 w-auto" />
|
||||
<p className="text-center md:text-left">copyright 2026 sculpted arc</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-5">
|
||||
<a href="/privacy-policy" className="hover:text-heading transition-colors">
|
||||
Privacy Policy
|
||||
</a>
|
||||
<SocialMediaLinks linkClassName="hover:text-heading transition-colors" />
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
.hero-main {
|
||||
width: 100%;
|
||||
min-height: 400px;
|
||||
padding-bottom: 200px;
|
||||
}
|
||||
|
||||
.hero-picture-stack {
|
||||
@@ -38,19 +39,9 @@
|
||||
}
|
||||
|
||||
@media (max-width: 750px) {
|
||||
.hero-picture-stack {
|
||||
.herp-main {
|
||||
width: 100%;
|
||||
min-height: 260px;
|
||||
}
|
||||
|
||||
.hero-picture {
|
||||
width: 75%;
|
||||
max-width: 240px;
|
||||
}
|
||||
|
||||
.hero-right {
|
||||
width: 100%;
|
||||
padding-left: 0;
|
||||
padding-top: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
import ContentCard from "./ui/content-cards";
|
||||
import './hero-section.css'
|
||||
import heroPictureOne from "../assets/hero-picture-1.png"
|
||||
import heroPictureTwo from "../assets/hero-picture-2.png"
|
||||
import heroPictureThree from "../assets/hero-picture-3.png"
|
||||
import heroPictureOne from "../assets/mxj-files-planet-27066.gif"
|
||||
|
||||
export default function HeroSection() {
|
||||
return(
|
||||
<>
|
||||
<div className="hero-main flex flex-wrap">
|
||||
<div className="hero-picture-stack p-4">
|
||||
<img className="hero-picture" src={heroPictureOne.src} alt="" />
|
||||
<img className="hero-picture hero-picture--tilt-20" src={heroPictureTwo.src} alt="" />
|
||||
<img className="hero-picture hero-picture--tilt-40" src={heroPictureThree.src} alt="" />
|
||||
<div className="flex flex-wrap-reverse justify-items-center ">
|
||||
<div className="p-4 w-full md:w-1/2 shrink-0">
|
||||
<img className="w-full h-auto rounded-2xl object-cover" src={heroPictureOne.src} alt="" />
|
||||
</div>
|
||||
<div className="hero-right">
|
||||
<h1 className="mb-4 text-5xl font-bold text-heading md:text-6xl lg:text-7xl"><span className="text-transparent bg-clip-text bg-gradient-to-r bg-violet-800 from-sky-400">Sculpted Arc</span><br/> Web Solutions</h1>
|
||||
<div className="w-full md:w-1/2">
|
||||
<h1 className="mb-4 font-bold text-heading text-6xl sm:text-7xl"><span className="text-transparent bg-clip-text bg-gradient-to-r bg-violet-800 from-sky-400">Sculpted Arc</span><br/> Web Solutions</h1>
|
||||
|
||||
<hr className="w-1/4 mb-7 mt-8 border-gray-700 border-2 rounded-2xl"/>
|
||||
|
||||
<p className="text-lg font-normal text-body lg:text-xl">Here at Sculpted Arc we are invested in creating great website and solving complex web problems Here at Sculpted Arc we are invested in creating great website and solving complex web problems</p>
|
||||
<br/>
|
||||
<a href="#" className="transition duration-150 flex w-55 justify-start rounded-3xl text-body bg-neutral-secondary-medium box-border border border-default-medium hover:bg-blue-200 hover:text-black hover:text-heading focus:ring-4 focus:ring-neutral-tertiary shadow-xs font-medium leading-5 rounded-base text-sm px-4 py-2.5 focus:outline-none mt-auto">
|
||||
Tell us about your project
|
||||
<svg className="w-3 h-3 ms-1.5 rtl:rotate-180 -me-0.5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" viewBox="0 0 20 20"><path stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 12H5m14 0-4 4m4-4-4-4"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { useRef, type MouseEvent } from 'react';
|
||||
import './Menubar.css';
|
||||
|
||||
const menuLinks = [
|
||||
{ label: 'Home', href: '/' },
|
||||
{ label: 'About', href: '/about-us' },
|
||||
{ label: 'Plans', href: '/plans' },
|
||||
{ label: 'Contact', href: '#' },
|
||||
];
|
||||
|
||||
export default function Menubar() {
|
||||
const menuToggleRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const closeMobileMenu = () => {
|
||||
if (menuToggleRef.current) {
|
||||
menuToggleRef.current.checked = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleLinkClick = (event: MouseEvent<HTMLAnchorElement>) => {
|
||||
closeMobileMenu();
|
||||
event.currentTarget.blur();
|
||||
};
|
||||
|
||||
return (
|
||||
<nav className="menubar">
|
||||
<div className="menubar-content">
|
||||
<div className="desktop-menu">
|
||||
{menuLinks.map((link) => (
|
||||
<a key={link.label} href={link.href} className="menu-item">
|
||||
{link.label}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
<div className="mobile-menu">
|
||||
<input
|
||||
ref={menuToggleRef}
|
||||
type="checkbox"
|
||||
id="mobile-nav-toggle"
|
||||
className="mobile-menu-checkbox"
|
||||
/>
|
||||
<label
|
||||
htmlFor="mobile-nav-toggle"
|
||||
className="burger-icon"
|
||||
aria-controls="mobile-nav-menu"
|
||||
>
|
||||
<span className="burger-icon-menu" aria-hidden="true">
|
||||
☰
|
||||
</span>
|
||||
<span className="burger-icon-close" aria-hidden="true">
|
||||
×
|
||||
</span>
|
||||
</label>
|
||||
<div id="mobile-nav-menu" className="mobile-dropdown">
|
||||
<div className="mobile-dropdown-inner">
|
||||
{menuLinks.map((link) => (
|
||||
<a
|
||||
key={link.label}
|
||||
href={link.href}
|
||||
className="menu-item mobile-dropdown-item"
|
||||
onClick={handleLinkClick}
|
||||
>
|
||||
{link.label}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
@@ -1,18 +1,29 @@
|
||||
import picOne from "../assets/hero-picture-1.png";
|
||||
|
||||
export default function PicWithText(){
|
||||
|
||||
interface PicWithTextProps {
|
||||
src?: string
|
||||
title?: string
|
||||
text?: string
|
||||
reverse: boolean
|
||||
}
|
||||
|
||||
|
||||
export default function PicWithText(content: PicWithTextProps){
|
||||
let flex_state = content.reverse ? "md:flex-row flex-col" : "md:flex-row-reverse flex-col-reverse";
|
||||
return (
|
||||
<div className="flex flex-col md:flex-row items-center md:items-start gap-8 w-full mt-10">
|
||||
<div className={`flex ${flex_state} items-center md:items-start gap-8 w-full mt-10 pb-30`}>
|
||||
<div className="w-full md:w-1/2 shrink-0">
|
||||
<img
|
||||
className="w-full h-auto rounded-2xl object-cover"
|
||||
src={picOne.src}
|
||||
src={content.src}
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full md:w-1/2">
|
||||
<h1 className="mb-4 text-5xl font-bold">Title of this section</h1>
|
||||
<p className="text-lg font-normal text-body lg:text-xl">This is the paragraph for this section. This is also just random filler text to see how things look. here are some extra words htat make no sence!</p>
|
||||
<h1 className="mb-4 text-5xl font-bold">{content.title}</h1>
|
||||
<p className="text-lg font-normal text-body lg:text-xl">{content.text}</p>
|
||||
|
||||
<div className="flex items-center w-full my-8">
|
||||
<div className="flex-1 h-px bg-neutral-300 dark:bg-neutral-700" />
|
||||
<div className="px-3">
|
||||
@@ -20,7 +31,7 @@ export default function PicWithText(){
|
||||
</div>
|
||||
<div className="flex-1 h-px bg-neutral-300 dark:bg-neutral-700" />
|
||||
</div>
|
||||
<p>This is more text, however, this is actually a quote, yes, a quote indeed!</p>
|
||||
<p className="text-gray-300 italic">This is more text, however, this is actually a quote, yes, a quote indeed!</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
463
components/site-header.css
Normal file
@@ -0,0 +1,463 @@
|
||||
.site-header-wrapper {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
padding: 16px 20px 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.site-header {
|
||||
position: relative;
|
||||
pointer-events: auto;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: calc(100% - 8px);
|
||||
min-width: 0;
|
||||
max-width: 1120px;
|
||||
margin: 0 auto;
|
||||
border-radius: 30px;
|
||||
padding: 12px 20px;
|
||||
background-color: rgba(0, 0, 0, 0.88);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
box-shadow:
|
||||
0 4px 30px rgba(0, 0, 0, 0.2),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.06);
|
||||
isolation: isolate;
|
||||
transition:
|
||||
padding 0.35s ease,
|
||||
border-radius 0.35s ease,
|
||||
box-shadow 0.35s ease,
|
||||
backdrop-filter 0.35s ease,
|
||||
background-color 0.35s ease;
|
||||
}
|
||||
|
||||
.site-header::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: -5px;
|
||||
background: black;
|
||||
filter: blur(25px);
|
||||
border-radius: 20px;
|
||||
z-index: -1;
|
||||
opacity: 0.12;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.site-header::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
padding: 2px;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
rgba(56, 189, 248, 0.5),
|
||||
rgba(91, 33, 182, 0.4),
|
||||
rgba(114, 8, 156, 0.5)
|
||||
);
|
||||
-webkit-mask:
|
||||
linear-gradient(#fff 0 0) content-box,
|
||||
linear-gradient(#fff 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
mask:
|
||||
linear-gradient(#fff 0 0) content-box,
|
||||
linear-gradient(#fff 0 0);
|
||||
mask-composite: exclude;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.site-header--scrolled {
|
||||
padding: 8px 16px;
|
||||
border-radius: 24px;
|
||||
background-color: rgba(20, 18, 18, 0.92);
|
||||
backdrop-filter: blur(18px);
|
||||
-webkit-backdrop-filter: blur(18px);
|
||||
box-shadow:
|
||||
0 8px 32px rgba(0, 0, 0, 0.28),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.site-header--scrolled::before {
|
||||
opacity: 0.18;
|
||||
}
|
||||
|
||||
.site-header-content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.site-header-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-shrink: 0;
|
||||
text-decoration: none;
|
||||
color: #e0e0e0;
|
||||
transition: opacity 0.25s ease;
|
||||
}
|
||||
|
||||
.site-header-logo:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.site-header-logo-img {
|
||||
height: 36px;
|
||||
width: auto;
|
||||
transition: height 0.35s ease;
|
||||
}
|
||||
|
||||
.site-header--scrolled .site-header-logo-img {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.site-header-logo-text {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.02em;
|
||||
background: linear-gradient(90deg, #38bdf8, #6d28d9);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.site-header-nav {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
color: #c8c8c8;
|
||||
text-decoration: none;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
padding: 8px 14px;
|
||||
border-radius: 9999px;
|
||||
/* Only animate transform — color/background transitions flash when active class toggles */
|
||||
transition: transform 0.25s ease;
|
||||
}
|
||||
|
||||
.menu-item:hover {
|
||||
color: #e0e0e0;
|
||||
background-color: rgba(66, 116, 255, 0.12);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.menu-item.menu-item--active,
|
||||
.menu-item.menu-item--active:hover {
|
||||
background-color: rgba(56, 189, 248, 0.12);
|
||||
background-image: linear-gradient(90deg, #38bdf8, #a78bfa);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
color: transparent;
|
||||
transform: none;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.site-header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.site-header-social {
|
||||
color: #c8c8c8;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.site-header-social-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 6px;
|
||||
border-radius: 9999px;
|
||||
transition:
|
||||
color 0.25s ease,
|
||||
background-color 0.25s ease,
|
||||
transform 0.25s ease;
|
||||
}
|
||||
|
||||
.site-header-social-link:hover {
|
||||
color: #38bdf8;
|
||||
background-color: rgba(56, 189, 248, 0.12);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.site-header-cta {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
padding: 8px 18px;
|
||||
border-radius: 9999px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
color: #fff;
|
||||
background: linear-gradient(90deg, #38bdf8, #6d28d9);
|
||||
box-shadow: 0 2px 12px rgba(56, 189, 248, 0.25);
|
||||
transition:
|
||||
transform 0.25s ease,
|
||||
box-shadow 0.25s ease,
|
||||
filter 0.25s ease;
|
||||
}
|
||||
|
||||
.site-header-cta:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 10px 20px rgba(109, 40, 217, 0.35);
|
||||
filter: brightness(1.08);
|
||||
}
|
||||
|
||||
.site-header-cta:focus-visible {
|
||||
outline: 2px solid #38bdf8;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.site-header-cta--mobile {
|
||||
margin-top: 8px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.site-header-social--mobile {
|
||||
margin-top: 12px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid rgba(60, 60, 60, 0.35);
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.mobile-menu {
|
||||
display: none;
|
||||
margin-left: auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mobile-menu-checkbox {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.burger-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: none;
|
||||
color: #e0e0e0;
|
||||
font-size: 2em;
|
||||
line-height: 1;
|
||||
padding: 5px 10px;
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
touch-action: manipulation;
|
||||
width: 1.4em;
|
||||
height: 1.4em;
|
||||
cursor: pointer;
|
||||
border-radius: 12px;
|
||||
transition: background-color 0.25s ease;
|
||||
}
|
||||
|
||||
.burger-icon:hover {
|
||||
background-color: rgba(66, 116, 255, 0.12);
|
||||
}
|
||||
|
||||
.burger-icon-menu,
|
||||
.burger-icon-close {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.25s ease, transform 0.25s ease;
|
||||
}
|
||||
|
||||
.burger-icon-menu {
|
||||
opacity: 1;
|
||||
transform: scale(1) rotate(0deg);
|
||||
}
|
||||
|
||||
.burger-icon-close {
|
||||
opacity: 0;
|
||||
transform: scale(0.8) rotate(-90deg);
|
||||
}
|
||||
|
||||
.mobile-menu-checkbox:checked + .burger-icon .burger-icon-menu {
|
||||
opacity: 0;
|
||||
transform: scale(0.8) rotate(90deg);
|
||||
}
|
||||
|
||||
.mobile-menu-checkbox:checked + .burger-icon .burger-icon-close {
|
||||
opacity: 1;
|
||||
transform: scale(1) rotate(0deg);
|
||||
}
|
||||
|
||||
.mobile-dropdown {
|
||||
display: grid;
|
||||
grid-template-rows: 0fr;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
opacity: 0;
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
border-top: 1px solid transparent;
|
||||
pointer-events: none;
|
||||
transition:
|
||||
grid-template-rows 0.35s ease,
|
||||
opacity 0.35s ease,
|
||||
margin-top 0.35s ease,
|
||||
padding-top 0.35s ease,
|
||||
border-color 0.35s ease;
|
||||
}
|
||||
|
||||
.mobile-menu-checkbox:checked ~ .mobile-dropdown {
|
||||
grid-template-rows: 1fr;
|
||||
opacity: 1;
|
||||
margin-top: 8px;
|
||||
padding-top: 12px;
|
||||
border-top-color: rgba(60, 60, 60, 0.3);
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.mobile-dropdown-inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.mobile-dropdown-item {
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
opacity: 0;
|
||||
transform: translateY(-6px);
|
||||
transition:
|
||||
opacity 0.3s ease,
|
||||
transform 0.3s ease,
|
||||
background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.mobile-menu-checkbox:checked ~ .mobile-dropdown .mobile-dropdown-item,
|
||||
.mobile-menu-checkbox:checked ~ .mobile-dropdown .site-header-cta--mobile,
|
||||
.mobile-menu-checkbox:checked ~ .mobile-dropdown .site-header-social--mobile {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.mobile-dropdown-item:hover {
|
||||
background-color: rgba(60, 60, 60, 0.4);
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.site-header-logo-text {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
.site-header-wrapper {
|
||||
padding: 12px 12px 0;
|
||||
}
|
||||
|
||||
.site-header {
|
||||
width: 100%;
|
||||
padding: 10px 14px;
|
||||
}
|
||||
|
||||
.site-header--scrolled {
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.desktop-menu,
|
||||
.desktop-actions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobile-menu {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.site-header-content {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.site-header:has(.mobile-menu-checkbox:checked) .site-header-content {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.site-header:has(.mobile-menu-checkbox:checked) .mobile-menu {
|
||||
width: 100%;
|
||||
order: 10;
|
||||
}
|
||||
|
||||
.mobile-dropdown {
|
||||
position: static;
|
||||
}
|
||||
|
||||
.mobile-menu-checkbox:checked ~ .mobile-dropdown .mobile-dropdown-item:nth-child(1) {
|
||||
transition-delay: 0.05s;
|
||||
}
|
||||
|
||||
.mobile-menu-checkbox:checked ~ .mobile-dropdown .mobile-dropdown-item:nth-child(2) {
|
||||
transition-delay: 0.1s;
|
||||
}
|
||||
|
||||
.mobile-menu-checkbox:checked ~ .mobile-dropdown .mobile-dropdown-item:nth-child(3) {
|
||||
transition-delay: 0.15s;
|
||||
}
|
||||
|
||||
.mobile-menu-checkbox:checked ~ .mobile-dropdown .mobile-dropdown-item:nth-child(4) {
|
||||
transition-delay: 0.2s;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.site-header,
|
||||
.site-header-logo-img,
|
||||
.burger-icon-menu,
|
||||
.burger-icon-close,
|
||||
.mobile-dropdown,
|
||||
.mobile-dropdown-item,
|
||||
.menu-item,
|
||||
.site-header-cta,
|
||||
.site-header-social-link {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.mobile-menu-checkbox:checked + .burger-icon .burger-icon-menu,
|
||||
.burger-icon-close {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.mobile-dropdown-item,
|
||||
.site-header-cta--mobile,
|
||||
.site-header-social--mobile {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
147
components/site-header.tsx
Normal file
@@ -0,0 +1,147 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useRef, useState, type MouseEvent } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import saws from '../assets/saws.png'
|
||||
import SocialMediaLinks from './social-media-links'
|
||||
import './site-header.css'
|
||||
|
||||
const menuLinks = [
|
||||
{ label: 'Home', href: '/' },
|
||||
{ label: 'About', href: '/about-us' },
|
||||
{ label: 'Plans', href: '/plans' },
|
||||
{ label: 'Contact', href: '/contact' },
|
||||
] as const
|
||||
|
||||
const ctaHref = '/contact'
|
||||
|
||||
function isActivePath(pathname: string, href: string) {
|
||||
if (href === '/') return pathname === '/'
|
||||
return pathname === href || pathname.startsWith(`${href}/`)
|
||||
}
|
||||
|
||||
export default function SiteHeader() {
|
||||
const pathname = usePathname()
|
||||
const menuToggleRef = useRef<HTMLInputElement>(null)
|
||||
const [scrolled, setScrolled] = useState(false)
|
||||
const [pendingPath, setPendingPath] = useState<string | null>(null)
|
||||
|
||||
const activePath = pendingPath ?? pathname
|
||||
|
||||
useEffect(() => {
|
||||
setPendingPath(null)
|
||||
}, [pathname])
|
||||
|
||||
useEffect(() => {
|
||||
const onScroll = () => setScrolled(window.scrollY > 24)
|
||||
onScroll()
|
||||
window.addEventListener('scroll', onScroll, { passive: true })
|
||||
return () => window.removeEventListener('scroll', onScroll)
|
||||
}, [])
|
||||
|
||||
const closeMobileMenu = () => {
|
||||
if (menuToggleRef.current) {
|
||||
menuToggleRef.current.checked = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleNavClick =
|
||||
(href: string) => (event: MouseEvent<HTMLAnchorElement>) => {
|
||||
setPendingPath(href)
|
||||
closeMobileMenu()
|
||||
event.currentTarget.blur()
|
||||
}
|
||||
|
||||
return (
|
||||
<header className="site-header-wrapper">
|
||||
<nav
|
||||
className={`site-header${scrolled ? ' site-header--scrolled' : ''}`}
|
||||
aria-label="Main navigation"
|
||||
>
|
||||
<div className="site-header-content">
|
||||
<Link href="/" className="site-header-logo" onClick={handleNavClick('/')}>
|
||||
<img src={saws.src} alt="Sculpted Arc" className="site-header-logo-img" />
|
||||
</Link>
|
||||
|
||||
<div className="site-header-nav desktop-menu">
|
||||
{menuLinks.map((link) => {
|
||||
const active = isActivePath(activePath, link.href)
|
||||
return (
|
||||
<Link
|
||||
key={link.label}
|
||||
href={link.href}
|
||||
className={`menu-item${active ? ' menu-item--active' : ''}`}
|
||||
aria-current={active ? 'page' : undefined}
|
||||
onClick={handleNavClick(link.href)}
|
||||
>
|
||||
{link.label}
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="site-header-actions desktop-actions">
|
||||
<SocialMediaLinks
|
||||
className="site-header-social"
|
||||
linkClassName="site-header-social-link"
|
||||
/>
|
||||
<Link href={ctaHref} className="site-header-cta">
|
||||
Get a quote
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="mobile-menu">
|
||||
<input
|
||||
ref={menuToggleRef}
|
||||
type="checkbox"
|
||||
id="mobile-nav-toggle"
|
||||
className="mobile-menu-checkbox"
|
||||
/>
|
||||
<label
|
||||
htmlFor="mobile-nav-toggle"
|
||||
className="burger-icon"
|
||||
aria-controls="mobile-nav-menu"
|
||||
>
|
||||
<span className="burger-icon-menu" aria-hidden="true">
|
||||
☰
|
||||
</span>
|
||||
<span className="burger-icon-close" aria-hidden="true">
|
||||
×
|
||||
</span>
|
||||
</label>
|
||||
<div id="mobile-nav-menu" className="mobile-dropdown">
|
||||
<div className="mobile-dropdown-inner">
|
||||
{menuLinks.map((link) => {
|
||||
const active = isActivePath(activePath, link.href)
|
||||
return (
|
||||
<Link
|
||||
key={link.label}
|
||||
href={link.href}
|
||||
className={`menu-item mobile-dropdown-item${active ? ' menu-item--active' : ''}`}
|
||||
aria-current={active ? 'page' : undefined}
|
||||
onClick={handleNavClick(link.href)}
|
||||
>
|
||||
{link.label}
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
<Link
|
||||
href={ctaHref}
|
||||
className="site-header-cta site-header-cta--mobile"
|
||||
onClick={handleNavClick(ctaHref)}
|
||||
>
|
||||
Get a quote
|
||||
</Link>
|
||||
<SocialMediaLinks
|
||||
className="site-header-social site-header-social--mobile"
|
||||
linkClassName="site-header-social-link"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
54
components/social-media-links.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
const socialLinks = [
|
||||
{
|
||||
href: 'https://www.instagram.com',
|
||||
label: 'Instagram',
|
||||
icon: (
|
||||
<path d="M7.75 2h8.5A5.75 5.75 0 0 1 22 7.75v8.5A5.75 5.75 0 0 1 16.25 22h-8.5A5.75 5.75 0 0 1 2 16.25v-8.5A5.75 5.75 0 0 1 7.75 2Zm0 1.5A4.25 4.25 0 0 0 3.5 7.75v8.5A4.25 4.25 0 0 0 7.75 20.5h8.5a4.25 4.25 0 0 0 4.25-4.25v-8.5a4.25 4.25 0 0 0-4.25-4.25h-8.5Zm9.5 1.75a1.25 1.25 0 1 1 0 2.5 1.25 1.25 0 0 1 0-2.5ZM12 7a5 5 0 1 1 0 10 5 5 0 0 1 0-10Zm0 1.5a3.5 3.5 0 1 0 0 7 3.5 3.5 0 0 0 0-7Z" />
|
||||
),
|
||||
},
|
||||
{
|
||||
href: 'https://www.linkedin.com',
|
||||
label: 'LinkedIn',
|
||||
icon: (
|
||||
<path d="M6.94 8.5A1.56 1.56 0 1 1 6.94 5.4a1.56 1.56 0 0 1 0 3.1ZM5.5 10h2.88v8.5H5.5V10Zm4.63 0H12.9v1.16h.04c.39-.74 1.35-1.52 2.79-1.52 2.99 0 3.54 1.97 3.54 4.53v4.33h-2.88V14.66c0-.91-.02-2.07-1.26-2.07-1.27 0-1.46.99-1.46 2.01v3.9h-2.88V10Z" />
|
||||
),
|
||||
},
|
||||
{
|
||||
href: 'https://x.com',
|
||||
label: 'X',
|
||||
icon: (
|
||||
<path d="M18.9 2H22l-6.77 7.74L23 22h-6.1l-4.78-6.26L6.63 22H3.5l7.25-8.28L1 2h6.26l4.32 5.7L18.9 2Zm-1.07 18h1.69L6.34 3.9H4.53L17.83 20Z" />
|
||||
),
|
||||
},
|
||||
] as const
|
||||
|
||||
interface SocialMediaLinksProps {
|
||||
className?: string
|
||||
iconClassName?: string
|
||||
linkClassName?: string
|
||||
}
|
||||
|
||||
export default function SocialMediaLinks({
|
||||
className = '',
|
||||
iconClassName = 'h-5 w-5',
|
||||
linkClassName = 'transition-colors hover:text-[#4274ff]',
|
||||
}: SocialMediaLinksProps) {
|
||||
return (
|
||||
<div className={`flex items-center gap-4 ${className}`.trim()}>
|
||||
{socialLinks.map((link) => (
|
||||
<a
|
||||
key={link.label}
|
||||
href={link.href}
|
||||
className={linkClassName}
|
||||
aria-label={link.label}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<svg className={iconClassName} viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
{link.icon}
|
||||
</svg>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
137
components/ui/content-cards.css
Normal file
@@ -0,0 +1,137 @@
|
||||
.content-card {
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
border-radius: 1.5rem;
|
||||
background-color: rgba(255, 255, 255, 0.95);
|
||||
box-shadow:
|
||||
0 4px 24px rgba(0, 0, 0, 0.08),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.8);
|
||||
transition:
|
||||
transform 0.25s ease,
|
||||
box-shadow 0.25s ease;
|
||||
}
|
||||
|
||||
.content-card::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
padding: 2px;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
rgba(56, 189, 248, 0.45),
|
||||
rgba(109, 40, 217, 0.35),
|
||||
rgba(60, 60, 60, 0.15)
|
||||
);
|
||||
-webkit-mask:
|
||||
linear-gradient(#fff 0 0) content-box,
|
||||
linear-gradient(#fff 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
mask:
|
||||
linear-gradient(#fff 0 0) content-box,
|
||||
linear-gradient(#fff 0 0);
|
||||
mask-composite: exclude;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.25s ease;
|
||||
}
|
||||
|
||||
.content-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow:
|
||||
0 12px 32px rgba(56, 189, 248, 0.12),
|
||||
0 8px 24px rgba(109, 40, 217, 0.1),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.content-card:hover::after {
|
||||
opacity: 1;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
rgba(56, 189, 248, 0.65),
|
||||
rgba(109, 40, 217, 0.55),
|
||||
rgba(60, 60, 60, 0.2)
|
||||
);
|
||||
}
|
||||
|
||||
.content-card-body {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.content-card-image {
|
||||
overflow: hidden;
|
||||
border-radius: 1rem;
|
||||
background-color: rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.content-card-image img {
|
||||
transition: transform 0.35s ease;
|
||||
}
|
||||
|
||||
.content-card:hover .content-card-image img {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.content-card-cta {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
border-radius: 9999px;
|
||||
padding: 0.625rem 1rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.25rem;
|
||||
text-decoration: none;
|
||||
color: #fff;
|
||||
background: linear-gradient(90deg, #38bdf8, #6d28d9);
|
||||
box-shadow: 0 2px 12px rgba(56, 189, 248, 0.25);
|
||||
transition:
|
||||
transform 0.25s ease,
|
||||
box-shadow 0.25s ease,
|
||||
filter 0.25s ease;
|
||||
}
|
||||
|
||||
.content-card-cta:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 20px rgba(109, 40, 217, 0.35);
|
||||
filter: brightness(1.08);
|
||||
}
|
||||
|
||||
.content-card-cta:focus-visible {
|
||||
outline: 2px solid #38bdf8;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.content-card {
|
||||
background-color: rgba(24, 24, 27, 0.95);
|
||||
box-shadow:
|
||||
0 4px 24px rgba(0, 0, 0, 0.35),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
.content-card:hover {
|
||||
box-shadow:
|
||||
0 12px 32px rgba(56, 189, 248, 0.15),
|
||||
0 8px 24px rgba(109, 40, 217, 0.12),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.content-card-image {
|
||||
background-color: rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.content-card,
|
||||
.content-card::after,
|
||||
.content-card-image img,
|
||||
.content-card-cta {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.content-card:hover,
|
||||
.content-card:hover .content-card-image img,
|
||||
.content-card-cta:hover {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import './content-cards.css'
|
||||
|
||||
interface cardProps {
|
||||
src?: string
|
||||
@@ -9,20 +10,44 @@ interface cardProps {
|
||||
|
||||
export default function ContentCard(CardProps: cardProps) {
|
||||
return (
|
||||
<>
|
||||
<div className="bg-neutral-primary-soft shadow-lg shadow-blue-500/50 rounded-3xl block p-6 border-2 border-blue-500 rounded-base m-3 min-w-60 max-w-80">
|
||||
<a href="#">
|
||||
<img className="rounded-2xl" src={CardProps.src} alt="" />
|
||||
<article className="content-card group flex h-full w-full min-h-80 flex-col p-6">
|
||||
<div className="content-card-body flex h-full flex-col">
|
||||
<a href="#" className="block shrink-0">
|
||||
<div className="content-card-image aspect-video w-full">
|
||||
<img
|
||||
className="h-full w-full object-cover"
|
||||
src={CardProps.src}
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
<a href="#">
|
||||
<h5 className="mt-6 mb-2 text-2xl font-semibold tracking-tight text-heading">{CardProps.heading}</h5>
|
||||
<a href="#" className="mt-6 block shrink-0">
|
||||
<h5 className="mb-2 text-2xl font-semibold tracking-tight text-heading">
|
||||
{CardProps.heading}
|
||||
</h5>
|
||||
</a>
|
||||
<p className="mb-6 text-body">{CardProps.card_text}</p>
|
||||
<a href="#" className="inline-flex rounded-3xl items-center text-body bg-neutral-secondary-medium box-border border border-default-medium hover:bg-neutral-tertiary-medium hover:text-heading focus:ring-4 focus:ring-neutral-tertiary shadow-xs font-medium leading-5 rounded-base text-sm px-4 py-2.5 focus:outline-none">
|
||||
<p className="mb-6 line-clamp-3 flex-1 text-body">{CardProps.card_text}</p>
|
||||
<a href="#" className="content-card-cta mt-auto self-start">
|
||||
{CardProps.button_text}
|
||||
<svg className="w-3 h-3 ms-1.5 rtl:rotate-180 -me-0.5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" viewBox="0 0 20 20"><path stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 12H5m14 0-4 4m4-4-4-4"/></svg>
|
||||
<svg
|
||||
className="ms-1.5 -me-0.5 h-3 w-3 rtl:rotate-180"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
fill="none"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M19 12H5m14 0-4 4m4-4-4-4"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</>
|
||||
</article>
|
||||
)
|
||||
}
|
||||