import React, { useState, useEffect, useRef, useCallback } from "react";
/* ============================================================
NIGHTINGALE EDUCATIONAL TRUST — portfolio site + admin panel
Design: "dawn chorus" — deep dusk teal fading to sunrise gold,
a nightingale drawn across a music-staff branch as the
signature motif (ties to the school's real anthem "Nightingale
ke Sitare"). Trust objectives use a numbered list because a
trust deed's objectives genuinely are an enumerated clause.
============================================================ */
const STORAGE_KEY = "site-content";
const ADMIN_KEY_STORE = "admin-password";
const DEFAULT_ADMIN_PASSWORD = "nightingale2026";
const DEFAULT_DATA = {
trustName: "NIGHTINGALE EDUCATIONAL TRUST",
shortName: "Nightingale Trust",
tagline: "Nurturing every voice, since 2003.",
established: "2003",
regLine: "A registered charitable education trust · Uluberia, Howrah",
logoUrl: "",
navLinks: [
{ label: "Home", page: "home" },
{ label: "About the Trust", page: "about" },
{ label: "Gallery", page: "gallery" },
{ label: "Contact", page: "contact" },
],
heroEyebrow: "Est. 2003 · Rajkhola, Uluberia",
heroHeading: "A trust built on English, computers, and one small bird's idea of courage.",
heroBody:
"Nightingale Educational Trust runs Nightingale Nursery, Uluberia's CBSE school for English & computer-based learning — and everything the trust does grows from the same conviction: every child deserves a clear, confident voice.",
aboutHeading: "Our story",
aboutBody:
"Nightingale Educational Trust was formed to give Uluberia a school that took English fluency and computer literacy seriously from the very first classroom. What began as a single nursery on Rajkhola Road has grown into a small community of students, teachers, and families who return every year for the annual function, the sports season, and the songs the children write themselves. The trust exists to keep that growing — responsibly, and for more children each year.",
objectives: [
"To establish, run, and manage educational institutions imparting quality education from pre-primary to secondary level.",
"To promote English-medium and computer-based learning accessible to children of Uluberia and the surrounding areas.",
"To provide scholarships, study materials, and financial assistance to meritorious and underprivileged students.",
"To organise co-curricular, cultural, and sports activities for the holistic development of every child.",
"To conduct teacher-training programmes and community welfare initiatives in the field of education.",
],
trustees: [
{ name: "Add trustee name", role: "Managing Trustee" },
{ name: "Add trustee name", role: "Trustee, Academics" },
{ name: "Add trustee name", role: "Trustee, Finance" },
],
gallery: [
{
url: "https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdnAmjJGIs9yw1xyIMOjYiDYE5urRkTki7rGeri2aB77UaZOeqGG8Buy-WXIh2Z8_ItIhNd5cwZQ71pJv5NDR1qhl5_Y-rvCKkuGoX3Ek1GaGmbpEWakotQxjWlemZcEfxMmzOzxMotSl6OhScDvfTQ0wEBBggZ2STKNgLOAXPMCfc6JXvzT5sVTsiz4qs/s1280/Yellow%20and%20White%20Simple%20Classroom%20Zoom%20Meet%20Zoom%20Virtual%20Background_20240715_232818_0000.png",
caption: "Classroom life at Nightingale Nursery",
},
{
url: "https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiR9ddKlKNeKyk19FMZ_3zsmWr4SP_vcAZ1xqHr8IvfJDdhqf-C_gLtpYUSxHv1CED23R-Ln5F4P7UExcFRlzUDl_FdObu_XicldI0Q2GS3VGHjBUZAVf4hTTv5TXJum7bUxopXxFdgRpJcsMKJwgF6nccAXH29ONwS4cdZCzINA9X0LlaflTecVPXraQ/s1280/IMG-20250328-WA0003.jpg",
caption: "Students at a school event",
},
{
url: "https://blogger.googleusercontent.com/img/a/AVvXsEhQ-L7ul2vZsW_Aq5UlF-49f_BOEMvGrdTjyzwSyqPrNbjnFQXzjK5v-2ZoMskJG9L_s6DBn8aEk0VB0f7lqc4PuPQjWHjN3zqxWF3UHiuvKObFbtzhW_UKnYuR-xOiBb5oH6JxMMXlUSputy9AW7DR_Z_cPTySAq5a2httN1FHKqUGL7Q0EeCfxe23=s530",
caption: "Practical science class",
},
{
url: "https://blogger.googleusercontent.com/img/a/AVvXsEh9IWIAYh_tKLAs5Vxf9RhQTZfAVrSKEgsjfW6krNTGPP_yA3cQwNgixjjQD0b8b35oCinyoIYRNOJLQgQJ7Xw2DUSgjtmWgvTG7Z9ZHkmTaV40HBla2Qe6wircqtT-cWDXQqfBCRbFu__HQfcDxH5wwKjGxC2zvsVcseP_XYNokF8_-gJfVEk3d0Ew=s662",
caption: "A page from the school's Daily Diary",
},
],
contact: {
address: "Nightingale Nursery, Rajkhola, Uluberia, Howrah, West Bengal 711316",
whatsapp: "+91 98741 92235",
email: "info@nightingalenursery.in",
website: "nightingalenursery.in",
},
footerText:
"Nightingale Educational Trust is the parent body of Nightingale Nursery, Uluberia.",
};
/* ---------------- storage helpers ---------------- */
async function loadSite() {
try {
const res = await window.storage.get(STORAGE_KEY, true);
if (res && res.value) return JSON.parse(res.value);
} catch (e) {
/* not found yet */
}
return null;
}
async function saveSite(data) {
await window.storage.set(STORAGE_KEY, JSON.stringify(data), true);
}
async function loadAdminPassword() {
try {
const res = await window.storage.get(ADMIN_KEY_STORE, true);
if (res && res.value) return res.value;
} catch (e) {
/* not found yet */
}
return null;
}
async function saveAdminPassword(pw) {
await window.storage.set(ADMIN_KEY_STORE, pw, true);
}
/* ---------------- scroll reveal hook ---------------- */
function useReveal() {
const ref = useRef(null);
const [shown, setShown] = useState(false);
useEffect(() => {
const el = ref.current;
if (!el) return;
const obs = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setShown(true);
obs.disconnect();
}
},
{ threshold: 0.15 }
);
obs.observe(el);
return () => obs.disconnect();
}, []);
return [ref, shown];
}
function Reveal({ children, className = "", delay = 0 }) {
const [ref, shown] = useReveal();
return (
{children}
);
}
/* ---------------- nightingale signature SVG ---------------- */
function NightingaleMark({ animate = true, className = "" }) {
return (
{/* staff / branch lines */}
{/* bird body */}
);
}
/* ============================================================
PUBLIC SITE
============================================================ */
function Header({ data, page, setPage, setAdminOpen }) {
const [open, setOpen] = useState(false);
return (
{
setPage("home");
setOpen(false);
}}
>
{data.logoUrl ? (
) : (
N
)}
{data.shortName}
Educational Trust
{data.navLinks.map((l) => (
{
setPage(l.page);
setOpen(false);
}}
>
{l.label}
))}
setAdminOpen(true)}>
Admin
setOpen((v) => !v)}>
);
}
function Hero({ data }) {
return (
{data.heroEyebrow}
{data.heroHeading}
{data.heroBody}
);
}
function QuickFacts({ data }) {
const facts = [
{ label: "Established", value: data.established },
{ label: "Location", value: "Uluberia, Howrah" },
{ label: "Medium", value: "English & Computer-based" },
{ label: "Runs", value: "Nightingale Nursery (CBSE)" },
];
return (
{facts.map((f, i) => (
))}
);
}
function HomePage({ data, setPage }) {
return (
<>
About the trust
{data.aboutHeading}
{data.aboutBody}
setPage("about")}>
Read our objectives →
A glimpse
From the school
{data.gallery.slice(0, 3).map((g, i) => (
{g.caption}
))}
setPage("gallery")}>
View full gallery →
>
);
}
function AboutPage({ data }) {
return (
Est. {data.established}
{data.trustName}
{data.aboutBody}
Objectives of the trust
{data.objectives.map((o, i) => (
{String(i + 1).padStart(2, "0")}
{o}
))}
Board of trustees
{data.trustees.map((t, i) => (
))}
);
}
function GalleryPage({ data }) {
return (
Gallery
Moments from Nightingale
{data.gallery.map((g, i) => (
{g.caption}
))}
{data.gallery.length === 0 && (
No photos yet — add some from the admin panel.
)}
);
}
function ContactPage({ data }) {
const c = data.contact;
const waLink = `https://wa.me/${c.whatsapp.replace(/[^\d]/g, "")}`;
return (
Get in touch
Contact the trust
);
}
function Footer({ data, setPage }) {
return (
);
}
/* ============================================================
ADMIN PANEL
============================================================ */
function AdminPanel({ data, setData, onClose, adminPassword, setAdminPassword }) {
const [authed, setAuthed] = useState(false);
const [pwInput, setPwInput] = useState("");
const [pwError, setPwError] = useState("");
const [draft, setDraft] = useState(data);
const [tab, setTab] = useState("header");
const [status, setStatus] = useState("");
useEffect(() => setDraft(data), [data]);
function tryLogin(e) {
e.preventDefault();
if (pwInput === adminPassword) {
setAuthed(true);
setPwError("");
} else {
setPwError("Wrong password. Try again.");
}
}
async function persist(next) {
setDraft(next);
setData(next);
await saveSite(next);
setStatus("Saved.");
setTimeout(() => setStatus(""), 1500);
}
function updateField(path, value) {
const next = { ...draft };
let obj = next;
for (let i = 0; i < path.length - 1; i++) obj = obj[path[i]];
obj[path[path.length - 1]] = value;
persist(next);
}
function updateListItem(listKey, idx, field, value) {
const next = { ...draft };
const list = [...next[listKey]];
if (field === null) {
list[idx] = value;
} else {
list[idx] = { ...list[idx], [field]: value };
}
next[listKey] = list;
persist(next);
}
function addListItem(listKey, blank) {
const next = { ...draft };
next[listKey] = [...next[listKey], blank];
persist(next);
}
function removeListItem(listKey, idx) {
const next = { ...draft };
next[listKey] = next[listKey].filter((_, i) => i !== idx);
persist(next);
}
if (!authed) {
return (
×
Admin access
Sign in to edit the site
Default password: {DEFAULT_ADMIN_PASSWORD}
);
}
const tabs = [
{ id: "header", label: "Header & Logo" },
{ id: "hero", label: "Home / Hero" },
{ id: "about", label: "About & Objectives" },
{ id: "trustees", label: "Trustees" },
{ id: "gallery", label: "Gallery" },
{ id: "contact", label: "Contact" },
{ id: "footer", label: "Footer" },
{ id: "security", label: "Password" },
];
return (
Editing {draft.shortName}
{status && {status} }
×
{tabs.map((t) => (
setTab(t.id)}
>
{t.label}
))}
{tab === "header" && (
Trust full name
updateField(["trustName"], e.target.value)}
/>
Short name (header)
updateField(["shortName"], e.target.value)}
/>
Logo image URL (leave blank to use the "N" mark)
updateField(["logoUrl"], e.target.value)}
/>
Navigation links
{draft.navLinks.map((l, i) => (
updateListItem("navLinks", i, "label", e.target.value)}
placeholder="Label"
/>
updateListItem("navLinks", i, "page", e.target.value)}
>
home
about
gallery
contact
removeListItem("navLinks", i)}>Remove
))}
addListItem("navLinks", { label: "New link", page: "home" })}
>
+ Add nav link
)}
{tab === "hero" && (
Eyebrow (small line above headline)
updateField(["heroEyebrow"], e.target.value)} />
Hero heading
)}
{tab === "about" && (
)}
{tab === "trustees" && (
Board of trustees
{draft.trustees.map((t, i) => (
updateListItem("trustees", i, "name", e.target.value)}
/>
updateListItem("trustees", i, "role", e.target.value)}
/>
removeListItem("trustees", i)}>Remove
))}
addListItem("trustees", { name: "New trustee", role: "Role" })}>
+ Add trustee
)}
{tab === "gallery" && (
Gallery images (paste an image URL — file upload isn't supported here)
{draft.gallery.map((g, i) => (
))}
addListItem("gallery", { url: "", caption: "" })}>
+ Add image
)}
{tab === "contact" && (
Address
updateField(["contact", "address"], e.target.value)} />
WhatsApp number
updateField(["contact", "whatsapp"], e.target.value)} />
Email
updateField(["contact", "email"], e.target.value)} />
Website
updateField(["contact", "website"], e.target.value)} />
)}
{tab === "footer" && (
Footer text
updateField(["footerText"], e.target.value)} />
)}
{tab === "security" && (
Change admin password
{
setAdminPassword(pw);
await saveAdminPassword(pw);
setStatus("Password updated.");
setTimeout(() => setStatus(""), 1500);
}}
/>
)}
);
}
function ChangePassword({ current, onChange }) {
const [val, setVal] = useState("");
return (
setVal(e.target.value)}
/>
{
if (val.trim().length >= 4) {
onChange(val.trim());
setVal("");
}
}}
>
Update
);
}
/* ============================================================
ROOT APP
============================================================ */
export default function App() {
const [data, setData] = useState(DEFAULT_DATA);
const [adminPassword, setAdminPasswordState] = useState(DEFAULT_ADMIN_PASSWORD);
const [page, setPage] = useState("home");
const [adminOpen, setAdminOpen] = useState(false);
const [ready, setReady] = useState(false);
useEffect(() => {
(async () => {
const [site, pw] = await Promise.all([loadSite(), loadAdminPassword()]);
if (site) setData(site);
else await saveSite(DEFAULT_DATA);
if (pw) setAdminPasswordState(pw);
else await saveAdminPassword(DEFAULT_ADMIN_PASSWORD);
setReady(true);
})();
}, []);
useEffect(() => {
window.scrollTo({ top: 0, behavior: "smooth" });
}, [page]);
const setAdminPassword = useCallback((pw) => setAdminPasswordState(pw), []);
return (
{!ready ? (
Loading Nightingale Educational Trust…
) : (
<>
{page === "home" && }
{page === "about" && }
{page === "gallery" && }
{page === "contact" && }
{adminOpen && (
setAdminOpen(false)}
adminPassword={adminPassword}
setAdminPassword={setAdminPassword}
/>
)}
>
)}
);
}
/* ============================================================
STYLES
============================================================ */
const CSS = `
@import url('https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,300;9..144,450;9..144,600;9..144,700&family=Manrope:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap');
:root{
--ink:#14201F;
--dusk:#123334;
--dusk-2:#0B2223;
--gold:#E8A33D;
--coral:#E2624A;
--cream:#FBF6EC;
--paper:#F3EEE0;
--sage:#93B589;
--line:rgba(20,32,31,0.12);
}
*{box-sizing:border-box;}
.app-root{
font-family:'Manrope', sans-serif;
color:var(--ink);
background:var(--cream);
min-height:100vh;
overflow-x:hidden;
}
.mono{font-family:'JetBrains Mono', monospace; letter-spacing:0.04em; text-transform:uppercase; font-size:0.72rem;}
h1,h2,h3{font-family:'Fraunces', serif; font-weight:600; line-height:1.08; margin:0;}
.loading{
min-height:100vh; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:1.2rem;
background:var(--dusk); color:var(--cream);
}
.loading-mark{width:220px; opacity:0.85;}
.loading .mono{color:var(--gold);}
/* ---------- header ---------- */
.site-header{
position:sticky; top:0; z-index:40;
background:rgba(251,246,236,0.9); backdrop-filter:blur(10px);
border-bottom:1px solid var(--line);
}
.header-inner{
max-width:1180px; margin:0 auto; padding:0.9rem 1.5rem;
display:flex; align-items:center; justify-content:space-between; gap:1rem;
}
.brand{display:flex; align-items:center; gap:0.7rem; background:none; border:none; cursor:pointer; padding:0;}
.brand-logo{width:40px; height:40px; object-fit:cover; border-radius:50%;}
.brand-mark{
width:40px; height:40px; border-radius:50%; background:var(--dusk); color:var(--gold);
display:flex; align-items:center; justify-content:center; font-family:'Fraunces',serif; font-weight:700; font-size:1.2rem;
}
.brand-text{display:flex; flex-direction:column; align-items:flex-start; text-align:left;}
.brand-name{font-family:'Fraunces', serif; font-weight:600; font-size:1.05rem; color:var(--ink);}
.brand-sub{font-size:0.68rem; letter-spacing:0.08em; text-transform:uppercase; color:var(--dusk); opacity:0.65;}
.footer-brand{font-size:1.2rem; color:var(--cream);}
.nav{display:flex; align-items:center; gap:1.6rem;}
.nav-link{background:none; border:none; cursor:pointer; font-size:0.92rem; font-weight:500; color:var(--ink); opacity:0.7; padding:0.3rem 0; position:relative;}
.nav-link:hover{opacity:1;}
.nav-link-active{opacity:1;}
.nav-link-active::after{content:''; position:absolute; left:0; right:0; bottom:-4px; height:2px; background:var(--coral);}
.nav-admin{
background:var(--dusk); color:var(--cream); border:none; border-radius:999px; padding:0.45rem 1rem;
font-size:0.8rem; font-weight:600; cursor:pointer; letter-spacing:0.03em;
}
.nav-admin:hover{background:var(--dusk-2);}
.burger{display:none; flex-direction:column; gap:5px; background:none; border:none; cursor:pointer; padding:0.4rem;}
.burger span{width:22px; height:2px; background:var(--ink); display:block;}
@media (max-width:820px){
.nav{
position:fixed; top:68px; left:0; right:0; background:var(--cream);
flex-direction:column; align-items:flex-start; gap:0; padding:0.5rem 1.5rem 1rem;
border-bottom:1px solid var(--line);
transform:translateY(-8px); opacity:0; pointer-events:none; transition:all 0.25s ease;
}
.nav-open{transform:translateY(0); opacity:1; pointer-events:auto;}
.nav-link, .nav-admin{width:100%; text-align:left; padding:0.7rem 0;}
.burger{display:flex;}
}
/* ---------- hero ---------- */
.hero{position:relative; overflow:hidden; background:linear-gradient(180deg, var(--dusk-2) 0%, var(--dusk) 55%, #3A5F4E 100%);}
.hero-sky{position:absolute; inset:0; overflow:hidden;}
.hero-sun{
position:absolute; right:8%; top:12%; width:220px; height:220px; border-radius:50%;
background:radial-gradient(circle, rgba(232,163,61,0.9) 0%, rgba(232,163,61,0.25) 45%, rgba(232,163,61,0) 70%);
animation:sunPulse 6s ease-in-out infinite;
}
@keyframes sunPulse{0%,100%{opacity:0.7; transform:scale(1);} 50%{opacity:1; transform:scale(1.08);}}
.hero-inner{position:relative; max-width:900px; margin:0 auto; padding:6.5rem 1.5rem 5rem; text-align:left;}
.eyebrow{color:var(--gold); margin-bottom:1rem; display:block;}
.hero-h1{color:var(--cream); font-size:clamp(2.1rem, 5vw, 3.4rem); font-weight:600; max-width:18ch; margin-bottom:1.4rem;
opacity:0; animation:riseIn 0.9s ease forwards 0.15s;}
.hero-body{color:rgba(251,246,236,0.82); font-size:1.08rem; max-width:56ch; line-height:1.65;
opacity:0; animation:riseIn 0.9s ease forwards 0.4s;}
@keyframes riseIn{from{opacity:0; transform:translateY(16px);} to{opacity:1; transform:translateY(0);}}
.hero-mark-wrap{margin-top:3rem; opacity:0.9;}
/* ---------- nightingale svg mark ---------- */
.ng-mark{width:100%; max-width:420px; height:auto;}
.ng-staff{stroke:rgba(251,246,236,0.25); stroke-width:1;}
.ng-bird{fill:var(--gold);}
.ng-wing{fill:var(--coral); opacity:0.85;}
.ng-leg{stroke:var(--gold); stroke-width:2; stroke-linecap:round;}
.ng-eye{fill:var(--dusk-2);}
.ng-mark-animate .ng-bird, .ng-mark-animate .ng-wing{
stroke:var(--gold); stroke-width:1;
stroke-dasharray:900; stroke-dashoffset:900;
animation:draw 2.2s ease forwards 0.6s;
}
.ng-mark-animate .ng-staff{
stroke-dasharray:400; stroke-dashoffset:400;
animation:draw 1.4s ease forwards;
}
.ng-mark-animate .ng-leg{stroke-dasharray:20; stroke-dashoffset:20; animation:draw 0.6s ease forwards 2.4s;}
.ng-mark-animate .ng-eye{opacity:0; animation:fadeIn 0.4s ease forwards 2.6s;}
@keyframes draw{to{stroke-dashoffset:0;}}
@keyframes fadeIn{to{opacity:1;}}
/* ---------- facts strip ---------- */
.facts{
display:grid; grid-template-columns:repeat(4, 1fr); gap:0;
background:var(--dusk); border-top:1px solid rgba(251,246,236,0.1);
}
.fact{padding:2rem 1.5rem; border-right:1px solid rgba(251,246,236,0.12);}
.fact:last-child{border-right:none;}
.fact-label{color:var(--gold); margin-bottom:0.5rem; display:block;}
.fact-value{color:var(--cream); font-family:'Fraunces', serif; font-size:1.15rem; font-weight:600;}
@media (max-width:720px){
.facts{grid-template-columns:repeat(2, 1fr);}
.fact{border-bottom:1px solid rgba(251,246,236,0.12);}
}
/* ---------- sections ---------- */
.section{max-width:900px; margin:0 auto; padding:5rem 1.5rem;}
.section-alt{background:var(--paper); max-width:none;}
.section-alt > *{max-width:900px; margin-left:auto; margin-right:auto;}
.page-top{padding-top:4.5rem;}
.section-eyebrow{color:var(--coral); margin-bottom:0.8rem; display:block;}
.section-h1{font-size:clamp(1.9rem, 4vw, 2.6rem); margin-bottom:1.2rem; max-width:20ch;}
.section-h2{font-size:clamp(1.6rem, 3vw, 2.1rem); margin-bottom:1.1rem; max-width:20ch;}
.section-h3{font-size:1.4rem; margin:2.2rem 0 1.2rem;}
.section-body{font-size:1.02rem; line-height:1.75; color:var(--ink); opacity:0.86; max-width:64ch;}
.link-btn{
background:none; border:none; color:var(--dusk); font-weight:600; cursor:pointer; padding:0;
margin-top:1.2rem; font-size:0.95rem; border-bottom:1px solid var(--coral); padding-bottom:2px;
display:inline-block; text-decoration:none;
}
.link-btn:hover{color:var(--coral);}
.muted{opacity:0.6; font-style:italic;}
/* ---------- reveal animation ---------- */
.reveal{opacity:0; transform:translateY(22px); transition:opacity 0.7s ease, transform 0.7s ease;}
.reveal-in{opacity:1; transform:translateY(0);}
/* ---------- objectives (real trust-deed clause list) ---------- */
.objectives{list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:1.1rem;}
.objectives li{display:flex; gap:1rem; align-items:baseline; padding-bottom:1.1rem; border-bottom:1px solid var(--line);}
.objectives li:last-child{border-bottom:none;}
.obj-num{color:var(--coral); flex-shrink:0; padding-top:0.15rem;}
.objectives li span:last-child{line-height:1.6; opacity:0.9;}
/* ---------- trustees ---------- */
.trustees{display:grid; grid-template-columns:repeat(auto-fill, minmax(200px,1fr)); gap:1rem;}
.trustee-card{background:var(--paper); border:1px solid var(--line); border-radius:10px; padding:1.2rem;}
.trustee-name{font-family:'Fraunces', serif; font-weight:600; font-size:1.05rem; margin-bottom:0.35rem;}
.trustee-role{color:var(--dusk); opacity:0.7;}
/* ---------- gallery ---------- */
.mini-gallery, .gallery-grid{
display:grid; grid-template-columns:repeat(3, 1fr); gap:1.2rem; margin:2rem 0;
max-width:900px; margin-left:auto; margin-right:auto;
}
.gallery-grid{grid-template-columns:repeat(3, 1fr); max-width:1180px; padding:0 1.5rem;}
.gallery-card{margin:0; border-radius:12px; overflow:hidden; background:var(--dusk); position:relative;}
.gallery-card img{width:100%; height:220px; object-fit:cover; display:block; transition:transform 0.5s ease;}
.gallery-card:hover img{transform:scale(1.06);}
.gallery-card figcaption{
position:absolute; left:0; right:0; bottom:0; padding:0.8rem 1rem;
background:linear-gradient(0deg, rgba(11,34,35,0.85), transparent);
color:var(--cream); font-size:0.82rem;
}
@media (max-width:820px){
.mini-gallery, .gallery-grid{grid-template-columns:1fr 1fr;}
}
@media (max-width:560px){
.mini-gallery, .gallery-grid{grid-template-columns:1fr;}
}
/* ---------- contact ---------- */
.contact-grid{display:grid; grid-template-columns:1fr 1fr; gap:1.2rem;}
.contact-card{background:var(--paper); border:1px solid var(--line); border-radius:10px; padding:1.4rem;}
.contact-value{font-size:1.02rem; margin-top:0.3rem; line-height:1.5;}
@media (max-width:600px){.contact-grid{grid-template-columns:1fr;}}
/* ---------- footer ---------- */
.site-footer{background:var(--dusk-2); color:var(--cream); padding:3.5rem 1.5rem 1.6rem;}
.footer-inner{max-width:1180px; margin:0 auto; display:flex; justify-content:space-between; flex-wrap:wrap; gap:2rem; padding-bottom:2rem; border-bottom:1px solid rgba(251,246,236,0.12);}
.footer-text{opacity:0.65; max-width:38ch; margin-top:0.6rem; line-height:1.5; font-size:0.92rem;}
.footer-links{display:flex; flex-direction:column; gap:0.6rem;}
.footer-links button{background:none; border:none; color:var(--cream); opacity:0.75; cursor:pointer; text-align:left; padding:0; font-size:0.92rem;}
.footer-links button:hover{opacity:1; color:var(--gold);}
.footer-copy{max-width:1180px; margin:1.4rem auto 0; opacity:0.4;}
/* ---------- admin ---------- */
.admin-overlay{position:fixed; inset:0; background:rgba(11,34,35,0.75); z-index:100; display:flex; align-items:center; justify-content:center; padding:1.2rem;}
.admin-login{background:var(--cream); border-radius:14px; padding:2.2rem; max-width:380px; width:100%; position:relative;}
.admin-login form{display:flex; flex-direction:column; gap:0.8rem; margin-top:1.2rem;}
.admin-input{padding:0.7rem 0.9rem; border-radius:8px; border:1px solid var(--line); font-size:1rem;}
.admin-error{color:var(--coral); font-size:0.85rem; margin:0;}
.admin-hint{margin-top:1rem; opacity:0.5;}
.btn-primary{background:var(--dusk); color:var(--cream); border:none; border-radius:8px; padding:0.75rem; font-weight:600; cursor:pointer; font-size:0.95rem;}
.btn-primary:hover{background:var(--dusk-2);}
.admin-panel{background:var(--cream); border-radius:14px; max-width:960px; width:100%; max-height:88vh; display:flex; flex-direction:column; overflow:hidden;}
.admin-topbar{display:flex; justify-content:space-between; align-items:center; padding:1.1rem 1.4rem; border-bottom:1px solid var(--line); background:var(--dusk); color:var(--cream);}
.admin-title{font-family:'Fraunces', serif; font-weight:600;}
.admin-topbar-right{display:flex; align-items:center; gap:1rem;}
.admin-status{color:var(--gold); font-size:0.85rem;}
.admin-close{background:none; border:none; color:inherit; font-size:1.6rem; cursor:pointer; line-height:1; padding:0 0.3rem;}
.admin-login .admin-close{position:absolute; top:1rem; right:1rem; color:var(--ink);}
.admin-body{display:flex; flex:1; overflow:hidden;}
.admin-tabs{display:flex; flex-direction:column; width:190px; border-right:1px solid var(--line); overflow-y:auto; flex-shrink:0; background:var(--paper);}
.admin-tab{background:none; border:none; text-align:left; padding:0.9rem 1.1rem; cursor:pointer; font-size:0.88rem; border-bottom:1px solid var(--line); color:var(--ink); opacity:0.7;}
.admin-tab-active{opacity:1; background:var(--cream); font-weight:600; color:var(--dusk); border-left:3px solid var(--coral);}
.admin-content{flex:1; overflow-y:auto; padding:1.6rem;}
.admin-form{display:flex; flex-direction:column; gap:0.5rem;}
.admin-form label{font-size:0.78rem; text-transform:uppercase; letter-spacing:0.05em; opacity:0.6; margin-top:0.9rem;}
.admin-form input, .admin-form textarea, .admin-form select{
padding:0.6rem 0.8rem; border-radius:7px; border:1px solid var(--line); font-family:inherit; font-size:0.95rem; width:100%;
background:#fff;
}
.admin-row{display:flex; gap:0.5rem; align-items:flex-start; margin-top:0.4rem;}
.admin-row input, .admin-row textarea, .admin-row select{flex:1;}
.admin-row-gallery{flex-wrap:wrap;}
.admin-thumb{width:52px; height:52px; object-fit:cover; border-radius:6px; flex-shrink:0;}
.btn-remove{background:none; border:1px solid var(--line); color:var(--coral); border-radius:7px; padding:0.5rem 0.7rem; cursor:pointer; font-size:0.8rem; flex-shrink:0;}
.btn-remove:hover{background:var(--coral); color:#fff; border-color:var(--coral);}
.btn-secondary{align-self:flex-start; margin-top:0.8rem; background:none; border:1px dashed var(--dusk); color:var(--dusk); border-radius:7px; padding:0.55rem 1rem; cursor:pointer; font-size:0.85rem; font-weight:600;}
.btn-secondary:hover{background:var(--dusk); color:var(--cream); border-style:solid;}
@media (max-width:720px){
.admin-body{flex-direction:column;}
.admin-tabs{flex-direction:row; width:100%; overflow-x:auto; white-space:nowrap;}
.admin-tab{border-bottom:none; border-right:1px solid var(--line);}
}
@media (prefers-reduced-motion: reduce){
.hero-h1, .hero-body, .reveal, .ng-mark-animate .ng-bird, .ng-mark-animate .ng-wing, .ng-mark-animate .ng-staff, .ng-mark-animate .ng-leg, .ng-mark-animate .ng-eye, .hero-sun{
animation:none !important; transition:none !important; opacity:1 !important; transform:none !important; stroke-dashoffset:0 !important;
}
}
`;