lostpage 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +24 -2
  2. package/dist/catalog.js +1 -1
  3. package/dist/catalog.json +1021 -4
  4. package/dist/cdn/c/broken-link-U47D6LR2.js +11 -0
  5. package/dist/cdn/c/brutalist-QFXIHFAD.js +15 -0
  6. package/dist/cdn/c/card-GH5VVMRG.js +15 -0
  7. package/dist/cdn/c/chunk-A44JMJWI.js +1 -0
  8. package/dist/cdn/c/chunk-QHXVYCR6.js +1 -0
  9. package/dist/cdn/c/chunk-YNREKTFR.js +70 -0
  10. package/dist/cdn/c/countdown-2TEFVO73.js +14 -0
  11. package/dist/cdn/c/divider-PMT4UZEF.js +9 -0
  12. package/dist/cdn/c/dot-grid-O7LJO44U.js +11 -0
  13. package/dist/cdn/c/editorial-4C5Z33WO.js +11 -0
  14. package/dist/cdn/c/emoji-EHAJDQSM.js +8 -0
  15. package/dist/cdn/c/gradient-404-YNSTOPNA.js +6 -0
  16. package/dist/cdn/c/helpful-links-UFGTWTRQ.js +18 -0
  17. package/dist/cdn/c/minimal-SB6FU3DE.js +9 -0
  18. package/dist/cdn/c/outline-404-REB5E5EN.js +9 -0
  19. package/dist/cdn/c/path-not-found-VUEXOVO7.js +14 -0
  20. package/dist/cdn/c/search-404-HFKZ4DUA.js +17 -0
  21. package/dist/cdn/c/server-default-RBMMRAHS.js +10 -0
  22. package/dist/cdn/c/soft-blobs-FYEFGINV.js +15 -0
  23. package/dist/cdn/c/split-panel-QGXZ24E4.js +12 -0
  24. package/dist/cdn/element.js +1 -1
  25. package/dist/cdn/index.js +1 -1
  26. package/dist/chunks/chunk-B2NBRLO2.js +1 -0
  27. package/dist/chunks/chunk-ES56TX4X.js +1 -0
  28. package/dist/chunks/chunk-URB3NNOB.js +70 -0
  29. package/dist/cli.js +1 -1
  30. package/dist/core/types.d.ts +1 -1
  31. package/dist/core.js +1 -1
  32. package/dist/generated/registry.d.ts +1 -1
  33. package/dist/index.js +1 -1
  34. package/dist/lostpage.global.js +401 -214
  35. package/dist/scenes/_simple.d.ts +10 -0
  36. package/dist/scenes/broken-link.d.ts +8 -0
  37. package/dist/scenes/broken-link.js +11 -0
  38. package/dist/scenes/brutalist.d.ts +7 -0
  39. package/dist/scenes/brutalist.js +15 -0
  40. package/dist/scenes/card.d.ts +8 -0
  41. package/dist/scenes/card.js +15 -0
  42. package/dist/scenes/countdown.d.ts +9 -0
  43. package/dist/scenes/countdown.js +14 -0
  44. package/dist/scenes/divider.d.ts +6 -0
  45. package/dist/scenes/divider.js +9 -0
  46. package/dist/scenes/dot-grid.d.ts +9 -0
  47. package/dist/scenes/dot-grid.js +11 -0
  48. package/dist/scenes/editorial.d.ts +6 -0
  49. package/dist/scenes/editorial.js +11 -0
  50. package/dist/scenes/emoji.d.ts +7 -0
  51. package/dist/scenes/emoji.js +8 -0
  52. package/dist/scenes/gradient-404.d.ts +9 -0
  53. package/dist/scenes/gradient-404.js +6 -0
  54. package/dist/scenes/helpful-links.d.ts +6 -0
  55. package/dist/scenes/helpful-links.js +18 -0
  56. package/dist/scenes/minimal.d.ts +6 -0
  57. package/dist/scenes/minimal.js +9 -0
  58. package/dist/scenes/outline-404.d.ts +8 -0
  59. package/dist/scenes/outline-404.js +9 -0
  60. package/dist/scenes/path-not-found.d.ts +6 -0
  61. package/dist/scenes/path-not-found.js +14 -0
  62. package/dist/scenes/search-404.d.ts +9 -0
  63. package/dist/scenes/search-404.js +17 -0
  64. package/dist/scenes/server-default.d.ts +6 -0
  65. package/dist/scenes/server-default.js +10 -0
  66. package/dist/scenes/soft-blobs.d.ts +9 -0
  67. package/dist/scenes/soft-blobs.js +15 -0
  68. package/dist/scenes/split-panel.d.ts +8 -0
  69. package/dist/scenes/split-panel.js +12 -0
  70. package/dist/vue.js +1 -1
  71. package/package.json +2 -2
  72. package/dist/cdn/c/chunk-JPP52QCZ.js +0 -70
  73. package/dist/chunks/chunk-UVHXBPO4.js +0 -70
