@joystyk/catalog 0.0.0-stage → 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 (56) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/LICENSE +21 -0
  3. package/NOTICE +36 -0
  4. package/README.md +31 -2
  5. package/dist/advertise.d.ts +82 -0
  6. package/dist/advertise.js +203 -0
  7. package/dist/advertise.js.map +1 -0
  8. package/dist/categories.d.ts +12 -0
  9. package/dist/categories.js +45 -0
  10. package/dist/categories.js.map +1 -0
  11. package/dist/client.d.ts +1 -0
  12. package/dist/client.js +9 -0
  13. package/dist/client.js.map +1 -0
  14. package/dist/components/advertise.d.ts +4 -0
  15. package/dist/components/advertise.js +1304 -0
  16. package/dist/components/advertise.js.map +1 -0
  17. package/dist/components/app.d.ts +25 -0
  18. package/dist/components/app.js +106 -0
  19. package/dist/components/app.js.map +1 -0
  20. package/dist/components/leaving.d.ts +2 -0
  21. package/dist/components/leaving.js +104 -0
  22. package/dist/components/leaving.js.map +1 -0
  23. package/dist/components/views.d.ts +13 -0
  24. package/dist/components/views.js +1410 -0
  25. package/dist/components/views.js.map +1 -0
  26. package/dist/head.d.ts +6 -0
  27. package/dist/head.js +33 -0
  28. package/dist/head.js.map +1 -0
  29. package/dist/index.d.ts +11 -0
  30. package/dist/index.js +12 -0
  31. package/dist/labels.d.ts +10 -0
  32. package/dist/labels.js +27 -0
  33. package/dist/labels.js.map +1 -0
  34. package/dist/placeholders.d.ts +3 -0
  35. package/dist/placeholders.js +7 -0
  36. package/dist/placeholders.js.map +1 -0
  37. package/dist/registry.d.ts +46 -0
  38. package/dist/registry.js +106 -0
  39. package/dist/registry.js.map +1 -0
  40. package/dist/routes.d.ts +56 -0
  41. package/dist/routes.js +89 -0
  42. package/dist/routes.js.map +1 -0
  43. package/dist/seo.d.ts +31 -0
  44. package/dist/seo.js +174 -0
  45. package/dist/seo.js.map +1 -0
  46. package/dist/server.d.ts +50 -0
  47. package/dist/server.js +162 -0
  48. package/dist/server.js.map +1 -0
  49. package/dist/store.d.ts +202 -0
  50. package/dist/store.js +136 -0
  51. package/dist/store.js.map +1 -0
  52. package/dist/styles.css +1121 -0
  53. package/dist/wire.d.ts +108 -0
  54. package/dist/wire.js +74 -0
  55. package/dist/wire.js.map +1 -0
  56. package/package.json +69 -3
