Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 21 additions & 6 deletions App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -216,26 +216,41 @@ const App: React.FC = () => {
</motion.div>
);

const routeSkeleton = (
<div
className="flex min-h-[50vh] items-center justify-center bg-[#080809]"
role="status"
aria-label="Loading"
>
<div className="flex w-40 flex-col items-center gap-3">
<div className="h-px w-full overflow-hidden rounded-full bg-white/[0.06]">
<div className="h-full w-1/2 animate-pulse rounded-full bg-[#E50914]/70" />
</div>
<div className="h-1.5 w-1.5 animate-pulse rounded-full bg-white/25" />
</div>
</div>
);

return (
<div className="bg-zinc-950 text-white min-h-screen overflow-x-clip selection:bg-accent selection:text-white relative">
<PageLoader onComplete={() => setLoaded(true)} minDuration={900} />

<>
{route === 'docs' && (
<Suspense key="docs" fallback={null}>
<Suspense key="docs" fallback={routeSkeleton}>
<DocsPage onOpenContact={() => setContactOpen(true)} />
</Suspense>
)}

{route === 'faq' && pageShell(
<Suspense key="faq" fallback={null}>
<Suspense key="faq" fallback={routeSkeleton}>
<FAQPage onOpenContact={() => setContactOpen(true)} />
</Suspense>,
{ hideNav: true }
)}

{route === 'case-studies' && pageShell(
<Suspense key="case-studies" fallback={null}>
<Suspense key="case-studies" fallback={routeSkeleton}>
<CaseStudiesIndex />
</Suspense>,
{ hideNav: true }
Expand All @@ -253,14 +268,14 @@ const App: React.FC = () => {
)}

{route === 'cs-humble-beginnings' && pageShell(
<Suspense key="cs-humble-beginnings" fallback={null}>
<Suspense key="cs-humble-beginnings" fallback={routeSkeleton}>
<HumbleBeginnings />
</Suspense>,
{ hideNav: true }
)}

{servicePageKey && pageShell(
<Suspense key={route} fallback={null}>
<Suspense key={route} fallback={routeSkeleton}>
<ServicePage pageKey={servicePageKey} onOpenContact={() => setContactOpen(true)} />
</Suspense>,
{ hideNav: true }
Expand All @@ -287,7 +302,7 @@ const App: React.FC = () => {
<>
<Suspense fallback={null}><AuditCTA onOpenContact={() => setContactOpen(true)} /></Suspense>
<Suspense fallback={null}><FeaturedWork /></Suspense>
<Suspense fallback={null}><Services /></Suspense>
<Suspense fallback={null}><Services onOpenContact={() => setContactOpen(true)} /></Suspense>
<Suspense fallback={null}><Process /></Suspense>
<Suspense fallback={null}><OurStory /></Suspense>
<Suspense fallback={null}><Testimonials /></Suspense>
Expand Down
50 changes: 45 additions & 5 deletions components/AI/ArdenoAIWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ const STARTER_PROMPTS: QuickAction[] = [

const STORAGE_KEY = "ardeno_ai_messages_v7";
const OPEN_KEY = "ardeno_ai_open_v7";
const AI_PANEL_ID = "ardeno-ai-panel";
const CHAR_LIMIT = 500;

const genId = () => Math.random().toString(36).slice(2, 9);

Expand Down Expand Up @@ -753,6 +755,27 @@ const STYLES = `
font-weight: 800;
}

.aw-disclaimer {
margin: 0 0 8px;
padding-inline: 4px;
font-family: var(--aw-body);
font-size: 11px;
line-height: 1.45;
color: rgba(255, 255, 255, 0.38);
}

.aw-char-count {
font-variant-numeric: tabular-nums;
}

.aw-char-count.near-limit {
color: rgba(255, 180, 80, 0.9);
}

.aw-char-count.at-limit {
color: rgba(255, 77, 87, 0.95);
}

.aw-reduced .aw-fab,
.aw-reduced .aw-quick,
.aw-reduced .aw-icon-button,
Expand Down Expand Up @@ -819,7 +842,8 @@ const STYLES = `
}

.aw-compose-meta {
display: none;
flex-wrap: wrap;
gap: 4px 10px;
}
}
`;
Expand Down Expand Up @@ -892,6 +916,9 @@ const ArdenoAIWidget: React.FC = () => {
const abortRef = useRef<AbortController | null>(null);

const isInputEmpty = useMemo(() => input.trim().length === 0, [input]);
const charCount = input.length;
const nearLimit = charCount >= CHAR_LIMIT - 50;
const atLimit = charCount >= CHAR_LIMIT;

const close = useCallback(() => {
abortRef.current?.abort();
Expand Down Expand Up @@ -1001,7 +1028,7 @@ const ArdenoAIWidget: React.FC = () => {
}, [close, open]);

const sendPrompt = async (value: string) => {
const trimmed = value.trim();
const trimmed = value.trim().slice(0, CHAR_LIMIT);
if (!trimmed || loading) return;

const userMsg: Message = {
Expand Down Expand Up @@ -1105,6 +1132,8 @@ const ArdenoAIWidget: React.FC = () => {
type="button"
onClick={toggle}
aria-label={open ? "Close AI assistant" : "Open AI assistant"}
aria-expanded={open}
aria-controls={AI_PANEL_ID}
className="aw-fab aw-fab-wrapper"
whileTap={reduced ? undefined : { scale: 0.98 }}
style={{
Expand Down Expand Up @@ -1185,6 +1214,7 @@ const ArdenoAIWidget: React.FC = () => {
<AnimatePresence>
{open && (
<motion.div
id={AI_PANEL_ID}
role="dialog"
aria-label="Ardeno AI assistant"
aria-modal="true"
Expand Down Expand Up @@ -1231,7 +1261,7 @@ const ArdenoAIWidget: React.FC = () => {
</div>
</header>

<div className="aw-scroll">
<div className="aw-scroll" aria-live="polite" aria-relevant="additions">
{messages.length === 0 && (
<>
<motion.section
Expand Down Expand Up @@ -1348,18 +1378,23 @@ const ArdenoAIWidget: React.FC = () => {
</div>

<footer className="aw-compose">
<p className="aw-disclaimer">
AI may be inaccurate — contact us for final scope.
</p>
<div className={`aw-input-wrap${focused ? " focused" : ""}`}>
<textarea
ref={textareaRef}
value={input}
onChange={(e) => setInput(e.target.value)}
onChange={(e) => setInput(e.target.value.slice(0, CHAR_LIMIT))}
onKeyDown={onInputKeyDown}
onFocus={() => setFocused(true)}
onBlur={() => setFocused(false)}
placeholder="Tell us what you want to build..."
disabled={loading}
rows={1}
maxLength={CHAR_LIMIT}
className="aw-input"
aria-describedby="aw-char-count"
/>

<button
Expand All @@ -1380,7 +1415,12 @@ const ArdenoAIWidget: React.FC = () => {

<div className="aw-compose-meta">
<span>Server-side Ardeno context</span>
<strong>Ardeno Studio</strong>
<span
id="aw-char-count"
className={`aw-char-count${atLimit ? " at-limit" : nearLimit ? " near-limit" : ""}`}
>
{charCount}/{CHAR_LIMIT}
</span>
</div>
</footer>
</div>
Expand Down
42 changes: 19 additions & 23 deletions components/CaseStudies/CaseStudiesIndex.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ interface CaseStudy {
year: string;
summary: string;
href: string;
tags: string[];
thumbnailClass?: string;
bgImage?: string;
}
Expand All @@ -25,6 +26,7 @@ const CASE_STUDIES: CaseStudy[] = [
year: '2026',
summary: "How two people built Sri Lanka's premium web studio from zero in two months.",
href: '/case-studies/humble-beginnings',
tags: ['Identity', 'Website', 'Launch Story'],
thumbnailClass: 'bg-zinc-900', // text-based placeholder bg
bgImage: '/case-studies/humble/ardeno-logo.svg',
},
Expand Down Expand Up @@ -109,7 +111,7 @@ export const CaseStudiesIndex: React.FC = () => {
key={study.id}
href={study.href}
onClick={(e) => handleNavClick(e, study.href)}
className="group block relative"
className="group block relative focus-visible:outline-none"
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
initial={{ opacity: 0, y: 40 }}
Expand All @@ -119,28 +121,13 @@ export const CaseStudiesIndex: React.FC = () => {
>
{/* Thumbnail Area - Glassmorphism & Glow */}
<div
className={`w-full aspect-[4/3] rounded-2xl mb-6 flex items-center justify-center overflow-hidden relative ${study.thumbnailClass || ''} transition-all duration-700 ease-out`}
style={{
border: '1px solid rgba(255,255,255,0.08)',
boxShadow: '0 0 0 0 rgba(229,9,20,0)',
backdropFilter: 'blur(12px)',
}}
onMouseEnter={(e) => {
e.currentTarget.style.boxShadow = '0 0 40px -10px rgba(229,9,20,0.5)';
e.currentTarget.style.border = '1px solid rgba(229,9,20,0.4)';
e.currentTarget.style.transform = 'translateY(-4px) scale(1.01)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.boxShadow = '0 0 0 0 rgba(229,9,20,0)';
e.currentTarget.style.border = '1px solid rgba(255,255,255,0.08)';
e.currentTarget.style.transform = 'translateY(0) scale(1)';
}}
className={`w-full aspect-[4/3] rounded-2xl mb-6 flex items-center justify-center overflow-hidden relative ${study.thumbnailClass || ''} border border-white/[0.08] shadow-none backdrop-blur-md transition-all duration-700 ease-out group-hover:border-[#E50914]/40 group-hover:shadow-[0_0_40px_-10px_rgba(229,9,20,0.5)] group-hover:-translate-y-1 group-hover:scale-[1.01] group-focus-visible:border-[#E50914]/40 group-focus-visible:shadow-[0_0_40px_-10px_rgba(229,9,20,0.5)] group-focus-visible:-translate-y-1 group-focus-visible:scale-[1.01]`}
>
<div className="absolute inset-0 bg-[#E50914]/0 group-hover:bg-[#E50914]/10 transition-colors duration-700 z-10 pointer-events-none mix-blend-screen" />
<div className="absolute inset-0 bg-[#E50914]/0 group-hover:bg-[#E50914]/10 group-focus-visible:bg-[#E50914]/10 transition-colors duration-700 z-10 pointer-events-none mix-blend-screen" />

{/* Cinematic Background Logo */}
{study.bgImage && (
<div className="absolute -top-[15%] -right-[15%] w-[130%] h-[130%] opacity-[0.03] group-hover:opacity-[0.07] transition-all duration-[1200ms] ease-[cubic-bezier(0.16,1,0.3,1)] group-hover:scale-105 group-hover:-translate-x-2 pointer-events-none">
<div className="absolute -top-[15%] -right-[15%] w-[130%] h-[130%] opacity-[0.03] group-hover:opacity-[0.07] group-focus-visible:opacity-[0.07] transition-all duration-[1200ms] ease-[cubic-bezier(0.16,1,0.3,1)] group-hover:scale-105 group-hover:-translate-x-2 group-focus-visible:scale-105 group-focus-visible:-translate-x-2 pointer-events-none">
<img src={study.bgImage} alt="" className="w-full h-full object-contain filter brightness-0 invert" />
</div>
)}
Expand All @@ -149,18 +136,18 @@ export const CaseStudiesIndex: React.FC = () => {
<div className="absolute bottom-0 left-0 w-full h-[60%] bg-gradient-to-t from-zinc-900 via-zinc-900/40 to-transparent pointer-events-none z-10" />

{/* Ambient backdrop glow */}
<div className="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity duration-1000 bg-[radial-gradient(circle_at_center,rgba(229,9,20,0.15)_0%,transparent_70%)] pointer-events-none" />
<div className="absolute inset-0 opacity-0 group-hover:opacity-100 group-focus-visible:opacity-100 transition-opacity duration-1000 bg-[radial-gradient(circle_at_center,rgba(229,9,20,0.15)_0%,transparent_70%)] pointer-events-none" />

<h3
className="font-bold group-hover:scale-[1.03] transition-all duration-700 ease-[cubic-bezier(0.16,1,0.3,1)] text-center px-8 text-transparent bg-clip-text bg-gradient-to-b from-zinc-300 to-zinc-700 group-hover:from-white group-hover:to-zinc-400 z-20 relative"
className="font-bold group-hover:scale-[1.03] group-focus-visible:scale-[1.03] transition-all duration-700 ease-[cubic-bezier(0.16,1,0.3,1)] text-center px-8 text-transparent bg-clip-text bg-gradient-to-b from-zinc-300 to-zinc-700 group-hover:from-white group-hover:to-zinc-400 group-focus-visible:from-white group-focus-visible:to-zinc-400 z-20 relative"
style={{ fontFamily: FONT_H, fontSize: 'clamp(2rem, 4.5vw, 4rem)', lineHeight: 1, letterSpacing: '-0.02em' }}
>
{study.title.toUpperCase()}
</h3>

{/* Hover Reveal Button */}
<div
className="absolute bottom-6 right-6 w-14 h-14 rounded-full bg-white overflow-hidden flex items-center justify-center opacity-0 translate-y-8 group-hover:opacity-100 group-hover:translate-y-0 transition-all duration-700 ease-[cubic-bezier(0.16,1,0.3,1)] z-20 shadow-[0_10px_30px_rgba(229,9,20,0.3)] hover:scale-110 group/btn"
className="absolute bottom-6 right-6 w-14 h-14 rounded-full bg-white overflow-hidden flex items-center justify-center opacity-0 translate-y-8 group-hover:opacity-100 group-hover:translate-y-0 group-focus-visible:opacity-100 group-focus-visible:translate-y-0 transition-all duration-700 ease-[cubic-bezier(0.16,1,0.3,1)] z-20 shadow-[0_10px_30px_rgba(229,9,20,0.3)] hover:scale-110 group/btn"
>
<div className="absolute inset-0 flex items-center justify-center transition-transform duration-500 ease-[cubic-bezier(0.16,1,0.3,1)] group-hover/btn:translate-x-[150%] group-hover/btn:-translate-y-[150%]">
<ArrowUpRight className="w-6 h-6 text-black" />
Expand Down Expand Up @@ -194,13 +181,22 @@ export const CaseStudiesIndex: React.FC = () => {
>
{study.summary}
</p>
<div className="flex items-center gap-4 mt-4">
<div className="flex flex-wrap items-center gap-2 mt-4">
<span
className="text-[11px] tracking-widest uppercase text-[#E50914] border border-[#E50914]/20 bg-[#E50914]/5 px-3 py-1.5 rounded-full"
style={{ fontFamily: FONT_B }}
>
{study.client}
</span>
{study.tags.map((tag) => (
<span
key={tag}
className="text-[11px] tracking-widest uppercase text-zinc-400 border border-white/[0.08] bg-white/[0.03] px-3 py-1.5 rounded-full"
style={{ fontFamily: FONT_B }}
>
{tag}
</span>
))}
</div>
</div>
</motion.a>
Expand Down
6 changes: 6 additions & 0 deletions components/CaseStudies/CaseStudiesPage.css
Original file line number Diff line number Diff line change
Expand Up @@ -714,9 +714,15 @@
.p-grid-capsule {
padding: 32px;
height: 200px;
width: 100%;
cursor: pointer;
justify-content: space-between;
transition: background 0.4s ease, border-color 0.4s ease;
color: inherit;
font: inherit;
text-align: left;
appearance: none;
-webkit-appearance: none;
}
.p-grid-item { list-style: none; }

Expand Down
Loading
Loading