@@ -0,0 +1,10 @@
1
+ import type { SceneContext } from "../core/types";
2
+ export declare const esc: (s: string) => string;
3
+ /** The current path, for "we couldn't find /x" lines. */
4
+ export declare const path: () => string;
5
+ /** Builds the preset/user action buttons wired to ctx.navigate. */
6
+ export declare function actions(ctx: SceneContext<any>, className?: string): HTMLDivElement;
7
+ /** Creates the scene's own content layer and removes it on destroy. */
8
+ export declare function layer(ctx: SceneContext<any>, className: string, html: string, vars?: Record<string, string | number>): HTMLDivElement;
9
+ /** Visually hidden "Error 404:" prefix so screen readers hear the status first. */
10
+ export declare const sr: (code: string) => string;
@@ -0,0 +1,8 @@
1
+ interface Params {
2
+ /** Line colour of the illustration. */
3
+ line: string;
4
+ /** Slowly swing the broken halves apart and back. */
5
+ swing: boolean;
6
+ }
7
+ declare const _default: import("..").SceneDef<Params>;
8
+ export default _default;
@@ -0,0 +1,11 @@
1
+ import"../chunks/chunk-KIYSQVWX.js";import"../chunks/chunk-7XMDQMDO.js";var broken_link_default={id:"broken-link",name:"Broken Link",description:"A minimal line illustration of a snapped chain link above the text. Says \u201Cbroken link\u201D without a single word.",category:"Simple",kind:"2d",motion:"live",tags:["simple","illustration","line-art","link","chain","fast","light"],layout:"center",copy:{eyebrow:"404",title:"This link is broken",message:"The page it pointed to has moved or no longer exists.",actions:[{label:"Back to home",href:"/"}]},params:{line:"#111827",swing:!0},theme:{bg:"#ffffff",fg:"#111827",accent:"#111827","accent-fg":"#ffffff"},presetName:"Ink",presets:{dark:{name:"Dark",params:{line:"#e5e7eb"},theme:{bg:"#0c0d10",fg:"#e5e7eb",accent:"#e5e7eb","accent-fg":"#0c0d10"}},teal:{name:"Teal",params:{line:"#0f766e"},theme:{bg:"#f0fdfa",fg:"#134e4a",accent:"#0f766e","accent-fg":"#fff"}}},css:`
2
+ & .bl{width:clamp(130px,18cqw,200px);margin:0 auto clamp(18px,4cqh,36px);pointer-events:none}
3
+ & .lp-stage>.bl{position:absolute;left:50%;top:20%;translate:-50% 0}
4
+ & .bl svg{width:100%;overflow:visible;fill:none;stroke:var(--ln);stroke-width:5;stroke-linecap:round;stroke-linejoin:round}
5
+ & .bl .sp{stroke-width:3;opacity:.6}
6
+ & .lp-code{display:none}
7
+ & .lp-title{font-weight:700;font-size:clamp(26px,4cqw,44px)}
8
+ `,setup(ctx){let el=document.createElement("div");el.className="bl",el.style.setProperty("--ln",ctx.params.line),el.innerHTML=`<svg viewBox="0 0 200 100" aria-hidden="true">
9
+ <g class="l"><path d="M88 34H52a16 16 0 0 0 0 32h36"/><path d="M60 50h20"/></g>
10
+ <g class="r"><path d="M112 34h36a16 16 0 0 1 0 32h-36"/><path d="M120 50h20"/></g>
11
+ <g class="sp"><path d="M100 18v8M88 22l4 6M112 22l-4 6M100 82v-8M88 78l4-6M112 78l-4-6"/></g></svg>`,(ctx.overlay?.querySelector(".lp-head")??ctx.stage).prepend(el);let l=el.querySelector(".l"),r=el.querySelector(".r");return{frame(t){if(!ctx.params.swing)return;let a=Math.sin(t*1.4)*5;l.setAttribute("transform",`translate(${-Math.abs(a)} 0) rotate(${-a} 88 50)`),r.setAttribute("transform",`translate(${Math.abs(a)} 0) rotate(${a} 112 50)`)},destroy:()=>el.remove()}}};export{broken_link_default as default};
@@ -0,0 +1,7 @@
1
+ interface Params {
2
+ block: string;
3
+ /** Hard shadow offset, in px. */
4
+ shadow: number;
5
+ }
6
+ declare const _default: import("..").SceneDef<Params>;
7
+ export default _default;
@@ -0,0 +1,15 @@
1
+ import{actions,esc,layer,sr}from"../chunks/chunk-ES56TX4X.js";import"../chunks/chunk-B2NBRLO2.js";import"../chunks/chunk-KIYSQVWX.js";import"../chunks/chunk-7XMDQMDO.js";var brutalist_default={id:"brutalist",name:"Brutalist",description:"Neo-brutalism: thick black borders, hard offset shadows, a loud colour block and chunky buttons that press in when you click.",category:"Simple",kind:"2d",motion:"static",tags:["simple","brutalism","neo-brutalist","bold","loud","static","fast","light"],layout:"none",copy:{eyebrow:"Whoops",title:"This page took a wrong turn.",message:"We broke something, or you typed something. Either way, nothing lives here.",actions:[{label:"Get me out",href:"/",icon:"none"},{label:"Report it",href:"/contact",variant:"secondary",icon:"none"}]},params:{block:"#ffde59",shadow:8},theme:{bg:"#fdf6e3",fg:"#111111",accent:"#ff6b6b","accent-fg":"#111111",font:'"Space Grotesk","Archivo","Inter",system-ui,sans-serif'},presetName:"Yellow",presets:{pink:{name:"Pink",params:{block:"#ff90e8"},theme:{bg:"#f4f4f0",accent:"#23a094","accent-fg":"#111"}},blue:{name:"Blue",params:{block:"#7aa2ff"},theme:{bg:"#fffbeb",accent:"#ffde59"}},green:{name:"Green",params:{block:"#b8f35e",shadow:6},theme:{bg:"#ffffff",accent:"#111","accent-fg":"#b8f35e"}}},css:`
2
+ & .br{position:absolute;inset:0;display:grid;place-items:center;padding:20px;overflow:auto}
3
+ & .br .box{width:min(640px,100%);border:3px solid #111;background:var(--lp-bg);box-shadow:var(--sh) var(--sh) 0 #111;padding:clamp(20px,4cqw,40px)}
4
+ & .br .num{display:inline-block;background:var(--blk);border:3px solid #111;box-shadow:calc(var(--sh) / 2) calc(var(--sh) / 2) 0 #111;padding:2px 18px;
5
+ font-weight:800;font-size:clamp(64px,12cqw,128px);line-height:1;letter-spacing:-.05em;transform:rotate(-2deg)}
6
+ & .br .ey{display:block;margin-top:26px;font-weight:700;text-transform:uppercase;letter-spacing:.1em;font-size:13px}
7
+ & .br h1{margin:8px 0 0;font-size:clamp(26px,4.2cqw,46px);font-weight:800;line-height:1.05;letter-spacing:-.03em}
8
+ & .br p{margin:14px 0 0;font-size:16px;line-height:1.5;max-width:46ch}
9
+ & .br .lp-actions{justify-content:flex-start;gap:14px}
10
+ & .br .lp-btn{border:3px solid #111;border-radius:0;padding:12px 20px;font-weight:800;font-size:15px;box-shadow:4px 4px 0 #111;transition:transform .08s,box-shadow .08s}
11
+ & .br .lp-btn--primary{background:var(--lp-accent);color:var(--lp-accent-fg)}
12
+ & .br .lp-btn--secondary{background:#fff;color:#111}
13
+ & .br .lp-btn:hover{transform:translate(-2px,-2px);box-shadow:6px 6px 0 #111}
14
+ & .br .lp-btn:active{transform:translate(4px,4px);box-shadow:0 0 0 #111}
15
+ `,setup(ctx){let c=ctx.copy,el=layer(ctx,"br",`<div class="box"><span class="num" aria-hidden="true">${esc(c.code)}</span><span class="ey">${esc(c.eyebrow)}</span><h1>${sr(c.code)}${esc(c.title)}</h1><p>${esc(c.message)}</p></div>`,{blk:ctx.params.block,sh:ctx.params.shadow+"px"});return el.querySelector(".box").append(actions(ctx)),{destroy:()=>el.remove()}}};export{brutalist_default as default};
@@ -0,0 +1,8 @@
1
+ interface Params {
2
+ /** Page colour around the card. */
3
+ page: string;
4
+ /** Icon in the badge: compass, search, link or alert. */
5
+ icon: string;
6
+ }
7
+ declare const _default: import("..").SceneDef<Params>;
8
+ export default _default;
@@ -0,0 +1,15 @@
1
+ import{actions,esc,layer,sr}from"../chunks/chunk-ES56TX4X.js";import"../chunks/chunk-B2NBRLO2.js";import"../chunks/chunk-KIYSQVWX.js";import"../chunks/chunk-7XMDQMDO.js";var ICONS={compass:'<circle cx="12" cy="12" r="9"/><path d="m15.5 8.5-2 5-5 2 2-5z"/>',search:'<circle cx="11" cy="11" r="6.5"/><path d="m20 20-4.2-4.2M9 9l4 4M13 9l-4 4"/>',link:'<path d="M10 14a4 4 0 0 0 5.7 0l3-3a4 4 0 0 0-5.7-5.7l-1 1M14 10a4 4 0 0 0-5.7 0l-3 3a4 4 0 0 0 5.7 5.7l1-1M4 4l16 16"/>',alert:'<path d="M12 3 2 20h20zM12 10v4M12 17v.5"/>'},card_default={id:"card",name:"Card",description:"A centred card with an icon badge, headline, message and buttons. Fits inside any dashboard or app shell without fighting its design.",category:"Simple",kind:"2d",motion:"static",tags:["simple","card","app","dashboard","saas","static","fast"],layout:"none",copy:{title:"Page not found",message:"The page you\u2019re looking for doesn\u2019t exist or you don\u2019t have access to it.",actions:[{label:"Back to dashboard",href:"/"},{label:"Get help",href:"/help",variant:"secondary",icon:"arrow"}]},params:{page:"#f4f5f7",icon:"compass"},theme:{bg:"#ffffff",fg:"#101828",accent:"#101828","accent-fg":"#ffffff",radius:"10px"},presetName:"Light",presets:{dark:{name:"Dark",params:{page:"#09090b"},theme:{bg:"#18181b",fg:"#fafafa",accent:"#fafafa","accent-fg":"#18181b"}},search:{name:"Search icon",params:{page:"#eef4ff",icon:"search"},theme:{accent:"#2563eb","accent-fg":"#fff"}},broken:{name:"Broken link",title:"This link is broken",params:{page:"#fff7ed",icon:"link"},theme:{accent:"#ea580c","accent-fg":"#fff"}}},css:`
2
+ &{background:var(--page)}
3
+ & .cr{position:absolute;inset:0;display:grid;place-items:center;padding:16px;overflow:auto}
4
+ & .cr .box{width:min(440px,100%);background:var(--lp-bg);border-radius:16px;padding:clamp(24px,4cqw,40px);text-align:center;
5
+ border:1px solid color-mix(in srgb,var(--lp-fg) 9%,transparent);box-shadow:0 1px 2px rgba(16,24,40,.05),0 20px 40px -20px rgba(16,24,40,.18)}
6
+ & .cr .ic{width:52px;height:52px;margin:0 auto 18px;border-radius:14px;display:grid;place-items:center;
7
+ background:color-mix(in srgb,var(--lp-accent) 10%,transparent);color:var(--lp-accent);outline:8px solid color-mix(in srgb,var(--lp-accent) 5%,transparent)}
8
+ & .cr .ic svg{width:26px;height:26px;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
9
+ & .cr .code{font-size:12.5px;font-weight:600;color:var(--lp-muted)}
10
+ & .cr h1{margin:6px 0 0;font-size:clamp(22px,2.6cqw,28px);font-weight:700;letter-spacing:-.02em}
11
+ & .cr p{margin:10px 0 0;color:var(--lp-muted);font-size:14.5px;line-height:1.6}
12
+ & .cr .lp-actions{margin-top:26px;flex-direction:column-reverse;gap:10px;align-items:stretch}
13
+ & .cr .lp-btn{justify-content:center;font-weight:600}
14
+ & .cr .lp-btn--secondary{border:1px solid color-mix(in srgb,var(--lp-fg) 16%,transparent);border-radius:var(--lp-radius);padding:12px 20px}
15
+ `,setup(ctx){let c=ctx.copy;ctx.root.style.setProperty("--page",ctx.params.page);let el=layer(ctx,"cr",`<div class="box"><div class="ic" aria-hidden="true"><svg viewBox="0 0 24 24">${ICONS[ctx.params.icon]??ICONS.compass}</svg></div><span class="code">Error ${esc(c.code)}</span><h1>${sr(c.code)}${esc(c.title)}</h1><p>${esc(c.message)}</p></div>`);return el.querySelector(".box").append(actions(ctx)),{destroy:()=>el.remove()}}};export{card_default as default};
@@ -0,0 +1,9 @@
1
+ interface Params {
2
+ /** Seconds before going home. */
3
+ seconds: number;
4
+ /** Actually follow the first button's link when the timer ends. Off by default so previews don't jump. */
5
+ redirect: boolean;
6
+ ring: string;
7
+ }
8
+ declare const _default: import("..").SceneDef<Params>;
9
+ export default _default;
@@ -0,0 +1,14 @@
1
+ import{esc,layer,sr}from"../chunks/chunk-ES56TX4X.js";import"../chunks/chunk-B2NBRLO2.js";import"../chunks/chunk-KIYSQVWX.js";import"../chunks/chunk-7XMDQMDO.js";var countdown_default={id:"countdown",name:"Countdown",description:"\u201CTaking you home in 10 seconds.\u201D A progress ring counts down; set redirect to true and it follows your first button when it hits zero.",category:"Simple",kind:"2d",motion:"live",tags:["simple","redirect","timer","countdown","useful","fast"],layout:"none",copy:{title:"This page doesn\u2019t exist",message:"We\u2019ll take you back to the homepage in a moment.",actions:[{label:"Go now",href:"/",icon:"arrow"}]},params:{seconds:10,redirect:!1,ring:"#2563eb"},theme:{bg:"#ffffff",fg:"#0f172a",accent:"#2563eb","accent-fg":"#ffffff",radius:"10px"},stillTime:3,presetName:"Light",presets:{dark:{name:"Dark",params:{ring:"#34d399"},theme:{bg:"#06110d",fg:"#ecfdf5",accent:"#34d399","accent-fg":"#06110d"}},quick:{name:"Quick (5s)",params:{seconds:5,ring:"#f97316"},theme:{bg:"#fff7ed",fg:"#431407",accent:"#f97316","accent-fg":"#fff"}}},css:`
2
+ & .cd{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:24px 16px}
3
+ & .cd .ring{position:relative;width:clamp(110px,16cqw,150px);aspect-ratio:1;margin-bottom:28px}
4
+ & .cd svg{width:100%;height:100%;transform:rotate(-90deg)}
5
+ & .cd circle{fill:none;stroke-width:6}
6
+ & .cd .tr{stroke:color-mix(in srgb,var(--lp-fg) 10%,transparent)}
7
+ & .cd .pg{stroke:var(--ring);stroke-linecap:round}
8
+ & .cd .n{position:absolute;inset:0;display:grid;place-items:center;font-size:clamp(34px,5cqw,48px);font-weight:700;font-variant-numeric:tabular-nums}
9
+ & .cd .code{font-size:12.5px;font-weight:700;letter-spacing:.14em;color:var(--lp-muted)}
10
+ & .cd h1{margin:8px 0 0;font-size:clamp(26px,4cqw,44px);font-weight:700;letter-spacing:-.03em;line-height:1.1}
11
+ & .cd p{margin:12px 0 0;color:var(--lp-muted);font-size:15px}
12
+ & .cd a{margin-top:24px;display:inline-flex;align-items:center;gap:8px;padding:12px 20px;border-radius:var(--lp-radius);background:var(--lp-accent);color:var(--lp-accent-fg);text-decoration:none;font-weight:600;font-size:14px}
13
+ `,setup(ctx){let p=ctx.params,c=ctx.copy,go=c.actions[0]??{label:"Go home",href:"/"},R=44,L=2*Math.PI*R,el=layer(ctx,"cd",`<div class="ring" aria-hidden="true"><svg viewBox="0 0 100 100"><circle class="tr" cx="50" cy="50" r="${R}"/><circle class="pg" cx="50" cy="50" r="${R}" stroke-dasharray="${L}"/></svg><span class="n"></span></div>
14
+ <span class="code">ERROR ${esc(c.code)}</span><h1>${sr(c.code)}${esc(c.title)}</h1><p>${esc(c.message)}</p><a href="${esc(go.href??"/")}">${esc(go.label)} \u2192</a>`,{ring:p.ring}),pg=el.querySelector(".pg"),n=el.querySelector(".n");el.querySelector("a").addEventListener("click",e=>{e.metaKey||e.ctrlKey||(e.preventDefault(),ctx.navigate(go.href??"/",e))});let done=!1;return{frame(t){let left=Math.max(0,p.seconds-t);n.textContent=String(Math.ceil(left)),pg.style.strokeDashoffset=String(L*(1-left/p.seconds)),left===0&&!done&&(done=!0,p.redirect&&!ctx.reducedMotion&&ctx.navigate(go.href??"/"))},destroy:()=>el.remove()}}};export{countdown_default as default};
@@ -0,0 +1,6 @@
1
+ interface Params {
2
+ /** Font size of the code and message, in px. */
3
+ size: number;
4
+ }
5
+ declare const _default: import("..").SceneDef<Params>;
6
+ export default _default;
@@ -0,0 +1,9 @@
1
+ import{esc,layer}from"../chunks/chunk-ES56TX4X.js";import"../chunks/chunk-B2NBRLO2.js";import"../chunks/chunk-KIYSQVWX.js";import"../chunks/chunk-7XMDQMDO.js";var divider_default={id:"divider",name:"Divider",description:"\u201C404 | This page could not be found.\u201D The tiny, honest framework-default look: code and message on one line, split by a thin rule.",category:"Simple",kind:"2d",motion:"static",tags:["simple","minimal","nextjs","default","static","fast","developer"],layout:"none",copy:{title:"This page could not be found.",actions:[{label:"Home",href:"/"}]},params:{size:14},theme:{bg:"#ffffff",fg:"#000000"},presetName:"Light",presets:{dark:{name:"Dark",theme:{bg:"#000000",fg:"#ffffff"}},mono:{name:"Mono",params:{size:13},theme:{bg:"#f6f6f4",fg:"#222",font:"ui-monospace,SFMono-Regular,Menlo,monospace"}},large:{name:"Large",params:{size:22},theme:{bg:"#111318",fg:"#e8eaf0"}}},css:`
2
+ & .dv{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:28px;font-size:calc(var(--s) * 1px);padding:16px}
3
+ & .dv .row{display:flex;align-items:center}
4
+ & .dv .code{margin:0 20px 0 0;padding-right:23px;font-size:calc(var(--s) * 1.7px);font-weight:500;line-height:49px;border-right:1px solid color-mix(in srgb,var(--lp-fg) 30%,transparent)}
5
+ & .dv h1{margin:0;font-size:inherit;font-weight:400;line-height:49px}
6
+ & .dv a{color:color-mix(in srgb,var(--lp-fg) 55%,transparent);text-decoration:none;font-size:.9em}
7
+ & .dv a:hover{color:var(--lp-fg);text-decoration:underline}
8
+ @container (max-width:380px){& .dv .row{flex-direction:column}& .dv .code{border:0;margin:0;padding:0}}
9
+ `,setup(ctx){let c=ctx.copy,el=layer(ctx,"dv",`<div class="row"><p class="code" aria-hidden="true">${esc(c.code)}</p><h1><span class="lp-sr">Error ${esc(c.code)}: </span>${esc(c.title)}</h1></div>`,{s:ctx.params.size});for(let a of c.actions){let link=document.createElement("a");link.href=a.href??"/",link.textContent=a.label,link.addEventListener("click",e=>{e.metaKey||e.ctrlKey||(e.preventDefault(),ctx.navigate(link.getAttribute("href"),e))}),el.append(link)}return{destroy:()=>el.remove()}}};export{divider_default as default};
@@ -0,0 +1,9 @@
1
+ interface Params {
2
+ dot: string;
3
+ /** Distance between dots, in px. */
4
+ gap: number;
5
+ /** Use lines instead of dots. */
6
+ lines: boolean;
7
+ }
8
+ declare const _default: import("..").SceneDef<Params>;
9
+ export default _default;
@@ -0,0 +1,11 @@
1
+ import"../chunks/chunk-KIYSQVWX.js";import"../chunks/chunk-7XMDQMDO.js";var dot_grid_default={id:"dot-grid",name:"Dot Grid",description:"A dotted (or lined) grid that fades out towards the edges, with a small status pill above the headline. The quiet developer-tool look.",category:"Simple",kind:"2d",motion:"static",tags:["simple","grid","dots","developer","saas","static","fast"],layout:"center",copy:{eyebrow:"404 error",title:"We can\u2019t find that page",message:"Check the URL for typos, or head back to the dashboard.",actions:[{label:"Back to dashboard",href:"/"},{label:"Documentation",href:"/docs",variant:"secondary",icon:"arrow"}]},params:{dot:"#cbd5e1",gap:22,lines:!1},theme:{bg:"#ffffff",fg:"#0f172a",accent:"#0f172a","accent-fg":"#ffffff",radius:"10px"},presetName:"Light",presets:{dark:{name:"Dark",params:{dot:"#273043"},theme:{bg:"#0a0d14",fg:"#f1f5f9",accent:"#f1f5f9","accent-fg":"#0a0d14"}},lines:{name:"Lines",params:{lines:!0,gap:40,dot:"#e5e7eb"}},blueprint:{name:"Blueprint",params:{lines:!0,gap:28,dot:"#2f5fb3"},theme:{bg:"#1b3f86",fg:"#ffffff",accent:"#ffffff","accent-fg":"#1b3f86"}}},css:`
2
+ & .dg{position:absolute;inset:0;-webkit-mask-image:radial-gradient(ellipse 60% 55% at 50% 50%,#000 30%,transparent 100%);mask-image:radial-gradient(ellipse 60% 55% at 50% 50%,#000 30%,transparent 100%)}
3
+ & .dg.dots{background-image:radial-gradient(var(--dot) 1.2px,transparent 1.6px);background-size:var(--gap) var(--gap);background-position:center}
4
+ & .dg.lines{background-image:linear-gradient(var(--dot) 1px,transparent 1px),linear-gradient(90deg,var(--dot) 1px,transparent 1px);background-size:var(--gap) var(--gap);background-position:center}
5
+ & .lp-code{display:none}
6
+ & .lp-eyebrow{letter-spacing:.04em;text-transform:none;font-size:12.5px;font-weight:600;color:var(--lp-fg);padding:6px 12px;border-radius:999px;
7
+ border:1px solid color-mix(in srgb,var(--lp-fg) 14%,transparent);background:color-mix(in srgb,var(--lp-bg) 80%,transparent);margin-bottom:22px}
8
+ & .lp-title{font-weight:700;font-size:clamp(28px,4.6cqw,54px)}
9
+ & .lp-btn--primary{font-weight:600}
10
+ & .lp-btn--secondary{border:0;font-weight:600}
11
+ `,setup(ctx){let p=ctx.params,el=document.createElement("div");return el.className="dg "+(p.lines?"lines":"dots"),el.style.setProperty("--dot",p.dot),el.style.setProperty("--gap",p.gap+"px"),ctx.stage.append(el),{destroy:()=>el.remove()}}};export{dot_grid_default as default};
@@ -0,0 +1,6 @@
1
+ interface Params {
2
+ /** Line above the headline, like a magazine section label. */
3
+ section: string;
4
+ }
5
+ declare const _default: import("..").SceneDef<Params>;
6
+ export default _default;
@@ -0,0 +1,11 @@
1
+ import{actions,esc,layer,sr}from"../chunks/chunk-ES56TX4X.js";import"../chunks/chunk-B2NBRLO2.js";import"../chunks/chunk-KIYSQVWX.js";import"../chunks/chunk-7XMDQMDO.js";var editorial_default={id:"editorial",name:"Editorial",description:"A calm, magazine-style page: an elegant italic serif headline, a hairline rule and understated links. For blogs, studios and writers.",category:"Simple",kind:"2d",motion:"static",tags:["simple","serif","editorial","blog","elegant","static","fast","light"],layout:"none",copy:{title:"The page you were reading has gone out of print.",message:"Perhaps it was moved to the archive, or perhaps it was never written.",actions:[{label:"Return to the front page",href:"/",icon:"none"},{label:"Browse the archive",href:"/archive",variant:"secondary",icon:"none"}]},params:{section:"Chapter 404"},theme:{bg:"#f7f4ee",fg:"#1d1b18",accent:"#1d1b18","accent-fg":"#f7f4ee","font-display":'"Fraunces","Playfair Display","Iowan Old Style",Georgia,serif'},presetName:"Ivory",presets:{night:{name:"Night",theme:{bg:"#14120f",fg:"#ede6da",accent:"#ede6da","accent-fg":"#14120f"}},rose:{name:"Rose",params:{section:"Errata \xB7 No. 404"},title:"We seem to have misplaced this page.",theme:{bg:"#f8ece9",fg:"#3d1a16",accent:"#9f2a20","accent-fg":"#fff"}}},css:`
2
+ & .ed{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;padding:clamp(24px,7cqw,110px) 20px;overflow:auto}
3
+ & .ed .in{width:min(760px,100%)}
4
+ & .ed .sec{display:flex;align-items:center;gap:14px;font-size:12px;letter-spacing:.24em;text-transform:uppercase;color:var(--lp-muted)}
5
+ & .ed .sec::after{content:"";flex:1;height:1px;background:color-mix(in srgb,var(--lp-fg) 25%,transparent)}
6
+ & .ed h1{margin:26px 0 0;font-family:var(--lp-font-display);font-style:italic;font-weight:400;font-size:clamp(34px,6cqw,76px);line-height:1.04;letter-spacing:-.02em;text-wrap:balance}
7
+ & .ed p{margin:24px 0 0;max-width:46ch;font-family:var(--lp-font-display);font-size:clamp(16px,1.6cqw,20px);line-height:1.55;color:var(--lp-muted)}
8
+ & .ed .lp-actions{justify-content:flex-start;margin-top:34px}
9
+ & .ed .lp-btn--primary{border-radius:0;padding:14px 22px;letter-spacing:.06em;text-transform:uppercase;font-size:12px}
10
+ & .ed .lp-btn--secondary{letter-spacing:.06em;text-transform:uppercase;font-size:12px}
11
+ `,setup(ctx){let c=ctx.copy,el=layer(ctx,"ed",`<div class="in"><div class="sec">${esc(ctx.params.section)}</div><h1>${sr(c.code)}${esc(c.title)}</h1><p>${esc(c.message)}</p></div>`);return el.querySelector(".in").append(actions(ctx)),{destroy:()=>el.remove()}}};export{editorial_default as default};
@@ -0,0 +1,7 @@
1
+ interface Params {
2
+ emoji: string;
3
+ /** Gentle bob animation. */
4
+ bob: boolean;
5
+ }
6
+ declare const _default: import("..").SceneDef<Params>;
7
+ export default _default;
@@ -0,0 +1,8 @@
1
+ import"../chunks/chunk-KIYSQVWX.js";import"../chunks/chunk-7XMDQMDO.js";var emoji_default={id:"emoji",name:"Emoji",description:"One big emoji does the talking. Friendly, instantly readable, and swaps to suit your brand with a single parameter.",category:"Simple",kind:"2d",motion:"live",tags:["simple","emoji","friendly","playful","fast","light"],layout:"center",copy:{eyebrow:"404",title:"Hmm, that page wandered off.",message:"We looked everywhere. It\u2019s not here.",actions:[{label:"Take me home",href:"/",icon:"home"}]},params:{emoji:"\u{1F914}",bob:!0},theme:{bg:"#fffdf7",fg:"#1f2937",accent:"#1f2937","accent-fg":"#fffdf7"},presetName:"Thinking",presets:{ufo:{name:"UFO",title:"Abducted by aliens.",message:"This page was beamed up. We\u2019re negotiating its return.",params:{emoji:"\u{1F6F8}"},theme:{bg:"#0b0b1a",fg:"#e0e7ff",accent:"#a5b4fc","accent-fg":"#0b0b1a"}},ghost:{name:"Ghost",title:"Nobody here but us ghosts.",message:"This page has passed on.",params:{emoji:"\u{1F47B}"},theme:{bg:"#f5f3ff",fg:"#2e1065",accent:"#7c3aed","accent-fg":"#fff"}},desert:{name:"Desert",title:"It\u2019s a desert out here.",message:"Nothing for miles. Let\u2019s head back.",params:{emoji:"\u{1F335}"},theme:{bg:"#fef3c7",fg:"#451a03",accent:"#b45309","accent-fg":"#fff"}},melting:{name:"Melting",title:"Well, this is awkward.",message:"The page you wanted has melted away.",params:{emoji:"\u{1FAE0}",bob:!1},theme:{bg:"#ecfeff",fg:"#083344",accent:"#0e7490","accent-fg":"#fff"}}},css:`
2
+ & .em{display:grid;place-items:center;margin-bottom:clamp(16px,3.5cqh,32px);pointer-events:none}
3
+ & .lp-stage>.em{position:absolute;left:50%;top:18%;translate:-50% 0}
4
+ & .em span{font-size:clamp(80px,14cqw,150px);line-height:1;font-family:"Apple Color Emoji","Segoe UI Emoji","Noto Color Emoji",sans-serif;filter:drop-shadow(0 18px 16px rgba(0,0,0,.12))}
5
+ & .lp-code{display:none}
6
+ & .lp-title{font-weight:700;font-size:clamp(26px,4.2cqw,46px)}
7
+ & .lp-eyebrow{letter-spacing:.2em}
8
+ `,setup(ctx){let el=document.createElement("div");el.className="em",el.setAttribute("aria-hidden","true");let s=document.createElement("span");return s.textContent=ctx.params.emoji,el.append(s),(ctx.overlay?.querySelector(".lp-head")??ctx.stage).prepend(el),{frame(t){ctx.params.bob&&(s.style.transform=`translateY(${Math.sin(t*1.6)*8}px) rotate(${Math.sin(t*.8)*4}deg)`)},destroy:()=>el.remove()}}};export{emoji_default as default};
@@ -0,0 +1,9 @@
1
+ interface Params {
2
+ from: string;
3
+ via: string;
4
+ to: string;
5
+ /** Soft colour glow behind the number. */
6
+ glow: boolean;
7
+ }
8
+ declare const _default: import("..").SceneDef<Params>;
9
+ export default _default;
@@ -0,0 +1,6 @@
1
+ import"../chunks/chunk-KIYSQVWX.js";import"../chunks/chunk-7XMDQMDO.js";var gradient_404_default={id:"gradient-404",name:"Gradient",description:"The modern SaaS 404: a heavy gradient-filled number with a soft glow, a short headline and a pill button.",category:"Simple",kind:"2d",motion:"static",tags:["simple","gradient","saas","startup","static","fast","modern"],layout:"center",copy:{eyebrow:"",title:"Page not found",message:"The link may be broken, or the page may have been removed.",actions:[{label:"Take me home",href:"/"},{label:"Help center",href:"/help",variant:"secondary"}]},params:{from:"#6366f1",via:"#a855f7",to:"#ec4899",glow:!0},theme:{bg:"#ffffff",fg:"#0f172a",accent:"#0f172a","accent-fg":"#ffffff","code-size":"clamp(110px,24cqw,260px)","code-weight":"800"},presetName:"Violet",presets:{night:{name:"Night",params:{from:"#22d3ee",via:"#6366f1",to:"#d946ef"},theme:{bg:"#070a13",fg:"#e2e8f0",accent:"#e2e8f0","accent-fg":"#070a13"}},sunset:{name:"Sunset",params:{from:"#f59e0b",via:"#ef4444",to:"#db2777"},theme:{bg:"#fffaf5",fg:"#2a1406",accent:"#2a1406","accent-fg":"#fffaf5"}},mint:{name:"Mint",params:{from:"#10b981",via:"#06b6d4",to:"#3b82f6",glow:!1},theme:{bg:"#f8fafc",fg:"#0f172a",accent:"#0f172a","accent-fg":"#fff"}}},css:`
2
+ & .lp-code{background:linear-gradient(120deg,var(--g1),var(--g2),var(--g3));-webkit-background-clip:text;background-clip:text;color:transparent;letter-spacing:-.06em;line-height:.95;padding:0 .04em}
3
+ & .gr-glow{position:absolute;left:50%;top:38%;width:min(70cqw,760px);aspect-ratio:2/1;translate:-50% -50%;filter:blur(70px);opacity:.28;
4
+ background:radial-gradient(closest-side,var(--g1),transparent),radial-gradient(closest-side at 70% 50%,var(--g3),transparent)}
5
+ & .lp-title{font-weight:700;font-size:clamp(24px,3.6cqw,44px)}
6
+ `,setup(ctx){let p=ctx.params;if(ctx.root.style.setProperty("--g1",p.from),ctx.root.style.setProperty("--g2",p.via),ctx.root.style.setProperty("--g3",p.to),!p.glow)return;let glow=document.createElement("div");return glow.className="gr-glow",ctx.stage.append(glow),{destroy:()=>glow.remove()}}};export{gradient_404_default as default};
@@ -0,0 +1,6 @@
1
+ interface Params {
2
+ /** Rows of "Title|Description|/href". */
3
+ links: string[];
4
+ }
5
+ declare const _default: import("..").SceneDef<Params>;
6
+ export default _default;
@@ -0,0 +1,18 @@
1
+ import{actions,esc,layer,sr}from"../chunks/chunk-ES56TX4X.js";import"../chunks/chunk-B2NBRLO2.js";import"../chunks/chunk-KIYSQVWX.js";import"../chunks/chunk-7XMDQMDO.js";var helpful_links_default={id:"helpful-links",name:"Helpful Links",description:"Headline on top, then a tidy list of where people probably wanted to go, each with a one-line description and an arrow.",category:"Simple",kind:"2d",motion:"static",tags:["simple","links","list","useful","saas","docs","static","fast"],layout:"none",copy:{eyebrow:"404 error",title:"This page does not exist",message:"Sorry, we couldn\u2019t find the page you\u2019re looking for. These might help:",actions:[{label:"Back to home",href:"/"}]},params:{links:["Documentation|Learn how to integrate our tools with your app.|/docs","API reference|A complete reference of every endpoint.|/docs/api","Guides|Step-by-step tutorials for common tasks.|/guides","Blog|Read our latest news and articles.|/blog"]},theme:{bg:"#ffffff",fg:"#111827",accent:"#4f46e5","accent-fg":"#ffffff"},presetName:"Light",presets:{dark:{name:"Dark",theme:{bg:"#0f1117",fg:"#e5e7eb",accent:"#a5b4fc","accent-fg":"#0f1117"}},sand:{name:"Sand",theme:{bg:"#f6f1e9",fg:"#292018",accent:"#b45309","accent-fg":"#fff"}}},css:`
2
+ & .hl{position:absolute;inset:0;overflow:auto;display:flex;justify-content:center;padding:clamp(28px,8cqh,96px) 16px}
3
+ & .hl .in{width:min(560px,100%);margin:auto 0}
4
+ & .hl .ey{font-size:13px;font-weight:600;color:var(--lp-accent)}
5
+ & .hl h1{margin:8px 0 0;font-size:clamp(28px,4.4cqw,48px);font-weight:700;letter-spacing:-.03em;line-height:1.1}
6
+ & .hl p{margin:14px 0 0;color:var(--lp-muted);font-size:15px;line-height:1.6}
7
+ & .hl ul{list-style:none;margin:28px 0 0;padding:0;border-top:1px solid color-mix(in srgb,var(--lp-fg) 12%,transparent)}
8
+ & .hl li a{display:flex;gap:16px;align-items:center;padding:16px 4px;border-bottom:1px solid color-mix(in srgb,var(--lp-fg) 12%,transparent);color:inherit;text-decoration:none}
9
+ & .hl li i{flex:none;width:40px;height:40px;border-radius:10px;display:grid;place-items:center;font-style:normal;font-weight:700;font-size:15px;
10
+ background:color-mix(in srgb,var(--lp-accent) 12%,transparent);color:var(--lp-accent)}
11
+ & .hl li b{display:block;font-size:15px;font-weight:600}
12
+ & .hl li small{display:block;color:var(--lp-muted);font-size:13.5px;margin-top:2px}
13
+ & .hl li span{flex:1;min-width:0}
14
+ & .hl li em{font-style:normal;color:var(--lp-muted);transition:transform .2s}
15
+ & .hl li a:hover em{transform:translateX(4px);color:var(--lp-accent)}
16
+ & .hl .lp-actions{justify-content:flex-start;margin-top:26px}
17
+ & .hl .lp-btn--primary{background:none;color:var(--lp-accent);padding:6px 0;font-weight:600}
18
+ `,setup(ctx){let c=ctx.copy,rows=ctx.params.links.map(l=>l.split("|")).map(([t,d,h])=>`<li><a href="${esc(h??"/")}"><i aria-hidden="true">${esc((t??"?").charAt(0))}</i><span><b>${esc(t??"")}</b><small>${esc(d??"")}</small></span><em aria-hidden="true">\u2192</em></a></li>`).join(""),el=layer(ctx,"hl",`<div class="in"><span class="ey">${esc(c.eyebrow||c.code)}</span><h1>${sr(c.code)}${esc(c.title)}</h1><p>${esc(c.message)}</p><ul>${rows}</ul></div>`);return el.querySelectorAll("ul a").forEach(a=>a.addEventListener("click",e=>{e.metaKey||e.ctrlKey||(e.preventDefault(),ctx.navigate(a.getAttribute("href"),e))})),el.querySelector(".in").append(actions(ctx)),{destroy:()=>el.remove()}}};export{helpful_links_default as default};
@@ -0,0 +1,6 @@
1
+ interface Params {
2
+ /** Show a thin rule between the code and the title. */
3
+ rule: boolean;
4
+ }
5
+ declare const _default: import("..").SceneDef<Params>;
6
+ export default _default;
@@ -0,0 +1,9 @@
1
+ import"../chunks/chunk-KIYSQVWX.js";import"../chunks/chunk-7XMDQMDO.js";var minimal_default={id:"minimal",name:"Minimal",description:"The classic: a large status code, one line of explanation and a way home. Nothing animates, nothing loads; it just works.",category:"Simple",kind:"2d",motion:"static",tags:["simple","minimal","clean","static","light","fast","classic"],layout:"center",copy:{eyebrow:"",title:"Page not found",message:"Sorry, we couldn\u2019t find the page you\u2019re looking for.",actions:[{label:"Go back home",href:"/",variant:"primary"},{label:"Contact support",href:"/contact",variant:"secondary",icon:"arrow"}]},params:{rule:!1},theme:{bg:"#ffffff",fg:"#0f172a",accent:"#4f46e5","accent-fg":"#ffffff","code-color":"#4f46e5","code-size":"clamp(15px,1.4cqw,17px)","code-weight":"600",radius:"8px"},presetName:"Indigo",presets:{dark:{name:"Dark",theme:{bg:"#0b0f19",fg:"#f1f5f9",accent:"#818cf8","accent-fg":"#0b0f19","code-color":"#818cf8"}},emerald:{name:"Emerald",theme:{bg:"#f7faf9",fg:"#052e22",accent:"#059669","accent-fg":"#fff","code-color":"#059669"}},big:{name:"Big number",title:"Lost your way?",params:{rule:!0},theme:{bg:"#fafafa",fg:"#18181b",accent:"#18181b","accent-fg":"#fafafa","code-color":"#18181b","code-size":"clamp(72px,16cqw,180px)","code-weight":"800",radius:"999px"}},warm:{name:"Warm",theme:{bg:"#fbf6ef",fg:"#3b2414",accent:"#c2410c","accent-fg":"#fff","code-color":"#c2410c"}}},css:`
2
+ & .lp-code{letter-spacing:.02em;line-height:1;margin-bottom:12px}
3
+ & .lp-title{font-weight:700;letter-spacing:-.03em;font-size:clamp(28px,5cqw,56px)}
4
+ & .lp-message{font-size:clamp(14px,1.4cqw,17px)}
5
+ & .lp-btn--primary{padding:12px 20px;font-weight:600}
6
+ & .lp-btn--secondary{border:0;font-weight:600}
7
+ &.lp-p-big .lp-code{letter-spacing:-.05em;line-height:.9}
8
+ &.mn-rule .lp-code::after{content:"";display:block;width:56px;height:3px;margin:22px auto 0;background:var(--lp-accent);border-radius:2px}
9
+ `,setup(ctx){ctx.root.classList.toggle("mn-rule",ctx.params.rule)}};export{minimal_default as default};
@@ -0,0 +1,8 @@
1
+ interface Params {
2
+ /** Stroke width of the outlined numerals, in px. */
3
+ stroke: number;
4
+ /** How far the numerals fade from top to bottom (0–1). */
5
+ fade: number;
6
+ }
7
+ declare const _default: import("..").SceneDef<Params>;
8
+ export default _default;
@@ -0,0 +1,9 @@
1
+ import{esc}from"../chunks/chunk-ES56TX4X.js";import"../chunks/chunk-B2NBRLO2.js";import"../chunks/chunk-KIYSQVWX.js";import"../chunks/chunk-7XMDQMDO.js";var outline_404_default={id:"outline-404",name:"Outline",description:"A giant outlined 404 sits behind a short, bold headline. One of the most common agency and portfolio looks, done with pure CSS.",category:"Simple",kind:"2d",motion:"static",tags:["simple","outline","stroke","typography","static","fast","portfolio"],layout:"center",copy:{eyebrow:"Error 404",title:"Oops! Nothing here.",message:"The page you\u2019re after has moved, been renamed or never existed.",actions:[{label:"Back to homepage",href:"/"}]},params:{stroke:2,fade:.7},theme:{bg:"#0d0d0f",fg:"#f4f4f5",accent:"#f4f4f5","accent-fg":"#0d0d0f"},presetName:"Night",presets:{paper:{name:"Paper",theme:{bg:"#f5f3ee",fg:"#141414",accent:"#141414","accent-fg":"#f5f3ee"}},lime:{name:"Lime",params:{stroke:3},theme:{bg:"#101010",fg:"#d9f99d",accent:"#bef264","accent-fg":"#101010"}},blue:{name:"Cobalt",theme:{bg:"#1d3bd8",fg:"#ffffff",accent:"#ffffff","accent-fg":"#1d3bd8"}}},css:`
2
+ & .ol{position:absolute;inset:0;display:grid;place-items:center;pointer-events:none;overflow:hidden}
3
+ & .ol span{font-family:var(--lp-font-display);font-weight:900;font-size:clamp(170px,42cqw,620px);line-height:1;letter-spacing:-.04em;white-space:nowrap;
4
+ color:transparent;-webkit-text-stroke:var(--sw) color-mix(in srgb,var(--lp-fg) 32%,transparent);
5
+ -webkit-mask-image:linear-gradient(180deg,#000 20%,rgba(0,0,0,calc(1 - var(--fade))));mask-image:linear-gradient(180deg,#000 20%,rgba(0,0,0,calc(1 - var(--fade))))}
6
+ & .lp-code{display:none}
7
+ & .lp-title{font-weight:800;font-size:clamp(30px,5.4cqw,68px)}
8
+ & .lp-eyebrow{color:var(--lp-fg);opacity:.7}
9
+ `,setup(ctx){let el=document.createElement("div");return el.className="ol",el.setAttribute("aria-hidden","true"),el.style.setProperty("--sw",ctx.params.stroke+"px"),el.style.setProperty("--fade",String(ctx.params.fade)),el.innerHTML=`<span>${esc(ctx.copy.code)}</span>`,ctx.stage.append(el),{destroy:()=>el.remove()}}};export{outline_404_default as default};
@@ -0,0 +1,6 @@
1
+ interface Params {
2
+ /** Show the HTTP request line (GET /path → 404) instead of a plain chip. */
3
+ request: boolean;
4
+ }
5
+ declare const _default: import("..").SceneDef<Params>;
6
+ export default _default;
@@ -0,0 +1,14 @@
1
+ import{actions,esc,layer,path,sr}from"../chunks/chunk-ES56TX4X.js";import"../chunks/chunk-B2NBRLO2.js";import"../chunks/chunk-KIYSQVWX.js";import"../chunks/chunk-7XMDQMDO.js";var path_not_found_default={id:"path-not-found",name:"Path Not Found",description:"Shows people exactly what went wrong: the address they asked for, in a code chip or a request line, so a typo is easy to spot.",category:"Simple",kind:"2d",motion:"static",tags:["simple","url","path","developer","docs","useful","static","fast"],layout:"none",copy:{title:"Nothing lives at this address",message:"Double-check the spelling, or go back to where you came from.",actions:[{label:"Go home",href:"/"},{label:"Report a broken link",href:"/contact",variant:"secondary",icon:"arrow"}]},params:{request:!1},theme:{bg:"#fafafa",fg:"#18181b",accent:"#18181b","accent-fg":"#fafafa",radius:"8px"},presetName:"Chip",presets:{request:{name:"Request line",params:{request:!0},theme:{bg:"#0b0b0e",fg:"#e4e4e7",accent:"#e4e4e7","accent-fg":"#0b0b0e"}},soft:{name:"Soft",theme:{bg:"#eef2ff",fg:"#1e1b4b",accent:"#4338ca","accent-fg":"#fff",radius:"999px"}}},css:`
2
+ & .pnf{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:24px 16px}
3
+ & .pnf h1{margin:0;font-size:clamp(26px,4.2cqw,46px);font-weight:700;letter-spacing:-.03em;line-height:1.1;text-wrap:balance}
4
+ & .pnf code{font-family:var(--lp-font-mono);font-size:14px;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
5
+ & .pnf .chip{display:inline-flex;gap:10px;align-items:center;max-width:min(560px,100%);margin-bottom:22px;padding:8px 14px;border-radius:8px;
6
+ background:color-mix(in srgb,var(--lp-fg) 6%,transparent);border:1px dashed color-mix(in srgb,var(--lp-fg) 22%,transparent)}
7
+ & .pnf .chip b{flex:none;color:#e11d48;font-family:var(--lp-font-mono);font-size:13px}
8
+ & .pnf .req{width:min(560px,100%);margin-bottom:26px;text-align:left;border-radius:10px;overflow:hidden;border:1px solid color-mix(in srgb,var(--lp-fg) 14%,transparent)}
9
+ & .pnf .req div{display:flex;gap:12px;padding:12px 16px;font-family:var(--lp-font-mono);font-size:13.5px;min-width:0}
10
+ & .pnf .req div+div{border-top:1px solid color-mix(in srgb,var(--lp-fg) 10%,transparent);color:var(--lp-muted)}
11
+ & .pnf .req em{font-style:normal;color:#22c55e;flex:none}
12
+ & .pnf .req strong{color:#f43f5e;flex:none}
13
+ & .pnf p{margin:14px 0 0;color:var(--lp-muted);font-size:15px;max-width:44ch}
14
+ `,setup(ctx){let c=ctx.copy,where=path(),head=ctx.params.request?`<div class="req" aria-hidden="true"><div><em>GET</em><code>${esc(where)}</code></div><div><strong>${esc(c.code)}</strong><span>Not Found</span></div></div>`:`<div class="chip"><b>${esc(c.code)}</b><code>${esc(where)}</code></div>`,el=layer(ctx,"pnf",`${head}<h1>${sr(c.code)}${esc(c.title)}</h1><p>${esc(c.message)}</p>`);return el.append(actions(ctx)),{destroy:()=>el.remove()}}};export{path_not_found_default as default};
@@ -0,0 +1,9 @@
1
+ interface Params {
2
+ /** Where the search form goes; the query is appended as ?q=… */
3
+ searchUrl: string;
4
+ placeholder: string;
5
+ /** Popular pages listed under the search box, as "Label|/href". */
6
+ links: string[];
7
+ }
8
+ declare const _default: import("..").SceneDef<Params>;
9
+ export default _default;
@@ -0,0 +1,17 @@
1
+ import{actions,esc,layer,sr}from"../chunks/chunk-ES56TX4X.js";import"../chunks/chunk-B2NBRLO2.js";import"../chunks/chunk-KIYSQVWX.js";import"../chunks/chunk-7XMDQMDO.js";var search_404_default={id:"search-404",name:"Search",description:"The most useful 404 for docs, shops and blogs: a search box that sends people straight to your search page, plus a few popular links.",category:"Simple",kind:"2d",motion:"static",tags:["simple","search","docs","ecommerce","blog","useful","static","fast"],layout:"none",copy:{eyebrow:"404",title:"We couldn\u2019t find that page",message:"Try searching for it, or start from one of these:",actions:[{label:"Go home",href:"/",icon:"home"}]},params:{searchUrl:"/search",placeholder:"Search the site\u2026",links:["Getting started|/docs","Pricing|/pricing","Blog|/blog","Contact|/contact"]},theme:{bg:"#ffffff",fg:"#111827",accent:"#2563eb","accent-fg":"#ffffff",radius:"10px"},presetName:"Light",presets:{dark:{name:"Dark",theme:{bg:"#0d1117",fg:"#e6edf3",accent:"#2f81f7","accent-fg":"#ffffff"}},shop:{name:"Shop",title:"This product has left the shelf",message:"Search our store or browse a popular collection:",params:{placeholder:"Search products\u2026",links:["New arrivals|/new","Best sellers|/best-sellers","Sale|/sale","Gift cards|/gift-cards"]},theme:{bg:"#faf7f2",fg:"#1c1917",accent:"#1c1917","accent-fg":"#faf7f2",radius:"999px"}},docs:{name:"Docs",title:"This doc page doesn\u2019t exist",message:"It may have moved in a recent update. Search the docs:",params:{placeholder:"Search documentation\u2026",links:["Quick start|/docs/start","API reference|/docs/api","Guides|/docs/guides","Changelog|/changelog"]},theme:{bg:"#fcfcfd",fg:"#18181b",accent:"#7c3aed","accent-fg":"#ffffff",font:"ui-sans-serif,system-ui,sans-serif"}}},css:`
2
+ & .sr4{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:24px 16px;text-align:center;overflow:auto}
3
+ & .sr4 .ey{font-size:13px;font-weight:700;color:var(--lp-accent);letter-spacing:.06em}
4
+ & .sr4 h1{margin:10px 0 0;font-size:clamp(26px,4.4cqw,48px);font-weight:700;letter-spacing:-.03em;line-height:1.1;text-wrap:balance}
5
+ & .sr4 p{margin:14px 0 0;color:var(--lp-muted);font-size:clamp(14px,1.4cqw,16px)}
6
+ & .sr4 form{margin-top:26px;display:flex;width:min(520px,100%);border:1px solid color-mix(in srgb,var(--lp-fg) 16%,transparent);border-radius:var(--lp-radius);background:color-mix(in srgb,var(--lp-fg) 3%,var(--lp-bg));overflow:hidden;transition:border-color .2s,box-shadow .2s}
7
+ & .sr4 form:focus-within{border-color:var(--lp-accent);box-shadow:0 0 0 4px color-mix(in srgb,var(--lp-accent) 18%,transparent)}
8
+ & .sr4 input{flex:1;min-width:0;border:0;background:none;padding:14px 16px;font:inherit;font-size:15px;color:var(--lp-fg);outline:none}
9
+ & .sr4 form button{border:0;margin:5px;padding:0 18px;border-radius:calc(var(--lp-radius) - 3px);background:var(--lp-accent);color:var(--lp-accent-fg);font:inherit;font-weight:600;font-size:14px;cursor:pointer}
10
+ & .sr4 ul{list-style:none;margin:22px 0 0;padding:0;display:flex;flex-wrap:wrap;justify-content:center;gap:8px}
11
+ & .sr4 ul a{display:block;padding:7px 14px;border-radius:999px;background:color-mix(in srgb,var(--lp-fg) 6%,transparent);color:var(--lp-fg);text-decoration:none;font-size:13.5px;font-weight:500}
12
+ & .sr4 ul a:hover{background:color-mix(in srgb,var(--lp-accent) 14%,transparent);color:var(--lp-accent)}
13
+ & .sr4 .lp-actions{margin-top:26px}
14
+ & .sr4 .lp-btn--primary{background:none;color:var(--lp-fg);padding:8px 4px;font-weight:600}
15
+ `,setup(ctx){let p=ctx.params,c=ctx.copy,links=p.links.map(l=>l.split("|")).map(([label,href])=>`<li><a href="${esc(href??"/")}">${esc(label)}</a></li>`).join(""),el=layer(ctx,"sr4",`<span class="ey">${esc(c.eyebrow||c.code)}</span><h1>${sr(c.code)}${esc(c.title)}</h1><p>${esc(c.message)}</p>
16
+ <form role="search" action="${esc(p.searchUrl)}"><input name="q" type="search" aria-label="Search" placeholder="${esc(p.placeholder)}" data-lp-interactive><button type="submit">Search</button></form>
17
+ <ul>${links}</ul>`);el.querySelectorAll("ul a").forEach(a=>a.addEventListener("click",e=>{e.metaKey||e.ctrlKey||(e.preventDefault(),ctx.navigate(a.getAttribute("href"),e))}));let form=el.querySelector("form");return form.addEventListener("submit",e=>{e.preventDefault();let q=form.elements.namedItem("q").value.trim();ctx.navigate(p.searchUrl+(p.searchUrl.includes("?")?"&":"?")+"q="+encodeURIComponent(q))}),el.append(actions(ctx)),{destroy:()=>el.remove()}}};export{search_404_default as default};
@@ -0,0 +1,6 @@
1
+ interface Params {
2
+ /** Footer line, e.g. "nginx" or "Apache/2.4.58 (Ubuntu) Server at example.com Port 443". */
3
+ server: string;
4
+ }
5
+ declare const _default: import("..").SceneDef<Params>;
6
+ export default _default;
@@ -0,0 +1,10 @@
1
+ import{esc,layer}from"../chunks/chunk-ES56TX4X.js";import"../chunks/chunk-B2NBRLO2.js";import"../chunks/chunk-KIYSQVWX.js";import"../chunks/chunk-7XMDQMDO.js";var server_default_default={id:"server-default",name:"Server Default",description:"The bare web-server error page everyone recognises: a Times heading, a rule and the server signature. Deadpan, tiny and on purpose.",category:"Simple",kind:"2d",motion:"static",tags:["simple","nginx","apache","server","plain","developer","joke","static","fast"],layout:"none",copy:{title:"Not Found",message:"The requested URL was not found on this server.",actions:[{label:"Return to the home page",href:"/"}]},params:{server:"nginx"},theme:{bg:"#ffffff",fg:"#000000"},presetName:"nginx",presets:{apache:{name:"Apache",params:{server:"Apache/2.4.58 (Ubuntu) Server at localhost Port 443"}},iis:{name:"IIS",title:"404 - File or directory not found.",message:"The resource you are looking for might have been removed, had its name changed, or is temporarily unavailable.",params:{server:"Microsoft-IIS/10.0"}},dark:{name:"Dark",theme:{bg:"#1e1e1e",fg:"#d4d4d4"}}},css:`
2
+ & .sd{position:absolute;inset:0;padding:8px 8px;font-family:"Times New Roman",Times,serif;font-size:16px;color:var(--lp-fg);overflow:auto}
3
+ & .sd h1{margin:21px 0;font-size:2em;font-weight:700;text-align:center;font-family:inherit}
4
+ &.sd-apache .sd h1,&.sd-apache .sd p{text-align:left}
5
+ & .sd p{margin:16px 0;text-align:center}
6
+ & .sd hr{border:0;border-top:1px solid color-mix(in srgb,var(--lp-fg) 40%,transparent);margin:8px 0}
7
+ & .sd address,& .sd center{font-style:normal;text-align:center;display:block}
8
+ & .sd a{color:#0000ee}
9
+ & .sd a:visited{color:#551a8b}
10
+ `,setup(ctx){let c=ctx.copy,p=ctx.params,apache=/apache/i.test(p.server);ctx.root.classList.toggle("sd-apache",apache);let title=/^\d/.test(c.title)?c.title:`${c.code} ${c.title}`,link=c.actions[0],el=layer(ctx,"sd",`<h1><span class="lp-sr">Error ${esc(c.code)}: </span>${esc(title)}</h1>${apache||c.message!=="The requested URL was not found on this server."?`<p>${esc(c.message)}</p>`:""}${link?`<p><a href="${esc(link.href??"/")}">${esc(link.label)}</a></p>`:""}<hr>${apache?`<address>${esc(p.server)}</address>`:`<center>${esc(p.server)}</center>`}`);return el.querySelector("a")?.addEventListener("click",e=>{e.metaKey||e.ctrlKey||(e.preventDefault(),ctx.navigate(link.href??"/",e))}),{destroy:()=>el.remove()}}};export{server_default_default as default};
@@ -0,0 +1,9 @@
1
+ interface Params {
2
+ colors: string[];
3
+ /** Blob drift speed. */
4
+ speed: number;
5
+ /** Frosted card behind the text. */
6
+ card: boolean;
7
+ }
8
+ declare const _default: import("..").SceneDef<Params>;
9
+ export default _default;
@@ -0,0 +1,15 @@
1
+ import"../chunks/chunk-KIYSQVWX.js";import"../chunks/chunk-7XMDQMDO.js";var soft_blobs_default={id:"soft-blobs",name:"Soft Blobs",description:"Blurred colour blobs drifting slowly behind a frosted card. Pure CSS animation, so it costs almost nothing to run.",category:"Simple",kind:"2d",motion:"live",tags:["simple","gradient","blobs","glass","frosted","saas","fast","light"],layout:"center",copy:{eyebrow:"",title:"Page not found",message:"The page you\u2019re looking for doesn\u2019t exist or has been moved.",actions:[{label:"Go home",href:"/"},{label:"Contact",href:"/contact",variant:"secondary",icon:"arrow"}]},params:{colors:["#a78bfa","#60a5fa","#f472b6"],speed:1,card:!0},theme:{bg:"#f8f7ff",fg:"#1e1b4b",accent:"#1e1b4b","accent-fg":"#ffffff","code-size":"clamp(64px,11cqw,120px)","code-weight":"800","code-color":"#1e1b4b"},presetName:"Lavender",presets:{peach:{name:"Peach",params:{colors:["#fdba74","#fda4af","#fde68a"]},theme:{bg:"#fffaf5",fg:"#431407",accent:"#431407","code-color":"#431407"}},aqua:{name:"Aqua",params:{colors:["#5eead4","#38bdf8","#a3e635"]},theme:{bg:"#f0fdfa",fg:"#042f2e",accent:"#042f2e","code-color":"#042f2e"}},night:{name:"Night",params:{colors:["#7c3aed","#2563eb","#db2777"]},theme:{bg:"#07071a",fg:"#eef2ff",accent:"#eef2ff","accent-fg":"#07071a","code-color":"#eef2ff"}}},css:`
2
+ & .sb{position:absolute;inset:-10%;filter:blur(clamp(50px,8cqw,110px));opacity:.75}
3
+ & .sb i{position:absolute;width:45%;aspect-ratio:1;border-radius:50%;animation:sb-drift calc(18s / var(--sp)) ease-in-out infinite alternate}
4
+ & .sb i:nth-child(1){left:12%;top:10%;background:var(--c0)}
5
+ & .sb i:nth-child(2){right:8%;top:30%;background:var(--c1);animation-delay:calc(-6s / var(--sp));animation-duration:calc(22s / var(--sp))}
6
+ & .sb i:nth-child(3){left:30%;bottom:2%;background:var(--c2);animation-delay:calc(-11s / var(--sp));animation-duration:calc(26s / var(--sp))}
7
+ @keyframes sb-drift{0%{transform:translate(0,0) scale(1)}50%{transform:translate(12%,-8%) scale(1.15)}100%{transform:translate(-10%,10%) scale(.92)}}
8
+ &.sb-still .sb i{animation:none}
9
+ & .lp-code{letter-spacing:-.05em;line-height:.9;margin-bottom:8px}
10
+ & .lp-title{font-weight:700;font-size:clamp(24px,3.4cqw,40px)}
11
+ &.sb-card .lp-head,&.sb-card .lp-body{position:relative}
12
+ &.sb-card .lp-overlay::before{content:"";position:absolute;left:50%;top:50%;translate:-50% -50%;width:min(520px,calc(100% - 32px));height:min(460px,calc(100% - 32px));
13
+ border-radius:28px;background:color-mix(in srgb,var(--lp-bg) 55%,transparent);border:1px solid color-mix(in srgb,#fff 55%,transparent);
14
+ backdrop-filter:blur(18px) saturate(1.3);-webkit-backdrop-filter:blur(18px) saturate(1.3);box-shadow:0 30px 60px -30px rgba(0,0,0,.25)}
15
+ `,setup(ctx){let p=ctx.params,el=document.createElement("div");return el.className="sb",el.innerHTML="<i></i><i></i><i></i>",p.colors.slice(0,3).forEach((c,i)=>el.style.setProperty(`--c${i}`,c)),el.style.setProperty("--sp",String(Math.max(.05,p.speed))),ctx.stage.append(el),ctx.root.classList.toggle("sb-card",p.card),ctx.root.classList.toggle("sb-still",ctx.reducedMotion),{destroy:()=>el.remove()}}};export{soft_blobs_default as default};
@@ -0,0 +1,8 @@
1
+ interface Params {
2
+ panel: string;
3
+ panelText: string;
4
+ /** Put the coloured panel on the left. */
5
+ flip: boolean;
6
+ }
7
+ declare const _default: import("..").SceneDef<Params>;
8
+ export default _default;
@@ -0,0 +1,12 @@
1
+ import{actions,esc,layer,sr}from"../chunks/chunk-ES56TX4X.js";import"../chunks/chunk-B2NBRLO2.js";import"../chunks/chunk-KIYSQVWX.js";import"../chunks/chunk-7XMDQMDO.js";var split_panel_default={id:"split-panel",name:"Split Panel",description:"Two halves: the explanation and buttons on one side, a solid colour block with a huge number on the other. Stacks on phones.",category:"Simple",kind:"2d",motion:"static",tags:["simple","split","two-column","bold","static","fast","brand"],layout:"none",copy:{eyebrow:"Error 404",title:"Looks like you\u2019re lost.",message:"The page you are looking for is not available. It might have been moved or deleted.",actions:[{label:"Go to homepage",href:"/"},{label:"Contact us",href:"/contact",variant:"secondary",icon:"arrow"}]},params:{panel:"#ff5a36",panelText:"#ffffff",flip:!1},theme:{bg:"#ffffff",fg:"#111111",accent:"#111111","accent-fg":"#ffffff",radius:"6px"},presetName:"Coral",presets:{forest:{name:"Forest",params:{panel:"#14532d",panelText:"#bbf7d0"},theme:{bg:"#f7f7f2",fg:"#14231a",accent:"#14532d","accent-fg":"#fff"}},ink:{name:"Ink",params:{panel:"#111111",panelText:"#fafafa",flip:!0},theme:{bg:"#fafafa",fg:"#111",accent:"#111","accent-fg":"#fafafa"}},sky:{name:"Sky",params:{panel:"#38bdf8",panelText:"#082f49"},theme:{bg:"#082f49",fg:"#e0f2fe",accent:"#38bdf8","accent-fg":"#082f49"}}},css:`
2
+ & .sp{position:absolute;inset:0;display:grid;grid-template-columns:1fr 1fr}
3
+ & .sp.flip .txt{order:2}
4
+ & .sp .txt{display:flex;flex-direction:column;justify-content:center;padding:clamp(24px,6cqw,96px)}
5
+ & .sp .ey{font-size:12px;font-weight:700;letter-spacing:.16em;text-transform:uppercase;color:var(--lp-muted)}
6
+ & .sp h1{margin:14px 0 0;font-size:clamp(30px,4.8cqw,64px);font-weight:800;letter-spacing:-.04em;line-height:1.02}
7
+ & .sp p{margin:18px 0 0;max-width:40ch;color:var(--lp-muted);font-size:clamp(14px,1.3cqw,17px);line-height:1.6}
8
+ & .sp .lp-actions{justify-content:flex-start}
9
+ & .sp .pn{background:var(--pn);color:var(--pt);display:grid;place-items:center;overflow:hidden}
10
+ & .sp .pn span{font-weight:900;font-size:clamp(90px,19cqw,300px);letter-spacing:-.06em;line-height:1}
11
+ @container (max-width:700px){& .sp{grid-template-columns:1fr;grid-template-rows:38% 1fr}& .sp .txt,& .sp.flip .txt{order:2;justify-content:flex-start;padding-top:28px}& .sp .pn span{font-size:clamp(80px,30cqw,160px)}}
12
+ `,setup(ctx){let p=ctx.params,c=ctx.copy,el=layer(ctx,"sp"+(p.flip?" flip":""),`<div class="txt"><span class="ey">${esc(c.eyebrow)}</span><h1>${sr(c.code)}${esc(c.title)}</h1><p>${esc(c.message)}</p></div><div class="pn" aria-hidden="true"><span>${esc(c.code)}</span></div>`,{pn:p.panel,pt:p.panelText});return el.querySelector(".txt").append(actions(ctx)),{destroy:()=>el.remove()}}};export{split_panel_default as default};
package/dist/vue.js CHANGED
@@ -1 +1 @@
1
- import{defineComponent,h,onBeforeUnmount,onMounted,ref,watch}from"vue";import{mount}from"lostpage";var LostPage=defineComponent({name:"LostPage",props:{scene:{type:[String,Object],required:!0},preset:String,code:String,eyebrow:String,title:String,message:String,actions:Array,params:Object,theme:Object,overlay:{type:Boolean,default:void 0},interactive:{type:Boolean,default:void 0},motion:String,height:String,maxDpr:Number,seed:Number},emits:["navigate"],setup(props,{slots,emit,attrs}){let host=ref(null),handle=null,options=()=>{let{scene:_s,...rest}=props,o={};for(let[k,v]of Object.entries(rest))v!==void 0&&(o[k]=v);return slots.default&&(o.overlay=!1),attrs.onNavigate&&(o.onNavigate=(href,e)=>emit("navigate",href,e)),o},start=()=>{handle?.destroy(),host.value&&(handle=mount(host.value,props.scene,options()))};return onMounted(start),onBeforeUnmount(()=>handle?.destroy()),watch(()=>typeof props.scene=="string"?props.scene:props.scene.id,start),watch(()=>JSON.stringify(options()),()=>handle?.update(options())),()=>h("div",{ref:host,"data-lostpage-host":"",style:{position:"relative",minHeight:props.height??"100dvh"}},slots.default?[h("div",{class:"lp-custom"},slots.default())]:[])}}),vue_default=LostPage;export{LostPage,vue_default as default};
1
+ import{defineComponent,getCurrentInstance,h,onBeforeUnmount,onMounted,ref,watch}from"vue";import{mount}from"lostpage";var LostPage=defineComponent({name:"LostPage",props:{scene:{type:[String,Object],required:!0},preset:String,code:String,eyebrow:String,title:String,message:String,actions:Array,params:Object,theme:Object,overlay:{type:Boolean,default:void 0},interactive:{type:Boolean,default:void 0},motion:String,height:String,maxDpr:Number,seed:Number},emits:["navigate"],setup(props,{slots,emit}){let host=ref(null),instance=getCurrentInstance(),hasNavigateListener=()=>!!instance?.vnode.props?.onNavigate,handle=null,options=()=>{let{scene:_s,...rest}=props,o={};for(let[k,v]of Object.entries(rest))v!==void 0&&(o[k]=v);return slots.default&&(o.overlay=!1),hasNavigateListener()&&(o.onNavigate=(href,e)=>emit("navigate",href,e)),o},start=()=>{handle?.destroy(),host.value&&(handle=mount(host.value,props.scene,options()))};return onMounted(start),onBeforeUnmount(()=>handle?.destroy()),watch(()=>typeof props.scene=="string"?props.scene:props.scene.id,start),watch(()=>JSON.stringify(options()),()=>handle?.update(options())),()=>h("div",{ref:host,"data-lostpage-host":"",style:{position:"relative",minHeight:props.height??"100dvh"}},slots.default?[h("div",{class:"lp-custom"},slots.default())]:[])}}),vue_default=LostPage;export{LostPage,vue_default as default};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lostpage",
3
- "version": "0.1.0",
4
- "description": "Immersive 404 pages for every framework: WebGL oceans, black holes, chrome 3D type, playable games, retro terminals and more. 99 scenes, 600+ designs, zero dependencies.",
3
+ "version": "0.2.0",
4
+ "description": "Immersive 404 pages for every framework: WebGL oceans, black holes, chrome 3D type, playable games, retro terminals, plus simple everyday 404s. 116 scenes, 650+ designs, zero dependencies.",
5
5
  "keywords": [
6
6
  "404",
7
7
  "404-page",