package/CHANGELOG.md ADDED
@@ -0,0 +1,24 @@
1
+ # @joystyk/catalog
2
+
3
+ ## 0.2.0
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [bb095c7]
8
+ - Updated dependencies [bb095c7]
9
+ - @joystyk/contracts@0.2.0
10
+ - @joystyk/launcher@0.2.0
11
+ - @joystyk/ui@0.2.0
12
+
13
+ ## 0.1.0
14
+
15
+ ### Minor Changes
16
+
17
+ - Prepare the first compiled library release: ESM and API declarations for every public entry point, runtime assets, and checks against installed npm tarballs. Games and applications remain private.
18
+
19
+ ### Patch Changes
20
+
21
+ - Updated dependencies
22
+ - @joystyk/contracts@0.1.0
23
+ - @joystyk/ui@0.1.0
24
+ - @joystyk/launcher@0.1.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Joystyk contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/NOTICE ADDED
@@ -0,0 +1,36 @@
1
+ Third-party material in the Joystyk library release
2
+
3
+ This notice preserves third-party attribution. Original Joystyk code in the 17
4
+ platform libraries under packages/ uses the MIT license in packages/LICENSE.
5
+ That choice does not license the private applications, games or other repository
6
+ material, and does not replace the third-party terms below.
7
+
8
+ @joystyk/contracts
9
+ ------------------
10
+ Standard Schema v1 type definitions from @standard-schema/spec 1.1.0:
11
+ https://github.com/standard-schema/standard-schema
12
+ https://standardschema.dev
13
+ Copyright (c) 2024 Colin McDonnell. MIT License.
14
+ The complete license and source/change notes accompany the distributed
15
+ dist/standard-schema.d.ts definitions. Changes: individually exported declarations
16
+ and formatting; the declarations themselves are unchanged.
17
+
18
+ @joystyk/identity and @joystyk/content
19
+ -----------------------------------
20
+ Word blocklists derived from List of Dirty, Naughty, Obscene, and Otherwise Bad
21
+ Words, copyright Shutterstock, Inc. and contributors:
22
+ https://github.com/LDNOOBW/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words
23
+ Licensed under Creative Commons Attribution 4.0 International:
24
+ https://creativecommons.org/licenses/by/4.0/
25
+ Identity uses the English list at commit
26
+ 5faf2ba42d7b1c0977169ec3611df25a3c08eb13. Changes include reducing or dropping
27
+ phrases, dropping words with everyday meanings, substituting stems for inflections
28
+ and adding entries. Identity's README and content's lists/blocklist.txt retain
29
+ their source-specific provenance and changes.
30
+
31
+ @joystyk/content
32
+ ---------------
33
+ SCOWL-derived common, vocabulary and ranked word lists use SCOWL 2020.12.07.
34
+ Their complete source licenses and attribution accompany the package in
35
+ lists/SCOWL-COPYRIGHT. Word-list transformations and build provenance are described
36
+ in the package README. Preserve that copyright file when redistributing the lists.
package/README.md CHANGED
@@ -1,3 +1,32 @@
1
- # Temporary Holding Version
1
+ # @joystyk/catalog
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Catalog models, server rendering and the browser application shell. Supply game metadata and routing data from your application. The browser entry registers the catalog components and installs router drivers on the document root; load it in the browser.
4
+
5
+ ## npm package
6
+
7
+ The first `0.1.0` release is being prepared; it has not been published. Once available:
8
+
9
+ ```sh
10
+ npm install @joystyk/catalog
11
+ ```
12
+
13
+ Node consumers require Node 24.15 or later. Browser entry points require a modern
14
+ browser and should be loaded in the browser. Tested with Gyral 0.3.1.
15
+
16
+ | Import | Format |
17
+ | --- | --- |
18
+ | `@joystyk/catalog` | ES module and TypeScript declarations |
19
+ | `@joystyk/catalog/client` | ES module and TypeScript declarations |
20
+ | `@joystyk/catalog/server` | ES module and TypeScript declarations |
21
+ | `@joystyk/catalog/styles.css` | CSS asset |
22
+
23
+ Only the entries listed above are public API. Installed packages contain compiled ESM,
24
+ type declarations and runtime assets. Repository-relative links describe the Joystyk
25
+ workspace and are not needed to use the npm package.
26
+
27
+ ```ts
28
+ import '@joystyk/catalog/client';
29
+ import '@joystyk/catalog/styles.css';
30
+
31
+ // Server rendering helpers are available separately from @joystyk/catalog/server.
32
+ ```
@@ -0,0 +1,82 @@
1
+ import type { FormFields } from '@gyral/core';
2
+ import type { SlotDefinition, SlotId, SlotSurface } from '@joystyk/ui/slots';
3
+ /** A slot's name on the advertise pages and in the contact form. */
4
+ export declare const SLOT_NAMES: Readonly<Record<SlotId, string>>;
5
+ /** Where a surface is, in words. */
6
+ export declare const SURFACE_NAMES: Readonly<Record<SlotSurface, string>>;
7
+ /** "728 × 90 px". */
8
+ export declare const sizeLabel: (size: {
9
+ readonly width: number;
10
+ readonly height: number;
11
+ }) => string;
12
+ /** The box a slot's preview draws: its wide box, or its narrow one if it has no wide box. */
13
+ export declare const previewSize: (s: SlotDefinition) => import("@joystyk/ui/slots").SlotSize;
14
+ /** The form's `data-intent` name: `IntentRejected.intent` when the server rejects a post. */
15
+ export declare const CONTACT_INTENT = "SendContact";
16
+ /** Longest value each text field takes, in characters. The markup and the server share them. */
17
+ export declare const CONTACT_LIMITS: {
18
+ readonly name: 100;
19
+ readonly company: 100;
20
+ readonly email: 254;
21
+ readonly message: 2000;
22
+ };
23
+ /**
24
+ * The honeypot: a field people never see (CSS hides it) and bots tend to fill. A post with
25
+ * anything in it is answered like a real one and thrown away.
26
+ */
27
+ export declare const CONTACT_HONEYPOT = "website";
28
+ /**
29
+ * The longest a message is kept, counted from when it arrives, handled or not
30
+ * (advertising-policy.md, section 9). The page quotes this number.
31
+ */
32
+ export declare const CONTACT_RETENTION_MONTHS = 12;
33
+ /** How long a message is kept after it is marked handled, if that comes sooner. */
34
+ export declare const CONTACT_HANDLED_RETENTION_DAYS = 90;
35
+ /** Where advertisers can write instead of using the form. */
36
+ export declare const ADVERTISE_EMAIL = "info@joystyk.com";
37
+ /** The reply time the thank-you page promises, in business days. */
38
+ export declare const REPLY_BUSINESS_DAYS = 3;
39
+ /** A rejected post, as the page re-renders it: what was sent and the errors per field. */
40
+ export interface ContactFormState {
41
+ readonly values: FormFields;
42
+ /** Errors per field name; '' holds form-level errors (rate limit, a full inbox). */
43
+ readonly errors: Readonly<Record<string, readonly string[]>>;
44
+ }
45
+ /**
46
+ * The technical limits every creative meets (advertising-policy.md, section 7). The site's
47
+ * ad store enforces these numbers, and the policy page quotes them.
48
+ */
49
+ export declare const CREATIVE_SPECS: {
50
+ /** Largest image file, in KB of 1,024 bytes. */
51
+ readonly imageKb: 150;
52
+ /** An image may be at most this many times its box's CSS pixels on each side. */
53
+ readonly density: 2;
54
+ /** Longest alt text, in characters. */
55
+ readonly alt: 125;
56
+ /** Longest headline, in characters. */
57
+ readonly headline: 40;
58
+ /** Longest headline for a creative that runs only as the daily presenting line. */
59
+ readonly presentingHeadline: 60;
60
+ };
61
+ /** The oldest ratings an advertised film, show, game or app may carry (policy sections 1 and 2). */
62
+ export declare const MEDIA_RATINGS = "G, U, PEGI 3 or ESRB Everyone";
63
+ export interface PolicyItem {
64
+ readonly name: string;
65
+ readonly detail: string;
66
+ }
67
+ /** The rules in brief. For the /advertise page; scripts/advertising-policy.test.ts checks it keeps the rating line. */
68
+ export declare const RULE_SUMMARY: readonly string[];
69
+ /** Never advertised, in any slot. */
70
+ export declare const PROHIBITED: readonly PolicyItem[];
71
+ /** Allowed only with the conditions given. */
72
+ export declare const RESTRICTED: readonly PolicyItem[];
73
+ /** How an ad may speak to the people who see it. */
74
+ export declare const CLAIMS_RULES: readonly string[];
75
+ /** Where a click may take someone. */
76
+ export declare const LANDING_RULES: readonly string[];
77
+ /** Format rules for every slot; the sizes come from the slot registry. */
78
+ export declare const FORMAT_RULES: readonly string[];
79
+ /** What happens between a booking and a creative going live. */
80
+ export declare const REVIEW_STEPS: readonly string[];
81
+ /** What happens to payment when an ad doesn't run (policy section 8). */
82
+ export declare const REFUND_RULES: readonly string[];
@@ -0,0 +1,203 @@
1
+ //#region src/advertise.ts
2
+ /** A slot's name on the advertise pages and in the contact form. */
3
+ var SLOT_NAMES = {
4
+ "index-hero": "Home page banner",
5
+ "index-inline": "Home page inline banner",
6
+ "category-rail": "Side rail",
7
+ "game-page": "Game page rectangle",
8
+ results: "Game-over rectangle",
9
+ "daily-presenting": "Daily presenting sponsor"
10
+ };
11
+ /** Where a surface is, in words. */
12
+ var SURFACE_NAMES = {
13
+ home: "the home page",
14
+ category: "category pages",
15
+ game: "game pages",
16
+ results: "the game-over screen",
17
+ daily: "daily games"
18
+ };
19
+ /** "728 × 90 px". */
20
+ var sizeLabel = (size) => `${String(size.width)} × ${String(size.height)} px`;
21
+ /** The box a slot's preview draws: its wide box, or its narrow one if it has no wide box. */
22
+ var previewSize = (s) => s.sizes.wide ?? s.sizes.narrow ?? {
23
+ width: 0,
24
+ height: 0
25
+ };
26
+ /** The form's `data-intent` name: `IntentRejected.intent` when the server rejects a post. */
27
+ var CONTACT_INTENT = "SendContact";
28
+ /** Longest value each text field takes, in characters. The markup and the server share them. */
29
+ var CONTACT_LIMITS = {
30
+ name: 100,
31
+ company: 100,
32
+ email: 254,
33
+ message: 2e3
34
+ };
35
+ /**
36
+ * The honeypot: a field people never see (CSS hides it) and bots tend to fill. A post with
37
+ * anything in it is answered like a real one and thrown away.
38
+ */
39
+ var CONTACT_HONEYPOT = "website";
40
+ /**
41
+ * The longest a message is kept, counted from when it arrives, handled or not
42
+ * (advertising-policy.md, section 9). The page quotes this number.
43
+ */
44
+ var CONTACT_RETENTION_MONTHS = 12;
45
+ /** How long a message is kept after it is marked handled, if that comes sooner. */
46
+ var CONTACT_HANDLED_RETENTION_DAYS = 90;
47
+ /** Where advertisers can write instead of using the form. */
48
+ var ADVERTISE_EMAIL = "info@joystyk.com";
49
+ /** The reply time the thank-you page promises, in business days. */
50
+ var REPLY_BUSINESS_DAYS = 3;
51
+ /**
52
+ * The technical limits every creative meets (advertising-policy.md, section 7). The site's
53
+ * ad store enforces these numbers, and the policy page quotes them.
54
+ */
55
+ var CREATIVE_SPECS = {
56
+ /** Largest image file, in KB of 1,024 bytes. */
57
+ imageKb: 150,
58
+ /** An image may be at most this many times its box's CSS pixels on each side. */
59
+ density: 2,
60
+ /** Longest alt text, in characters. */
61
+ alt: 125,
62
+ /** Longest headline, in characters. */
63
+ headline: 40,
64
+ /** Longest headline for a creative that runs only as the daily presenting line. */
65
+ presentingHeadline: 60
66
+ };
67
+ /** The oldest ratings an advertised film, show, game or app may carry (policy sections 1 and 2). */
68
+ var MEDIA_RATINGS = "G, U, PEGI 3 or ESRB Everyone";
69
+ /** The rules in brief. For the /advertise page; scripts/advertising-policy.test.ts checks it keeps the rating line. */
70
+ var RULE_SUMMARY = [
71
+ "No alcohol, tobacco or vaping, gambling, dating, weapons, adult content, food and drink, charities, or political and issue ads.",
72
+ `Games, apps, films and shows only when rated ${MEDIA_RATINGS}, with the rating shown.`,
73
+ "Honest claims and no pressure to buy: nothing that tells children to ask a parent or to hurry.",
74
+ "Nothing that looks like part of the site or a game, such as fake buttons, scores or alerts.",
75
+ "A static image with alt text, a short headline and one link to a page that suits every age.",
76
+ "We review every creative before it runs, and may refuse or remove any ad."
77
+ ];
78
+ /** Never advertised, in any slot. */
79
+ var PROHIBITED = [
80
+ {
81
+ name: "Alcohol",
82
+ detail: "Drinks, brands, bars and alcohol-branded merchandise."
83
+ },
84
+ {
85
+ name: "Tobacco and vaping",
86
+ detail: "Cigarettes, e-cigarettes, vapes, nicotine pouches, cannabis and their accessories."
87
+ },
88
+ {
89
+ name: "Gambling",
90
+ detail: "Betting, casinos, lotteries, sweepstakes casinos, loot boxes sold for money and skin gambling."
91
+ },
92
+ {
93
+ name: "Dating and relationships",
94
+ detail: "Dating apps, matchmaking and companion chat services."
95
+ },
96
+ {
97
+ name: "Weapons",
98
+ detail: "Firearms, ammunition, knives sold as weapons, fireworks and realistic replica guns."
99
+ },
100
+ {
101
+ name: "Adult content",
102
+ detail: "Sexual products and services, and suggestive or violent imagery."
103
+ },
104
+ {
105
+ name: "Political and issue ads",
106
+ detail: "Candidates, parties, elections, ballot measures and campaigns on social or political issues."
107
+ },
108
+ {
109
+ name: "Drugs and supplements",
110
+ detail: "Prescription and over-the-counter medicines, diet and weight-loss products, and supplements."
111
+ },
112
+ {
113
+ name: "Financial products",
114
+ detail: "Loans, credit, buy-now-pay-later, trading, crypto-assets and get-rich schemes."
115
+ },
116
+ {
117
+ name: "Cosmetic procedures",
118
+ detail: "Surgery, injectables and other cosmetic treatments."
119
+ },
120
+ {
121
+ name: "Media rated for older audiences",
122
+ detail: `Films, shows, games and apps rated above ${MEDIA_RATINGS}, or an equivalent.`
123
+ },
124
+ {
125
+ name: "Food and drink",
126
+ detail: "All food and drink, including brand-only ads from companies that sell food high in fat, salt or sugar."
127
+ },
128
+ {
129
+ name: "Charities",
130
+ detail: "Ads from charities and other good causes. We may accept them in a later version."
131
+ },
132
+ {
133
+ name: "Data collection",
134
+ detail: "Offers that ask for personal details, such as sign-ups, quizzes, surveys and contests."
135
+ },
136
+ {
137
+ name: "Unmoderated chat and social apps",
138
+ detail: "Services where children could talk with strangers."
139
+ },
140
+ {
141
+ name: "Deceptive or harmful offers",
142
+ detail: "Scams, miracle claims, malware, fake downloads and anything illegal where it is shown."
143
+ }
144
+ ];
145
+ /** Allowed only with the conditions given. */
146
+ var RESTRICTED = [
147
+ {
148
+ name: "Games, apps, films and shows",
149
+ detail: `Rated ${MEDIA_RATINGS}, or an equivalent, with the rating shown. In-game purchases must be stated, and the ad may not push them.`
150
+ },
151
+ {
152
+ name: "Toys and products for children",
153
+ detail: "Shown as they are, at their real size, doing what they really do."
154
+ },
155
+ {
156
+ name: "Paid services and subscriptions",
157
+ detail: "The ad must say when something costs money. Free trials must say what happens when they end."
158
+ },
159
+ {
160
+ name: "Education",
161
+ detail: "Learning products and courses, with no promise of grades or results."
162
+ }
163
+ ];
164
+ /** How an ad may speak to the people who see it. */
165
+ var CLAIMS_RULES = [
166
+ "Be truthful. Every claim must be one you can prove, and the image must show the product as it is, at its real size.",
167
+ "No pressure to buy: no \"buy now\", \"ask your parents\", countdowns, \"only today\" or \"last chance\", and no \"only\" or \"just\" in front of a price.",
168
+ "Never suggest that someone is less popular, clever or grown-up without the product.",
169
+ "Show prices plainly, and say when something is sold separately or needs batteries.",
170
+ "Endorsements must be genuine, and any paid connection must be stated.",
171
+ "Show nothing unsafe that a child could copy.",
172
+ "No imitation of the site or a game: no fake buttons, close marks, scores, rewards, timers, alerts or system messages.",
173
+ "No content that mocks, stereotypes or frightens."
174
+ ];
175
+ /** Where a click may take someone. */
176
+ var LANDING_RULES = [
177
+ "The page the ad links to must match the ad, load over HTTPS and be suitable for every age.",
178
+ "No pop-ups, automatic downloads, redirects to other offers or requests for personal details.",
179
+ "The link may not carry tracking that identifies a visitor. We count clicks on our own server, without cookies.",
180
+ "Before anyone leaves Joystyk, a page of ours says they're leaving and names your site's domain. They continue from there straight to your page."
181
+ ];
182
+ /** Format rules for every slot; the sizes come from the slot registry. */
183
+ var FORMAT_RULES = [
184
+ "A static image in AVIF, WebP or PNG, at twice the box size so it stays sharp on high-density screens, and no larger.",
185
+ `At most ${String(CREATIVE_SPECS.imageKb)} KB per image file.`,
186
+ `Alt text that says what the image shows, in ${String(CREATIVE_SPECS.alt)} characters or fewer.`,
187
+ `A headline of ${String(CREATIVE_SPECS.headline)} characters or fewer; the daily presenting line may run to ${String(CREATIVE_SPECS.presentingHeadline)}.`,
188
+ "One link, to an HTTPS page.",
189
+ "No HTML, JavaScript, animation, sound or video. Animated images may come later; we will say when."
190
+ ];
191
+ /** What happens between a booking and a creative going live. */
192
+ var REVIEW_STEPS = [
193
+ "You send the images, headline, alt text, link and dates.",
194
+ "We check the content against these rules and the technical specs, and look at the landing page.",
195
+ "We approve the creative, or tell you what to change.",
196
+ "Only approved creatives run. We look again if the landing page or the product changes."
197
+ ];
198
+ /** What happens to payment when an ad doesn't run (policy section 8). */
199
+ var REFUND_RULES = ["If an ad can't run through our own fault, we refund the days it missed or run it for as many extra days.", "If we take an ad down because it breaks these rules, we refund only the days it has not yet run."];
200
+ //#endregion
201
+ export { ADVERTISE_EMAIL, CLAIMS_RULES, CONTACT_HANDLED_RETENTION_DAYS, CONTACT_HONEYPOT, CONTACT_INTENT, CONTACT_LIMITS, CONTACT_RETENTION_MONTHS, CREATIVE_SPECS, FORMAT_RULES, LANDING_RULES, MEDIA_RATINGS, PROHIBITED, REFUND_RULES, REPLY_BUSINESS_DAYS, RESTRICTED, REVIEW_STEPS, RULE_SUMMARY, SLOT_NAMES, SURFACE_NAMES, previewSize, sizeLabel };
202
+
203
+ //# sourceMappingURL=advertise.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"advertise.js","names":[],"sources":["../src/advertise.ts"],"sourcesContent":["// What the \"Advertise with us\" pages and the site share: slot names for people, the contact\n// form's fields and limits, how long a message is kept, the creative specs the ad store\n// enforces, and the advertiser-facing summary of the creative policy. The policy itself, with\n// its sources, is docs/product-specs/advertising-policy.md; change both together\n// (scripts/advertising-policy.test.ts checks the categories, the rating line and the spec numbers).\nimport type { FormFields } from '@gyral/core';\nimport type { SlotDefinition, SlotId, SlotSurface } from '@joystyk/ui/slots';\n\n/** A slot's name on the advertise pages and in the contact form. */\nexport const SLOT_NAMES: Readonly<Record<SlotId, string>> = {\n 'index-hero': 'Home page banner',\n 'index-inline': 'Home page inline banner',\n 'category-rail': 'Side rail',\n 'game-page': 'Game page rectangle',\n results: 'Game-over rectangle',\n 'daily-presenting': 'Daily presenting sponsor',\n};\n\n/** Where a surface is, in words. */\nexport const SURFACE_NAMES: Readonly<Record<SlotSurface, string>> = {\n home: 'the home page',\n category: 'category pages',\n game: 'game pages',\n results: 'the game-over screen',\n daily: 'daily games',\n};\n\n/** \"728 × 90 px\". */\nexport const sizeLabel = (size: { readonly width: number; readonly height: number }): string =>\n `${String(size.width)} × ${String(size.height)} px`;\n\n/** The box a slot's preview draws: its wide box, or its narrow one if it has no wide box. */\nexport const previewSize = (s: SlotDefinition) => s.sizes.wide ?? s.sizes.narrow ?? { width: 0, height: 0 };\n\n// --- The contact form ---------------------------------------------------------------------\n\n/** The form's `data-intent` name: `IntentRejected.intent` when the server rejects a post. */\nexport const CONTACT_INTENT = 'SendContact';\n\n/** Longest value each text field takes, in characters. The markup and the server share them. */\nexport const CONTACT_LIMITS = { name: 100, company: 100, email: 254, message: 2000 } as const;\n\n/**\n * The honeypot: a field people never see (CSS hides it) and bots tend to fill. A post with\n * anything in it is answered like a real one and thrown away.\n */\nexport const CONTACT_HONEYPOT = 'website';\n\n/**\n * The longest a message is kept, counted from when it arrives, handled or not\n * (advertising-policy.md, section 9). The page quotes this number.\n */\nexport const CONTACT_RETENTION_MONTHS = 12;\n\n/** How long a message is kept after it is marked handled, if that comes sooner. */\nexport const CONTACT_HANDLED_RETENTION_DAYS = 90;\n\n/** Where advertisers can write instead of using the form. */\nexport const ADVERTISE_EMAIL = 'info@joystyk.com';\n\n/** The reply time the thank-you page promises, in business days. */\nexport const REPLY_BUSINESS_DAYS = 3;\n\n/** A rejected post, as the page re-renders it: what was sent and the errors per field. */\nexport interface ContactFormState {\n readonly values: FormFields;\n /** Errors per field name; '' holds form-level errors (rate limit, a full inbox). */\n readonly errors: Readonly<Record<string, readonly string[]>>;\n}\n\n// --- Creative specs ---------------------------------------------------------------------\n\n/**\n * The technical limits every creative meets (advertising-policy.md, section 7). The site's\n * ad store enforces these numbers, and the policy page quotes them.\n */\nexport const CREATIVE_SPECS = {\n /** Largest image file, in KB of 1,024 bytes. */\n imageKb: 150,\n /** An image may be at most this many times its box's CSS pixels on each side. */\n density: 2,\n /** Longest alt text, in characters. */\n alt: 125,\n /** Longest headline, in characters. */\n headline: 40,\n /** Longest headline for a creative that runs only as the daily presenting line. */\n presentingHeadline: 60,\n} as const;\n\n// --- The creative policy, for advertisers ------------------------------------------------\n\n/** The oldest ratings an advertised film, show, game or app may carry (policy sections 1 and 2). */\nexport const MEDIA_RATINGS = 'G, U, PEGI 3 or ESRB Everyone';\n\nexport interface PolicyItem {\n readonly name: string;\n readonly detail: string;\n}\n\n/** The rules in brief. For the /advertise page; scripts/advertising-policy.test.ts checks it keeps the rating line. */\nexport const RULE_SUMMARY: readonly string[] = [\n 'No alcohol, tobacco or vaping, gambling, dating, weapons, adult content, food and drink, charities, or political and issue ads.',\n `Games, apps, films and shows only when rated ${MEDIA_RATINGS}, with the rating shown.`,\n 'Honest claims and no pressure to buy: nothing that tells children to ask a parent or to hurry.',\n 'Nothing that looks like part of the site or a game, such as fake buttons, scores or alerts.',\n 'A static image with alt text, a short headline and one link to a page that suits every age.',\n 'We review every creative before it runs, and may refuse or remove any ad.',\n];\n\n/** Never advertised, in any slot. */\nexport const PROHIBITED: readonly PolicyItem[] = [\n { name: 'Alcohol', detail: 'Drinks, brands, bars and alcohol-branded merchandise.' },\n {\n name: 'Tobacco and vaping',\n detail: 'Cigarettes, e-cigarettes, vapes, nicotine pouches, cannabis and their accessories.',\n },\n {\n name: 'Gambling',\n detail: 'Betting, casinos, lotteries, sweepstakes casinos, loot boxes sold for money and skin gambling.',\n },\n { name: 'Dating and relationships', detail: 'Dating apps, matchmaking and companion chat services.' },\n { name: 'Weapons', detail: 'Firearms, ammunition, knives sold as weapons, fireworks and realistic replica guns.' },\n { name: 'Adult content', detail: 'Sexual products and services, and suggestive or violent imagery.' },\n {\n name: 'Political and issue ads',\n detail: 'Candidates, parties, elections, ballot measures and campaigns on social or political issues.',\n },\n {\n name: 'Drugs and supplements',\n detail: 'Prescription and over-the-counter medicines, diet and weight-loss products, and supplements.',\n },\n {\n name: 'Financial products',\n detail: 'Loans, credit, buy-now-pay-later, trading, crypto-assets and get-rich schemes.',\n },\n { name: 'Cosmetic procedures', detail: 'Surgery, injectables and other cosmetic treatments.' },\n {\n name: 'Media rated for older audiences',\n detail: `Films, shows, games and apps rated above ${MEDIA_RATINGS}, or an equivalent.`,\n },\n {\n name: 'Food and drink',\n detail: 'All food and drink, including brand-only ads from companies that sell food high in fat, salt or sugar.',\n },\n { name: 'Charities', detail: 'Ads from charities and other good causes. We may accept them in a later version.' },\n {\n name: 'Data collection',\n detail: 'Offers that ask for personal details, such as sign-ups, quizzes, surveys and contests.',\n },\n { name: 'Unmoderated chat and social apps', detail: 'Services where children could talk with strangers.' },\n {\n name: 'Deceptive or harmful offers',\n detail: 'Scams, miracle claims, malware, fake downloads and anything illegal where it is shown.',\n },\n];\n\n/** Allowed only with the conditions given. */\nexport const RESTRICTED: readonly PolicyItem[] = [\n {\n name: 'Games, apps, films and shows',\n detail: `Rated ${MEDIA_RATINGS}, or an equivalent, with the rating shown. In-game purchases must be stated, and the ad may not push them.`,\n },\n {\n name: 'Toys and products for children',\n detail: 'Shown as they are, at their real size, doing what they really do.',\n },\n {\n name: 'Paid services and subscriptions',\n detail: 'The ad must say when something costs money. Free trials must say what happens when they end.',\n },\n { name: 'Education', detail: 'Learning products and courses, with no promise of grades or results.' },\n];\n\n/** How an ad may speak to the people who see it. */\nexport const CLAIMS_RULES: readonly string[] = [\n 'Be truthful. Every claim must be one you can prove, and the image must show the product as it is, at its real size.',\n 'No pressure to buy: no \"buy now\", \"ask your parents\", countdowns, \"only today\" or \"last chance\", and no \"only\" or \"just\" in front of a price.',\n 'Never suggest that someone is less popular, clever or grown-up without the product.',\n 'Show prices plainly, and say when something is sold separately or needs batteries.',\n 'Endorsements must be genuine, and any paid connection must be stated.',\n 'Show nothing unsafe that a child could copy.',\n 'No imitation of the site or a game: no fake buttons, close marks, scores, rewards, timers, alerts or system messages.',\n 'No content that mocks, stereotypes or frightens.',\n];\n\n/** Where a click may take someone. */\nexport const LANDING_RULES: readonly string[] = [\n 'The page the ad links to must match the ad, load over HTTPS and be suitable for every age.',\n 'No pop-ups, automatic downloads, redirects to other offers or requests for personal details.',\n 'The link may not carry tracking that identifies a visitor. We count clicks on our own server, without cookies.',\n \"Before anyone leaves Joystyk, a page of ours says they're leaving and names your site's domain. They continue from there straight to your page.\",\n];\n\n/** Format rules for every slot; the sizes come from the slot registry. */\nexport const FORMAT_RULES: readonly string[] = [\n 'A static image in AVIF, WebP or PNG, at twice the box size so it stays sharp on high-density screens, and no larger.',\n `At most ${String(CREATIVE_SPECS.imageKb)} KB per image file.`,\n `Alt text that says what the image shows, in ${String(CREATIVE_SPECS.alt)} characters or fewer.`,\n `A headline of ${String(CREATIVE_SPECS.headline)} characters or fewer; the daily presenting line may run to ${String(CREATIVE_SPECS.presentingHeadline)}.`,\n 'One link, to an HTTPS page.',\n 'No HTML, JavaScript, animation, sound or video. Animated images may come later; we will say when.',\n];\n\n/** What happens between a booking and a creative going live. */\nexport const REVIEW_STEPS: readonly string[] = [\n 'You send the images, headline, alt text, link and dates.',\n 'We check the content against these rules and the technical specs, and look at the landing page.',\n 'We approve the creative, or tell you what to change.',\n 'Only approved creatives run. We look again if the landing page or the product changes.',\n];\n\n/** What happens to payment when an ad doesn't run (policy section 8). */\nexport const REFUND_RULES: readonly string[] = [\n \"If an ad can't run through our own fault, we refund the days it missed or run it for as many extra days.\",\n 'If we take an ad down because it breaks these rules, we refund only the days it has not yet run.',\n];\n"],"mappings":";;AASA,IAAa,aAA+C;CAC1D,cAAc;CACd,gBAAgB;CAChB,iBAAiB;CACjB,aAAa;CACb,SAAS;CACT,oBAAoB;AACtB;;AAGA,IAAa,gBAAuD;CAClE,MAAM;CACN,UAAU;CACV,MAAM;CACN,SAAS;CACT,OAAO;AACT;;AAGA,IAAa,aAAa,SACxB,GAAG,OAAO,KAAK,KAAK,EAAE,KAAK,OAAO,KAAK,MAAM,EAAE;;AAGjD,IAAa,eAAe,MAAsB,EAAE,MAAM,QAAQ,EAAE,MAAM,UAAU;CAAE,OAAO;CAAG,QAAQ;AAAE;;AAK1G,IAAa,iBAAiB;;AAG9B,IAAa,iBAAiB;CAAE,MAAM;CAAK,SAAS;CAAK,OAAO;CAAK,SAAS;AAAK;;;;;AAMnF,IAAa,mBAAmB;;;;;AAMhC,IAAa,2BAA2B;;AAGxC,IAAa,iCAAiC;;AAG9C,IAAa,kBAAkB;;AAG/B,IAAa,sBAAsB;;;;;AAenC,IAAa,iBAAiB;;CAE5B,SAAS;;CAET,SAAS;;CAET,KAAK;;CAEL,UAAU;;CAEV,oBAAoB;AACtB;;AAKA,IAAa,gBAAgB;;AAQ7B,IAAa,eAAkC;CAC7C;CACA,gDAAgD,cAAc;CAC9D;CACA;CACA;CACA;AACF;;AAGA,IAAa,aAAoC;CAC/C;EAAE,MAAM;EAAW,QAAQ;CAAwD;CACnF;EACE,MAAM;EACN,QAAQ;CACV;CACA;EACE,MAAM;EACN,QAAQ;CACV;CACA;EAAE,MAAM;EAA4B,QAAQ;CAAwD;CACpG;EAAE,MAAM;EAAW,QAAQ;CAAsF;CACjH;EAAE,MAAM;EAAiB,QAAQ;CAAmE;CACpG;EACE,MAAM;EACN,QAAQ;CACV;CACA;EACE,MAAM;EACN,QAAQ;CACV;CACA;EACE,MAAM;EACN,QAAQ;CACV;CACA;EAAE,MAAM;EAAuB,QAAQ;CAAsD;CAC7F;EACE,MAAM;EACN,QAAQ,4CAA4C,cAAc;CACpE;CACA;EACE,MAAM;EACN,QAAQ;CACV;CACA;EAAE,MAAM;EAAa,QAAQ;CAAmF;CAChH;EACE,MAAM;EACN,QAAQ;CACV;CACA;EAAE,MAAM;EAAoC,QAAQ;CAAqD;CACzG;EACE,MAAM;EACN,QAAQ;CACV;AACF;;AAGA,IAAa,aAAoC;CAC/C;EACE,MAAM;EACN,QAAQ,SAAS,cAAc;CACjC;CACA;EACE,MAAM;EACN,QAAQ;CACV;CACA;EACE,MAAM;EACN,QAAQ;CACV;CACA;EAAE,MAAM;EAAa,QAAQ;CAAuE;AACtG;;AAGA,IAAa,eAAkC;CAC7C;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;AAGA,IAAa,gBAAmC;CAC9C;CACA;CACA;CACA;AACF;;AAGA,IAAa,eAAkC;CAC7C;CACA,WAAW,OAAO,eAAe,OAAO,EAAE;CAC1C,+CAA+C,OAAO,eAAe,GAAG,EAAE;CAC1E,iBAAiB,OAAO,eAAe,QAAQ,EAAE,6DAA6D,OAAO,eAAe,kBAAkB,EAAE;CACxJ;CACA;AACF;;AAGA,IAAa,eAAkC;CAC7C;CACA;CACA;CACA;AACF;;AAGA,IAAa,eAAkC,CAC7C,4GACA,kGACF"}
@@ -0,0 +1,12 @@
1
+ export interface CategoryDefinition {
2
+ /** Kebab-case; appears in /categories/<id>. */
3
+ readonly id: string;
4
+ /** Display name, e.g. "Dice". */
5
+ readonly name: string;
6
+ /** Lowercase singular for prose and titles, e.g. "dice game". */
7
+ readonly noun: string;
8
+ /** One or two sentences for the category page and its meta description. */
9
+ readonly description: string;
10
+ }
11
+ export declare const CATEGORIES: readonly CategoryDefinition[];
12
+ export declare const findCategory: (id: string) => CategoryDefinition | undefined;
@@ -0,0 +1,45 @@
1
+ //#region src/categories.ts
2
+ var CATEGORIES = [
3
+ {
4
+ id: "arcade",
5
+ name: "Arcade",
6
+ noun: "arcade game",
7
+ description: "Fast, real-time games built on reflexes and timing. One more go is always one click away."
8
+ },
9
+ {
10
+ id: "board",
11
+ name: "Board",
12
+ noun: "board game",
13
+ description: "Classic board games for two: play the computer or pass the device to a friend."
14
+ },
15
+ {
16
+ id: "cards",
17
+ name: "Cards",
18
+ noun: "card game",
19
+ description: "Card games to play alone, against computer opponents, or with friends at one table."
20
+ },
21
+ {
22
+ id: "dice",
23
+ name: "Dice",
24
+ noun: "dice game",
25
+ description: "Dice games of luck and nerve, for a quick turn or a long party."
26
+ },
27
+ {
28
+ id: "puzzle",
29
+ name: "Puzzle",
30
+ noun: "puzzle",
31
+ description: "Puzzles to solve at your own pace, with no clock unless you want one."
32
+ },
33
+ {
34
+ id: "word",
35
+ name: "Word",
36
+ noun: "word game",
37
+ description: "Word games and daily puzzles, the same for everyone each day."
38
+ }
39
+ ];
40
+ var byId = new Map(CATEGORIES.map((c) => [c.id, c]));
41
+ var findCategory = (id) => byId.get(id);
42
+ //#endregion
43
+ export { CATEGORIES, findCategory };
44
+
45
+ //# sourceMappingURL=categories.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"categories.js","names":[],"sources":["../src/categories.ts"],"sourcesContent":["// The categories a game's manifest may name. A category gets a page only once it has a\n// game (the registry counts them), so planned categories can be listed here early.\n\nexport interface CategoryDefinition {\n /** Kebab-case; appears in /categories/<id>. */\n readonly id: string;\n /** Display name, e.g. \"Dice\". */\n readonly name: string;\n /** Lowercase singular for prose and titles, e.g. \"dice game\". */\n readonly noun: string;\n /** One or two sentences for the category page and its meta description. */\n readonly description: string;\n}\n\nexport const CATEGORIES: readonly CategoryDefinition[] = [\n {\n id: 'arcade',\n name: 'Arcade',\n noun: 'arcade game',\n description: 'Fast, real-time games built on reflexes and timing. One more go is always one click away.',\n },\n {\n id: 'board',\n name: 'Board',\n noun: 'board game',\n description: 'Classic board games for two: play the computer or pass the device to a friend.',\n },\n {\n id: 'cards',\n name: 'Cards',\n noun: 'card game',\n description: 'Card games to play alone, against computer opponents, or with friends at one table.',\n },\n {\n id: 'dice',\n name: 'Dice',\n noun: 'dice game',\n description: 'Dice games of luck and nerve, for a quick turn or a long party.',\n },\n {\n id: 'puzzle',\n name: 'Puzzle',\n noun: 'puzzle',\n description: 'Puzzles to solve at your own pace, with no clock unless you want one.',\n },\n {\n id: 'word',\n name: 'Word',\n noun: 'word game',\n description: 'Word games and daily puzzles, the same for everyone each day.',\n },\n];\n\nconst byId = new Map(CATEGORIES.map((c) => [c.id, c]));\n\nexport const findCategory = (id: string): CategoryDefinition | undefined => byId.get(id);\n"],"mappings":";AAcA,IAAa,aAA4C;CACvD;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,aAAa;CACf;CACA;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,aAAa;CACf;CACA;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,aAAa;CACf;CACA;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,aAAa;CACf;CACA;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,aAAa;CACf;CACA;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,aAAa;CACf;AACF;AAEA,IAAM,OAAO,IAAI,IAAI,WAAW,KAAK,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;AAErD,IAAa,gBAAgB,OAA+C,KAAK,IAAI,EAAE"}
@@ -0,0 +1 @@
1
+ import './components/app.ts';
package/dist/client.js ADDED
@@ -0,0 +1,9 @@
1
+ import "./components/app.js";
2
+ import { provideDrivers } from "@gyral/core";
3
+ import { makeRouter } from "@gyral/router";
4
+ //#region src/client.ts
5
+ provideDrivers(document.documentElement, { router: makeRouter({ captureLinks: true }) });
6
+ //#endregion
7
+ export {};
8
+
9
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","names":[],"sources":["../src/client.ts"],"sourcesContent":["// The browser entry: importing the components is all hydration needs. Core restores the\n// catalog store from the page's seed before <jy-app> hydrates.\nimport './components/app.ts';\nimport { provideDrivers } from '@gyral/core';\nimport { makeRouter } from '@gyral/router';\n\nprovideDrivers(document.documentElement, { router: makeRouter({ captureLinks: true }) });\n"],"mappings":";;;;AAMA,eAAe,SAAS,iBAAiB,EAAE,QAAQ,WAAW,EAAE,cAAc,KAAK,CAAC,EAAE,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { CatalogState } from '../store.js';
2
+ export declare function advertiseView(c: CatalogState): import("@gyral/core").TemplateResult;
3
+ export declare function policyView(): import("@gyral/core").TemplateResult;
4
+ export declare const sentView: () => import("@gyral/core").TemplateResult;