Embeddable corner widget with path-based profile matching and Short.io URL shortening integration.
- Path-based profile system - Widget auto-detects URL and loads matching profile
- Profile management UI - Dashboard for managing multiple widget profiles
- Domain + path matching rules - Define where each profile appears using patterns
- Short.io integration - One-click URL shortening in the config interface
- Configurable CTA text, accent color, and links
- Multiple link types (GitHub, Substack, generic links)
- Auto-detect icons from URLs
- Optional GitHub star button with live star count
-
Install dependencies:
bun install
-
Configure Short.io API (optional, for URL shortening):
cp .env.example .env
Edit
.envand add your Short.io API key:- Get your API key from: https://app.short.io/settings/integrations/api-key
- Update
SHORTIO_API_KEYwith your actual key - Update
SHORTIO_DOMAINwith your custom domain (default:l.jurrejan.com)
-
Start the dev server:
bun run dev
-
Open http://localhost:5173 to access the profile management UI
-
Build for production:
bun run build bun run build:widget
The widget uses a path-matching system to determine which profile to display:
- Profiles - Widget configurations stored in
profiles/profiles/*.json - Matching Rules - Domain + path patterns that determine when a profile appears
- API Lookup - Widget fetches config from
/api/widget-config?domain=X&pathname=Y - Specificity - Most specific rule wins (exact paths beat wildcards)
/blog/featured → Exact path (priority: 1200)
/blog/* → Single-level wildcard (priority: 110)
/blog/** → Multi-level wildcard (priority: 101)
/** → Catch-all (priority: 1)
Access the dashboard at http://localhost:5173 to:
- Create profiles - Define widget appearance and links
- Configure rules - Set domain and path patterns for each profile
- Test URLs - Verify which profile matches a given URL
- Get embed code - Single-line script tag with no configuration needed
Single line, no configuration - widget auto-detects URL:
<script src="https://your-host.com/widget.js"></script>The widget will:
- Detect the current page domain and path
- Call
/api/widget-config?domain=example.com&pathname=/blog/post - Load the matching profile configuration
- Hide itself if no profile matches
Still supported for backward compatibility:
<script src="https://your-host.com/widget.js"
data-links='[{"label":"My Project","url":"https://...","icon":"github"}]'
data-cta="Projects"
data-color="#e63946">
</script>src/
├── lib/
│ ├── server/profiles/ # Backend profile system
│ │ ├── types.ts # TypeScript types
│ │ ├── storage.ts # JSON file storage
│ │ └── matcher.ts # Path matching + specificity
│ └── widget/ # Widget components
│ ├── Widget.svelte # Main widget UI
│ ├── LinkItem.svelte # Link item component
│ ├── config.ts # Config parsing
│ ├── icons.ts # SVG icons
│ └── mount.ts # Widget initialization
├── routes/
│ ├── +page.svelte # Profile management UI
│ └── api/
│ ├── widget-config/ # Widget config endpoint
│ ├── profiles/ # Profile CRUD
│ ├── rules/ # Rule CRUD
│ └── shorten/ # Short.io integration
└── hooks.server.ts # CORS for widget embedding
profiles/
├── profiles/ # Profile JSON files
└── rules/ # Rule JSON files
GET /api/widget-config?domain=X&pathname=Y- Get matching profile configGET /api/profiles- List all profilesPOST /api/profiles- Create profilePUT /api/profiles/[id]- Update profileDELETE /api/profiles/[id]- Delete profileGET /api/rules?profileId=X- List rules for profilePOST /api/rules- Create rulePUT /api/rules/[id]- Update ruleDELETE /api/rules/[id]- Delete rulePOST /api/shorten- Shorten URL via Short.io
MIT