{
  "site": {
    "name": "Passionfruit Design",
    "owner": "Dr Matt Dennis",
    "url": "https://passionfruit.design",
    "email": "hello@passionfruit.design",
    "phone": {
      "display": "07385 509278",
      "tel": "+447385509278",
      "whatsapp": "https://wa.me/447385509278"
    },
    "location": {
      "town": "Portsmouth",
      "region": "Hampshire",
      "country": "UK",
      "countryCode": "GB"
    },
    "serviceArea": {
      "radiusMiles": 30,
      "latitude": 50.8198,
      "longitude": -1.088,
      "places": [
        "Portsmouth",
        "Southsea",
        "Gosport",
        "Fareham",
        "Havant",
        "Waterlooville",
        "Chichester",
        "Southampton",
        "Winchester",
        "Isle of Wight"
      ],
      "remote": "United Kingdom"
    },
    "person": {
      "name": "Matt Dennis",
      "prefix": "Dr",
      "jobTitle": "Human-centred designer and developer",
      "credential": "PhD in human-computer interaction",
      "alumniOf": {
        "text": "University of Aberdeen",
        "href": "https://www.abdn.ac.uk"
      },
      "memberOf": {
        "text": "BCS, The Chartered Institute for IT",
        "href": "https://www.bcs.org"
      }
    },
    "profiles": [
      {
        "text": "GitHub",
        "href": "https://github.com/matt-thepie"
      },
      {
        "text": "LinkedIn",
        "href": "https://www.linkedin.com/in/drmattdennis"
      }
    ],
    "indexNowKey": "26752be6ba04488b94c8dbe957c8f20b",
    "repo": null,
    "title": "Websites your team can update",
    "description": "Websites and web apps your own team can update. Researched, designed, built, kept safe and kept running by one person. Portsmouth and UK-wide.",
    "versions": {
      "plain": "Plain English",
      "technical": "For IT people"
    },
    "labels": {
      "home": "Home",
      "questions": "Questions people ask",
      "technical": "For IT people",
      "plain": "In plain English",
      "technicalWork": "All the work, with the tools named",
      "proof": "Work like this",
      "services": "What I did here",
      "specialities": "Specialities",
      "visit": "Visit",
      "sources": "Sources",
      "plan": "The plan for this note",
      "allServices": "All services",
      "allWork": "All the work",
      "facts": "How it is built"
    },
    "hero": {
      "headline": "The whole job. One person.",
      "lede": "I make websites and web apps that your own team can update. I find out what your people need. Then I design it, build it, keep it safe and keep it running."
    },
    "what": {
      "heading": "What I do",
      "lead": {
        "title": "Less to pay for later.",
        "text": "Every extra part in a website is something to fix, update and pay for in the years to come. I only add a part when it earns its place, and I take out any that stop earning it. Everything is checked, tested and written down well enough for someone else to take over. The rest follows from that."
      },
      "items": [
        {
          "title": "I start with your people.",
          "text": "I talk to the people who will use it and watch how they work. Then I test what I build with them."
        },
        {
          "title": "You change the words yourself.",
          "text": "Your team updates the text and pictures from a simple editing screen. Nobody has to phone a developer."
        },
        {
          "title": "It works for everyone.",
          "text": "On phones, on slow connections and with screen readers. I build to the recognised accessibility standard from the start."
        },
        {
          "title": "It is quick and cheap to run.",
          "text": "I keep things small, so pages load fast and the hosting bill stays low."
        },
        {
          "title": "It is kept safe.",
          "text": "I keep the software up to date and close everything that does not need to be open. I set up your email so nobody can send fakes from your address."
        },
        {
          "title": "It can take money.",
          "text": "Card payments, ticket sales and bookings, when you need them."
        },
        {
          "title": "It is yours to keep.",
          "text": "I use standard, widely used tools. If you ever move on, you take everything with you."
        },
        {
          "title": "I make sense of your data.",
          "text": "I plan surveys and studies, run them, analyse the results and report them plainly. That includes large amounts of data. One tool I built covers a whole institution's survey results, and was running within three hours of the results being published."
        },
        {
          "title": "It is built quickly.",
          "text": "I use AI tools to speed up the building. I check and test everything myself, and I answer for the result."
        },
        {
          "title": "One person to ask.",
          "text": "I do the design, the building, the hosting and the email myself. When something needs fixing, you know who to call."
        }
      ]
    },
    "technical": {
      "title": "Freelance full-stack developer, UK",
      "description": "Freelance full-stack developer in the UK. Web apps on a headless CMS, researched with users, then built, secured, hosted and run by one person.",
      "hero": {
        "headline": "The whole stack. One person.",
        "lede": "Modern web apps with a CMS your team can actually use. Researched with the people who will use them, then designed, built, secured, hosted and run by me. Interface to DNS."
      }
    },
    "principles": {
      "heading": "How I build",
      "lead": {
        "title": "Minimal technical debt.",
        "text": "Every dependency, build step and moving part is a future cost to you. I only add one when it pays for itself, and I remove what stops paying. Code is linted, typed or validated at its edges, tested in CI, and documented well enough that someone else could take it over. Everything below follows from that."
      },
      "items": [
        {
          "title": "Slim servers.",
          "text": "Small, single-purpose Node services with thin request handlers. No heavyweight runtime, no plugin pile. Several production apps share one 2GB VPS, and some have no npm dependencies at all. You get speed and a small hosting bill."
        },
        {
          "title": "JSON throughout.",
          "text": "Content is structured data from a headless or JSON-backed CMS, not pages locked in a theme. Schemas are validated at the boundary with Zod or JSON Schema. The same data feeds your website, a PWA, a dashboard or another system without rework."
        },
        {
          "title": "Built in the browser.",
          "text": "The server sends an app skeleton once, then JSON over a REST API. The logic runs on the client, which fills HTML templates and web components with that data. The server never assembles a page, so it does less work and costs less to run. Content goes in through the DOM. No innerHTML, no HTML built from strings, no server-side includes. The markup is semantic elements, not layers of divs hung with ids. No jQuery, no getElementById, no var. Search engines and link previews still get whole pages: on the chorus sites every public route is rendered once in a headless browser in CI and served as a static snapshot, and an edit in the CMS re-renders the pages it touches within a couple of minutes."
        },
        {
          "title": "Build in CI or not at all.",
          "text": "Servers run code. They do not build it. Where a server needs TypeScript, Node runs it directly with type stripping."
        },
        {
          "title": "Current versions, always.",
          "text": "Dependencies stay on current releases. They are not left to rot until an upgrade becomes a project."
        },
        {
          "title": "Standard and portable.",
          "text": "Nothing proprietary. The same app runs on a VPS, on Vercel, on Cloud Run or on Azure, and you can take it elsewhere."
        },
        {
          "title": "AI-assisted, engineer-owned.",
          "text": "I use AI tools to write and review code faster. That is how one person delivers at this pace. I read, test and answer for everything that ships."
        }
      ]
    },
    "about": {
      "heading": "About",
      "photo": {
        "src": "img/matt.webp",
        "alt": "Matt, smiling with his arms folded, standing outside in front of a tree with yellow leaves.",
        "width": 540,
        "height": 270
      },
      "paragraphs": [
        "I'm Matt. I'm based in Portsmouth, UK. I'm a human-centred designer who writes the code and runs the servers.",
        "My doctorate, from the University of Aberdeen, is in human-computer interaction: how software should adapt to the person using it. User research, usability and accessibility are what I trained in, not something I added later.",
        "I'm an academic. I have taught web technologies for over a decade and published peer-reviewed research. I have run production systems since 2018.",
        "I'm a member of the BCS and a charity trustee, and I have held a senior role covering safeguarding, regulatory compliance and data protection. I write the policy as well as the code."
      ]
    },
    "advice": {
      "heading": "Advice",
      "intro": "I also advise and consult, especially for charities and non-profits. I'm a charity trustee myself, so I know how far the money has to go.",
      "items": [
        "Networking",
        "Websites",
        "Choosing and buying software",
        "Regulatory compliance",
        "Data protection"
      ],
      "outro": "Tell me what you have and what worries you. I'll tell you plainly what to do next."
    },
    "contact": {
      "heading": "Tell me what you need",
      "intro": "A few lines about the project is enough. I read every message myself. I meet people face to face within about 30 miles of Portsmouth, and work with organisations anywhere in the UK.",
      "link": "Send me a message",
      "sent": "Thanks. Your message has been sent. I will reply to the address you gave.",
      "failed": "That did not send. Please email me directly instead."
    },
    "legal": "Passionfruit Design is the trading name of Dr Matt Dennis."
  },
  "stack": {
    "heading": "The lot",
    "intro": "Every layer, from the people using it down to the network. No subcontractors. No gaps.",
    "layers": [
      {
        "name": "Research and design",
        "text": "I find out what people need before I build it, then test it with them.",
        "items": [
          "User research",
          "Interviews",
          "Usability testing",
          "Prototyping",
          "Interaction design",
          "UX",
          "Accessibility to WCAG 2.2 AA"
        ]
      },
      {
        "name": "Research and data analysis",
        "text": "I design the study, run it, analyse the results and report them so a person can read them. Large datasets included.",
        "items": [
          "Study design",
          "Surveys",
          "Data analysis",
          "Large datasets",
          "Python",
          "R",
          "Bespoke analysis tools",
          "Dashboards",
          "Reporting",
          "Peer-reviewed publication"
        ]
      },
      {
        "name": "Architecture",
        "text": "Designed for the job and sized to run fast on modest hardware.",
        "items": [
          "Headless CMS",
          "REST and JSON APIs",
          "Small services",
          "App skeleton",
          "Client-side logic",
          "HTML templates",
          "Web components"
        ]
      },
      {
        "name": "Web apps with a CMS",
        "text": "Your staff edit the content. Nobody phones a developer.",
        "items": [
          "Payload",
          "Directus",
          "A custom CMS written for the job"
        ]
      },
      {
        "name": "Interface",
        "text": "Hand-written where that is enough, a framework where the app warrants it.",
        "items": [
          "HTML",
          "CSS",
          "JavaScript",
          "React",
          "Next.js",
          "TypeScript",
          "Tailwind"
        ]
      },
      {
        "name": "Application and data",
        "text": "APIs, databases, and the data you already have, moved in safely.",
        "items": [
          "Node",
          "Express",
          "Fastify",
          "REST and JSON APIs",
          "Postgres",
          "SQLite",
          "Prisma",
          "Full-text search",
          "Data migrations",
          "Legacy imports",
          "Feeds to and from other systems"
        ]
      },
      {
        "name": "Legacy applications",
        "text": "I know the older frameworks, and move applications from server state to true RESTful APIs.",
        "items": [
          "MVC",
          "PHP",
          "Laravel",
          "CodeIgniter",
          "Ruby on Rails",
          "JSP"
        ]
      },
      {
        "name": "Identity",
        "text": "One sign-in for every app, with roles that match how your organisation works.",
        "items": [
          "Passkeys (WebAuthn)",
          "OIDC",
          "Single sign-on",
          "Google sign-in",
          "Magic links",
          "Role hierarchies",
          "Central identity service"
        ]
      },
      {
        "name": "Payments and ticketing",
        "items": [
          "Stripe",
          "Box office",
          "Events",
          "Bookings"
        ]
      },
      {
        "name": "Integrations",
        "items": [
          "Social media hooks",
          "Webhooks",
          "Media pipelines",
          "Third-party feeds"
        ]
      },
      {
        "name": "AI",
        "text": "LLM features behind a provider you can swap, with privacy designed into the schema.",
        "items": [
          "LLM features",
          "Swappable provider interface",
          "Privacy by schema"
        ]
      },
      {
        "name": "DevSecOps",
        "items": [
          "GitHub Actions",
          "Automated deploys",
          "Dependency patching",
          "Content Security Policy",
          "Security headers",
          "Secrets management",
          "pm2",
          "Docker",
          "Monitoring",
          "Health checks"
        ]
      },
      {
        "name": "Hosting",
        "text": "Several production apps on one small box.",
        "items": [
          "Linux VPS",
          "nginx",
          "Vercel",
          "Neon",
          "Google Cloud",
          "Managed Postgres",
          "Backblaze B2",
          "Object storage behind a CDN"
        ]
      },
      {
        "name": "Cloudflare",
        "items": [
          "DNS",
          "CDN",
          "Caching",
          "TLS",
          "Edge protection"
        ]
      },
      {
        "name": "DNS and email",
        "text": "Your domain cannot be spoofed, and your mail arrives.",
        "items": [
          "DNS management",
          "Domain migrations",
          "SPF",
          "DKIM",
          "DMARC to p=reject",
          "BIMI",
          "MTA-STS",
          "Mail forwarding",
          "Mailing lists",
          "Transactional email"
        ]
      },
      {
        "name": "Home and business automation",
        "text": "Devices from different makers, working together on a small computer you own.",
        "items": [
          "Home Assistant",
          "Docker",
          "Zigbee",
          "MQTT",
          "433 MHz radio",
          "Presence sensors",
          "Heating control",
          "Dashboards"
        ]
      },
      {
        "name": "Network",
        "items": [
          "Reverse proxies",
          "TLS",
          "Private networking",
          "VPN (Tailscale)",
          "Network design"
        ]
      },
      {
        "name": "Policy and governance",
        "text": "I write the policy as well as the code.",
        "items": [
          "Data protection by design",
          "Accessibility",
          "Security policy",
          "Safeguarding",
          "Regulatory compliance"
        ]
      }
    ]
  },
  "services": {
    "index": {
      "name": "Services",
      "title": "Web design and development services",
      "description": "What you can hire me for: websites your team can update, web apps, payments, email security, accessibility, research, hosting and advice.",
      "lede": "Each of these is something I do myself, from the first conversation to keeping it running."
    },
    "headings": {
      "for": "Who it is for",
      "get": "What you get",
      "how": "How it works",
      "cost": "What it costs to run"
    },
    "pricing": "I do not list prices here yet. Tell me what you need and I will tell you what it costs to build and what it costs to run.",
    "items": [
      {
        "slug": "websites-you-can-update",
        "name": "Websites you can update yourself",
        "title": "Small business website you can update",
        "description": "A small business website you update yourself. Change the words and pictures from a simple editing screen. Designed, built and kept running by me.",
        "lede": "A website your own team can change. You edit the words and pictures from a simple editing screen. I design it, build it and keep it running.",
        "for": [
          "Small businesses, sole traders, clubs and community groups who want a proper website and want to change it themselves.",
          "It suits you if you are tired of waiting for someone else to make a small change, or if your current site is broken and nobody knows how to fix it."
        ],
        "get": [
          "A website designed around what your customers need to find.",
          "A simple editing screen for the words and pictures.",
          "Pages that work on phones, on slow connections and with screen readers.",
          "Your email set up so nobody can send fakes from your address.",
          "One person to ask when something needs changing or fixing."
        ],
        "how": [
          "I start by talking to you and to the people who will use the site. I design it around what they need, build it and test it with them. Then I show you how to edit it.",
          "After launch I keep the software up to date and the site running."
        ],
        "cost": [
          "A small site is cheap to run. I keep things small, so pages load fast and the hosting bill stays low."
        ],
        "questions": [
          {
            "question": "How do I update the website?",
            "answer": "You sign in and change the words and pictures from an editing screen. You do not need to know any code, and nobody has to phone a developer."
          },
          {
            "question": "Who owns the website?",
            "answer": "You do. I use standard, widely used tools. If you ever move on, you take everything with you."
          },
          {
            "question": "How long does a website take?",
            "answer": "It depends on the size of the job. I use AI tools to speed up the building, then check and test everything myself, so one person can deliver quickly. Tell me what you need and I will give you a date before I start."
          }
        ],
        "work": [
          "alicedennis-net",
          "morganharnett-tattoo",
          "solent-gay-mens-chorus"
        ]
      },
      {
        "slug": "charities",
        "name": "Websites and advice for charities",
        "title": "Charity website design and advice",
        "description": "Charity website design and plain advice from a charity trustee. A site your volunteers can update, built to keep running costs low.",
        "lede": "I build websites for charities and non-profits, and I advise them. I'm a charity trustee myself, so I know how far the money has to go.",
        "for": [
          "Charities, choirs, community groups and other non-profits. Often there is nobody whose job is the website: a few staff, some volunteers and a board of trustees."
        ],
        "get": [
          "A website your staff and volunteers can update themselves.",
          "Card payments, ticket sales and bookings, when you need them.",
          "Email set up so nobody can send fakes from your charity's address.",
          "Plain advice on websites, networking, choosing and buying software, regulatory compliance and data protection.",
          "Answers your trustees can follow. In my own charity work I answer to trustees who are not technical people."
        ],
        "how": [
          "Tell me what you have and what worries you. I'll tell you plainly what to do next.",
          "If you need something built, I find out what your people need, design it, build it and look after it. If you only need advice, that is fine too."
        ],
        "cost": [
          "I keep things small, so running costs stay low. One charity I work with runs its website, ticket sales, members' area and sign-in on a single small server."
        ],
        "questions": [
          {
            "question": "What does a charity website cost?",
            "answer": "It depends on what it has to do. A few pages of information cost far less than ticket sales and a members' area. I do not list prices here yet. Tell me what you need and I will give you a price to build it and a price to run it."
          },
          {
            "question": "Can our volunteers update it?",
            "answer": "Yes. They change the words and pictures from a simple editing screen. Nobody has to phone a developer."
          },
          {
            "question": "What happens if you are not available?",
            "answer": "Everything I build is tested and written down well enough for someone else to take over. I use standard, widely used tools, so another developer can pick it up."
          }
        ],
        "work": [
          "solent-gay-mens-chorus"
        ]
      },
      {
        "slug": "web-apps-and-booking",
        "name": "Web apps and booking systems",
        "title": "Bespoke web apps and booking systems",
        "description": "Bespoke web apps and booking systems for schools, charities and small businesses. Designed with the people who will use them, then built and run by me.",
        "lede": "When a website is not enough, I build the system behind it: bookings, sign-ins, records and the screens your staff use to run it.",
        "for": [
          "Organisations with a job that off-the-shelf software does not fit: a school, a university department, a charity, a small business.",
          "For example a booking system, a members' area, a marking system or a private archive."
        ],
        "get": [
          "A system designed from research with the people who will use it.",
          "Sign-in that matches how your organisation works, with the right access for each role.",
          "Bookings, payments and tickets, when you need them.",
          "The data you already have, moved in safely.",
          "A record of who did what, when the job needs one."
        ],
        "how": [
          "I talk to the people who will use it and watch how they work. I build an early version, test it with them and change it until it fits.",
          "Then I host it, keep it safe and keep it running."
        ],
        "cost": [
          "The running cost depends on how many people use it and how much it has to store. I size each system to run fast on modest hardware."
        ],
        "questions": [
          {
            "question": "How long does a web app take to build?",
            "answer": "It depends on the size of the job. I use AI tools to speed up the building, then check and test everything myself, so one person can deliver quickly. Tell me what you need and I will give you a date before I start."
          },
          {
            "question": "Can it work with the systems we already have?",
            "answer": "Usually. I connect new systems to existing ones, bring in old data, and set up sign-in with the accounts your people already use."
          },
          {
            "question": "Who owns the system?",
            "answer": "You do. I use standard, widely used tools. If you ever move on, you take everything with you."
          }
        ],
        "work": [
          "sums",
          "student-feedback-platform",
          "photo-archive-for-a-school",
          "asica"
        ],
        "technical": {
          "page": "passkeys-and-single-sign-on"
        }
      },
      {
        "slug": "payments-and-tickets",
        "name": "Payments and ticket sales",
        "title": "Take payments and sell tickets online",
        "description": "Take card payments, sell tickets and take bookings on your own website, under your own name. Built for choirs, community groups and small businesses.",
        "lede": "Take card payments, sell tickets and take bookings on your own website, under your own name.",
        "for": [
          "Choirs, theatres, community groups, clubs and small businesses who want to sell on their own site and not send people somewhere else."
        ],
        "get": [
          "Card payments on your own website.",
          "Ticket sales and a box office for your events.",
          "Bookings.",
          "Event pages your team adds and changes themselves."
        ],
        "how": [
          "I set up the payment side with an established card payment company. Card details go to them and not to your website.",
          "I build the pages your customers see and connect them to your events and your records."
        ],
        "cost": [
          "The card payment company takes a fee from each payment, and publishes its fees. On top of that there is the hosting, which I keep low."
        ],
        "questions": [
          {
            "question": "Is it safe to take card payments on our own website?",
            "answer": "Yes. Card details go straight to the card payment company. Your website never sees or stores them."
          },
          {
            "question": "Can we sell tickets for our own events?",
            "answer": "Yes. I built the ticket sales and box office for a choir, on the choir's own website."
          }
        ],
        "work": [
          "solent-gay-mens-chorus"
        ]
      },
      {
        "slug": "email-security",
        "name": "Email security",
        "title": "Stop people faking your email address",
        "description": "Stop people sending fake email from your address. I set up your domain so fakes are rejected and your real email still arrives. No new provider needed.",
        "lede": "Anyone can send an email that claims to come from your address, unless your domain name says otherwise. I set it up so fakes are rejected and your real email still arrives.",
        "for": [
          "Any organisation that sends email from its own domain name: charities, schools, small businesses.",
          "It matters most if you send invoices, appeals for money or anything else a fraudster would like to copy."
        ],
        "get": [
          "A check of how your email is set up today.",
          "Every service that sends email in your name found and set up properly.",
          "Fakes rejected by the systems that receive them, not just marked as suspicious.",
          "Your own email still arriving."
        ],
        "how": [
          "I look up how your domain name is set up today. I find everything that sends email in your name: your mailbox provider, your website, your newsletter tool. I set each one up to sign its messages.",
          "Then I tighten the rules in stages, reading the reports as I go, until fakes are rejected."
        ],
        "cost": [
          "Nothing extra each month. The settings live in the records for your domain name, which you already pay for. The work is done once, then checked whenever you add a new service that sends email."
        ],
        "questions": [
          {
            "question": "How do I know if our email address can be faked?",
            "answer": "Send me your domain name. The settings are public, so I can look them up and tell you what I find."
          },
          {
            "question": "Will it stop our own email arriving?",
            "answer": "No. That is why I do it in stages. I make sure every service that sends for you is set up before fakes are rejected."
          },
          {
            "question": "Do we need to change email provider?",
            "answer": "No. It works with the email provider you already use."
          }
        ],
        "work": [
          "solent-gay-mens-chorus"
        ],
        "technical": {
          "paragraphs": [
            "The standards are SPF, DKIM and DMARC. I take domains to DMARC p=reject with DKIM aligned."
          ],
          "page": "email-authentication"
        }
      },
      {
        "slug": "accessibility-and-user-research",
        "name": "Accessibility and user research",
        "title": "Accessible websites and user research",
        "description": "Accessible websites and user research from a designer with a doctorate in how people use software. I find out what your people need, then build it.",
        "lede": "I find out what your people need before I build, and I make sure everyone can use the result. This is what I trained in: my doctorate is in how software should adapt to the person using it.",
        "for": [
          "Organisations whose website or app has to work for everyone: the public, patients, students, staff, and people who use a screen reader or only a keyboard."
        ],
        "get": [
          "Interviews and observation with the people who will use it.",
          "Designs tested with those people, before and after they are built.",
          "Websites and apps built to the recognised accessibility standard from the start.",
          "A plain list of what to fix on a site you already have."
        ],
        "how": [
          "I talk to the people who will use it and watch how they work. I turn what I learn into designs, and test those with the same people. I have done this with students, university staff and NHS patients.",
          "When I build, accessibility is part of the work from the first page. It is not a check at the end."
        ],
        "cost": [
          "Accessibility is part of how I build, not an extra. Research takes time with real people, so its cost depends on how many people and how many rounds of testing."
        ],
        "questions": [
          {
            "question": "What accessibility standard do you build to?",
            "answer": "The current international standard for accessible websites, at the level called AA. Every page is checked by automated tests before it goes live, and I check by hand that it works with a keyboard and a screen reader."
          },
          {
            "question": "What is user research?",
            "answer": "Finding out what people need by asking them and watching them work, before deciding what to build. Then testing what gets built with the same people."
          }
        ],
        "work": [
          "asica",
          "student-feedback-platform"
        ],
        "technical": {
          "paragraphs": [
            "I build to WCAG 2.2 AA. This site is tested against it with axe on every change, with JavaScript off, by keyboard and at 320px."
          ]
        }
      },
      {
        "slug": "research-and-data-analysis",
        "name": "Research and data analysis",
        "title": "Survey, research and data analysis",
        "description": "Survey design, research and data analysis, including large amounts of data. You get findings in plain English that you can act on, not a spreadsheet.",
        "lede": "I plan the study, run it, analyse the results and report them plainly. Large amounts of data included. You get answers you can read, not another spreadsheet.",
        "for": [
          "Organisations with a question and the data that could answer it: survey results nobody has had time to read, records that have outgrown a spreadsheet, a service you want to evaluate."
        ],
        "get": [
          "A study or survey designed to answer your question.",
          "The analysis done, however much data there is.",
          "Findings written in plain English, for the people who have to act on them.",
          "A dashboard, when you need to keep watching the numbers.",
          "Analysis tools written for your data, when the usual ones do not fit."
        ],
        "how": [
          "We start with what you need to know. I work out what data would answer it: what you already hold, and what needs collecting. I run the survey or study, analyse the results and write up what they mean.",
          "My own research is peer-reviewed and published."
        ],
        "cost": [
          "A one-off study has no running cost. A dashboard or an analysis tool is a small web app. I host it and keep it running like any other."
        ],
        "questions": [
          {
            "question": "Can you work with data we already have?",
            "answer": "Yes. Send me what you hold and what you want to know. I'll tell you plainly what it can and cannot answer."
          },
          {
            "question": "How much data can you handle?",
            "answer": "Large amounts. One tool I built covers a whole institution's survey results. It was running within three hours of the results being published."
          },
          {
            "question": "What do we get at the end?",
            "answer": "A report in plain English that says what was found. The charts, the numbers and the method are there for anyone who wants to check."
          }
        ],
        "work": [
          "survey-analysis-dashboard",
          "student-feedback-platform",
          "asica"
        ],
        "technical": {
          "paragraphs": [
            "I work in Python and R, and I write bespoke analysis tools when the standard ones do not fit. The output is still something a person can read."
          ]
        }
      },
      {
        "slug": "hosting-and-care",
        "name": "Hosting and care",
        "title": "Website hosting and care",
        "description": "Website hosting and care from the person who built it. Software kept up to date, security kept tight, and one person to call when something needs fixing.",
        "lede": "I host what I build and look after it. The software is kept up to date, everything that does not need to be open is closed, and you have one person to call.",
        "for": [
          "Organisations that want their website looked after by the person who built it, not passed between a designer, a developer and a hosting company."
        ],
        "get": [
          "Hosting on a server I run, or with a hosting company you choose.",
          "Software kept up to date.",
          "Everything that does not need to be open, closed.",
          "Your domain name and email settings looked after.",
          "One person to call when something needs fixing."
        ],
        "how": [
          "I do the design, the building, the hosting and the email myself. Every change is tested before it goes live.",
          "I keep the software underneath on current versions, so an upgrade never grows into a project of its own."
        ],
        "cost": [
          "I keep things small, so the hosting bill stays low. Several of the systems I run share one small server."
        ],
        "questions": [
          {
            "question": "Who owns my website?",
            "answer": "You do. I use standard, widely used tools. If you ever move on, you take everything with you."
          },
          {
            "question": "What happens if you are unavailable?",
            "answer": "Everything I build is tested and written down well enough for someone else to take over. I use standard, widely used tools, so another developer can pick it up."
          },
          {
            "question": "Can I move my website somewhere else later?",
            "answer": "Yes. Nothing I build is tied to one hosting company."
          }
        ],
        "work": [
          "solent-gay-mens-chorus",
          "alicedennis-net",
          "geraldlarner-com"
        ]
      },
      {
        "slug": "moving-off-wordpress",
        "name": "Moving off WordPress",
        "title": "Moving your website off WordPress",
        "description": "Moving your website off WordPress, with your content and web addresses kept. A smaller site your team can still edit, built and looked after by me.",
        "lede": "WordPress is widely used and suits many organisations. If yours has become slow, hard to update or a worry to keep secure, I can move it to something smaller that your team can still edit.",
        "for": [
          "Organisations whose WordPress site has become hard to live with: updates that break things, add-ons nobody remembers installing, pages that load slowly.",
          "If your site works and you are happy with it, stay where you are. I will tell you so."
        ],
        "get": [
          "Your pages, posts and pictures moved across.",
          "A simple editing screen your team can use.",
          "Fewer moving parts, so there is less to update and less to go wrong.",
          "Your web addresses kept or redirected, so existing links still work."
        ],
        "how": [
          "I look at what your site does today and what your team needs from it. I build the new site alongside the old one, move the content across and check it with you.",
          "Then I switch over, with the old addresses pointing to the new pages."
        ],
        "cost": [
          "A smaller site has less to host and less to look after."
        ],
        "questions": [
          {
            "question": "Do we have to leave WordPress?",
            "answer": "No. It is widely used and suits many organisations. Moving makes sense when the site has become slow, hard to update or hard to keep secure. If it has not, I will say so."
          },
          {
            "question": "Will we lose our content?",
            "answer": "No. I move your pages, posts and pictures across and check them with you before the switch."
          },
          {
            "question": "Will our old links still work?",
            "answer": "Yes. I keep your web addresses the same where I can and redirect the old ones where I cannot."
          }
        ],
        "work": [
          "alicedennis-net",
          "geraldlarner-com"
        ],
        "technical": {
          "page": "legacy-app-modernisation"
        }
      },
      {
        "slug": "home-and-business-automation",
        "name": "Home and business automation",
        "title": "Smart home and business automation",
        "description": "Home and business automation that costs less and is not tied to one maker. Lights, heating and sensors working together on a small computer you own.",
        "lede": "Lights, heating, sensors and switches from different makers, working together. It runs on a small computer you own, so you are not tied to one company's app.",
        "for": [
          "Homes and small businesses that want their lights, heating and sensors to work together.",
          "It suits you if you have devices from several makers and an app for each, or if you do not want to be tied to one company."
        ],
        "get": [
          "Devices from different makers, controlled from one place.",
          "Lights that follow whether anyone is in the room.",
          "Heating and radiator valves that work together.",
          "Away and holiday settings for when nobody is in.",
          "A screen that shows what is on and what is off, and alerts when something stops working."
        ],
        "how": [
          "I look at what you already have and what you want it to do. I set it up, test it with you and show you how to use it.",
          "I run all of this in my own house, so I know where it goes wrong."
        ],
        "cost": [
          "The software is free and has no monthly fee. It runs on a small computer you own. It works with devices from many makers, so you can choose them on price."
        ],
        "questions": [
          {
            "question": "Do I have to replace the devices I already have?",
            "answer": "It works with devices from many makers. I check yours before I start and tell you which can stay."
          },
          {
            "question": "What happens if the internet goes down?",
            "answer": "The computer is in your building, so devices that talk to it directly keep working. Devices that depend on their maker's own service wait until it comes back."
          }
        ],
        "technical": {
          "paragraphs": [
            "I use Home Assistant, which is free and open source, running in Docker. Devices join over Zigbee, MQTT and 433 MHz radio."
          ],
          "page": "home-assistant"
        }
      }
    ]
  },
  "work": {
    "heading": "Work",
    "intro": "A charity platform, small-business sites on custom CMSs, university systems and a public archive.",
    "plainIntro": "A charity, small businesses, a university, a school and a public archive.",
    "index": {
      "name": "Work",
      "title": "Websites and web apps I have built",
      "description": "Case studies of websites and web apps I have built and run: a charity, small businesses, a university, a school, a public archive and a health study.",
      "lede": "A charity, small businesses, a university, a school and a public archive. Each one designed, built and run by me."
    },
    "projects": [
      {
        "slug": "solent-gay-mens-chorus",
        "name": "Solent Gay Men's Chorus",
        "title": "Solent Gay Men's Chorus website",
        "description": "How I built and run everything Solent Gay Men's Chorus has online: the website, ticket sales, the members' area and one sign-in for all of it.",
        "published": "2026-10-02",
        "url": "https://sgmc.org.uk",
        "kind": "Registered charity. The whole platform, built and run by me.",
        "plain": "A choir and registered charity. I built and run everything they have online: the website, ticket sales and the members' area. The committee updates the site itself, and tickets are sold with no ticket company's fee on top.",
        "flagship": true,
        "sections": [
          {
            "heading": "Who they are",
            "paragraphs": [
              "Solent Gay Men's Chorus is a choir and a registered charity. I answer to its trustees, who are not technical people."
            ]
          },
          {
            "heading": "The people",
            "paragraphs": [
              "The trustees, the committee who update the site, the members who sign in, and the audiences who buy tickets."
            ]
          },
          {
            "heading": "What they needed",
            "paragraphs": [
              "A new website that the committee can edit, to recruit members, advertise events and post updates.",
              "A way to sell tickets without paying a ticket company's handling fee on top of the card fee.",
              "A site for members, and a way to manage the chorus becoming a charitable incorporated organisation."
            ]
          },
          {
            "heading": "What I built",
            "paragraphs": [
              "The main website, a box office for ticket sales, a members' site, a governance system, and one sign-in that works across all of them.",
              "The committee edits the website itself. When they change a page, the copy that search engines read is rebuilt within a couple of minutes.",
              "Share a concert link in a message and the preview shows that concert, with its own picture and wording.",
              "Card payments are taken on the chorus's own site. The chorus pays the card fee and nothing on top.",
              "The governance system is new. It was built to manage the chorus becoming a charitable incorporated organisation.",
              "It was not only software. I also wrote the chorus's policies and its new constitution."
            ]
          },
          {
            "heading": "What it does for them",
            "paragraphs": [
              "One person answers for all of it."
            ],
            "items": [
              "The committee updates the site itself: recruiting members, advertising events and posting updates.",
              "Tickets are sold on the chorus's own website, with no ticket company's handling fee.",
              "Members sign in once for everything.",
              "The move to a charitable incorporated organisation is managed in one place.",
              "Email pretending to come from the chorus's address is rejected."
            ]
          }
        ],
        "facts": [
          "One identity service for every app, with passkey sign-in and a role hierarchy.",
          "Stripe payments taken directly by the box office, so there is no third-party handling fee on top of Stripe's. Ticket sales and events.",
          "Client-rendered and still indexed. Every public route is snapshotted in headless Chromium on GitHub Actions and served by nginx as static HTML, so crawlers and link unfurlers that run no JavaScript get the whole page. The browser is never run on the server.",
          "A CMS edit re-renders the routes it touches and pings IndexNow; the snapshot lands in a minute or two. A full render twice a day is the backstop and prunes deleted pages.",
          "Per-page title, description, canonical, Open Graph tags and JSON-LD (Event with offers, Article), set on the client and captured in the snapshot. The sitemap is generated from the CMS, with images.",
          "A governance system built to manage becoming a CIO. I also wrote the policies and the new constitution.",
          "Media library on object storage behind Cloudflare.",
          "Social media hooks, member email and a CMS the committee edits itself.",
          "Five Node services: main website, box office, governance, members portal and identity. Content in Payload.",
          "All five share one 2GB server behind nginx, deployed from GitHub Actions.",
          "Email domain at DMARC p=reject, with DKIM aligned."
        ]
      },
      {
        "slug": "sums",
        "name": "SUMS",
        "title": "SUMS: university marking system",
        "description": "SUMS is a marking system for a university. Two markers grade each student project blind and every step is recorded. It has been in use since 2018.",
        "published": "2026-10-02",
        "url": null,
        "kind": "Single-blind double-marking platform for a university, with automated reconciliation.",
        "plain": "A marking system for a university. Two markers grade each student project without seeing each other's marks, and the system reconciles the two marks automatically. In use since 2018, with about 600 projects and 100 markers a year.",
        "sections": [
          {
            "heading": "Who it is for",
            "paragraphs": [
              "A university, which uses it to mark student projects."
            ]
          },
          {
            "heading": "The people",
            "paragraphs": [
              "About 100 markers a year, and the students whose projects they mark: about 600 projects a year."
            ]
          },
          {
            "heading": "What they needed",
            "paragraphs": [
              "The university requires every student project to be marked twice, by two markers who do not see each other's marks. It also had a new marking scheme for them to follow."
            ]
          },
          {
            "heading": "What I built",
            "paragraphs": [
              "A system where two markers grade each student project without seeing each other's marks. When both marks are in, the system reconciles them automatically.",
              "It handles the allocation of supervisors and moderators to projects.",
              "Marking follows the new scheme. Each marker gets clear steps to follow, so the process is easy to pick up.",
              "Markers sign in with the university accounts they already have. There are no separate accounts to look after, and no extra passwords to become a security hole.",
              "It runs on Google's cloud, so there are no servers for anyone to run. Every step is recorded, so there is a full trail of who did what."
            ]
          },
          {
            "heading": "What it does for them",
            "paragraphs": [
              "It has been in use since 2018. It handles undergraduate and master's projects, resits and two international partner programmes."
            ]
          }
        ],
        "facts": [
          "Single-blind double marking with automated reconciliation, and allocation of supervisors and moderators to projects.",
          "Federated login through the institution's single sign-on: no local accounts to manage or to attack.",
          "In production since 2018 on Google Cloud, with no servers to run and a full audit trail.",
          "About 600 projects a year: 350 undergraduate and 250 master's, plus resits and two international partner programmes. About 100 markers a year.",
          "More than 4,800 projects marked since 2018."
        ]
      },
      {
        "slug": "student-feedback-platform",
        "name": "Student feedback platform",
        "title": "Student feedback platform",
        "description": "A student feedback platform built for a university to replace old survey software. No answer can be traced to a student. It is in testing.",
        "published": "2026-10-02",
        "url": null,
        "kind": "Commissioned by a university, designed from research with students and staff. In testing.",
        "plain": "A university asked me for a way to collect feedback from students, to replace its old survey software. It runs annual surveys and short pulse surveys for each module. Nobody can trace an answer back to the student who gave it. It is in testing.",
        "sections": [
          {
            "heading": "Who it is for",
            "paragraphs": [
              "A university, which commissioned it."
            ]
          },
          {
            "heading": "What they had before",
            "paragraphs": [
              "The university collected student feedback with Evasys, an old survey product. It could not give a student a direct link to a survey, and it did not connect to the university's other systems."
            ]
          },
          {
            "heading": "The people",
            "paragraphs": [
              "The students who give feedback, and the staff and student reps who read it. I did research with students and staff before I designed anything."
            ]
          },
          {
            "heading": "What I built",
            "paragraphs": [
              "A way to collect feedback from students. It runs the annual surveys, and short pulse surveys for each module.",
              "Nobody can trace an answer back to the student who gave it. That is not a promise in a policy. The system is made so that the link does not exist.",
              "Some features use AI. When a student writes something worrying, it is picked up straight away. The common themes in the answers are gathered together for staff and student reps.",
              "The AI features are built so that the AI provider can be swapped for another."
            ]
          },
          {
            "heading": "Where it is now",
            "paragraphs": [
              "It is in testing."
            ]
          }
        ],
        "facts": [
          "Replaces Evasys, which had no direct links to surveys and no integration with other systems.",
          "Annual surveys and module pulse surveys.",
          "Next.js and TypeScript on Vercel, with Postgres on Neon and Prisma.",
          "Anonymity is enforced by the schema: a response cannot be joined to the student who wrote it.",
          "LLM features flag concerning sentiment immediately and aggregate common themes for staff and reps. They sit behind a swappable provider interface.",
          "In testing."
        ]
      },
      {
        "slug": "survey-analysis-dashboard",
        "name": "Survey analysis dashboard",
        "title": "Survey analysis dashboard",
        "description": "A dashboard of a university's National Student Survey results, down to each course. Running within three hours of the results. A faculty adopted it.",
        "published": "2026-10-02",
        "url": null,
        "kind": "National Student Survey analysis for a whole university. Adopted across a faculty. In use since July 2026.",
        "plain": "A dashboard of a whole university's National Student Survey results. It was running within three hours of the results being published. It shows each faculty, school and course what its students scored and what they wrote. A university faculty adopted it.",
        "sections": [
          {
            "heading": "Who it is for",
            "paragraphs": [
              "A university faculty, which adopted it. It covers every faculty, school and course in the university."
            ]
          },
          {
            "heading": "The problem",
            "paragraphs": [
              "Every year, students across the UK answer the National Student Survey. The results are published as large spreadsheets.",
              "The published results are sorted by subject, not by course, and not by the school or faculty that runs the course. Somebody has to work out which numbers belong to whom before anyone can act on them."
            ]
          },
          {
            "heading": "The people",
            "paragraphs": [
              "Staff at the university who need to know how their own courses did, and what to do about it."
            ]
          },
          {
            "heading": "What I built",
            "paragraphs": [
              "A dashboard that takes the published results and works through all of them. I had it running within three hours of the results being published.",
              "It sorts the results by faculty, school and course, so each person sees their own part. For each one it picks out the three things students rated highest and the three they rated lowest, and names the question behind each.",
              "It compares this year with last year. It flags any course that has fallen well below the university's own figure, or dropped sharply since last year, and says why it was flagged.",
              "It reads what students wrote as well as what they scored. It gathers the comments into themes and counts how many students raised each one. Across nine years of comments it shows which themes come up every year, which are new and which are fading.",
              "Each course, school and faculty gets a short written summary. The outcome is a page a person can read, not another spreadsheet."
            ]
          },
          {
            "heading": "Keeping it right and keeping it private",
            "paragraphs": [
              "Every average on the dashboard is worked out a second time by a separate check before it goes out.",
              "The AI that reads the comments and writes the summaries runs on my own computer. No outside AI service ever saw what students wrote.",
              "Staff sign in with their university account. The dashboard shows summaries and counts. Students' own words are shown only to the senior staff allowed to read them."
            ]
          },
          {
            "heading": "Where it is now",
            "paragraphs": [
              "It has been in use since July 2026, for that year's results. Next year's results go in the same way."
            ]
          }
        ],
        "facts": [
          "Running within three hours of the national results being published.",
          "Reads the Office for Students' National Student Survey workbooks and full-data extract directly.",
          "Rolls up institution, faculty, school and course, with year-on-year comparison when two years are loaded.",
          "Concern flags: five points below the institution's own figure, below 70% overall, or a fall of more than five points in a year.",
          "Nine years of tagged free-text comments (2018 to 2026) analysed deterministically for consistent, new and receding themes.",
          "A local model through Ollama handles the sentiment analysis and writes the per-unit narratives, so no cloud AI service saw student comments. Output is paraphrase and counts only, scanned for personal details.",
          "Node and vanilla JavaScript ES modules, with tokenised CSS. No framework.",
          "Google sign-in limited to the institution's domain, verified server-side, with a signed session cookie. Raw comments sit behind an allowlist of senior staff.",
          "The test suite recomputes every school and faculty mean and standard deviation independently, and renders every view in jsdom.",
          "nginx and pm2 on a VPS."
        ]
      },
      {
        "slug": "geraldlarner-com",
        "name": "geraldlarner.com",
        "title": "geraldlarner.com: a public archive",
        "description": "How about 6,700 old word-processor files became geraldlarner.com, a free public archive of programme notes on 445 composers that anyone can search.",
        "published": "2026-10-02",
        "url": "https://geraldlarner.com",
        "kind": "A public, searchable archive, built from a pile of old files.",
        "plain": "About 6,700 old word-processor files, turned into a free public archive that anyone can search. It holds 5,388 programme notes on 445 composers.",
        "sections": [
          {
            "heading": "Who it is for",
            "paragraphs": [
              "Gerald Larner (1936 to 2018) was a music critic. Over five decades he wrote notes for concert programmes. The archive makes them free to performers, students and listeners."
            ]
          },
          {
            "heading": "What I started with",
            "paragraphs": [
              "About 6,700 old word-processor files, in folders by composer. They were in several old formats, some of which today's software cannot open."
            ]
          },
          {
            "heading": "What I built",
            "paragraphs": [
              "A set of steps that reads every file, whatever its format, and pulls out the writing. It works out which composer and which piece of music each note is about. It sets aside the files that are not notes at all, such as indexes and logs.",
              "Where the steps could not be sure, I decided by hand and recorded the decision. The whole archive can be rebuilt from the files and come out the same.",
              "A report lists every entry that still needs a human eye. It now lists none.",
              "Then the website: a page for each composer and each piece, and a search across every word.",
              "The site is small and quick. The pages are made in advance, so the server has very little to do when someone asks for one."
            ]
          },
          {
            "heading": "What it does",
            "paragraphs": [
              "Writing that sat in old files can now be read and searched by anyone: 5,388 notes on 3,522 pieces of music by 445 composers."
            ]
          },
          {
            "heading": "Where it is now",
            "paragraphs": [
              "It has been public since May 2026."
            ]
          }
        ],
        "facts": [
          "About 6,700 legacy word-processor files: WordPerfect, RTF (including old Nisus RTF) and Word.",
          "A Python pipeline converts them with headless LibreOffice, with its own parser for the files LibreOffice cannot read, then classifies each as a work, an essay or source material.",
          "Manual corrections are keyed by file path, so a rebuild is repeatable.",
          "5,388 notes on 3,522 works by 445 composers.",
          "A thin Express server over one SQLite file of pre-rendered pages, with full-text search and a JSON API."
        ]
      },
      {
        "slug": "alicedennis-net",
        "name": "alicedennis.net",
        "title": "alicedennis.net: a teacher's website",
        "description": "alicedennis.net is a website for a piano and singing teacher. It replaced one that was broken, and she updates it herself from her own editing page.",
        "published": "2026-10-02",
        "url": "https://alicedennis.net",
        "kind": "Site for a piano and singing teacher. Replaced a broken legacy site.",
        "plain": "A site for a piano and singing teacher. It replaced one that was broken. She updates it herself.",
        "sections": [
          {
            "heading": "Who it is for",
            "paragraphs": [
              "A piano and singing teacher in West Cornwall."
            ]
          },
          {
            "heading": "The problem",
            "paragraphs": [
              "Her old website was broken. Search results still pointed people at its old addresses."
            ]
          },
          {
            "heading": "What I built",
            "paragraphs": [
              "A new site with a page for each thing people look for: piano lessons, singing lessons, a gallery, how to find her and how to get in touch.",
              "It has its own editing page. She signs in with her Google account and changes the words, the photos and the reviews herself. Lesson lengths are entered once and appear on every page that needs them.",
              "Photos she adds are made smaller before they are sent, so the site stays quick.",
              "The old site's addresses still work. Each one leads to the page that replaced it, so nobody arriving from a search result meets a dead end.",
              "The contact form is guarded against junk in several ways. The rule I worked to: a lost enquiry costs her a pupil, and a junk message costs her ten seconds. So almost nothing is thrown away. A doubtful message is delivered with a label on it, not deleted."
            ]
          },
          {
            "heading": "What it does for her",
            "paragraphs": [
              "She updates her own site. Nobody has to do it for her."
            ]
          },
          {
            "heading": "Where it is now",
            "paragraphs": [
              "It has been live since September 2026."
            ]
          }
        ],
        "facts": [
          "A custom lightweight CMS with a built-in admin page and Google sign-in, so the owner edits it herself.",
          "One small Express 5 process. No database, no build step and no client framework: the content is one validated JSON file.",
          "Uploads are resized to WebP in the browser, so the server never decodes an image.",
          "Every page is server-rendered with its own title, description, canonical link and structured data. The old site's URLs get a 301 to the page that replaced them.",
          "Contact form: honeypot, signed timing cookie, rate limits, content scoring and blocklists. Suspect mail is flagged, not dropped.",
          "Deployed by GitHub Actions after lint and tests, through a key that can only run the deploy script.",
          "nginx and pm2 on my VPS."
        ]
      },
      {
        "slug": "morganharnett-tattoo",
        "name": "morganharnett.tattoo",
        "title": "morganharnett.tattoo: artist's site",
        "description": "morganharnett.tattoo is a tattoo artist's website. Its gallery fills itself from his Instagram, and clients book and pay a deposit on the site.",
        "published": "2026-10-02",
        "url": "https://morganharnett.tattoo",
        "kind": "Site and booking system for a tattoo artist, on a custom CMS written for the job.",
        "plain": "A site for a tattoo artist. It fills itself from his Instagram, so he updates one place. Clients ask for a tattoo, pick a time and pay a deposit on the site.",
        "sections": [
          {
            "heading": "Who it is for",
            "paragraphs": [
              "A tattoo artist in Southsea, Portsmouth."
            ]
          },
          {
            "heading": "What I built",
            "paragraphs": [
              "A site that fills itself from his Instagram. He posts his work there, and the site checks for new work four times a day and shows it.",
              "A booking system. A client describes the tattoo they want: the idea, where on the body and how big, with up to four reference photos. He reads it, and either declines or makes an offer.",
              "A client with an offer picks a time from the hours he is really free, and pays a deposit by card. The booking goes straight into his own calendar. Both of them get a confirmation, and a reminder goes out 48 hours before.",
              "Every picture has a written description for people who cannot see it. New pictures are described by AI, and he can rewrite any of them. Clients' names are kept out.",
              "It can be installed on a phone like an app."
            ]
          },
          {
            "heading": "What it does for him",
            "paragraphs": [
              "He updates one place. A request arrives with everything he needs to decide, and an accepted booking lands in his calendar without anyone retyping it."
            ]
          },
          {
            "heading": "Where it is now",
            "paragraphs": [
              "It has been live since October 2026."
            ]
          }
        ],
        "facts": [
          "It fills itself from Instagram, so the artist updates one place. The official Instagram API, synced every six hours in its own short-lived process.",
          "Booking flow: apply, offer, pick a slot, pay a deposit through Stripe Checkout. A slot is held for 35 minutes while the client pays.",
          "Availability is his working hours, minus his Apple Calendar over CalDAV, existing bookings and held slots.",
          "A Fastify API in TypeScript, run directly by Node with no server build step. State lives in one SQLite file.",
          "A React front end rendered on the server, so crawlers get real pages, then precached by a service worker as an installable PWA.",
          "Alt text for new images from Claude Haiku. The backlog was described locally with Ollama.",
          "Google sign-in for the admin. Deployed by GitHub Actions after tests."
        ]
      },
      {
        "slug": "photo-archive-for-a-school",
        "name": "Photo archive for a school",
        "title": "Photo archive for a school",
        "description": "A private photo archive for a school. Artists' photographs are kept in one place so that students can learn from them. Built and run by me.",
        "published": "2026-10-02",
        "url": null,
        "kind": "Private. Built on Directus.",
        "plain": "A private archive for a school. Artists' photographs are kept in one place so students can learn from them.",
        "sections": [
          {
            "heading": "Who it is for",
            "paragraphs": [
              "A school. The archive is private, so I do not name it. A teacher runs it, and students use it."
            ]
          },
          {
            "heading": "What I built",
            "paragraphs": [
              "A private archive where artists' photographs are kept and managed in one place: about 2,100 images.",
              "Each artist has a page with their work, a short biography, videos and a link to their own website. Students browse by genre, such as portraiture, landscape and documentary, or search the whole archive.",
              "The home page features an Artist of the Month, chosen by the teacher as part of the school's work on equality, diversity and inclusion. Every past choice is kept.",
              "The teacher adds artists and photographs from one editing screen, and sets the order of the genres.",
              "Only people the school allows can sign in. Every page carries a notice about copyright and fair dealing."
            ]
          },
          {
            "heading": "What it does for them",
            "paragraphs": [
              "Students can learn from the photographs. The teacher keeps the archive up to date without asking anyone."
            ]
          }
        ],
        "facts": [
          "Artists' photographs managed in one place so students can learn from them. About 2,100 images.",
          "Directus on Postgres for the data and the admin, with a custom interface extension for editing photographs. Schema snapshots are committed.",
          "A Vite and React front end in TypeScript, served as static files by nginx.",
          "Images in S3-compatible object storage.",
          "The whole site sits behind Google sign-in through oauth2-proxy. The Directus API is same-origin and private."
        ]
      },
      {
        "slug": "asica",
        "name": "ASICA",
        "title": "ASICA: a skin self-check research app",
        "description": "ASICA is a research app on a tablet that helps people treated for melanoma check their own skin. I built the prototype for a six-month NHS pilot.",
        "published": "2026-10-02",
        "url": null,
        "kind": "Research tablet app for melanoma self-monitoring. University of Aberdeen, published in BMJ Open in 2015.",
        "plain": "A research app on a tablet that helps people treated for melanoma check their own skin at home. Designed with patients and run in a six-month NHS pilot with twenty of them. I built the working prototype with a colleague.",
        "sections": [
          {
            "heading": "What it is",
            "paragraphs": [
              "A research project at the University of Aberdeen. ASICA is an app on a tablet that helps people who have been treated for melanoma, a skin cancer, to check their own skin at home every month.",
              "People who have had melanoma are asked to check their skin between hospital visits. The research found that they saw the point of it, but did not feel equipped to do it properly or to keep it up."
            ]
          },
          {
            "heading": "The people",
            "paragraphs": [
              "Patients. The app was designed with them. Before anything was built, the research team interviewed 21 people who had been treated for melanoma.",
              "They wanted to be shown how to check, to be reminded when, and to be able to report what they found to a specialist who would answer quickly.",
              "The whole process was then acted out in mock-up rooms by volunteers, a family doctor and a specialist nurse, to find the problems before any patient met them. The main lesson was to use clear, simple words, and the same words in the training as in the app."
            ]
          },
          {
            "heading": "What I built",
            "paragraphs": [
              "With a colleague, I turned the research team's design into a working app.",
              "Each month the patient gets a reminder. The app takes them through the check one part of the body at a time, with a short animated film and a checklist for each part. They can compare what they see with photographs of their own skin taken at the start.",
              "If something has changed, they mark where it is on a diagram, describe it, photograph it and send a report to a specialist nurse. The nurse phones them within 24 hours.",
              "With the same colleague, I wrote the training sessions and materials for patients, and I helped to deliver them. I was one of the four who gathered and analysed the results."
            ]
          },
          {
            "heading": "What happened",
            "paragraphs": [
              "Twenty people who had been treated for melanoma used it at home for six months, supported by their family doctors and a specialist nurse. It was a research pilot, not an NHS service.",
              "Fifteen of the twenty kept to their checks well. Four checked less regularly and one withdrew. Patients were largely positive about using it.",
              "Two people had skin surgery because of something they found during a check. One was a melanoma that had come back. The other was harmless.",
              "It was a small pilot, and it showed what to fix. The photographs patients sent were often not clear enough for the nurse to judge, so advice on taking a good one was added to the app."
            ]
          },
          {
            "heading": "Where it is now",
            "paragraphs": [
              "The pilot was published in the journal BMJ Open in 2015. A Cancer Research UK grant followed, to carry the work on."
            ]
          }
        ],
        "sources": [
          {
            "text": "Murchie P, Allan JL, Brant W, Dennis M and others. Total skin self-examination at home for people treated for cutaneous melanoma: development and pilot of a digital intervention. BMJ Open, 2015.",
            "href": "https://doi.org/10.1136/bmjopen-2015-007993"
          },
          {
            "text": "Cancer Research UK: a study using the ASICA app to help people who have had treatment for melanoma (ASICA Optimization Project).",
            "href": "https://www.cancerresearchuk.org/about-cancer/find-a-clinical-trial/a-study-using-the-asica-app-to-help-people-who-have-had-treatment-for-melanoma-asica-optimization"
          }
        ],
        "facts": [
          "Designed with patients and run in a six-month NHS pilot.",
          "An app for the Google Nexus 7 tablet. Reports, with photographs taken on the tablet, went to a secure remote server for review by a clinical nurse specialist.",
          "Developed under the Medical Research Council framework for complex interventions. Behaviour change techniques are built into the flow: prompts, demonstration, checklists and feedback.",
          "Pilot in NHS Grampian: 20 patients, six general practitioners and one nurse specialist. 15 adhered well. Two had lesions excised, one of them a recurrent melanoma.",
          "Murchie P, et al. BMJ Open 2015;5:e007993. Funded by the RCUK Digital Economy programme through the dot.rural hub at the University of Aberdeen."
        ]
      }
    ]
  },
  "technical": {
    "intro": "Six parts of the work, each with a page of its own.",
    "items": [
      {
        "slug": "payload-cms",
        "name": "Payload CMS",
        "title": "Payload CMS developer, UK",
        "description": "Payload CMS developer in the UK. I build and run production sites on Payload, including a charity's whole platform of five Node services on one server.",
        "lede": "I build and run production sites on Payload. The largest is a charity's whole platform: five Node services with their content in Payload, edited by the charity itself.",
        "sections": [
          {
            "heading": "What I do with Payload",
            "paragraphs": [
              "I model the content, set up who can edit what, and build the services around it: sign-in, payments, media and email. Editors get an admin they can use without me."
            ]
          },
          {
            "heading": "In production",
            "paragraphs": [
              "Solent Gay Men's Chorus is a registered charity. Everything it has online is built and run by me."
            ],
            "items": [
              "Five Node services: main website, box office, governance, members portal and identity. Content in Payload.",
              "A CMS the chorus edits itself.",
              "One identity service for every app, with passkey sign-in and a role hierarchy.",
              "All five share one 2GB server behind nginx, deployed from GitHub Actions."
            ]
          },
          {
            "heading": "How I build it",
            "paragraphs": [
              "Minimal technical debt. Small services with thin handlers, content as JSON over REST, pages put together in the browser from templates, and no build step on the server."
            ],
            "links": [
              {
                "text": "How I build, in full",
                "href": "/technical#how"
              }
            ]
          }
        ],
        "questions": [
          {
            "question": "Why Payload?",
            "answer": "It is a headless CMS written in TypeScript that keeps its configuration in code and runs in your own Node process. Editors get a clean admin. Developers get typed JSON."
          },
          {
            "question": "Do you work remotely?",
            "answer": "Yes. I work with organisations anywhere in the UK, and meet face to face within about 30 miles of Portsmouth."
          }
        ],
        "work": [
          "solent-gay-mens-chorus"
        ]
      },
      {
        "slug": "directus",
        "name": "Directus",
        "title": "Directus developer, UK",
        "description": "Directus developer in the UK. I build on Directus when the data comes first: a SQL database with a clean editing screen and a JSON API on top.",
        "lede": "I build on Directus when the data comes first: a SQL database that needs a clean editing screen and a JSON API on top.",
        "sections": [
          {
            "heading": "What I do with Directus",
            "paragraphs": [
              "I model the data, set up roles and permissions, handle the files, and build the front end that reads from the API."
            ]
          },
          {
            "heading": "In production",
            "paragraphs": [
              "A private photo archive for a school, built on Directus. Artists' photographs are managed in one place so students can learn from them."
            ]
          },
          {
            "heading": "When I choose it",
            "paragraphs": [
              "Directus sits on top of a SQL database and leaves the tables as they are. That suits collections, archives and records, where the data will outlive the app that shows it."
            ]
          }
        ],
        "questions": [
          {
            "question": "Payload or Directus?",
            "answer": "Payload when the content model belongs in code beside the app. Directus when the database is the thing that matters and it needs an admin and an API."
          }
        ],
        "work": [
          "photo-archive-for-a-school"
        ]
      },
      {
        "slug": "email-authentication",
        "name": "Email authentication",
        "title": "DMARC p=reject setup, SPF and DKIM",
        "description": "DMARC p=reject setup with SPF and DKIM aligned, without losing legitimate mail on the way. In production for a charity and for this domain.",
        "lede": "I take domains to DMARC p=reject with DKIM aligned, without losing legitimate mail on the way.",
        "sections": [
          {
            "heading": "What I set up",
            "paragraphs": [
              "Everything a receiving server checks."
            ],
            "items": [
              "SPF, trimmed to the hosts that really send.",
              "DKIM signing on every sending service, aligned with the From domain.",
              "DMARC with reporting, tightened to p=reject as the reports come clean.",
              "MTA-STS and TLS reporting.",
              "BIMI, once the policy allows it."
            ]
          },
          {
            "heading": "How I do it",
            "paragraphs": [
              "I list every sender first: the mailbox provider, the website, the newsletter tool, anything that sends as the domain. I fix alignment for each one.",
              "I publish DMARC with reporting, read the aggregate reports, and tighten the policy only when every legitimate source passes."
            ]
          },
          {
            "heading": "In production",
            "paragraphs": [
              "The email domain for Solent Gay Men's Chorus is at p=reject with DKIM aligned. So is this one. The policy for passionfruit.design is read live from DNS and shown in the Receipts."
            ],
            "links": [
              {
                "text": "The live DMARC policy, in the Receipts",
                "href": "/technical#receipts"
              }
            ]
          }
        ],
        "questions": [
          {
            "question": "Will p=reject block our own mail?",
            "answer": "Not if every sender is aligned first. That is what the reporting stage is for."
          },
          {
            "question": "What about forwarding and mailing lists?",
            "answer": "Forwarding breaks SPF but not DKIM. That is why I align DKIM on every sender and do not rely on SPF alone."
          }
        ],
        "work": [
          "solent-gay-mens-chorus"
        ]
      },
      {
        "slug": "passkeys-and-single-sign-on",
        "name": "Passkeys and single sign-on",
        "title": "Passkey login and single sign-on",
        "description": "Passkey login and single sign-on: one identity service for every app, with WebAuthn, OIDC and roles that match how the organisation works.",
        "lede": "One sign-in for every app, with passkeys instead of passwords and roles that match how the organisation works.",
        "sections": [
          {
            "heading": "What I build",
            "paragraphs": [
              "Identity as its own small service, shared by every app."
            ],
            "items": [
              "Passkey sign-in with WebAuthn.",
              "A central identity service using OIDC, so every app shares one sign-in.",
              "Role hierarchies that match the organisation.",
              "Google sign-in and magic links, where they fit better.",
              "Single sign-on with the accounts an institution already has."
            ]
          },
          {
            "heading": "In production",
            "paragraphs": [
              "Three systems, three kinds of sign-in."
            ],
            "items": [
              "Solent Gay Men's Chorus: one identity service for every app, with passkey sign-in and a role hierarchy.",
              "SUMS, a university marking platform: single sign-on and a full audit trail, in production since 2018.",
              "alicedennis.net: the owner signs in to her admin page with Google."
            ]
          }
        ],
        "questions": [
          {
            "question": "Why passkeys?",
            "answer": "There is nothing to remember and nothing to phish. The private key never leaves the user's device."
          }
        ],
        "work": [
          "solent-gay-mens-chorus",
          "sums",
          "alicedennis-net"
        ]
      },
      {
        "slug": "legacy-app-modernisation",
        "name": "Legacy apps to REST APIs",
        "title": "Legacy app modernisation to REST APIs",
        "description": "Legacy app modernisation: moving PHP, Laravel, CodeIgniter, Ruby on Rails and JSP applications from server-held state to true RESTful APIs.",
        "lede": "I help companies move applications that keep their state on the server to true RESTful API architectures. I know the frameworks they are moving from.",
        "sections": [
          {
            "heading": "What I know",
            "paragraphs": [
              "MVC applications, and the languages and frameworks they are written in."
            ],
            "items": [
              "PHP.",
              "Laravel.",
              "CodeIgniter.",
              "Ruby on Rails.",
              "JSP."
            ]
          },
          {
            "heading": "From server state to REST",
            "paragraphs": [
              "A server-state application keeps each user's session on the server and sends back whole pages. The interface and the logic are tied together.",
              "A RESTful API holds no session between requests. Each request carries what it needs. Each resource has its own address, and the HTTP methods and status codes mean what they say.",
              "Once the API exists, a website, a mobile app and other systems can all use the same one."
            ]
          },
          {
            "heading": "What I build",
            "paragraphs": [
              "REST and JSON APIs on Node, with Postgres or SQLite underneath."
            ],
            "links": [
              {
                "text": "How I build, in full",
                "href": "/technical#how"
              }
            ]
          }
        ],
        "questions": [
          {
            "question": "What makes an API truly RESTful?",
            "answer": "No session held on the server. Resources at stable addresses. HTTP methods and status codes used as they are defined. Many APIs that are called REST are remote procedure calls sent over HTTP."
          },
          {
            "question": "Can you work with our developers remotely?",
            "answer": "Yes. I work with organisations anywhere in the UK, and meet face to face within about 30 miles of Portsmouth."
          }
        ]
      },
      {
        "slug": "home-assistant",
        "name": "Home Assistant",
        "title": "Home Assistant setup and automation",
        "description": "Home Assistant setup and automation in Docker: Zigbee, MQTT and 433 MHz radio, presence lighting, heating, watchdogs and a nightly config backup.",
        "lede": "I build and run Home Assistant for cheaper, vendor-agnostic automation in homes and businesses. The proof is my own house.",
        "sections": [
          {
            "heading": "What I do with Home Assistant",
            "paragraphs": [
              "I bring devices from different makers under one local controller, write the automations, and keep the configuration recoverable."
            ]
          },
          {
            "heading": "In production",
            "paragraphs": [
              "My own house runs on it."
            ],
            "items": [
              "Home Assistant, Mosquitto and rtl_433 in Docker on one small home server.",
              "A Zigbee network of bulbs, buttons, switches, radiator valves and presence sensors.",
              "More than 60 automations and more than 60 scripts.",
              "Lighting that follows presence from radar and PIR sensors, gated on the light level in each room.",
              "Radiator valves that follow the wall thermostats.",
              "A doorbell received over 433 MHz radio, with a backup chime for when the internet is down.",
              "Away and holiday modes.",
              "Watchdogs for the radio, the sensors and the database, with alerts.",
              "Dashboards, including an occupancy view on a 3D model of the house."
            ]
          },
          {
            "heading": "Kept recoverable",
            "paragraphs": [
              "The server pushes its configuration to a private Git repository every night. A regression check fails on YAML errors, duplicate ids and calls to scripts that do not exist."
            ]
          },
          {
            "heading": "Why Home Assistant",
            "paragraphs": [
              "It is free and open source, it runs on hardware you own, and it talks to devices from many makers. Nothing is tied to one company's app."
            ]
          }
        ],
        "questions": [
          {
            "question": "Does Home Assistant need the cloud?",
            "answer": "Home Assistant itself runs locally. Zigbee, MQTT and 433 MHz devices need no cloud at all. Some makers' devices still go through their own service, and I say which."
          },
          {
            "question": "What does Home Assistant run on?",
            "answer": "A small computer or an old laptop. Mine is a 2018 laptop running Docker."
          }
        ]
      }
    ]
  },
  "notes": {
    "index": {
      "name": "Notes",
      "title": "Notes for small organisations",
      "description": "Short, plain notes for charities and small organisations on websites, email security and accessibility, with a source for every claim.",
      "lede": "Short notes on websites, email and accessibility for people who run small organisations. Every factual or legal claim has a source."
    },
    "feed": {
      "title": "Passionfruit Design notes",
      "subtitle": "Short notes on websites, email and accessibility for small organisations."
    },
    "items": []
  },
  "pages": {
    "colophon": {
      "name": "Colophon",
      "title": "Colophon",
      "description": "How passionfruit.design is built, in plain language: one small server, two dependencies, no tracking, and every change tested before it goes live.",
      "lede": "How this site is built. It follows the same rule as the work I do for clients: minimal technical debt.",
      "sections": [
        {
          "heading": "As little as possible",
          "paragraphs": [
            "Every dependency, build step and moving part is a future cost. Each one here has to pay for itself. The rest of this page is what that leaves.",
            "The code is linted. The content and every form field are checked. Each change is tested before it goes live. Dependencies are kept on current releases, with updates proposed automatically every week."
          ]
        },
        {
          "heading": "Words are data",
          "paragraphs": [
            "Every word on this site lives in a few JSON files. The server checks them when it starts and refuses to run if anything is missing.",
            "The same content is published as JSON, so you can read the whole site as data."
          ],
          "links": [
            {
              "text": "This site as JSON",
              "href": "/api/site.json"
            },
            {
              "text": "The live figures as JSON",
              "href": "/api/receipts.json"
            }
          ]
        },
        {
          "heading": "Made on the server",
          "paragraphs": [
            "One small Node process using Express builds each page and sends it complete. There is no framework, no bundler and no build step.",
            "It has two runtime dependencies: Express, and Nodemailer for the contact form. Everything else is a few lines written for the job.",
            "The page works with JavaScript turned off. A few lines of JavaScript make the contact form reply without reloading the page. That is all it does."
          ]
        },
        {
          "heading": "Easy to find, easy to read",
          "paragraphs": [
            "Every page has its own title, description, address and a summary that machines can read. The sitemap, the feed and a plain-text summary for AI tools are all made from the same content as the pages.",
            "Search engines and AI crawlers are welcome."
          ],
          "links": [
            {
              "text": "The sitemap",
              "href": "/sitemap.xml"
            },
            {
              "text": "The plain-text summary",
              "href": "/llms.txt"
            },
            {
              "text": "The feed",
              "href": "/notes/feed.xml"
            }
          ]
        },
        {
          "heading": "Locked down",
          "paragraphs": [
            "The server sets a strict content security policy. The browser may load nothing from anywhere but this domain, so third-party requests are impossible, not just absent.",
            "Other headers stop the site being framed, stop the browser guessing file types, send no referrer and turn off every browser feature the site does not use.",
            "The contact form checks where it was sent from, limits how often one address can use it and catches bots without a CAPTCHA."
          ],
          "links": [
            {
              "text": "Check the headers with Mozilla Observatory",
              "href": "https://developer.mozilla.org/en-US/observatory/analyze?host=passionfruit.design"
            },
            {
              "text": "Check TLS with SSL Labs",
              "href": "https://www.ssllabs.com/ssltest/analyze.html?d=passionfruit.design"
            }
          ]
        },
        {
          "heading": "Hosted on a small box",
          "paragraphs": [
            "The app runs under pm2 on a Linux VPS it shares with other production apps. nginx sits in front of it and handles TLS with a Let's Encrypt certificate.",
            "GitHub Actions lints and tests every change, checks accessibility, page weight and Lighthouse scores, then deploys. Nothing is built on the server."
          ]
        },
        {
          "heading": "AI-assisted, engineer-owned",
          "paragraphs": [
            "I use AI tools to write and review code faster. I read, test and answer for everything that goes live."
          ]
        },
        {
          "heading": "Type and colour",
          "paragraphs": [
            "The type is your device's own system font, so there is nothing to download. The colours come from the fruit: aubergine skin, yellow pulp, pale pith. Every pairing passes WCAG 2.2 AA contrast."
          ]
        }
      ]
    },
    "privacy": {
      "name": "Privacy",
      "title": "Privacy",
      "description": "What passionfruit.design collects, which is very little: no cookies, nothing from other companies, and nothing kept unless you send me a message.",
      "lede": "This site collects nothing unless you send me a message.",
      "sections": [
        {
          "heading": "Who I am",
          "paragraphs": [
            "Passionfruit Design is the trading name of Dr Matt Dennis. I am responsible for this site and for anything you send through it."
          ]
        },
        {
          "heading": "No cookies, no tracking",
          "paragraphs": [
            "This site sets no cookies and loads nothing from any other company. Nothing runs in your browser to watch what you do. That is why there is no cookie banner."
          ]
        },
        {
          "heading": "If you use the contact form",
          "paragraphs": [
            "Your name, email address and message are sent to me by email. The site does not store them.",
            "I use them only to reply and to handle any work that follows. I keep them in my mailbox for as long as that takes, and I never pass them on or add you to a mailing list."
          ]
        },
        {
          "heading": "If you message me on WhatsApp",
          "paragraphs": [
            "WhatsApp is run by Meta, not by me. If you message me there, Meta's privacy policy covers what they do with it. I use your messages only to reply and to handle any work that follows.",
            "The WhatsApp link on this site is an ordinary link. Nothing from WhatsApp or Meta loads unless you click it."
          ]
        },
        {
          "heading": "Server logs and counting visits",
          "paragraphs": [
            "Like every web server, mine records the address each request came from, the page asked for and the time. I use these logs to keep the site running and secure, and to count how many times each page is read.",
            "The counting happens on my own server, from those logs. Nothing is sent to anyone else and I do not follow individual visitors. The logs are deleted automatically within 14 days.",
            "Google and Bing also tell me which searches led people here. They collect that on their own search pages, not on this site."
          ]
        },
        {
          "heading": "Your rights",
          "paragraphs": [
            "You can ask me for a copy of anything I hold about you, or ask me to delete it, by email. If you are unhappy with how I handle your data, you can complain to the Information Commissioner's Office."
          ],
          "links": [
            {
              "text": "Information Commissioner's Office",
              "href": "https://ico.org.uk/make-a-complaint/"
            }
          ]
        }
      ]
    },
    "portsmouth": {
      "name": "Portsmouth",
      "title": "Web designer in Portsmouth, Hampshire",
      "description": "Web designer and developer in Portsmouth. I meet charities, schools and small businesses face to face within about 30 miles, and work UK-wide.",
      "lede": "I'm Matt, and I'm based in Portsmouth. I design, build and run websites and web apps for small organisations. If you are nearby, we can meet.",
      "sections": [
        {
          "heading": "Meeting in person",
          "paragraphs": [
            "I meet people face to face within about 30 miles of Portsmouth. That takes in Southsea, Gosport, Fareham, Havant, Waterlooville, Chichester, Southampton, Winchester and the Isle of Wight.",
            "I can come to you, see how your people work and talk it through across a table."
          ]
        },
        {
          "heading": "Further away",
          "paragraphs": [
            "I work with organisations anywhere in the UK by video call, phone and email."
          ]
        },
        {
          "heading": "Who I work with",
          "paragraphs": [
            "Charities, schools, community groups and small businesses. I'm a charity trustee myself, so I know how far the money has to go."
          ]
        },
        {
          "heading": "What I do",
          "paragraphs": [
            "Websites your own team can update. Web apps and booking systems. Card payments and ticket sales. Email that cannot be faked. Accessibility and user research. Research and data analysis. Hosting and care."
          ],
          "links": [
            {
              "text": "All services",
              "href": "/services"
            },
            {
              "text": "Work I have built and run",
              "href": "/work"
            }
          ]
        }
      ],
      "questions": [
        {
          "question": "Do you only work with organisations near Portsmouth?",
          "answer": "No. I meet people in person within about 30 miles of Portsmouth, and I work with organisations anywhere in the UK."
        },
        {
          "question": "Where are you based?",
          "answer": "Portsmouth, in Hampshire, on the south coast of England."
        }
      ]
    }
  }
}