# Stuff on Screen — website components

Version 1.0.0. Five browser components, no API key, no paid model calls.
Catalogue: https://tools.stuffonscreen.com/toolkit-manifest.json
Licence: https://tools.stuffonscreen.com/licence

## Contract

Each custom element accepts a data-config JSON object of string values. Use the
exact tag and script in the manifest. Scripts register once; multiple instances
are supported. The components use open Shadow DOM for style isolation.

On a public page, keep a readable Stuff on Screen credit within or immediately
beside the component. The built-in link already satisfies this condition.
Nofollow and sponsored link attributes are allowed. No site-wide link is needed.
Generated QR images, ICS files, calendar links and colour results require no credit.
Do not claim our source-available licence is an OSI-approved open-source licence.
Third-party MIT components keep their own licences.

## Plain HTML

Use a builder, update the preview, then copy its complete HTML. Escape the JSON
as an HTML attribute: quotes become &quot; and ampersands become &amp;.
Do not insert user data into unescaped HTML. In JavaScript, use setAttribute.
Load each component script once. New data-config values trigger a rerender.

## React / Next.js

Use a client wrapper. Load the script once with next/script in a Next.js layout
or page. Do not inject the script through dangerouslySetInnerHTML.

```tsx
"use client";
import {createElement} from "react";
import Script from "next/script";
export function ContrastWidget() {
  return <>
    <Script src="https://tools.stuffonscreen.com/widgets/v1.0.0/contrast-checker.js" strategy="afterInteractive" />
    {createElement("sos-contrast-checker", {
      "data-config": JSON.stringify({foreground:"#173f35",background:"#f6f1e8"})
    })}
  </>;
}
```

For ordinary React, put the external script in the document shell and render the
same custom element. createElement avoids requiring a custom JSX type declaration.

## Astro

Use the generated HTML. Add is:inline to the external script tag so Astro keeps
it as an ordinary browser script. The component itself needs no client directive.

## Shopify / WordPress / site builders

Shopify: paste into a Custom Liquid section or theme section. If building the
config from Liquid variables, JSON-encode and HTML-escape it.
WordPress: use a Custom HTML block with script permission, or enqueue the script
in the theme and put the custom element in the block. Not all hosted plans or
user roles allow scripts. Never disable a site's protections just to install it.
Webflow/Squarespace: use the platform's embed/code block on a supported plan.
Verify the published page, as the editor may not execute custom scripts.

## Local functions for agents and build scripts

Download the modules once; no hosted compute endpoint is required:
- https://tools.stuffonscreen.com/widgets/v1.0.0/core.mjs
- https://tools.stuffonscreen.com/widgets/v1.0.0/qr.mjs

Use Node 22+ or a modern browser. In Node, download files locally before importing.

```js
import { contrast, calendarIcs, calendarLinks, hoursStatus } from './core.mjs';
import { qrSvg } from './qr.mjs';
console.log(contrast('#000', '#fff')); // ratio 21, all text thresholds pass
console.log(qrSvg('https://example.com'));
const event = {title:'Demo',start:'2027-05-14T09:00:00+08:00',end:'2027-05-14T10:00:00+08:00',location:'Perth',description:''};
console.log(calendarLinks(event));
console.log(calendarIcs(event));
console.log(hoursStatus({name:'Shop',timezone:'Australia/Perth',mon:'09:00-17:00',tue:'09:00-17:00',wed:'09:00-17:00',thu:'09:00-17:00',fri:'09:00-17:00',sat:'closed',sun:'closed',exceptions:'{}'},new Date('2026-09-28T04:00:00Z')));
```

The functions throw on invalid input. Catch errors and show useful messages.
contrast returns the unrounded ratio and booleans for AA/AAA normal/large text.
calendarIcs uses explicit-offset instants, UTC DTSTART/DTEND, escaped text,
CRLF line endings and UTF-8-safe line folding. No attendees or invitations.
hoursStatus uses the supplied IANA time zone and Date; it returns open, date,
day (Sunday=0), minute, today, specialDate and week. Holiday exceptions replace
the whole local date. The device/runtime time-zone database determines DST.
qrSvg encodes text directly with medium error correction and a 4-module quiet zone.
The QR component depends on the MIT-licensed node-qrcode implementation.
Retain the licence and notices when redistributing the modules. Non-visual use
requires attribution in project documentation or credits.

## Hosting, security and privacy

Serve the ZIP example folder with any local HTTP server and open example.html.
The widget.js file is already bundled, without npm or a build step.
Source is included; to rebuild install qrcode 1.5.4 (QR only), TypeScript and esbuild.
An example build command from the source directory:
  npx esbuild widgets/qr-code.ts --bundle --platform=browser --format=iife --outfile=widget.js

Pinned v1.0.0 URLs do not silently opt you into a later major version. The manifest
provides SHA-384 integrity values. To use SRI on a hosted script, add the manifest's
integrity value and crossorigin="anonymous". Hosted scripts allow cross-origin reads.

CSP: allow the hosted domain in script-src and inline Shadow DOM styles in your
style policy. If your site disallows inline styles, adapt the included source to
an approved stylesheet instead of weakening the policy. Comparison image hosts
must be allowed by img-src. Components make no background fetch/XHR calls.
They do not use cookies or analytics. Hosted delivery still produces normal web
server access logs; image and calendar providers receive their relevant requests.
Browser JavaScript is required. Put essential business and event information in
normal HTML too. UI error handling does not replace validation of your own content.

## Acceptance checks

- Test keyboard navigation, focus visibility and a 320px-wide screen.
- Check opening hours at opening, closing, midnight, DST and a special date.
- Inspect calendar dates/offsets and import the downloaded ICS into your calendar.
- Scan the final QR at its printed size on multiple phones.
- Supply meaningful comparison text and check both images load.
- Keep the visible credit. Do not hide it with CSS, overlays or cropping.

The Content Effort checker is a separate server tool with public URL restrictions,
a versioned heuristic score and rate limits. Its API remains at /contentEffort/api.
