Breaks of 3 nights or more · weekly stays £588–£900
No deposit taken at this stage
Or call Nick on {C.PHONE}
);
}
function Highlights() {
const items = [
['trees', 'Set in a 100-acre wood', 'Total seclusion, with four acres of private woodland attached'],
['flame', 'Log burner & cosy living', 'A proper sitting room with views over the garden'],
['bird', 'Birdwatching from the windows', 'Feeders bring nuthatches, woodpeckers, deer and owls'],
];
return (
{items.map(([ic, t, d]) => (
{t}
{d}
))}
);
}
function VideoTour() {
return (
Take a look around
);
}
// Bedroom data — read from assets/bedrooms.json (which Decap CMS edits) on the live
// host; fall back to the embedded window.GK_BEDROOMS for local file:// previews.
let _bedroomsCache = null, _bedroomsPromise = null;
function loadBedrooms() {
if (_bedroomsCache) return Promise.resolve(_bedroomsCache);
if (_bedroomsPromise) return _bedroomsPromise;
_bedroomsPromise = (async () => {
try {
const res = await fetch('assets/bedrooms.json', { cache: 'no-store' });
if (res.ok) {
const data = await res.json();
if (data && Array.isArray(data.bedrooms)) { _bedroomsCache = data.bedrooms; return _bedroomsCache; }
}
} catch (e) { /* fall through */ }
if (window.GK_BEDROOMS && Array.isArray(window.GK_BEDROOMS.bedrooms)) { _bedroomsCache = window.GK_BEDROOMS.bedrooms; return _bedroomsCache; }
_bedroomsCache = [];
return _bedroomsCache;
})();
return _bedroomsPromise;
}
function useBedrooms() {
const [rooms, setRooms] = useState(_bedroomsCache || []);
useEffect(() => { let live = true; loadBedrooms().then(r => { if (live) setRooms(r); }); return () => { live = false; }; }, []);
return rooms;
}
// Pull a YouTube video ID from a full URL (youtu.be/ID, watch?v=ID, embed/ID,
// shorts/ID) or accept a bare ID.
function ytId(s) {
if (!s) return '';
const m = String(s).match(/(?:youtu\.be\/|[?&]v=|embed\/|shorts\/)([A-Za-z0-9_-]{6,})/);
return m ? m[1] : (/^[A-Za-z0-9_-]{6,}$/.test(String(s).trim()) ? String(s).trim() : '');
}
// Build an embed URL that autoplays muted and loops (browsers only allow autoplay
// when muted; loop needs playlist=). playsinline keeps it inline on mobile.
function ytEmbed(id, start) {
return 'https://www.youtube-nocookie.com/embed/' + id +
'?rel=0&autoplay=1&mute=1&loop=1&playsinline=1&playlist=' + id +
(start ? '&start=' + start : '');
}
function BedroomModal({ room, onClose }) {
const [active, setActive] = useState(0);
const dialogRef = useRef(null);
useEffect(() => {
const prev = document.activeElement;
if (dialogRef.current) dialogRef.current.focus();
const onKey = (e) => e.key === 'Escape' && onClose();
window.addEventListener('keydown', onKey);
if (window.lucide) window.lucide.createIcons();
return () => { window.removeEventListener('keydown', onKey); if (prev && prev.focus) prev.focus(); };
}, [onClose, active]);
if (!room) return null;
const photos = room.photos || [];
const vid = ytId(room.video);
// Photos and the video form a single gallery; the video is a slide marked with a
// play badge and autoplays (muted, looping) when selected.
const media = photos.map((src) => ({ type: 'image', src }));
if (vid) media.push({ type: 'video', id: vid, thumb: 'https://img.youtube.com/vi/' + vid + '/hqdefault.jpg' });
const cur = media[active] || media[0];
const pick = (i) => setActive(i);
const facts = [
['bed-double', room.bed],
['users', room.sleeps ? 'Sleeps ' + room.sleeps : null],
['home', room.location],
['bath', room.bathroom],
].filter(([, v]) => v);
return (
);
}
// Full amenities list — read from assets/amenities.json (Decap-editable) on the
// live host; fall back to embedded window.GK_AMENITIES for local file:// previews.
let _amenCache = null, _amenPromise = null;
function loadAmenities() {
if (_amenCache) return Promise.resolve(_amenCache);
if (_amenPromise) return _amenPromise;
_amenPromise = (async () => {
try {
const res = await fetch('assets/amenities.json', { cache: 'no-store' });
if (res.ok) { const d = await res.json(); if (d && Array.isArray(d.groups)) { _amenCache = d.groups; return _amenCache; } }
} catch (e) { /* fall through */ }
if (window.GK_AMENITIES && Array.isArray(window.GK_AMENITIES.groups)) { _amenCache = window.GK_AMENITIES.groups; return _amenCache; }
_amenCache = [];
return _amenCache;
})();
return _amenPromise;
}
function useAmenities() {
const [groups, setGroups] = useState(_amenCache || []);
useEffect(() => { let live = true; loadAmenities().then(g => { if (live) setGroups(g); }); return () => { live = false; }; }, []);
return groups;
}
function AmenitiesModal({ groups, onClose }) {
const dialogRef = useRef(null);
useEffect(() => {
const prev = document.activeElement;
if (dialogRef.current) dialogRef.current.focus();
const onKey = (e) => e.key === 'Escape' && onClose();
window.addEventListener('keydown', onKey);
if (window.lucide) window.lucide.createIcons();
return () => { window.removeEventListener('keydown', onKey); if (prev && prev.focus) prev.focus(); };
}, [onClose, groups]);
return (
e.stopPropagation()}>
Amenities & services
What this place offers
{groups.map((g) => (
{g.title}
{(g.items || []).map((it) => (
{it}
))}
))}
);
}
function Offers() {
const items = [
['Full kitchen', 'utensils-crossed', 'Hob, oven, grill, dishwasher & washing machine'],
['Log burner', 'flame', 'Logs provided through the cooler months'],
['Free parking', 'square-parking', 'Space for up to three vehicles'],
['Wi-Fi included', 'wifi', 'Typically ~45 Mb/sec; the dining room doubles as an office'],
['Separate dining room', 'utensils', 'Seats six (or five plus a high chair)'],
['Private garden & woodland', 'trees', 'Four acres of woodland to roam'],
['Smart TV & books', 'tv', 'For quiet evenings by the fire'],
['Security cameras', 'cctv', 'Covering the parking area, never the garden'],
];
const groups = useAmenities();
const [open, setOpen] = useState(false);
return (
Your departure date is before your arrival date — please pick a later departure.
}
)}
);
}
function ReviewsStrip() {
const seed = [
['One of the loveliest places we have ever stayed — cosy and characterful, with a stunning garden and an abundance of woodland wildlife.', 'JP', 'Jan 2026', 5],
['Such a beautiful, secluded location. We all loved it — just how life should be.', 'Mark Rawlinson', 'Sep 2025', 5],
['We saw deer, buzzard and woodpeckers in the wood. I couldn’t recommend it more.', 'William Ripper', 'Apr 2025', 5],
];
const live = useReviews();
const stats = reviewStats(live);
const liveQuotes = (live || []).map((r) => [r.body, r.name, reviewMonth(r.date), r.rating || 5]);
const shown = (liveQuotes.length ? liveQuotes : seed).slice(0, 3);
return (
Guest reviews
Loved by our guests
{stats.count > 0 && }
{shown.map(([q, who, when, rating], i) => (
“{q}”
{who}{when ? ' · ' + when : ''}
))}
);
}
function MapNearby() {
const nearby = [
['uploads/The-Eels-Foot-Inn.webp', 'the-eels-foot-inn-in-suffolk', 'The Eels Foot Inn', 'A characterful country pub near Minsmere'],
['uploads/https___www.southwoldpier.co_.uk_.webp', 'southwold-pier', 'Southwold Pier', 'The classic seaside pier, 20 minutes away'],
['uploads/sole-bay-fish-company.webp', 'solebay-fish-company-southwold', 'Solebay Fish Company', 'Fresh-off-the-boat seafood in Southwold'],
];
return (
Where you'll be staying
The Wood, Brampton — near Beccles & Southwold
The exact location is shared once your stay is confirmed. The cottage sits down a private drive, completely surrounded by woodland.
This was my family home as a teenager, and we look after the cottage ourselves. We keep it more like a home than a rental, and we love sharing the wood with our guests. Please don't hesitate to get in touch — I'll gladly answer any questions before you book.
The original Gamekeeper's Cottage sits in the middle of a hundred-acre wood, in a wonderfully secluded position with four acres of private woodland attached. It's warm, homely and full of character — and for the length of your stay, the wood is yours.
Inside there's a well-equipped kitchen, a cosy sitting room with a log burner and a separate dining room. Outside, a generous garden gives way to woodland walks, with feeders that bring deer, pheasants, owls and woodpeckers close to the windows.