diff --git a/.agents/instructions/ArchieML/ARCHIEML-BETTY.md b/.agents/instructions/ArchieML/ARCHIEML-BETTY.md new file mode 100644 index 0000000..77e603f --- /dev/null +++ b/.agents/instructions/ArchieML/ARCHIEML-BETTY.md @@ -0,0 +1,647 @@ +# ArchieML-Veronica Language Reference + +Veronica is a more specific dialect of [ArchieML](https://archieml.org) designed for newsrooms and content editors. While ArchieML is "forgiving," Veronica makes some syntax more explicit to prevent common parsing errors, especially when working with multiline content and nested structures. + +## Table of Contents + +- [Keys and Values](#keys-and-values) +- [Objects](#objects) + - [Nested Objects](#nested-objects) + - [Named Closures](#named-closures-veronica-extension) +- [Arrays](#arrays) + - [Arrays of Objects](#arrays-of-objects) + - [Arrays of Strings (Simple Arrays)](#arrays-of-strings-simple-arrays) + - [Freeform Arrays](#freeform-arrays) + - [Nested Arrays](#nested-arrays) + - [Named Array Closures](#named-array-closures-veronica-extension) + - [Repeating Keys in Arrays](#repeating-keys-in-arrays-veronica-extension) +- [Multiline Values](#multiline-values) + - [Veronica Multiline Syntax](#veronica-multiline-syntax-veronica-extension) + - [ArchieML Multiline Syntax](#archieml-multiline-syntax) +- [Comments and Ignored Content](#comments-and-ignored-content) +- [Escaping](#escaping) +- [Hooks and Customization](#hooks-and-customization) + +## Keys and Values + +The simplest form of ArchieML is a key-value pair. Keys are defined by a colon, with the key on the left and the value on the right. + +``` +title: My Article +author: Jane Smith +published: 2024-01-15 +``` + +**Result:** +```json +{ + "title": "My Article", + "author": "Jane Smith", + "published": "2024-01-15" +} +``` + +### Key Rules + +- Keys can contain letters, numbers, underscores, hyphens, and Unicode characters +- Keys **cannot** contain: whitespace, `{`, `}`, `[`, `]`, `:`, `.`, or `+` +- Keys are case-sensitive (`Title` and `title` are different) +- Whitespace around keys and values is automatically trimmed + +### Ignored Content + +Any text that doesn't match ArchieML syntax is ignored, allowing you to include notes and comments freely: + +``` +This is just a note that will be ignored. + +title: My Article +Here's another note about the article. +author: Jane Smith +``` + +## Objects + +### Dot Notation + +You can create nested objects using dot notation: + +``` +colors.red: #ff0000 +colors.green: #00ff00 +colors.blue: #0000ff +``` + +**Result:** +```json +{ + "colors": { + "red": "#ff0000", + "green": "#00ff00", + "blue": "#0000ff" + } +} +``` + +### Object Blocks + +For more complex objects, use curly braces: + +``` +{colors} +red: #ff0000 +green: #00ff00 +blue: #0000ff +{} +``` + +**Result:** Same as above. + +### Nested Objects + +Prepend a period (`.`) to a block name to nest it within the current object: + +``` +{author} +name: Jane Smith +email: jane@example.com + +{.social} +twitter: @janesmith +github: janesmith +{} + +bio: Award-winning journalist +{} +``` + +**Result:** +```json +{ + "author": { + "name": "Jane Smith", + "email": "jane@example.com", + "social": { + "twitter": "@janesmith", + "github": "janesmith" + }, + "bio": "Award-winning journalist" + } +} +``` + +### Closing Objects + +You can close an object in several ways: + +1. **Empty braces** `{}` - closes the current object +2. **Opening a new object** at the same level +3. **Named closure** (Veronica extension) - see below + +### Named Closures (Veronica Extension) + +Veronica extends ArchieML with named closures, allowing you to close a specific object by name, even if you're nested several levels deep: + +``` +{outer} +value: test + +{.middle} +value: nested + +{.inner} +value: deep +{/middle} + +This closes middle and inner, returning to outer scope. +stillOuter: yes +{} +``` + +**Result:** +```json +{ + "outer": { + "value": "test", + "middle": { + "value": "nested", + "inner": { + "value": "deep" + } + }, + "stillOuter": "yes" + } +} +``` + +**Important:** The slash must be flush with the opening brace: `{/name}` works, but `{ /name }` does not. + +## Arrays + +### Arrays of Objects + +Arrays are defined using square brackets. The first key that repeats signals the start of a new item: + +``` +[people] +name: Alice +age: 30 + +name: Bob +age: 25 +[] +``` + +**Result:** +```json +{ + "people": [ + {"name": "Alice", "age": 30}, + {"name": "Bob", "age": 25} + ] +} +``` + +### Arrays of Strings (Simple Arrays) + +For simple lists of strings, use asterisks: + +``` +[tags] +* news +* technology +* AI +[] +``` + +**Result:** +```json +{ + "tags": ["news", "technology", "AI"] +} +``` + +### Freeform Arrays + +Freeform arrays (marked with `[+arrayName]`) preserve the order of different types of content. They're useful for mixed content like articles with text, images, and pull quotes: + +``` +[+content] +This is a paragraph of text. + +Another paragraph here. + +{.image} +src: photo.jpg +caption: A beautiful photo +{} + +More text after the image. + +{.quote} +text: An inspiring quotation +author: Famous Person +{} +[] +``` + +**Result:** +```json +{ + "content": [ + {"type": "text", "value": "This is a paragraph of text."}, + {"type": "text", "value": "Another paragraph here."}, + {"type": "image", "value": {"src": "photo.jpg", "caption": "A beautiful photo"}}, + {"type": "text", "value": "More text after the image."}, + {"type": "quote", "value": {"text": "An inspiring quotation", "author": "Famous Person"}} + ] +} +``` + +### Nested Arrays + +Prepend a period to an array name to nest it: + +``` +[sections] +title: Introduction + +[.subsections] +heading: Background +content: Context here +[] + +heading: Methodology +content: How we did it +[] + +[/sections] +``` + +**Result:** +```json +{ + "sections": [ + { + "title": "Introduction", + "subsections": [ + {"heading": "Background", "content": "Context here"}, + {"heading": "Methodology", "content": "How we did it"} + ] + } + ] +} +``` + +### Named Array Closures (Veronica Extension) + +Like objects, arrays support named closures to jump out of nested structures: + +``` +[parent] +value: test + +[.nested] +* one +* two +[/parent] + +This is outside the parent array. +``` + +This is especially useful when you have deeply nested arrays and want to exit multiple levels at once. + +### Repeating Keys in Arrays (Veronica Extension) + +**This is a key difference from standard ArchieML.** + +In Veronica, arrays start a new item when **any** key is redefined, not just the first key: + +``` +[items] +name: First +description: First item +color: red + +description: Second item +name: Second +color: blue +[] +``` + +**Result:** +```json +{ + "items": [ + {"name": "First", "description": "First item", "color": "red"}, + {"name": "Second", "description": "Second item", "color": "blue"} + ] +} +``` + +In standard ArchieML, the second item would need to redefine `name` (the first key) to start a new item. Veronica is more flexible: redefining `description` also triggers a new item. + +## Multiline Values + +### Veronica Multiline Syntax (Veronica Extension) + +Veronica introduces an explicit multiline syntax that's less ambiguous than ArchieML's `:end` syntax: + +``` +description:: +This is the first line of my description. + +This is the second paragraph. + +This can contain [brackets] and {braces} safely. +::description +``` + +**Result:** +```json +{ + "description": "This is the first line of my description.\n\nThis is the second paragraph.\n\nThis can contain [brackets] and {braces} safely." +} +``` + +**Syntax:** +- Open with `key::` (key followed by double colon) +- Write your content on following lines +- Close with `::key` (double colon followed by the same key name) + +This syntax is more explicit and helps prevent accidentally consuming subsequent content. + +### ArchieML Multiline Syntax + +Veronica still supports the traditional ArchieML multiline syntax with `:end`: + +``` +description: +This is a multiline value. +It continues until :end is found. +:end +``` + +**Important:** Within multiline blocks, ArchieML syntax is **not** parsed. To include literal backslashes or the `:end` marker, use a backslash escape: + +``` +code: +To end a multiline, use \:end +A literal backslash: \\ +:end +``` + +## Comments and Ignored Content + +### Block Comments + +Use `:skip` and `:endskip` to comment out entire sections: + +``` +title: My Article + +:skip +This entire section is ignored. +author: Will Not Parse +{test} +{} +:endskip + +published: 2024-01-15 +``` + +**Result:** +```json +{ + "title": "My Article", + "published": "2024-01-15" +} +``` + +### Stop Parsing + +Use `:ignore` to immediately stop parsing. Everything after `:ignore` is ignored: + +``` +title: My Article +author: Jane Smith + +:ignore + +This and everything below is completely ignored. +Nothing here will be parsed. +``` + +**Result:** +```json +{ + "title": "My Article", + "author": "Jane Smith" +} +``` + +## Escaping + +Use a backslash (`\`) to escape special ArchieML characters when they appear at the start of a line in a multiline context: + +``` +description: +\[This is not an array] +\{This is not an object} +\:end is not the end marker +The real end: +:end +``` + +**Note:** Escaping only works in multiline contexts and only for characters at the start of a line. Outside of multiline blocks, special characters are generally ignored if they don't form valid syntax. + +## Hooks and Customization + +Veronica provides several hooks for customizing parsing behavior: + +### `onFieldName(name: string) => string` + +Transform field names during parsing. Useful for normalizing keys: + +```javascript +const result = parse(text, { + onFieldName: (name) => name.toLowerCase() +}); +``` + +This is particularly helpful when working with Google Docs, which may capitalize the first word of a line. + +### `onValue(value: any, key: string) => any` + +Transform values during parsing. Useful for type coercion: + +```javascript +const result = parse(text, { + onValue: (value, key) => { + // Auto-convert boolean strings + if (value === "true") return true; + if (value === "false") return false; + + // Auto-convert numbers + if (/^\d+$/.test(value)) return parseInt(value); + if (/^\d+\.\d+$/.test(value)) return parseFloat(value); + + // Auto-parse ISO dates + if (/^\d{4}-\d{2}-\d{2}/.test(value)) { + return new Date(value); + } + + return value; + } +}); +``` + +### `onEnter(keypath: string[], item: any) => void` + +Called when entering an object or array. The `keypath` is an array of strings representing the path from the root: + +```javascript +const result = parse(text, { + onEnter: (keypath, item) => { + console.log(`Entering: ${keypath.join('.')}`); + console.log(`Type: ${Array.isArray(item) ? 'array' : 'object'}`); + } +}); +``` + +### `onExit(keypath: string[], item: any) => any` + +Called when exiting an object or array. This is ideal for validation and adding computed properties. If you return a value, it replaces the item in the output: + +```javascript +const result = parse(text, { + onExit: (keypath, item) => { + // Validate required fields + if (keypath[0] === "person" && !item.name) { + throw new Error("Person must have a name"); + } + + // Add computed properties + if (keypath[0] === "person" && item.firstName && item.lastName) { + item.fullName = `${item.firstName} ${item.lastName}`; + } + + return item; // Return the modified item + } +}); +``` + +**Example with validation:** + +```javascript +const text = ` +{author} +firstName: Jane +lastName: Smith +{/author} +`; + +const result = parse(text, { + onExit: (keypath, item) => { + if (keypath[0] === "author") { + if (!item.firstName || !item.lastName) { + throw new Error("Author must have both firstName and lastName"); + } + // Add computed fullName + item.fullName = `${item.firstName} ${item.lastName}`; + } + return item; + } +}); + +// Result: { author: { firstName: "Jane", lastName: "Smith", fullName: "Jane Smith" } } +``` + +### `verbose: boolean` + +Enable verbose logging to see detailed parsing information: + +```javascript +const result = parse(text, { + verbose: true +}); +``` + +## Complete Example + +Here's a comprehensive example showing many Veronica features: + +``` +title: Understanding Veronica +subtitle: A Guide to Structured Content +published: 2024-01-15 + +{metadata} +tags.primary: archieml +tags.secondary: parsing +wordCount: 1500 +{/metadata} + +intro:: +This is a multiline introduction to Veronica. + +It supports multiple paragraphs and preserves formatting. +::intro + +[sections] +heading: Introduction +body: Welcome to Veronica, a dialect of ArchieML. + +heading: Features +body: Veronica adds several improvements over standard ArchieML. + +[.examples] +* Named closures +* Explicit multiline syntax +* Better array handling +[/sections] + +[+mixedContent] +This is a text paragraph. + +{.callout} +type: warning +message: Remember to close your arrays! +{} + +Another text paragraph here. +[] + +{author} +firstName: Jane +lastName: Smith +email: jane@example.com + +{.social} +twitter: @janesmith +github: janesmith +{/author} + +footer: Copyright 2024 +``` + +This example demonstrates: +- Simple key-value pairs +- Object blocks with nested objects +- Dot notation for nested keys +- Veronica multiline syntax (`key::` / `::key`) +- Arrays of objects with nested arrays +- Freeform arrays with mixed content +- Named closures (`{/author}`, `[/sections]`) + +## Summary of Veronica Extensions + +Veronica extends ArchieML with these key features: + +1. **Named closures** - `{/name}` and `[/name]` to exit specific nesting levels +2. **Explicit multiline syntax** - `key::` / `::key` delimiters for unambiguous multiline values +3. **Flexible array items** - Any redefined key starts a new array item, not just the first key +4. **Lifecycle hooks** - `onEnter` and `onExit` callbacks for validation and transformation +5. **Value transformation** - `onFieldName` and `onValue` hooks for custom processing + +These changes make Veronica more predictable and robust when working with complex structured content, especially in collaborative editing environments like Google Docs. diff --git a/.agents/instructions/graphics-components/llms.md b/.agents/instructions/graphics-components/llms.md new file mode 100644 index 0000000..143584e --- /dev/null +++ b/.agents/instructions/graphics-components/llms.md @@ -0,0 +1,483 @@ +--- +applyTo: "**/*.svelte" +--- + +## Graphics components + +The `@reuters-graphics/graphics-components` library includes pre-styled components for easily adding graphics or other elements to a page. + +### Adding new components to a page + +Components from the `@reuters-graphics/graphics-components` library are often added to the `#each` loop in `src/lib/App.svelte` that loops over `content.blocks`. + +`content` represents text content pulled from our CMS as JSON that is passed into components via props. + +Each block in `content.blocks` (i.e., "content block") is usually an object with a `type` property and additional properties specific to the block type. For example: + +- **Text Block**: + ```json + { + "type": "text", + "text": "This is a text block." + } + ``` +- **AI Graphic Block** + ```json + { + "type": "ai-graphic", + "chart": "AiMap", + "width": "normal", + "textWidth": "normal", + "title": "Optional title of the graphic", + "description": "Optional chatter describes more about the graphic.", + "notes": "Note: Optional note clarifying something in the data.\r\n\r\nSource: Optional source of the data.", + "altText": "Add a description of the graphic for screen readers. This is invisible on the page." + } + ``` + +To add a new component to the loop: + +1. Import the component in the script portion of the Svelte component. +2. Add a new `else if` condition in the `{#each content.blocks as block}` loop to handle the new block type. +3. Pass the required props to the component, ensuring they match the structure of the content block object. + +For example, to add a new FeaturePhoto: + +```svelte + + +{#each content.blocks as block} + + {#if block.type === 'text'} + + + + + + {:else if block.type === 'feature-photo'} + + {:else} + + {/if} +{/each} +``` + +### Paths to multimedia files + +Notice in the example above, we append the `assets` variable from SvelteKit's `$app/paths` module to the `src` path we got from the content block. + +Always assume that paths to local multimedia files including images and videos specified in content blocks are relative and must be prefixed with the `assets` variable to make them absolute. For example: + +```svelte + +``` + +### Adding AI Graphics + +AI (i.e., Adobe Illustrator) graphics are added to the page by importing a component from the `src/lib/ai2svelte/` directory and then adding that graphic to the `aiCharts` object in `src/lib/App.svelte`. Then the object key for that chart is included in the content block's `chart` property. + +For example, to use an AI graphic from `src/lib/ai2svelte/map.svelte`: + +- The AI graphic component is imported and added to the `aiCharts` object in `src/lib/App.svelte`: + + ```svelte + + ``` + +- Now the content block will specify the key to the chart in `aiCharts` in the `chart` property: + + ```json + { + "type": "ai-graphic", + "chart": "Map", + "width": "normal", + "textWidth": "normal", + "title": "My map", + "description": "A map of the area", + "notes": "Source: DataSource.org", + "altText": "A map of a specific area showing something interesting" + } + ``` + +- Now the `{:else if block.type === 'ai-graphic'}` block in `src/lib/App.svelte` uses that key to get the component: + + ```svelte + {#each content.blocks as block} + + {#if block.type === 'text'} + + + + {:else if block.type === 'ai-graphic'} + {#if !aiCharts[block.chart]} + + {:else} + {@const AiChart = aiCharts[block.chart]} + + + + {/if} + + + {/if} + {/each} + ``` + +### Refer to graphics components Storybook + +If you're unsure how to implement a particular graphics component, suggest the user check the Storybook documentation site, which is hosted on GitHub at https://reuters-graphics.github.io/graphics-components/. + +### Writing content blocks in our CMS + +While the text content in the `content` object is formatted as JSON, that data is written into our CMS (which is called "RNGS.io") using ArchieML syntax. + +**When suggesting what content blocks to add for components, please also suggest how to write that content in our CMS (RNGS.io) using ArchieML.** + +#### ArchieML + +ArchieML is a lightweight and intuitive markup language that allows for easy structuring of data within text documents. It is designed to be human-readable, very flexible, and is particularly useful for creating structured data by users who may never have seen ArchieML or any other markup language before. + +##### Basic Syntax + +- **Keys and Values** + + - Definition: Key-value pairs are defined by a line starting with a key followed by a colon. Keys can include any unicode character except whitespace and specific characters used within ArchieML ({ } [ ] : . +). + + - Example: + + ``` + key: This is a value + ☃: Unicode Snowman for you and you and you! + ``` + + - Parsed JSON: + + ```json + { + "key": "This is a value", + "☃": "Unicode Snowman for you and you and you!" + } + ``` + + - Whitespace around keys and values is ignored. Keys are case-sensitive. + +- **Multi-line Values**: Multi-line values are anchored with `:end`. All whitespace is preserved. + + - Example: + + ``` + key: value + More value + + Even more value + :end + ``` + + - Parsed JSON: + ```json + { + "key": "value\n More value\n\nEven more value" + } + ``` + - Escape Characters: Lines that would be interpreted as keys or commands can be escaped with a backslash `\`. + - Example: + ``` + key: value + \:end + :end + ``` + - Parsed JSON: + ```json + { + "key": "value\n:end" + } + ``` + +- **Nested Structures** + + - **Dot-Notation**: Use dot-notation for creating nested objects. + - Example: + ``` + colors.red: #f00 + colors.green: #0f0 + colors.blue: #00f + ``` + - Parsed JSON: + ```json + { + "colors": { + "red": "#f00", + "green": "#0f0", + "blue": "#00f" + } + } + ``` + - **Object Blocks**: Group keys using object blocks defined by {}. Close an object with {} or by starting a new object. + + - Example: + + ``` + {colors} + red: #f00 + green: #0f0 + blue: #00f + {} + + {numbers} + one: 1 + ten: 10 + one-hundred: 100 + {} + ``` + + - Parsed JSON: + ```json + { + "colors": { + "red": "#f00", + "green": "#0f0", + "blue": "#00f" + }, + "numbers": { + "one": "1", + "ten": "10", + "one-hundred": "100" + } + } + ``` + +- **Arrays** + + - **Arrays of Objects**: Define arrays with brackets [arrayName]. New objects start when the first key is re-encountered. + + - Example: + + ``` + [arrayName] + name: Amanda + age: 26 + + name: Tessa + age: 30 + [] + ``` + + - Parsed JSON: + ```json + { + "arrayName": [ + { + "name": "Amanda", + "age": "26" + }, + { + "name": "Tessa", + "age": "30" + } + ] + } + ``` + + - **Arrays of Strings**: Simple arrays use _ for elements. If _ is first, the array ignores key-value pairs. + - Example: + ``` + [days] + * Sunday + * Monday + * Tuesday + * Wednesday + * Thursday + * Friday + * Saturday + [] + ``` + - Parsed JSON: + ```json + { + "days": [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday" + ] + } + ``` + - **Nested Arrays**: Nested arrays use dot notation and are closed with `[]`. + + - Example: + + ``` + [days] + name: Monday + [.tasks] + * Clean dishes + * Pick up room + [] + + name: Tuesday + [.tasks] + * Buy milk + [] + ``` + + - Parsed JSON: + ```json + { + "days": [ + { + "name": "Monday", + "tasks": ["Clean dishes", "Pick up room"] + }, + { + "name": "Tuesday", + "tasks": ["Buy milk"] + } + ] + } + ``` + +#### ArchieML conventions in our CMS + +Usually, content blocks are written in our CMS as objects inside an ArchieML array called `blocks` and always start with a `type:` key/value pair that defines the type of the content block object. + +For example, a user adding a content block for a FeaturePhoto component, might add the following to the existing `[blocks]` array in our CMS: + +``` +[blocks] + +type: feature-photo +src: images/myPhoto.jpg +alt: Alt text for my photo... + +... which extends over multiple lines. +:end +caption: A photo of something interesting. +credit: Jane Doe + +[] +``` + +### Graphics components style tokens + +As well as Svelte components, the graphics components library includes a tailwind-like style system that can be used to style components and other page elements by adding a class or through SCSS mixins. + +These classes and mixins are defined as individual "tokens" representing the value for an individual style rule, like `font-size` or `color`. Each token sets just one style rule, and multiple tokens are combined together to style an element, like a `
`. + +Each set of tokens has several levels that represent the different values a style rule can take in our design system and are grouped in how they're named to make them easier to remember. + +For example, font weight tokens include `font-thin`, `font-light`, and `font-bold` which correspond to the style rules `font-weight: 100;`, `font-weight: 300;`, and `font-weight: 700;`, respectively. And those tokens can be applied via class name or SCSS mixin. For example: + +```svelte + +

Here is some bold text with some thin text in it!

+ + +``` + +Not all our style tokens have both class names as well as SCSS mixins available to apply them. + +**Please use the tokens defined in the SCSS partials in the [@reuters-graphics/graphics-components/dist/scss/tokens/ directory](./../../../node_modules/@reuters-graphics/graphics-components/dist/scss/tokens/) liberally in instructions and code samples, BUT be sure the token exists before suggesting it. DO NOT MAKE UP TOKENS, CLASS NAMES OR SCSS MIXINS!** + +If you're not sure if there is a token to apply a particular style, you can refer the user to our Storybook documentation site for them at: https://reuters-graphics.github.io/graphics-components/?path=/docs/styles-intro--docs. + +#### Using style tokens + +To use a token to style an element, you can apply it directly to the element through a class name. For example, to apply the `text-primary` token (controlling font colour) you can apply it like this: + +```svelte +

Lorem ipsum...

+``` + +OR you can apply some tokens via an SCSS mixin. For example: + +```svelte +

Lorem ipsum...

+ + +``` + +**Be sure to always include the `@use` line that imports the SCSS mixins from the library in your SCSS/styling suggestions.** + +Please consider the SCSS mixins and classes defined in the [@reuters-graphics/graphics-components/dist/scss/tokens/ directory](./../../../node_modules/@reuters-graphics/graphics-components/dist/scss/tokens/). + +#### Spacing tokens + +We have a special set of tokens to control spacing, i.e., paddings and margins. They operate like tailwind's padding and margin system. For example, `mt-1` represents `margin-top: 0.25rem;` and `px-2` represents `padding-right: 0.5rem; padding-left: 0.5rem;`, etc. These tokens can be applied only through a class. + +These tokens are all defined as a combination of a prefix and a level. The prefix is something like `mb` for bottom margin or `py` for padding top and bottom. The level is a number representing how large the padding are margin should be. The levels go like this: 0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, then increasing by 4 each time up to 96. For example, the full token set for top margin would be `mt-0`, `mt-0.5`, `mt-1`, `mt-1.5`, and so on. + +We also have a set of spacing tokens designed to work with _fluid_ typography. These are prefixed beginning with the letter `f`, for example, `fmb-1` represents a _fluid_ margin bottom, `margin-bottom: clamp(0.31rem, 0.31rem + 0vw, 0.31rem);`. These tokens can be applied through a class AND an SCSS mixin. For example: + +```svelte +

Some text with margin and padding

+ + +``` + +You should recommend fluid margin and padding tokens for spacing fluidly-sized typographical elements or elements that are spaced _next to_ fluidly-sized typographical elements. Typographical elements include page headings, paragraphs or elements containing text, generally. diff --git a/.agents/skills/svelte-code-writer/SKILL.md b/.agents/skills/svelte-code-writer/SKILL.md new file mode 100644 index 0000000..f90b996 --- /dev/null +++ b/.agents/skills/svelte-code-writer/SKILL.md @@ -0,0 +1,66 @@ +--- +name: svelte-code-writer +description: CLI tools for Svelte 5 documentation lookup and code analysis. MUST be used whenever creating, editing or analyzing any Svelte component (.svelte) or Svelte module (.svelte.ts/.svelte.js). If possible, this skill should be executed within the svelte-file-editor agent for optimal results. +--- + +# Svelte 5 Code Writer + +## CLI Tools + +You have access to `@sveltejs/mcp` CLI for Svelte-specific assistance. Use these commands via `npx`: + +### List Documentation Sections + +```bash +npx @sveltejs/mcp list-sections +``` + +Lists all available Svelte 5 and SvelteKit documentation sections with titles and paths. + +### Get Documentation + +```bash +npx @sveltejs/mcp get-documentation ",,..." +``` + +Retrieves full documentation for specified sections. Use after `list-sections` to fetch relevant docs. + +**Example:** + +```bash +npx @sveltejs/mcp get-documentation "$state,$derived,$effect" +``` + +### Svelte Autofixer + +```bash +npx @sveltejs/mcp svelte-autofixer "" [options] +``` + +Analyzes Svelte code and suggests fixes for common issues. + +**Options:** + +- `--async` - Enable async Svelte mode (default: false) +- `--svelte-version` - Target version: 4 or 5 (default: 5) + +**Examples:** + +```bash +# Analyze inline code (escape $ as \$) +npx @sveltejs/mcp svelte-autofixer '' + +# Analyze a file +npx @sveltejs/mcp svelte-autofixer ./src/lib/Component.svelte + +# Target Svelte 4 +npx @sveltejs/mcp svelte-autofixer ./Component.svelte --svelte-version 4 +``` + +**Important:** When passing code with runes (`$state`, `$derived`, etc.) via the terminal, escape the `$` character as `\$` to prevent shell variable substitution. + +## Workflow + +1. **Uncertain about syntax?** Run `list-sections` then `get-documentation` for relevant topics +2. **Reviewing/debugging?** Run `svelte-autofixer` on the code to detect issues +3. **Always validate** - Run `svelte-autofixer` before finalizing any Svelte component \ No newline at end of file diff --git a/.github/agents/Svelte.agent.md b/.github/agents/Svelte.agent.md new file mode 100644 index 0000000..9f81164 --- /dev/null +++ b/.github/agents/Svelte.agent.md @@ -0,0 +1,201 @@ +--- +name: Svelte +description: "Use when: building Svelte components, writing SvelteKit routes, debugging Svelte reactivity, migrating from Svelte 4 to 5, using runes ($state, $derived, $effect, $props), creating stores, animations, transitions, form actions, or any Svelte/SvelteKit question." +argument-hint: "Describe the Svelte component or SvelteKit feature you want to build, fix, or explain." +tools: [read, edit, search, todo, mcp_svelte_offici/*] +--- + +You are a Svelte expert tasked to build components and utilities for Svelte developers. If you need documentation for anything related to Svelte you can invoke the tool `get-documentation` with one of the following paths. However: before invoking the `get-documentation` tool, try to answer the users query using your own knowledge and the `svelte-autofixer` tool. Be mindful of how many section you request, since it is token-intensive! + + +- title: Overview, use_cases: use title and path to estimate use case, path: ai/overview +- title: Local setup, use_cases: use title and path to estimate use case, path: ai/local-setup +- title: Remote setup, use_cases: use title and path to estimate use case, path: ai/remote-setup +- title: Tools, use_cases: use title and path to estimate use case, path: ai/tools +- title: Resources, use_cases: use title and path to estimate use case, path: ai/resources +- title: Prompts, use_cases: use title and path to estimate use case, path: ai/prompts +- title: Overview, use_cases: use title and path to estimate use case, path: ai/plugin +- title: Subagent, use_cases: use title and path to estimate use case, path: ai/subagent +- title: Overview, use_cases: use title and path to estimate use case, path: ai/opencode-plugin +- title: Subagent, use_cases: use title and path to estimate use case, path: ai/opencode-subagent +- title: Overview, use_cases: use title and path to estimate use case, path: ai/skills +- title: Overview, use_cases: project setup, creating new svelte apps, scaffolding, cli tools, initializing projects, path: cli/overview +- title: Frequently asked questions, use_cases: project setup, initializing new svelte projects, troubleshooting cli installation, package manager configuration, path: cli/faq +- title: sv create, use_cases: project setup, starting new sveltekit app, initializing project, creating from playground, choosing project template, path: cli/sv-create +- title: sv add, use_cases: project setup, adding features to existing projects, integrating tools, testing setup, styling setup, authentication, database setup, deployment adapters, path: cli/sv-add +- title: sv check, use_cases: code quality, ci/cd pipelines, error checking, typescript projects, pre-commit hooks, finding unused css, accessibility auditing, production builds, path: cli/sv-check +- title: sv migrate, use_cases: migration, upgrading svelte versions, upgrading sveltekit versions, modernizing codebase, svelte 3 to 4, svelte 4 to 5, sveltekit 1 to 2, adopting runes, refactoring deprecated apis, path: cli/sv-migrate +- title: devtools-json, use_cases: development setup, chrome devtools integration, browser-based editing, local development workflow, debugging setup, path: cli/devtools-json +- title: drizzle, use_cases: database setup, sql queries, orm integration, data modeling, postgresql, mysql, sqlite, server-side data access, database migrations, type-safe queries, path: cli/drizzle +- title: eslint, use_cases: code quality, linting, error detection, project setup, code standards, team collaboration, typescript projects, path: cli/eslint +- title: better-auth, use_cases: use title and path to estimate use case, path: cli/better-auth +- title: mcp, use_cases: use title and path to estimate use case, path: cli/mcp +- title: mdsvex, use_cases: blog, content sites, markdown rendering, documentation sites, technical writing, cms integration, article pages, path: cli/mdsvex +- title: paraglide, use_cases: internationalization, multi-language sites, i18n, translation, localization, language switching, global apps, multilingual content, path: cli/paraglide +- title: playwright, use_cases: browser testing, e2e testing, integration testing, test automation, quality assurance, ci/cd pipelines, testing user flows, path: cli/playwright +- title: prettier, use_cases: code formatting, project setup, code style consistency, team collaboration, linting configuration, path: cli/prettier +- title: storybook, use_cases: component development, design systems, ui library, isolated component testing, documentation, visual testing, component showcase, path: cli/storybook +- title: sveltekit-adapter, use_cases: deployment, production builds, hosting setup, choosing deployment platform, configuring adapters, static site generation, node server, vercel, cloudflare, netlify, path: cli/sveltekit-adapter +- title: tailwindcss, use_cases: project setup, styling, css framework, rapid prototyping, utility-first css, design systems, responsive design, adding tailwind to svelte, path: cli/tailwind +- title: vitest, use_cases: testing, unit tests, component testing, test setup, quality assurance, ci/cd pipelines, test-driven development, path: cli/vitest +- title: add-on, use_cases: use title and path to estimate use case, path: cli/add-on +- title: sv-utils, use_cases: use title and path to estimate use case, path: cli/sv-utils +- title: Introduction, use_cases: learning sveltekit, project setup, understanding framework basics, choosing between svelte and sveltekit, getting started with full-stack apps, path: kit/introduction +- title: Creating a project, use_cases: project setup, starting new sveltekit app, initial development environment, first-time sveltekit users, scaffolding projects, path: kit/creating-a-project +- title: Project types, use_cases: deployment, project setup, choosing adapters, ssg, spa, ssr, serverless, mobile apps, desktop apps, pwa, offline apps, browser extensions, separate backend, docker containers, path: kit/project-types +- title: Project structure, use_cases: project setup, understanding file structure, organizing code, starting new project, learning sveltekit basics, path: kit/project-structure +- title: Web standards, use_cases: always, any sveltekit project, data fetching, forms, api routes, server-side rendering, deployment to various platforms, path: kit/web-standards +- title: Routing, use_cases: routing, navigation, multi-page apps, project setup, file structure, api endpoints, data loading, layouts, error pages, always, path: kit/routing +- title: Loading data, use_cases: data fetching, api calls, database queries, dynamic routes, page initialization, loading states, authentication checks, ssr data, form data, content rendering, path: kit/load +- title: Form actions, use_cases: forms, user input, data submission, authentication, login systems, user registration, progressive enhancement, validation errors, path: kit/form-actions +- title: Page options, use_cases: prerendering static sites, ssr configuration, spa setup, client-side rendering control, url trailing slash handling, adapter deployment config, build optimization, path: kit/page-options +- title: State management, use_cases: sveltekit, server-side rendering, ssr, state management, authentication, data persistence, load functions, context api, navigation, component lifecycle, path: kit/state-management +- title: Remote functions, use_cases: data fetching, server-side logic, database queries, type-safe client-server communication, forms, user input, mutations, authentication, crud operations, optimistic updates, path: kit/remote-functions +- title: Building your app, use_cases: production builds, deployment preparation, build process optimization, adapter configuration, preview before deployment, path: kit/building-your-app +- title: Adapters, use_cases: deployment, production builds, hosting setup, choosing deployment platform, configuring adapters, path: kit/adapters +- title: Zero-config deployments, use_cases: deployment, production builds, hosting setup, choosing deployment platform, ci/cd configuration, path: kit/adapter-auto +- title: Node servers, use_cases: deployment, production builds, node.js hosting, custom server setup, environment configuration, reverse proxy setup, docker deployment, systemd services, path: kit/adapter-node +- title: Static site generation, use_cases: static site generation, ssg, prerendering, deployment, github pages, spa mode, blogs, documentation sites, marketing sites, path: kit/adapter-static +- title: Single-page apps, use_cases: spa mode, single-page apps, client-only rendering, static hosting, mobile app wrappers, no server-side logic, adapter-static setup, fallback pages, path: kit/single-page-apps +- title: Cloudflare, use_cases: deployment, cloudflare workers, cloudflare pages, hosting setup, production builds, serverless deployment, edge computing, path: kit/adapter-cloudflare +- title: Cloudflare Workers, use_cases: deploying to cloudflare workers, cloudflare workers sites deployment, legacy cloudflare adapter, wrangler configuration, cloudflare platform bindings, path: kit/adapter-cloudflare-workers +- title: Netlify, use_cases: deployment, netlify hosting, production builds, serverless functions, edge functions, static site hosting, path: kit/adapter-netlify +- title: Vercel, use_cases: deployment, vercel hosting, production builds, serverless functions, edge functions, isr, image optimization, environment variables, path: kit/adapter-vercel +- title: Writing adapters, use_cases: custom deployment, building adapters, unsupported platforms, adapter development, custom hosting environments, path: kit/writing-adapters +- title: Advanced routing, use_cases: advanced routing, dynamic routes, file viewers, nested paths, custom 404 pages, url validation, route parameters, multi-level navigation, path: kit/advanced-routing +- title: Hooks, use_cases: authentication, logging, error tracking, request interception, api proxying, custom routing, internationalization, database initialization, middleware logic, session management, path: kit/hooks +- title: Errors, use_cases: error handling, custom error pages, 404 pages, api error responses, production error logging, error tracking, type-safe errors, path: kit/errors +- title: Link options, use_cases: routing, navigation, multi-page apps, performance optimization, link preloading, forms with get method, search functionality, focus management, scroll behavior, path: kit/link-options +- title: Service workers, use_cases: offline support, pwa, caching strategies, performance optimization, precaching assets, network resilience, progressive web apps, path: kit/service-workers +- title: Server-only modules, use_cases: api keys, environment variables, sensitive data protection, backend security, preventing data leaks, server-side code isolation, path: kit/server-only-modules +- title: Snapshots, use_cases: forms, user input, preserving form data, multi-step forms, navigation state, preventing data loss, textarea content, input fields, comment systems, surveys, path: kit/snapshots +- title: Shallow routing, use_cases: modals, dialogs, image galleries, overlays, history-driven ui, mobile-friendly navigation, photo viewers, lightboxes, drawer menus, path: kit/shallow-routing +- title: Observability, use_cases: performance monitoring, debugging, observability, tracing requests, production diagnostics, analyzing slow requests, finding bottlenecks, monitoring server-side operations, path: kit/observability +- title: Packaging, use_cases: building component libraries, publishing npm packages, creating reusable svelte components, library development, package distribution, path: kit/packaging +- title: Auth, use_cases: authentication, login systems, user management, session handling, jwt tokens, protected routes, user credentials, authorization checks, path: kit/auth +- title: Performance, use_cases: performance optimization, slow loading pages, production deployment, debugging performance issues, reducing bundle size, improving load times, path: kit/performance +- title: Icons, use_cases: icons, ui components, styling, css frameworks, tailwind, unocss, performance optimization, dependency management, path: kit/icons +- title: Images, use_cases: image optimization, responsive images, performance, hero images, product photos, galleries, cms integration, cdn setup, asset management, path: kit/images +- title: Accessibility, use_cases: always, any sveltekit project, screen reader support, keyboard navigation, multi-page apps, client-side routing, internationalization, multilingual sites, path: kit/accessibility +- title: SEO, use_cases: seo optimization, search engine ranking, content sites, blogs, marketing sites, public-facing apps, sitemaps, amp pages, meta tags, performance optimization, path: kit/seo +- title: Frequently asked questions, use_cases: troubleshooting package imports, library compatibility issues, client-side code execution, external api integration, middleware setup, database configuration, view transitions, yarn configuration, path: kit/faq +- title: Integrations, use_cases: project setup, css preprocessors, postcss, scss, sass, less, stylus, typescript setup, adding integrations, tailwind, testing, auth, linting, formatting, path: kit/integrations +- title: Breakpoint Debugging, use_cases: debugging, breakpoints, development workflow, troubleshooting issues, vscode setup, ide configuration, inspecting code execution, path: kit/debugging +- title: Migrating to SvelteKit v2, use_cases: migration, upgrading from sveltekit 1 to 2, breaking changes, version updates, path: kit/migrating-to-sveltekit-2 +- title: Migrating from Sapper, use_cases: migrating from sapper, upgrading legacy projects, sapper to sveltekit conversion, project modernization, path: kit/migrating +- title: Additional resources, use_cases: troubleshooting, getting help, finding examples, learning sveltekit, project templates, common issues, community support, path: kit/additional-resources +- title: Glossary, use_cases: rendering strategies, performance optimization, deployment configuration, seo requirements, static sites, spas, server-side rendering, prerendering, edge deployment, pwa development, path: kit/glossary +- title: @sveltejs/kit, use_cases: forms, form actions, server-side validation, form submission, error handling, redirects, json responses, http errors, server utilities, path: kit/@sveltejs-kit +- title: @sveltejs/kit/hooks, use_cases: middleware, request processing, authentication chains, logging, multiple hooks, request/response transformation, path: kit/@sveltejs-kit-hooks +- title: @sveltejs/kit/node/polyfills, use_cases: node.js environments, custom servers, non-standard runtimes, ssr setup, web api compatibility, polyfill requirements, path: kit/@sveltejs-kit-node-polyfills +- title: @sveltejs/kit/node, use_cases: node.js adapter, custom server setup, http integration, streaming files, node deployment, server-side rendering with node, path: kit/@sveltejs-kit-node +- title: @sveltejs/kit/vite, use_cases: project setup, vite configuration, initial sveltekit setup, build tooling, path: kit/@sveltejs-kit-vite +- title: $app/environment, use_cases: always, conditional logic, client-side code, server-side code, build-time logic, prerendering, development vs production, environment detection, path: kit/$app-environment +- title: $app/forms, use_cases: forms, user input, data submission, progressive enhancement, custom form handling, form validation, path: kit/$app-forms +- title: $app/navigation, use_cases: routing, navigation, multi-page apps, programmatic navigation, data reloading, preloading, shallow routing, navigation lifecycle, scroll handling, view transitions, path: kit/$app-navigation +- title: $app/paths, use_cases: static assets, images, fonts, public files, base path configuration, subdirectory deployment, cdn setup, asset urls, links, navigation, path: kit/$app-paths +- title: $app/server, use_cases: remote functions, server-side logic, data fetching, form handling, api endpoints, client-server communication, prerendering, file reading, batch queries, path: kit/$app-server +- title: $app/state, use_cases: routing, navigation, multi-page apps, loading states, url parameters, form handling, error states, version updates, page metadata, shallow routing, path: kit/$app-state +- title: $app/stores, use_cases: legacy projects, sveltekit pre-2.12, migration from stores to runes, maintaining older codebases, accessing page data, navigation state, app version updates, path: kit/$app-stores +- title: $app/types, use_cases: routing, navigation, type safety, route parameters, dynamic routes, link generation, pathname validation, multi-page apps, path: kit/$app-types +- title: $env/dynamic/private, use_cases: api keys, secrets management, server-side config, environment variables, backend logic, deployment-specific settings, private data handling, path: kit/$env-dynamic-private +- title: $env/dynamic/public, use_cases: environment variables, client-side config, runtime configuration, public api keys, deployment-specific settings, multi-environment apps, path: kit/$env-dynamic-public +- title: $env/static/private, use_cases: server-side api keys, backend secrets, database credentials, private configuration, build-time optimization, server endpoints, authentication tokens, path: kit/$env-static-private +- title: $env/static/public, use_cases: environment variables, public config, client-side data, api endpoints, build-time configuration, public constants, path: kit/$env-static-public +- title: $lib, use_cases: project setup, component organization, importing shared components, reusable ui elements, code structure, path: kit/$lib +- title: $service-worker, use_cases: offline support, pwa, service workers, caching strategies, progressive web apps, offline-first apps, path: kit/$service-worker +- title: Configuration, use_cases: project setup, configuration, adapters, deployment, build settings, environment variables, routing customization, prerendering, csp security, csrf protection, path configuration, typescript setup, path: kit/configuration +- title: Command Line Interface, use_cases: project setup, typescript configuration, generated types, ./$types imports, initial project configuration, path: kit/cli +- title: Types, use_cases: typescript, type safety, route parameters, api endpoints, load functions, form actions, generated types, jsconfig setup, path: kit/types +- title: Overview, use_cases: always, any svelte project, getting started, learning svelte, introduction, project setup, understanding framework basics, path: svelte/overview +- title: Getting started, use_cases: project setup, starting new svelte project, initial installation, choosing between sveltekit and vite, editor configuration, path: svelte/getting-started +- title: .svelte files, use_cases: always, any svelte project, component creation, project setup, learning svelte basics, path: svelte/svelte-files +- title: .svelte.js and .svelte.ts files, use_cases: shared reactive state, reusable reactive logic, state management across components, global stores, custom reactive utilities, path: svelte/svelte-js-files +- title: What are runes?, use_cases: always, any svelte 5 project, understanding core syntax, learning svelte 5, migration from svelte 4, path: svelte/what-are-runes +- title: $state, use_cases: always, any svelte project, core reactivity, state management, counters, forms, todo apps, interactive ui, data updates, class-based components, path: svelte/$state +- title: $derived, use_cases: always, any svelte project, computed values, reactive calculations, derived data, transforming state, dependent values, path: svelte/$derived +- title: $effect, use_cases: canvas drawing, third-party library integration, dom manipulation, side effects, intervals, timers, network requests, analytics tracking, path: svelte/$effect +- title: $props, use_cases: always, any svelte project, passing data to components, component communication, reusable components, component props, path: svelte/$props +- title: $bindable, use_cases: forms, user input, two-way data binding, custom input components, parent-child communication, reusable form fields, path: svelte/$bindable +- title: $inspect, use_cases: debugging, development, tracking state changes, reactive state monitoring, troubleshooting reactivity issues, path: svelte/$inspect +- title: $host, use_cases: custom elements, web components, dispatching custom events, component library, framework-agnostic components, path: svelte/$host +- title: Basic markup, use_cases: always, any svelte project, basic markup, html templating, component structure, attributes, events, props, text rendering, path: svelte/basic-markup +- title: {#if ...}, use_cases: always, conditional rendering, showing/hiding content, dynamic ui, user permissions, loading states, error handling, form validation, path: svelte/if +- title: {#each ...}, use_cases: always, lists, arrays, iteration, product listings, todos, tables, grids, dynamic content, shopping carts, user lists, comments, feeds, path: svelte/each +- title: {#key ...}, use_cases: animations, transitions, component reinitialization, forcing component remount, value-based ui updates, resetting component state, path: svelte/key +- title: {#await ...}, use_cases: async data fetching, api calls, loading states, promises, error handling, lazy loading components, dynamic imports, path: svelte/await +- title: {#snippet ...}, use_cases: reusable markup, component composition, passing content to components, table rows, list items, conditional rendering, reducing duplication, path: svelte/snippet +- title: {@render ...}, use_cases: reusable ui patterns, component composition, conditional rendering, fallback content, layout components, slot alternatives, template reuse, path: svelte/@render +- title: {@html ...}, use_cases: rendering html strings, cms content, rich text editors, markdown to html, blog posts, wysiwyg output, sanitized html injection, dynamic html content, path: svelte/@html +- title: {@attach ...}, use_cases: tooltips, popovers, dom manipulation, third-party libraries, canvas drawing, element lifecycle, interactive ui, custom directives, wrapper components, path: svelte/@attach +- title: {@const ...}, use_cases: computed values in loops, derived calculations in blocks, local variables in each iterations, complex list rendering, path: svelte/@const +- title: {@debug ...}, use_cases: debugging, development, troubleshooting, tracking state changes, monitoring variables, reactive data inspection, path: svelte/@debug +- title: bind:, use_cases: forms, user input, two-way data binding, interactive ui, media players, file uploads, checkboxes, radio buttons, select dropdowns, contenteditable, dimension tracking, path: svelte/bind +- title: use:, use_cases: custom directives, dom manipulation, third-party library integration, tooltips, click outside, gestures, focus management, element lifecycle hooks, path: svelte/use +- title: transition:, use_cases: animations, interactive ui, modals, dropdowns, notifications, conditional content, show/hide elements, smooth state changes, path: svelte/transition +- title: in: and out:, use_cases: animation, transitions, interactive ui, conditional rendering, independent enter/exit effects, modals, tooltips, notifications, path: svelte/in-and-out +- title: animate:, use_cases: sortable lists, drag and drop, reorderable items, todo lists, kanban boards, playlist editors, priority queues, animated list reordering, path: svelte/animate +- title: style:, use_cases: dynamic styling, conditional styles, theming, dark mode, responsive design, interactive ui, component styling, path: svelte/style +- title: class, use_cases: always, conditional styling, dynamic classes, tailwind css, component styling, reusable components, responsive design, path: svelte/class +- title: await, use_cases: async data fetching, loading states, server-side rendering, awaiting promises in components, async validation, concurrent data loading, path: svelte/await-expressions +- title: Scoped styles, use_cases: always, styling components, scoped css, component-specific styles, preventing style conflicts, animations, keyframes, path: svelte/scoped-styles +- title: Global styles, use_cases: global styles, third-party libraries, css resets, animations, styling body/html, overriding component styles, shared keyframes, base styles, path: svelte/global-styles +- title: Custom properties, use_cases: theming, custom styling, reusable components, design systems, dynamic colors, component libraries, ui customization, path: svelte/custom-properties +- title: Nested diff --git a/src/components/AdSlot/InlineAd.mdx b/src/components/AdSlot/InlineAd.mdx new file mode 100644 index 0000000..ef65d15 --- /dev/null +++ b/src/components/AdSlot/InlineAd.mdx @@ -0,0 +1,56 @@ +import { Meta, Canvas } from '@storybook/blocks'; + +import * as InlineAdStories from './InlineAd.stories.svelte'; + + + +# InlineAd + +Add programmatic ads inline on your page. + +> **IMPORTANT!** Make sure ads are only used on dotcom pages, never on embeds. + +```svelte + + + + + +``` + +```svelte + + + +{#each content.blocks as block} + + + {#if block.Type === 'inline-ad'} + + {#if !embedded} + + {/if} + {/if} + + +{/each} +``` + +You may add **up to three** inline ads per page, but must set the `n` prop on multiple ads in sequential order, 1 - 3. + +```svelte + + + + + + +``` + + diff --git a/src/components/AdSlot/InlineAd.stories.svelte b/src/components/AdSlot/InlineAd.stories.svelte new file mode 100644 index 0000000..cac455c --- /dev/null +++ b/src/components/AdSlot/InlineAd.stories.svelte @@ -0,0 +1,20 @@ + + +{#snippet template()} +
+ + + +
+{/snippet} + + diff --git a/src/components/AdSlot/InlineAd.svelte b/src/components/AdSlot/InlineAd.svelte new file mode 100644 index 0000000..09cb4d2 --- /dev/null +++ b/src/components/AdSlot/InlineAd.svelte @@ -0,0 +1,74 @@ + + + + + +
+
Advertisement · Scroll to continue
+
+
+
+ +
+
+
+
+
+ + diff --git a/src/components/AdSlot/LeaderboardAd.mdx b/src/components/AdSlot/LeaderboardAd.mdx new file mode 100644 index 0000000..a2550aa --- /dev/null +++ b/src/components/AdSlot/LeaderboardAd.mdx @@ -0,0 +1,31 @@ +import { Meta, Canvas } from '@storybook/blocks'; + +import * as LeaderboardAdStories from './LeaderboardAd.stories.svelte'; + + + +# LeaderboardAd + +Add a leaderboard ad to your page. + +> **IMPORTANT!** Make sure ads are only used on dotcom pages, never on embeds. + +```svelte + + + + + + + + + +``` + + diff --git a/src/components/AdSlot/LeaderboardAd.stories.svelte b/src/components/AdSlot/LeaderboardAd.stories.svelte new file mode 100644 index 0000000..2fef379 --- /dev/null +++ b/src/components/AdSlot/LeaderboardAd.stories.svelte @@ -0,0 +1,29 @@ + + +{#snippet template()} +
+ + +
+{/snippet} + + + + diff --git a/src/components/AdSlot/LeaderboardAd.svelte b/src/components/AdSlot/LeaderboardAd.svelte new file mode 100644 index 0000000..95aada3 --- /dev/null +++ b/src/components/AdSlot/LeaderboardAd.svelte @@ -0,0 +1,102 @@ + + + + + +
+
+
+
+
+ +
+
+
+
+
+ + diff --git a/src/components/AdSlot/OneTrust.svelte b/src/components/AdSlot/OneTrust.svelte new file mode 100644 index 0000000..1b5d191 --- /dev/null +++ b/src/components/AdSlot/OneTrust.svelte @@ -0,0 +1,44 @@ + + diff --git a/src/components/AdSlot/ResponsiveAd.svelte b/src/components/AdSlot/ResponsiveAd.svelte new file mode 100644 index 0000000..55a9edd --- /dev/null +++ b/src/components/AdSlot/ResponsiveAd.svelte @@ -0,0 +1,78 @@ + + + + +{#if windowWidth} + {#key placementName} + + {/key} +{/if} diff --git a/src/components/AdSlot/SponsorshipAd.mdx b/src/components/AdSlot/SponsorshipAd.mdx new file mode 100644 index 0000000..78eb0cd --- /dev/null +++ b/src/components/AdSlot/SponsorshipAd.mdx @@ -0,0 +1,37 @@ +import { Meta, Canvas } from '@storybook/blocks'; + +import * as SponsorshipAdStories from './SponsorshipAd.stories.svelte'; + + + +# SponsorshipAd + +Add a sponsorship ad to your page. + +> **IMPORTANT!** Make sure ads are only used on dotcom pages, never on embeds. + +```svelte + + + + + +``` + +```svelte + + + + +{#if !embedded} + +{/if} +``` + + diff --git a/src/components/AdSlot/SponsorshipAd.stories.svelte b/src/components/AdSlot/SponsorshipAd.stories.svelte new file mode 100644 index 0000000..ecf866b --- /dev/null +++ b/src/components/AdSlot/SponsorshipAd.stories.svelte @@ -0,0 +1,20 @@ + + +{#snippet template()} +
+ + +
+{/snippet} + + diff --git a/src/components/AdSlot/SponsorshipAd.svelte b/src/components/AdSlot/SponsorshipAd.svelte new file mode 100644 index 0000000..8f3b3c5 --- /dev/null +++ b/src/components/AdSlot/SponsorshipAd.svelte @@ -0,0 +1,85 @@ + + + + + +
+ {#if adLabel} +
+
{adLabel}
+
+ {/if} +
+
+
+ +
+
+
+
+
+ + diff --git a/src/components/AdSlot/adScripts/bootstrap.ts b/src/components/AdSlot/adScripts/bootstrap.ts new file mode 100644 index 0000000..0ff6a51 --- /dev/null +++ b/src/components/AdSlot/adScripts/bootstrap.ts @@ -0,0 +1,105 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import getParameterByName from './getParameterByName'; +import Ias from './ias'; + +const ONETRUST_LOGS = 'ot_logs'; +const ONETRUST_GEOLOCATION_MOCK = 'ot_geolocation_mock'; +const ONETRUST_SCRIPT_ID = '38cb75bd-fbe1-4ac8-b4af-e531ab368caf'; + +export const loadBootstrap = () => { + (window).freestar = (window).freestar || {}; + const freestar = (window).freestar; + freestar.debug = true; + freestar.queue = freestar.queue || []; + freestar.config = freestar.config || {}; + freestar.config.enabled_slots = []; + freestar.initCallback = function () { + if (freestar.config.enabled_slots.length === 0) { + freestar.initCallbackCalled = false; + } else { + freestar.newAdSlots(freestar.config.enabled_slots); + } + }; + + freestar.config.channel = '/4735792/reuters.com/graphics'; + + (window).initBootstrap( + { + onetrust_logs: getParameterByName(ONETRUST_LOGS) || 'false', + geolocation_mock: + getParameterByName(ONETRUST_GEOLOCATION_MOCK) || 'default', + onetrust_script_id: ONETRUST_SCRIPT_ID, + }, + (onetrustResponse: any) => { + const iasPromise = Ias(); + return Promise.all([iasPromise]).then((responses) => { + const [iasResponse] = responses; + + return { + ...onetrustResponse, + ias: iasResponse, + }; + }); + } + ); + + (window).bootstrap.getResults(() => { + // Set GAM + window.googletag = (window).googletag || { cmd: [] }; + window.googletag.cmd.push(() => { + window.googletag.pubads().enableSingleRequest(); + /** + * @TODO Property 'enableAsyncRendering' does not exist on type 'PubAdsService'. + */ + // @ts-ignore window global + window.googletag.pubads().enableAsyncRendering(); + window.googletag.pubads().collapseEmptyDivs(true); + + window.googletag + .pubads() + .addEventListener('slotRenderEnded', function (event) { + const adDiv = document.getElementById(event.slot.getSlotElementId()); + if (!adDiv) return; + // If the ad slot is empty + if (event.isEmpty) { + adDiv.classList.add('unfulfilled-ad'); + } else { + adDiv.classList.remove('unfulfilled-ad'); + } + }); + }); + + // Set page-level key-values + // cf: https://help.freestar.com/help/using-key-values + freestar.queue.push(function () { + // Global Ads test targeting + const adstest = new URL(document.location.href).searchParams.get( + 'adstest' + ); + if (adstest) { + window.googletag.pubads().setTargeting('adstest', adstest); + } + + // Use the URL path to create a unique ID for the page. + const graphicId = + window.location.pathname + .split('/') + // Get the first lowercase slug in the pathname, which is the graphic UID. + .filter((d) => d.match(/[a-z0-9]+/) && d !== 'graphics')[0] || + 'unknown-graphic'; + window.googletag.pubads().setTargeting('template', 'graphics'); + window.googletag.pubads().setTargeting('graphicId', graphicId); + }); + + if (!Array.isArray((window).graphicsAdQueue)) { + console.error('Ad queue not initialized!'); + } + + freestar.queue.push(function () { + freestar.newAdSlots( + (window).graphicsAdQueue || [], + freestar.config.channel + ); + }); + }); +}; diff --git a/src/components/AdSlot/adScripts/getParameterByName.ts b/src/components/AdSlot/adScripts/getParameterByName.ts new file mode 100644 index 0000000..d759bdd --- /dev/null +++ b/src/components/AdSlot/adScripts/getParameterByName.ts @@ -0,0 +1,12 @@ +export default (name: string, url = window.location.href) => { + // eslint-disable-next-line no-useless-escape + name = name.replace(/[\[\]]/g, '\\$&'); + const regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'); + const results = regex.exec(url); + + if (!results) return null; + + if (!results[2]) return ''; + + return decodeURIComponent(results[2].replace(/\+/g, ' ')); +}; diff --git a/src/components/AdSlot/adScripts/ias.ts b/src/components/AdSlot/adScripts/ias.ts new file mode 100644 index 0000000..1377e97 --- /dev/null +++ b/src/components/AdSlot/adScripts/ias.ts @@ -0,0 +1,28 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +const IAS_REQUEST_TIMEOUT = 600; + +export default () => { + return new Promise((resolve) => { + const timerId = setTimeout(() => { + resolve('Resolved with timeout'); + }, IAS_REQUEST_TIMEOUT); + + const setupIAS = () => { + clearTimeout(timerId); + (window).__iasPET = (window).__iasPET || {}; + (window).__iasPET.queue = (window).__iasPET.queue || []; + (window).__iasPET.pubId = '931336'; // Ask Rachel + resolve('loaded'); + }; + + // Set up IAS pet.js + const script = document.createElement('script'); + script.src = '//static.adsafeprotected.com/iasPET.1.js'; + script.setAttribute('async', 'async'); + document.head.appendChild(script); + script.onload = setupIAS; + script.onerror = () => { + resolve('error'); + }; + }); +}; diff --git a/src/components/AdSlot/adScripts/loadScript.ts b/src/components/AdSlot/adScripts/loadScript.ts new file mode 100644 index 0000000..5c3c8e6 --- /dev/null +++ b/src/components/AdSlot/adScripts/loadScript.ts @@ -0,0 +1,17 @@ +interface attributesInterface { + onload?: () => void; + async?: boolean; +} + +export const loadScript = (src: string, attributes?: attributesInterface) => { + const { onload, async = true } = attributes || {}; + + const existingScript = document.querySelector(`script[src="${src}"]`); + if (existingScript) return; + + const script = document.createElement('script'); + if (onload) script.addEventListener('load', onload); + script.async = async; + script.src = src; + document.head.append(script); +}; diff --git a/src/components/AdSlot/utils.ts b/src/components/AdSlot/utils.ts new file mode 100644 index 0000000..fd73c93 --- /dev/null +++ b/src/components/AdSlot/utils.ts @@ -0,0 +1,6 @@ +const random4 = () => + Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); + +export const getRandomAdId = () => 'ad-' + random4() + random4(); diff --git a/src/components/Analytics/Analytics.mdx b/src/components/Analytics/Analytics.mdx new file mode 100644 index 0000000..60e7afd --- /dev/null +++ b/src/components/Analytics/Analytics.mdx @@ -0,0 +1,73 @@ +import { Meta } from '@storybook/blocks'; + +import * as AnalyticsStories from './Analytics.stories.svelte'; + + + +# Analytics + +The `Analytics` component adds Google and Chartbeat analytics to your page. + +```svelte + + + +``` + +## Environments + +Generally, you only want to send page analytics in production environments. + +In a SvelteKit context, you can use `$app` stores to restrict when you send analytics. + +For example, the following excludes analytics from pages in development or hosted on our preview server: + +```svelte + + +{#if !dev && $page.url?.hostname !== 'graphics.thomsonreuters.com'} + +{/if} +``` + +## Multipage apps + +If you're using analytics to measure a multipage newsapp that uses [client-side routing](https://kit.svelte.dev/docs/glossary#routing), then you may need to trigger analytics after virtual page navigation. + +This component exports a function you can call to register pageviews. + +For example, here's how you can use SvelteKit's [`afterNavigate`](https://kit.svelte.dev/docs/modules#$app-navigation-afternavigate) lifecycle to capture additional pageviews: + +```svelte + + + +``` diff --git a/src/components/Analytics/Analytics.stories.svelte b/src/components/Analytics/Analytics.stories.svelte new file mode 100644 index 0000000..cf9c24a --- /dev/null +++ b/src/components/Analytics/Analytics.stories.svelte @@ -0,0 +1,17 @@ + + + diff --git a/src/components/Analytics/Analytics.svelte b/src/components/Analytics/Analytics.svelte new file mode 100644 index 0000000..1a2cfcb --- /dev/null +++ b/src/components/Analytics/Analytics.svelte @@ -0,0 +1,39 @@ + + + + + + diff --git a/src/components/Analytics/GTM.svelte b/src/components/Analytics/GTM.svelte new file mode 100644 index 0000000..a00fdcb --- /dev/null +++ b/src/components/Analytics/GTM.svelte @@ -0,0 +1,30 @@ + + + + + + + + + + + + + diff --git a/src/components/Analytics/providers/chartbeat.ts b/src/components/Analytics/providers/chartbeat.ts new file mode 100644 index 0000000..61089b3 --- /dev/null +++ b/src/components/Analytics/providers/chartbeat.ts @@ -0,0 +1,23 @@ +// Reuters Chartbeat UID +const UID = 52639; + +export default (authors: { name: string }[]) => { + window._sf_async_config = { + uid: UID, + domain: 'reuters.com', + flickerControl: false, + useCanonical: true, + useCanonicalDomain: true, + sections: 'Graphics', + authors: authors.map((a) => a?.name || '').join(','), + ...(window._sf_async_config || {}), + }; +}; + +export const registerPageview = () => { + if (typeof window === 'undefined' || !window.pSUPERFLY) return; + window.pSUPERFLY.virtualPage({ + path: window.location.pathname, + title: document?.title, + }); +}; diff --git a/src/components/Analytics/providers/ga.ts b/src/components/Analytics/providers/ga.ts new file mode 100644 index 0000000..29b3f33 --- /dev/null +++ b/src/components/Analytics/providers/ga.ts @@ -0,0 +1,24 @@ +export default () => { + try { + window.dataLayer = window.dataLayer || []; + if (!window.gtag) { + /** @type {Gtag.Gtag} */ + window.gtag = function () { + // eslint-disable-next-line prefer-rest-params + window.dataLayer.push(arguments); + }; + window.gtag('js', new Date()); + registerPageview(); + } + } catch (e) { + console.warn(`Error initialising Google Analytics: ${e}`); + } +}; + +export const registerPageview = () => { + if (typeof window === 'undefined' || !window.gtag) return; + window.gtag('event', 'page_view', { + page_location: window.location.origin + window.location.pathname, + page_title: document?.title, + }); +}; diff --git a/src/components/Analytics/providers/index.ts b/src/components/Analytics/providers/index.ts new file mode 100644 index 0000000..9b2fdba --- /dev/null +++ b/src/components/Analytics/providers/index.ts @@ -0,0 +1,2 @@ +export { default as ga } from './ga'; +export { default as chartbeat } from './chartbeat'; diff --git a/src/components/BeforeAfter/BeforeAfter.mdx b/src/components/BeforeAfter/BeforeAfter.mdx new file mode 100644 index 0000000..478e5f1 --- /dev/null +++ b/src/components/BeforeAfter/BeforeAfter.mdx @@ -0,0 +1,111 @@ +import { Meta, Canvas } from '@storybook/blocks'; + +import * as BeforeAfterStories from './BeforeAfter.stories.svelte'; + + + +# BeforeAfter + +The `BeforeAfter` component shows a before-and-after comparison of an image. + +```svelte + + + +``` + + + +## Using with ArchieML docs + +With the graphics kit, you'll likely get your text value from an ArchieML doc... + +```yaml +# ArchieML doc +[blocks] + +type: before-after +beforeSrc: images/before-after/myrne-before.jpg +beforeAlt: Satellite image of Russian base at Myrne taken on July 7, 2020. +afterSrc: images/before-after/myrne-after.jpg +afterAlt: Satellite image of Russian base at Myrne taken on Oct. 20, 2020. + +[] +``` + +... which you'll parse out of a ArchieML block object before passing to the `BeforeAfter` component. + +```svelte + +{#each content.blocks as block} + {#if block.type === 'before-after'} + + {/if} +{/each} +``` + + + +## Adding text + +To add text overlays and captions, use [snippets](https://svelte.dev/docs/svelte/snippet) for `beforeOverlay`, `afterOverlay` and `caption`. You can style the snippets to match your page design, like in [this demo](./?path=/story/components-multimedia-beforeafter--with-overlays). + +> 💡**NOTE:** The text in the overlays are used as [ARIA descriptions](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-describedby) for your before and after images. You must always use the `beforeAlt` / `afterAlt` props to label your image for visually impaired readers, but these ARIA descriptions provide additional information or context that the reader might need. + +```svelte + + + {#snippet beforeOverlay()} +
+

July 7, 2020

+

Initially, this site was far smaller.

+
+ {/snippet} + + + {#snippet afterOverlay()} +
+

Oct. 20, 2020

+

But then forces built up.

+
+ {/snippet} + + + {#snippet caption()} +

Photos by MAXAR Technologies, 2021.

+ {/snippet} +
+ + +``` + + diff --git a/src/components/BeforeAfter/BeforeAfter.stories.svelte b/src/components/BeforeAfter/BeforeAfter.stories.svelte new file mode 100644 index 0000000..4e23d53 --- /dev/null +++ b/src/components/BeforeAfter/BeforeAfter.stories.svelte @@ -0,0 +1,68 @@ + + + + + + + + + {#snippet beforeOverlay()} +
+

July 7, 2020

+

Initially, this site was far smaller.

+
+ {/snippet} + {#snippet afterOverlay()} +
+

Oct. 20, 2020

+

But then forces built up.

+
+ {/snippet} + {#snippet caption()} +

Photos by MAXAR Technologies, 2021.

+ {/snippet} +
+ + +
diff --git a/src/components/BeforeAfter/BeforeAfter.svelte b/src/components/BeforeAfter/BeforeAfter.svelte new file mode 100644 index 0000000..1cd5ddd --- /dev/null +++ b/src/components/BeforeAfter/BeforeAfter.svelte @@ -0,0 +1,376 @@ + + + + + + +{#if beforeSrc && beforeAlt && afterSrc && afterAlt} + +
+ +
+ {#if caption} + + + + {/if} +
+{/if} + + diff --git a/src/components/BeforeAfter/images/myrne-after.jpg b/src/components/BeforeAfter/images/myrne-after.jpg new file mode 100644 index 0000000..62d97fd Binary files /dev/null and b/src/components/BeforeAfter/images/myrne-after.jpg differ diff --git a/src/components/BeforeAfter/images/myrne-before.jpg b/src/components/BeforeAfter/images/myrne-before.jpg new file mode 100644 index 0000000..96ed524 Binary files /dev/null and b/src/components/BeforeAfter/images/myrne-before.jpg differ diff --git a/src/components/Block/Block.svelte b/src/components/Block/Block.svelte index c3ebd66..3facf2d 100644 --- a/src/components/Block/Block.svelte +++ b/src/components/Block/Block.svelte @@ -42,7 +42,7 @@
diff --git a/src/components/ClockWall/Clock.svelte b/src/components/ClockWall/Clock.svelte new file mode 100644 index 0000000..e6eaeb4 --- /dev/null +++ b/src/components/ClockWall/Clock.svelte @@ -0,0 +1,264 @@ + + +
+ {#if showClock} + + + + + + + + + + + + + + + + + + + + {#each Array(12) as _, i (i)} + + {/each} + + + + + + + + + + + + + + {/if} +
+

+ {name} +

+

+ {time} +

+
+
+ + diff --git a/src/components/ClockWall/ClockWall.mdx b/src/components/ClockWall/ClockWall.mdx new file mode 100644 index 0000000..a005021 --- /dev/null +++ b/src/components/ClockWall/ClockWall.mdx @@ -0,0 +1,27 @@ +import { Meta, Canvas } from '@storybook/blocks'; + +import * as ClockWallStories from './ClockWall.stories.svelte'; + + + +# ClockWall + +The `ClockWall` component displays a row of analog clocks for different cities and timezones. Use it paired with the overall headline of a graphics blog page to show the time of multiple cities involved in a breaking news event. + +Use the [IANA tz database](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) to find valid `tzIdentifier` strings. + +```svelte + + + +``` + + diff --git a/src/components/ClockWall/ClockWall.stories.svelte b/src/components/ClockWall/ClockWall.stories.svelte new file mode 100644 index 0000000..328ffd5 --- /dev/null +++ b/src/components/ClockWall/ClockWall.stories.svelte @@ -0,0 +1,21 @@ + + + diff --git a/src/components/ClockWall/ClockWall.svelte b/src/components/ClockWall/ClockWall.svelte new file mode 100644 index 0000000..5d90005 --- /dev/null +++ b/src/components/ClockWall/ClockWall.svelte @@ -0,0 +1,61 @@ + + + +
+ {#each cities as city (city.tzIdentifier)} + + {/each} +
+
+ + diff --git a/src/components/DatawrapperChart/DatawrapperChart.mdx b/src/components/DatawrapperChart/DatawrapperChart.mdx new file mode 100644 index 0000000..e444f37 --- /dev/null +++ b/src/components/DatawrapperChart/DatawrapperChart.mdx @@ -0,0 +1,45 @@ +import { Meta, Canvas } from '@storybook/blocks'; + +import * as DatawrapperChartStories from './DatawrapperChart.stories.svelte'; + + + +# DatawrapperChart + +Easily add a responsive Datawrapper embed on your page. + +```svelte + + + +``` + +##### Getting the chart URL for `src` + +Copy the source url for the Datawrapper chart in the `src` prop. +You can get this from the published url on Reuters Graphics. + +- Publish the chart on Datawrapper. +- Go to the **Datawrapper charts** Teams channel, wait for the graphic to finish publishing. +- Inside **Embed code (for developers only)**, find and copy the url inside the `src` prop. (It ends in `media-embed.html`.) + +**Note:** There is no need to update the url if you update the chart inside Datawrapper. Any changes will be automatically reflected. + + + +## With chatter + +By default, Datawrapper will export your chart with the chart chatter like title, description and notes. + +At the moment, these don't _exactly_ match our styles and can't be made to fit into the article well. + +Instead, it's often better to remove all the text from your Datawrapper chart before publishing it and add that text back via the component props. + + diff --git a/src/components/DatawrapperChart/DatawrapperChart.stories.svelte b/src/components/DatawrapperChart/DatawrapperChart.stories.svelte new file mode 100644 index 0000000..412d1ad --- /dev/null +++ b/src/components/DatawrapperChart/DatawrapperChart.stories.svelte @@ -0,0 +1,41 @@ + + + + + diff --git a/src/components/DatawrapperChart/DatawrapperChart.svelte b/src/components/DatawrapperChart/DatawrapperChart.svelte new file mode 100644 index 0000000..b2fd86d --- /dev/null +++ b/src/components/DatawrapperChart/DatawrapperChart.svelte @@ -0,0 +1,117 @@ + + + + + {#if titleSnippet} + + {@render titleSnippet()} + {/if} + +
+ +
+ + {#if notesSnippet} + {@render notesSnippet()} + {/if} +
diff --git a/src/components/DocumentCloud/DocumentCloud.mdx b/src/components/DocumentCloud/DocumentCloud.mdx new file mode 100644 index 0000000..88dec3d --- /dev/null +++ b/src/components/DocumentCloud/DocumentCloud.mdx @@ -0,0 +1,26 @@ +import { Meta, Canvas } from '@storybook/blocks'; + +import * as DocumentCloudStories from './DocumentCloud.stories.svelte'; + + + +# DocumentCloud + +The `DocumentCloud` component embeds a document hosted by [DocumentCloud](https://documentcloud.org). + +The document must have its access level set to **public** before it can be embedded. The `slug` can be found after the final slash in the document's URL. + +For instance, the document included in the example is found at [documentcloud.org/documents/3259984-Trump-Intelligence-Allegations](https://www.documentcloud.org/documents/3259984-Trump-Intelligence-Allegations). The `slug` is `3259984-Trump-Intelligence-Allegations`. + +```svelte + + + +``` + + diff --git a/src/components/DocumentCloud/DocumentCloud.stories.svelte b/src/components/DocumentCloud/DocumentCloud.stories.svelte new file mode 100644 index 0000000..22f69e6 --- /dev/null +++ b/src/components/DocumentCloud/DocumentCloud.stories.svelte @@ -0,0 +1,23 @@ + + + diff --git a/src/components/DocumentCloud/DocumentCloud.svelte b/src/components/DocumentCloud/DocumentCloud.svelte new file mode 100644 index 0000000..01a86cc --- /dev/null +++ b/src/components/DocumentCloud/DocumentCloud.svelte @@ -0,0 +1,41 @@ + + + + + + diff --git a/src/components/EmbedPreviewerLink/EmbedPreviewerLink.mdx b/src/components/EmbedPreviewerLink/EmbedPreviewerLink.mdx new file mode 100644 index 0000000..12693e2 --- /dev/null +++ b/src/components/EmbedPreviewerLink/EmbedPreviewerLink.mdx @@ -0,0 +1,19 @@ +import { Meta } from '@storybook/blocks'; + +import * as EmbedPreviewerLinkStories from './EmbedPreviewerLink.stories.svelte'; + + + +# EmbedPreviewerLink + +The `EmbedPreviewerLink` component is a tool for previewing the embeds in development. It adds an icon at the bottom of the page that, when clicked, opens a previewer with the embeds. + +```svelte + + + +``` diff --git a/src/components/EmbedPreviewerLink/EmbedPreviewerLink.stories.svelte b/src/components/EmbedPreviewerLink/EmbedPreviewerLink.stories.svelte new file mode 100644 index 0000000..80f2fa4 --- /dev/null +++ b/src/components/EmbedPreviewerLink/EmbedPreviewerLink.stories.svelte @@ -0,0 +1,17 @@ + + + diff --git a/src/components/EmbedPreviewerLink/EmbedPreviewerLink.svelte b/src/components/EmbedPreviewerLink/EmbedPreviewerLink.svelte new file mode 100644 index 0000000..365fa22 --- /dev/null +++ b/src/components/EmbedPreviewerLink/EmbedPreviewerLink.svelte @@ -0,0 +1,32 @@ + + +{#if dev} +
+ + + +
+{/if} + + diff --git a/src/components/EndNotes/EndNotes.mdx b/src/components/EndNotes/EndNotes.mdx new file mode 100644 index 0000000..90e8bf9 --- /dev/null +++ b/src/components/EndNotes/EndNotes.mdx @@ -0,0 +1,67 @@ +import { Meta, Canvas } from '@storybook/blocks'; + +import * as EndNotesStories from './EndNotes.stories.svelte'; + + + +# EndNotes + +The `EndNotes` component adds notes such as sources, clarifiying notes and minor corrections that come at the end of a story. + +```svelte + + + +``` + + + +## Using with ArchieML docs + +With the graphics kit, you'll likely get your text value from an ArchieML doc... + +```yaml +# ArchieML doc +[endNotes] +title: Note +text: Data is current as of today + +title: Sources +text: Data, Inc. + +title: Edited by +text: Editor, Copyeditor +[] +``` + +... which you'll pass to the `EndNotes` component. + +```svelte + + + + +``` + + diff --git a/src/components/EndNotes/EndNotes.stories.svelte b/src/components/EndNotes/EndNotes.stories.svelte new file mode 100644 index 0000000..1979205 --- /dev/null +++ b/src/components/EndNotes/EndNotes.stories.svelte @@ -0,0 +1,29 @@ + + + + + diff --git a/src/components/EndNotes/EndNotes.svelte b/src/components/EndNotes/EndNotes.svelte new file mode 100644 index 0000000..71d3818 --- /dev/null +++ b/src/components/EndNotes/EndNotes.svelte @@ -0,0 +1,58 @@ + + + + + {#each notes as note} +
+ +
+
+ +
+ {/each} +
+ + diff --git a/src/components/FeaturePhoto/FeaturePhoto.mdx b/src/components/FeaturePhoto/FeaturePhoto.mdx new file mode 100644 index 0000000..71d90e5 --- /dev/null +++ b/src/components/FeaturePhoto/FeaturePhoto.mdx @@ -0,0 +1,72 @@ +import { Meta, Canvas } from '@storybook/blocks'; + +import * as FeaturePhotoStories from './FeaturePhoto.stories.svelte'; + + + +# FeaturePhoto + +The `FeaturePhoto` component adds a full-width photo. + +```svelte + + + +``` + + + +## Using with ArchieML docs + +With the graphics kit, you'll likely get your text value from an ArchieML doc... + +```yaml +# ArchieML doc +[blocks] + +type: photo +width: normal +src: images/shark.jpg +altText: The king of the sea +caption: Carcharodon carcharias - REUTERS + +[] +``` + +... which you'll parse out of a ArchieML block object before passing to the `FeaturePhoto` component. + +```svelte + + + +{#each content.blocks as block} + {#if block.Type === 'text'} + + {:else if block.type === 'photo'} + + {/if} +{/each} +``` + +## Missing alt text + +`altText` is required in this component. If your photo is missing it, a small red text box will overlay the image. + + diff --git a/src/components/FeaturePhoto/FeaturePhoto.stories.svelte b/src/components/FeaturePhoto/FeaturePhoto.stories.svelte new file mode 100644 index 0000000..5576996 --- /dev/null +++ b/src/components/FeaturePhoto/FeaturePhoto.stories.svelte @@ -0,0 +1,41 @@ + + + + + + + diff --git a/src/components/FeaturePhoto/FeaturePhoto.svelte b/src/components/FeaturePhoto/FeaturePhoto.svelte new file mode 100644 index 0000000..a750d23 --- /dev/null +++ b/src/components/FeaturePhoto/FeaturePhoto.svelte @@ -0,0 +1,145 @@ + + + + +
+ {#if !lazy || (intersectable && intersecting)} + {altText} + {:else} +
+ {/if} + {#if caption} + + +
+ {caption} +
+
+
+ {/if} + {#if !altText} +
altText
+ {/if} +
+
+ + diff --git a/src/components/FeaturePhoto/images/shark.jpg b/src/components/FeaturePhoto/images/shark.jpg new file mode 100644 index 0000000..5b54e9f Binary files /dev/null and b/src/components/FeaturePhoto/images/shark.jpg differ diff --git a/src/components/Framer/Dropdown/index.svelte b/src/components/Framer/Dropdown/index.svelte index 8798d5b..c3393be 100644 --- a/src/components/Framer/Dropdown/index.svelte +++ b/src/components/Framer/Dropdown/index.svelte @@ -21,7 +21,7 @@ diff --git a/src/components/Headpile/Headshot.svelte b/src/components/Headpile/Headshot.svelte new file mode 100644 index 0000000..dd2afff --- /dev/null +++ b/src/components/Headpile/Headshot.svelte @@ -0,0 +1,41 @@ + + +
+
+
+
+ + diff --git a/src/components/Headpile/KeyFigure.svelte b/src/components/Headpile/KeyFigure.svelte new file mode 100644 index 0000000..a4cec85 --- /dev/null +++ b/src/components/Headpile/KeyFigure.svelte @@ -0,0 +1,108 @@ + + +
+
+
+ +
+
+
{name}
+
+ {role || ''} +
+ {#if !mobile.current} +
+ +
+ {/if} +
+
+ + {#if mobile.current} +
+ +
+ {/if} +
+ + diff --git a/src/components/Headpile/images/abdel.png b/src/components/Headpile/images/abdel.png new file mode 100644 index 0000000..dcaef5c Binary files /dev/null and b/src/components/Headpile/images/abdel.png differ diff --git a/src/components/Headpile/images/hemedti.png b/src/components/Headpile/images/hemedti.png new file mode 100644 index 0000000..c432113 Binary files /dev/null and b/src/components/Headpile/images/hemedti.png differ diff --git a/src/components/HeroHeadline/HeroHeadline.mdx b/src/components/HeroHeadline/HeroHeadline.mdx new file mode 100644 index 0000000..1091015 --- /dev/null +++ b/src/components/HeroHeadline/HeroHeadline.mdx @@ -0,0 +1,329 @@ +import { Meta, Canvas } from '@storybook/blocks'; + +import * as HeroHeadlineStories from './HeroHeadline.stories.svelte'; + + + +# HeroHeadline + +The `HeroHeadline` component creates a Reuters Graphics headline with a hero media, which can be a graphic, photo, video or other media. + +By default, the hero is in the background, i.e., the headline and dek are stacked on top of the hero. You can unstack and insert the hero media inline -- i.e., before or after the headline -- by setting `stacked: false`. [Read more.](?/iframe.html?viewMode=docs&id=components-text-elements-heroheadline--docs&globals=&args=#inline-hero) + +## Photo hero + +To use a photo as the hero, simply pass the image source to the `img` prop. + +```svelte + + + + +``` + + + +## Transparent site header + +In the graphics kit, set styles in `global.scss` to make the Reuters site header transparent and make the hero go all the way to the top of the page: + +```scss +// global.scss +.nav-container { + background-color: transparent !important; +} +.nav-container .inner { + background-color: transparent !important; + border: none !important; +} +.hero-wrapper { + margin-block-start: -64px; +} +``` + + + +## Ai2svelte hero + +To use an ai2svelte graphic as the hero, wrap your ai2svelte component in a `GraphicBlock` component and insert it inside `HeroHeadline`. + +To customise styles, use CSS to target the class passed to `HeroHeadline`. + +> **Note:** Pass `notes` and `ariaDescription` to the `GraphicBlock` component to provide additional information about the ai2svelte graphic. + +```svelte + + + + + + + + + +``` + +Add styles in `global.scss`: + +```scss +// global.scss +// Customise styles using the class (e.g. `custom-hero` here) passed to `HeroHeadline` +.hero-wrapper { + .custom-hero.headline { + // Adjust vertical positioning + align-items: flex-end !important; + + @media (max-width: 1100px) { + // Adjust line length of title + max-width: var(--normal-column-width) !important; + } + } + + // Make hero shorter than 100vh + --heroHeight: 85svh; + + @media (max-width: 960px) { + --heroHeight: 65svh; + } + + // For small height + @media (max-height: 850px) { + --heroHeight: 100svh; + } + + // Custom hero sizing for landscape mobile + @media (max-width: 960px) and (orientation: landscape) { + --heroHeight: 200svh; + } +} + +// Override default fixed height for hero layout in embeds +.hero-wrapper.embedded { + --heroHeight: 1000px; +} +``` + + + +## Video hero + +To add a video as the hero, use the [Video](?path=/docs/components-multimedia-video--docs) component. To customise styles, use CSS to target the class passed to `HeroHeadline`. + +> **Note:** Pass `notes` and `ariaDescription` to the `GraphicBlock` component to provide additional information about the video. + +```svelte + + + + +``` + +Add styles in `global.scss`: + +```scss +// global.scss +// Customise styles using the class (e.g. `video-hero` here) passed to `HeroHeadline` +.hero-wrapper { + --heroHeight: calc(100svh - 60px); + .video-hero.headline { + header { + // Adjust vertical position as offset from default center + top: calc(50svh - 250px); + } + + h1 { + color: #ffd430; + text-shadow: 3px 4px 7px rgba(81, 67, 21, 0.8); + } + } +} +``` + + + +## Inline hero + +To use a photo, graphic, video, etc. as an inline hero -- i.e., to make the hero appear _after_ the headline and dek, instead of stacked underneath -- set `stacked` to `false`. Otherwise, add your hero media in the same way as documented above. + +```svelte + + + + + + + + + + +``` + + + +## Custom hed, dek and byline + +The `HeroHeadline` component internally uses the [Headline](?path=/docs/components-text-elements-headline--docs) component to render the headline and dek, which lets you to customise the headline and dek by passing [snippets](https://svelte.dev/docs/svelte/snippet) into the `hed` and `dek` props. + +Since `Headline` internally uses the [Byline](?path=/docs/components-text-elements-headline--docs) component, you can also customise the author page hyperlink and bylines with the `getAuthorPage`, `byline`, `published` and `updated` props. + +```svelte + + { + return `mailto:${author.replace(' ', '')}@example.com`; + }} +> + + {#snippet hed()} +

+
A visual guide to
+
EUROVISION
+

+ {/snippet} + + + {#snippet dek()} +
+

+ Performers from 37 countries are coming together May 9-13 in Liverpool, + England, for the 67th annual Eurovision Song Contest. The winner gets + the trophy and their country gets the right to host next year’s event, + produced by the European Broadcasting Union (EBU). +

+
+ {/snippet} +
+``` + +Add styles in `global.scss`: + +```scss +// global.scss +.custom-hed { + h1 { + .body-note { + color: #ffffff; + } + .title { + color: #ffffff; + text-shadow: 1px 1px 8px #ff7c88; + filter: drop-shadow(0px 0px 12px #ff7c88); + } + } + + .dek { + margin-block-start: 1rem; + p { + color: #ffffff; + text-shadow: 1px 1px 8px #ff7c88; + filter: drop-shadow(0px 0px 12px #ff7c88); + } + } +} +``` + + diff --git a/src/components/HeroHeadline/HeroHeadline.stories.svelte b/src/components/HeroHeadline/HeroHeadline.stories.svelte new file mode 100644 index 0000000..5e4000e --- /dev/null +++ b/src/components/HeroHeadline/HeroHeadline.stories.svelte @@ -0,0 +1,287 @@ + + + + + + + + + + + + +
+ + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { + return `mailto:${author.replace(' ', '')}@example.com`; + }} + > + {#snippet hed()} +

+
A visual guide to
+
EUROVISION
+

+ {/snippet} + + {#snippet dek()} +
+

+ Performers from 37 countries are coming together May 9-13 in + Liverpool, England, for the 67th annual Eurovision Song Contest. The + winner gets the trophy and their country gets the right to host next + year’s event, produced by the European Broadcasting Union (EBU). +

+
+ {/snippet} +
+ + +
+ + diff --git a/src/components/HeroHeadline/HeroHeadline.svelte b/src/components/HeroHeadline/HeroHeadline.svelte new file mode 100644 index 0000000..1813379 --- /dev/null +++ b/src/components/HeroHeadline/HeroHeadline.svelte @@ -0,0 +1,276 @@ + + + +
+
+ + {#if stacked} + + + + +
+ + {#if children} + {@render children()} + + + {:else if img} + +
+
+ {/if} +
+
+ {/if} + + + {#if stacked === false} + + + + +
+ + {#if children} + {@render children()} + + + {:else if img} + + {/if} +
+
+ {/if} +
+ + +
+ + diff --git a/src/components/HeroHeadline/demo/eurovis.jpeg b/src/components/HeroHeadline/demo/eurovis.jpeg new file mode 100644 index 0000000..ad298a8 Binary files /dev/null and b/src/components/HeroHeadline/demo/eurovis.jpeg differ diff --git a/src/components/HeroHeadline/demo/graphics/CRASH_1-lg.jpeg b/src/components/HeroHeadline/demo/graphics/CRASH_1-lg.jpeg new file mode 100644 index 0000000..60e6304 Binary files /dev/null and b/src/components/HeroHeadline/demo/graphics/CRASH_1-lg.jpeg differ diff --git a/src/components/HeroHeadline/demo/graphics/CRASH_1-md.jpeg b/src/components/HeroHeadline/demo/graphics/CRASH_1-md.jpeg new file mode 100644 index 0000000..abe9b17 Binary files /dev/null and b/src/components/HeroHeadline/demo/graphics/CRASH_1-md.jpeg differ diff --git a/src/components/HeroHeadline/demo/graphics/CRASH_1-sm.jpeg b/src/components/HeroHeadline/demo/graphics/CRASH_1-sm.jpeg new file mode 100644 index 0000000..ca46080 Binary files /dev/null and b/src/components/HeroHeadline/demo/graphics/CRASH_1-sm.jpeg differ diff --git a/src/components/HeroHeadline/demo/graphics/CRASH_1-xl.jpeg b/src/components/HeroHeadline/demo/graphics/CRASH_1-xl.jpeg new file mode 100644 index 0000000..0619957 Binary files /dev/null and b/src/components/HeroHeadline/demo/graphics/CRASH_1-xl.jpeg differ diff --git a/src/components/HeroHeadline/demo/graphics/CRASH_1-xl_copy.jpeg b/src/components/HeroHeadline/demo/graphics/CRASH_1-xl_copy.jpeg new file mode 100644 index 0000000..e150f58 Binary files /dev/null and b/src/components/HeroHeadline/demo/graphics/CRASH_1-xl_copy.jpeg differ diff --git a/src/components/HeroHeadline/demo/graphics/CRASH_1-xs.jpeg b/src/components/HeroHeadline/demo/graphics/CRASH_1-xs.jpeg new file mode 100644 index 0000000..4a8245f Binary files /dev/null and b/src/components/HeroHeadline/demo/graphics/CRASH_1-xs.jpeg differ diff --git a/src/components/HeroHeadline/demo/graphics/crash.svelte b/src/components/HeroHeadline/demo/graphics/crash.svelte new file mode 100644 index 0000000..9f2ac0f --- /dev/null +++ b/src/components/HeroHeadline/demo/graphics/crash.svelte @@ -0,0 +1,650 @@ + + + + +
+ + {#if width && width >= 0 && width < 510} +
+
+
+
+

Cruising at

+

29,100 feet

+
+
+

2.21 pm

+

Steep drop from

+

27,025 feet

+
+
+

Typical path to

+

Guangzhou

+
+
+

Last known

+

location

+
+
+

Crash

+

site

+
+
+ {/if} + + {#if width && width >= 510 && width < 660} +
+
+
+
+

Cruising at

+

29,100 feet

+
+
+

2.21 pm

+

Steep drop from

+

27,025 feet

+
+
+

Typical path to

+

Guangzhou

+
+
+

Last known

+

location

+
+
+

Crash

+

site

+
+
+ {/if} + + {#if width && width >= 660 && width < 930} +
+
+
+
+

Cruising at

+

29,100 feet

+
+
+

2.20 pm

+

Slight descent

+
+
+

2.21 pm

+

Steep drop from

+

27,025 feet

+
+
+

Typical path to

+

Guangzhou

+
+
+

Last known

+

location

+
+
+

Approximate

+

crash site

+
+
+ {/if} + + {#if width && width >= 930 && width < 1200} +
+
+
+
+

Cruising at

+

29,100 feet

+
+
+

2.20 pm

+

Slight descent

+
+
+

2.21 pm

+

Steep drop from

+

27,025 feet

+
+
+

Typical path to

+

Guangzhou

+
+
+

Last known

+

location

+
+
+

Approximate

+

crash site

+
+
+ {/if} + + {#if width && width >= 1200 && width < 1350} +
+
+
+
+

Cruising at

+

29,100 feet

+
+
+

2.20 pm

+

Slight descent

+
+
+

2.21 pm

+

Steep drop from

+

27,025 feet

+
+
+

Typical path to

+

Guangzhou

+
+
+

Last known

+

location

+
+
+

Approximate

+

crash site

+
+
+ {/if} + + {#if width && width >= 1350} +
+
+
+
+

Cruising at

+

29,100 feet

+
+
+

2.20 pm

+

Slight descent

+
+
+

2.21 pm

+

Steep drop from

+

27,025 feet

+
+
+

Typical path to

+

Guangzhou

+
+
+

Last known

+

location

+
+
+

Approximate

+

crash site

+
+
+ {/if} +
+ + + + + diff --git a/src/components/HeroHeadline/demo/graphics/quake-map-top-lg.jpeg b/src/components/HeroHeadline/demo/graphics/quake-map-top-lg.jpeg new file mode 100644 index 0000000..67096d6 Binary files /dev/null and b/src/components/HeroHeadline/demo/graphics/quake-map-top-lg.jpeg differ diff --git a/src/components/HeroHeadline/demo/graphics/quake-map-top-md.jpeg b/src/components/HeroHeadline/demo/graphics/quake-map-top-md.jpeg new file mode 100644 index 0000000..157b8b3 Binary files /dev/null and b/src/components/HeroHeadline/demo/graphics/quake-map-top-md.jpeg differ diff --git a/src/components/HeroHeadline/demo/graphics/quake-map-top-sm.jpeg b/src/components/HeroHeadline/demo/graphics/quake-map-top-sm.jpeg new file mode 100644 index 0000000..f93377a Binary files /dev/null and b/src/components/HeroHeadline/demo/graphics/quake-map-top-sm.jpeg differ diff --git a/src/components/HeroHeadline/demo/graphics/quake-map-top-xl.jpeg b/src/components/HeroHeadline/demo/graphics/quake-map-top-xl.jpeg new file mode 100644 index 0000000..a150f58 Binary files /dev/null and b/src/components/HeroHeadline/demo/graphics/quake-map-top-xl.jpeg differ diff --git a/src/components/HeroHeadline/demo/graphics/quake-map-top-xs.jpeg b/src/components/HeroHeadline/demo/graphics/quake-map-top-xs.jpeg new file mode 100644 index 0000000..34601bd Binary files /dev/null and b/src/components/HeroHeadline/demo/graphics/quake-map-top-xs.jpeg differ diff --git a/src/components/HeroHeadline/demo/graphics/quakemap.svelte b/src/components/HeroHeadline/demo/graphics/quakemap.svelte new file mode 100644 index 0000000..30fba5c --- /dev/null +++ b/src/components/HeroHeadline/demo/graphics/quakemap.svelte @@ -0,0 +1,863 @@ + + +
+ + {#if width && width >= 0 && width < 510} +
+
+
+
+

Kabul

+
+
+

Shaking

+
+
+

Very strong

+
+
+

Weak

+
+
+

AFGHANISTAN

+
+
+

Gardez

+
+
+

Khost

+
+
+

Epicentre

+
+
+

Bannu

+
+
+

PAKISTAN

+
+
+ {/if} + + {#if width && width >= 510 && width < 660} +
+
+
+
+

Shaking

+
+
+

Very strong

+
+
+

AFGHANISTAN

+
+
+

Weak

+
+
+

Gardez

+
+
+

Ghazni

+
+
+

Khost

+
+
+

Epicentre

+
+
+

Bannu

+
+
+

PAKISTAN

+
+
+ {/if} + + {#if width && width >= 660 && width < 1200} +
+
+
+
+

Shaking

+
+
+

Very strong

+
+
+

Afghanistan

+
+
+

Weak

+
+
+

AFGHANISTAN

+
+
+

Gardez

+
+
+

Ghazni

+
+
+

Khost

+
+
+

Epicentre

+
+
+

Bannu

+
+
+

PAKISTAN

+
+
+ {/if} + + {#if width && width >= 1200 && width < 1300} +
+
+
+
+

Shaking

+
+
+

Very strong

+
+
+

Afghanistan

+
+
+

AFGHANISTAN

+
+
+

Weak

+
+
+

Gardez

+
+
+

Ghazni

+
+
+

PAKISTAN

+
+
+

Khost

+
+
+

Epicentre

+
+
+

Bannu

+
+
+ {/if} + + {#if width && width >= 1300} +
+
+
+
+

Shaking

+
+
+

Afghanistan

+
+
+

Very strong

+
+
+

AFGHANISTAN

+
+
+

Weak

+
+
+

Gardez

+
+
+

Ghazni

+
+
+

PAKISTAN

+
+
+

Khost

+
+
+

Epicentre

+
+
+

Bannu

+
+
+ {/if} +
+ + + + + + + diff --git a/src/components/HeroHeadline/demo/polar.jpg b/src/components/HeroHeadline/demo/polar.jpg new file mode 100644 index 0000000..fe7cac6 Binary files /dev/null and b/src/components/HeroHeadline/demo/polar.jpg differ diff --git a/src/components/InfoBox/InfoBox.mdx b/src/components/InfoBox/InfoBox.mdx new file mode 100644 index 0000000..1704e58 --- /dev/null +++ b/src/components/InfoBox/InfoBox.mdx @@ -0,0 +1,122 @@ +import { Meta, Canvas } from '@storybook/blocks'; + +import * as InfoBoxStories from './InfoBox.stories.svelte'; + + + +# InfoBox + +The `InfoBox` component creates a stylised text box that provides additional information that needs to be visually separate from the main content flow, such as methodology, detailed notes about data and extra context. + +```svelte + + + +``` + + + +## Using with ArchieML docs + +With the graphics kit, you'll likely get your text value from an ArchieML doc... + +```yaml +# Archie ML doc +[blocks] + +type: info-box +title: What you need to know about the war +text: Reuters is collecting daily COVID-19 infections and deaths data for 240 countries and territories around the world, updated regularly throughout each day. + +Every country reports those figures a little differently and, inevitably, misses undiagnosed infections and deaths. With this project we are focusing on the trends within countries as they try to contain the virus’ spread, whether they are approaching or past peak infection rates, or if they are seeing a resurgence of infections or deaths. +:end +notes: [Read more about our methodology](https://www.reuters.com/world-coronavirus-tracker-and-maps/en/methodology/) +[] +``` + +... which you'll parse out of a ArchieML block object before passing to the `InfoBox` component. + +```svelte + + + +# Graphics kit +{#each content.blocks as block} + {#if block.type === 'info-box'} + + + {/if} +{/each} +``` + + + +## Lists + +Use markdown to add lists to `InfoBox`. + +```svelte + + + +``` + + +## Customisation + +Use [snippets](https://svelte.dev/docs/svelte/snippet) to customise the `InfoBox`, such as adding tables, icons and thumbnail images. + +```svelte + + + {#snippet header()} +

Global video game market

+ {/snippet} + + {#snippet body()} + + + + + + + + + + + + + + + + + + + + + +
YearMarket size ($bln)
2024274.63
2023281.77
2022249.55
+ {/snippet} + + {#snippet updated()} +
Source: Precedence Research
+ {/snippet} +
+``` + + diff --git a/src/components/InfoBox/InfoBox.stories.svelte b/src/components/InfoBox/InfoBox.stories.svelte new file mode 100644 index 0000000..f07fc99 --- /dev/null +++ b/src/components/InfoBox/InfoBox.stories.svelte @@ -0,0 +1,98 @@ + + + + + + + + + + + {#snippet header()} +

Global video game market

+ {/snippet} + {#snippet body()} + + + + + + + + + + + + + + + + + + + + + +
YearMarket size ($bln)
2024274.63
2023281.77
2022249.55
+ {/snippet} + {#snippet footer()} +
Source: Precedence Research
+ {/snippet} +
+
+ + diff --git a/src/components/InfoBox/InfoBox.svelte b/src/components/InfoBox/InfoBox.svelte new file mode 100644 index 0000000..6a9674a --- /dev/null +++ b/src/components/InfoBox/InfoBox.svelte @@ -0,0 +1,160 @@ + + + + + + diff --git a/src/components/KinesisLogo/KinesisLogo.mdx b/src/components/KinesisLogo/KinesisLogo.mdx new file mode 100644 index 0000000..83bdafd --- /dev/null +++ b/src/components/KinesisLogo/KinesisLogo.mdx @@ -0,0 +1,19 @@ +import { Meta, Canvas } from '@storybook/blocks'; + +import * as KinesisLogoStories from './KinesisLogo.stories.svelte'; + + + +# KinesisLogo + +The `KinesisLogo` component contains the official Kinesis logo. + +```svelte + + + +``` + + diff --git a/src/components/KinesisLogo/KinesisLogo.stories.svelte b/src/components/KinesisLogo/KinesisLogo.stories.svelte new file mode 100644 index 0000000..1d14157 --- /dev/null +++ b/src/components/KinesisLogo/KinesisLogo.stories.svelte @@ -0,0 +1,14 @@ + + + diff --git a/src/components/KinesisLogo/KinesisLogo.svelte b/src/components/KinesisLogo/KinesisLogo.svelte new file mode 100644 index 0000000..52f527b --- /dev/null +++ b/src/components/KinesisLogo/KinesisLogo.svelte @@ -0,0 +1,140 @@ + + + + + + diff --git a/src/components/LanguageButton/LanguageButton.mdx b/src/components/LanguageButton/LanguageButton.mdx new file mode 100644 index 0000000..c7d4ef8 --- /dev/null +++ b/src/components/LanguageButton/LanguageButton.mdx @@ -0,0 +1,118 @@ +import { Meta, Canvas } from '@storybook/blocks'; + +import * as LanguageButtonStories from './LanguageButton.stories.svelte'; + + + +# LanguageButton + +The `LanguageButton` component creates a button that switches between an article written in English and another language. + +> 💡**NOTE:** If a translated embed page does not exist, add `LanguageButton` conditionally so it only renders when `embedded` is `false`. This prevents a toggle button from appearing on the English embed page. + +```svelte + + + + +``` + + + +## Customise language and button label + +By default, the toggle button will switch between English and Spanish, and the `buttonOptions` prop does not need to be passed. To customise the language and button label, pass the `locale` and the button `label` for the translation language as an object to the `buttonOptions` prop. + +```svelte + + + + +``` + + + +## Styling + +Use global CSS to style the toggle button. The container div has the class `language-button` and the nested button has an id of `translate-button`. + +```scss +:global(div.language-button) { + /* Custom styles for the container div */ + + button#translate-button { + /* Custom styles for the toggle button */ + } +} + + + +``` + +## Customising URLs + +By default, the `LanguageButton` component will switch from English to the translated version of the main article by appending `/{locale}/` to the base URL. For example, if the English version of the article is at `www.example.com/article/`, the translated version will be at `www.example.com/article/{locale}/`. + +If `embedded` is passed, it will switch between English and translated versions of the embed page by appending `/embeds/{locale}/page/` to the base URL. For example, if the English version of the embed page is at `www.example.com/article/embeds/en/page/`, the translated version will be at `www.example.com/article/embeds/{locale}/page/`. + +To customise this, pass a function to the `setUrl` prop that returns the URLs you want to link to. + +```svelte + + + +``` diff --git a/src/components/LanguageButton/LanguageButton.stories.svelte b/src/components/LanguageButton/LanguageButton.stories.svelte new file mode 100644 index 0000000..113d5c7 --- /dev/null +++ b/src/components/LanguageButton/LanguageButton.stories.svelte @@ -0,0 +1,167 @@ + + +{#snippet demoToggle( + locale: string, + translatedLocale: string, + translatedLabel: string, + ariaLabel: string, + onToggle: () => void +)} +
+
+

Demo control

+

+ Use this toggle to simulate a locale switch in the demo +

+
+
+ EN + + {translatedLabel} +
+
+{/snippet} + + +
+ {@render demoToggle( + spanishLocale, + 'es', + 'ES', + 'Toggle between English and Spanish', + () => { + spanishLocale = spanishLocale === 'en' ? 'es' : 'en'; + } + )} + +
+
+ + +
+ {@render demoToggle( + frenchLocale, + 'fr', + 'FR', + 'Toggle between English and French', + () => { + frenchLocale = frenchLocale === 'en' ? 'fr' : 'en'; + } + )} + +
+
+ + diff --git a/src/components/LanguageButton/LanguageButton.svelte b/src/components/LanguageButton/LanguageButton.svelte new file mode 100644 index 0000000..49d1cce --- /dev/null +++ b/src/components/LanguageButton/LanguageButton.svelte @@ -0,0 +1,97 @@ + + + + + + diff --git a/src/components/LogBlock.svelte b/src/components/LogBlock.svelte new file mode 100644 index 0000000..86890e9 --- /dev/null +++ b/src/components/LogBlock.svelte @@ -0,0 +1,62 @@ + + + + + +
+ {level.toUpperCase()} + {message} +
+
+ + + diff --git a/src/components/Lottie/LottieForeground.svelte b/src/components/Lottie/LottieForeground.svelte index ed74a21..dc1156e 100644 --- a/src/components/Lottie/LottieForeground.svelte +++ b/src/components/Lottie/LottieForeground.svelte @@ -69,7 +69,7 @@ diff --git a/src/components/PaddingReset/PaddingReset.svelte b/src/components/PaddingReset/PaddingReset.svelte new file mode 100644 index 0000000..cfc012b --- /dev/null +++ b/src/components/PaddingReset/PaddingReset.svelte @@ -0,0 +1,34 @@ + + + +{#if containerIsFluid} +
+ + {@render children()} +
+{:else} + + {@render children()} +{/if} + + diff --git a/src/components/PaddingReset/shark.jpg b/src/components/PaddingReset/shark.jpg new file mode 100644 index 0000000..5b54e9f Binary files /dev/null and b/src/components/PaddingReset/shark.jpg differ diff --git a/src/components/PhotoPack/PhotoPack.svelte b/src/components/PhotoPack/PhotoPack.svelte index 971b29a..e1a981e 100644 --- a/src/components/PhotoPack/PhotoPack.svelte +++ b/src/components/PhotoPack/PhotoPack.svelte @@ -128,7 +128,7 @@ diff --git a/src/components/ReferralBlock/filterCurrentPage.ts b/src/components/ReferralBlock/filterCurrentPage.ts new file mode 100644 index 0000000..2536555 --- /dev/null +++ b/src/components/ReferralBlock/filterCurrentPage.ts @@ -0,0 +1,35 @@ +import type { Article } from './types'; + +const getUrlFromPath = (path: string) => { + const base = 'https://www.reuters.com'; + + try { + return new URL(path); + } catch { + try { + return new URL(path, base); + } catch { + return null; + } + } +}; + +const isCurrentPage = (urlPath: string) => { + if (typeof window === 'undefined' || typeof window.location === 'undefined') { + return false; + } + const url = getUrlFromPath(urlPath); + if (!url) return false; + return ( + window.location.origin === url.origin && + window.location.pathname === url.pathname + ); +}; + +export const articleIsNotCurrentPage = (article: Article) => { + const { redirect_url: redirectUrl, canonical_url: canonicalUrl } = article; + + if (redirectUrl) return !isCurrentPage(redirectUrl); + if (canonicalUrl) return !isCurrentPage(canonicalUrl); + return true; +}; diff --git a/src/components/ReferralBlock/types.ts b/src/components/ReferralBlock/types.ts new file mode 100644 index 0000000..b087500 --- /dev/null +++ b/src/components/ReferralBlock/types.ts @@ -0,0 +1,109 @@ +export interface Referrals { + statusCode: number; + message: string; + result: Result; +} + +interface Result { + date_modified: Date; + pagination: Pagination; + fetch_type: string; + title: string; + articles: Article[]; +} + +export interface Article { + id: string; + canonical_url: string; + basic_headline: string; + title: string; + lead_art: LeadArt; + description: string; + web: string; + content_code: string; + updated_time: Date; + published_time: Date; + display_time: Date; + thumbnail: LeadArt; + primary_media_type: string; + source: Source; + redirect_url: string; + distributor: string; + authors: Author[]; + kicker: Kicker; + content_elements: unknown[]; + headline_category?: unknown; + content?: { + third_party?: unknown; + }; +} + +interface Author { + topic_url: string; + thumbnail: Thumbnail; + id: string; + name: string; + first_name: string; + last_name: string; + company: string; + social_links: SocialLink[]; + byline: string; +} + +interface SocialLink { + url: string; + site: string; +} + +interface Thumbnail { + url: string; + resizer_url: string; + renditions: Renditions; +} + +interface Renditions { + square: Landscape; + landscape: Landscape; + portrait: Landscape; + original: Landscape; +} + +interface Landscape { + '60w': string; + '120w': string; + '240w': string; + '480w': string; + '960w': string; + '1080w': string; + '1200w': string; + '1920w': string; +} + +interface Kicker { + name: string; + path: string; + names: string[]; +} + +interface LeadArt { + type: string; + url: string; + resizer_url: string; + renditions: Renditions; + id: string; + caption?: string; + alt_text: string; + width: number; + height: number; + subtitle: string; + updated_at: Date; +} + +interface Source { + name: string; +} + +interface Pagination { + size: number; + expected_size: number; +} diff --git a/src/components/ReutersGraphicsLogo/ReutersGraphicsLogo.mdx b/src/components/ReutersGraphicsLogo/ReutersGraphicsLogo.mdx new file mode 100644 index 0000000..6e7c817 --- /dev/null +++ b/src/components/ReutersGraphicsLogo/ReutersGraphicsLogo.mdx @@ -0,0 +1,21 @@ +import { Meta, Canvas } from '@storybook/blocks'; + +import * as ReutersGraphicsLogoStories from './ReutersGraphicsLogo.stories.svelte'; + + + +# ReutersGraphicsLogo + +The `ReutersGraphicsLogo` component contains the Reuters Graphics team logo. + +> Generally, used only for internal tools. For public pages, use the [ReutersLogo](./?path=/docs/components-logos-reuterslogo--docs) component. + +```svelte + + + +``` + + diff --git a/src/components/ReutersGraphicsLogo/ReutersGraphicsLogo.stories.svelte b/src/components/ReutersGraphicsLogo/ReutersGraphicsLogo.stories.svelte new file mode 100644 index 0000000..3409313 --- /dev/null +++ b/src/components/ReutersGraphicsLogo/ReutersGraphicsLogo.stories.svelte @@ -0,0 +1,14 @@ + + + diff --git a/src/components/ReutersGraphicsLogo/ReutersGraphicsLogo.svelte b/src/components/ReutersGraphicsLogo/ReutersGraphicsLogo.svelte new file mode 100644 index 0000000..cb6e31b --- /dev/null +++ b/src/components/ReutersGraphicsLogo/ReutersGraphicsLogo.svelte @@ -0,0 +1,215 @@ + + + + + + diff --git a/src/components/ReutersLogo/ReutersLogo.mdx b/src/components/ReutersLogo/ReutersLogo.mdx new file mode 100644 index 0000000..e4d3b63 --- /dev/null +++ b/src/components/ReutersLogo/ReutersLogo.mdx @@ -0,0 +1,19 @@ +import { Meta, Canvas } from '@storybook/blocks'; + +import * as ReutersLogoStories from './ReutersLogo.stories.svelte'; + + + +# ReutersLogo + +The `ReutersLogo` component contains the official Reuters logo. + +```svelte + + + +``` + + diff --git a/src/components/ReutersLogo/ReutersLogo.stories.svelte b/src/components/ReutersLogo/ReutersLogo.stories.svelte new file mode 100644 index 0000000..5ab6725 --- /dev/null +++ b/src/components/ReutersLogo/ReutersLogo.stories.svelte @@ -0,0 +1,15 @@ + + + diff --git a/src/components/ReutersLogo/ReutersLogo.svelte b/src/components/ReutersLogo/ReutersLogo.svelte new file mode 100644 index 0000000..aadffae --- /dev/null +++ b/src/components/ReutersLogo/ReutersLogo.svelte @@ -0,0 +1,188 @@ + + + + + + diff --git a/src/components/SEO/SEO.mdx b/src/components/SEO/SEO.mdx new file mode 100644 index 0000000..e56e7ca --- /dev/null +++ b/src/components/SEO/SEO.mdx @@ -0,0 +1,80 @@ +import { Meta, Canvas } from '@storybook/blocks'; + +import * as SEOStories from './SEO.stories.svelte'; + + + +# SEO + +The `SEO` component adds essential metadata to pages. + +```svelte + + + +``` + +## Using with ArchieML docs + +With the graphics kit, you'll likely get many of your text values from an ArchieML doc... + +```yaml +# ArchieML doc +slug: ROOT-SLUG/WILD +seoTitle: Page title for search +seoDescription: Page description for search +shareTitle: Page title for social media +shareDescription: Page description for social media +shareImgPath: images/reuters-graphics.jpg +shareImgAlt: Alt text for share image. +``` + +... which you'll pass to the `SEO` component. + +```svelte + + + +``` + +> **Note:** For _reasons_, we can't document the value of `VITE_BASE_URL` below. It's `import` + `.meta.env.BASE_URL` (concatenate all that) in the graphics kit and other Vite-based rigs. + + +``` diff --git a/src/components/SEO/SEO.stories.svelte b/src/components/SEO/SEO.stories.svelte new file mode 100644 index 0000000..dc35435 --- /dev/null +++ b/src/components/SEO/SEO.stories.svelte @@ -0,0 +1,23 @@ + + +
View page source to see the SEO metadata.
+ diff --git a/src/components/SEO/SEO.svelte b/src/components/SEO/SEO.svelte new file mode 100644 index 0000000..19bc694 --- /dev/null +++ b/src/components/SEO/SEO.svelte @@ -0,0 +1,197 @@ + + + + + {#key canonicalUrl} + {seoTitle} + + + + + + + + + + + + + + + + + + + + + + {#if shareImgAlt} + + {/if} + + + + + + + + {@html `<${'script'} type="application/ld+json">${JSON.stringify( + orgLdJson + )}`} + + {@html `<${'script'} type="application/ld+json">${JSON.stringify( + articleLdJson + )}`} + {/key} + diff --git a/src/components/Scroller/Foreground.svelte b/src/components/Scroller/Foreground.svelte index e42912f..9115e26 100644 --- a/src/components/Scroller/Foreground.svelte +++ b/src/components/Scroller/Foreground.svelte @@ -41,7 +41,7 @@ {/each} diff --git a/src/components/ScrollerVideo/ScrollerVideoForeground.svelte b/src/components/ScrollerVideo/ScrollerVideoForeground.svelte index 545432f..bfa6a88 100644 --- a/src/components/ScrollerVideo/ScrollerVideoForeground.svelte +++ b/src/components/ScrollerVideo/ScrollerVideoForeground.svelte @@ -79,7 +79,7 @@ diff --git a/src/components/SearchInput/components/MagnifyingGlass.svelte b/src/components/SearchInput/components/MagnifyingGlass.svelte new file mode 100644 index 0000000..a799801 --- /dev/null +++ b/src/components/SearchInput/components/MagnifyingGlass.svelte @@ -0,0 +1,21 @@ + + + + + diff --git a/src/components/SearchInput/components/X.svelte b/src/components/SearchInput/components/X.svelte new file mode 100644 index 0000000..2e3d7d1 --- /dev/null +++ b/src/components/SearchInput/components/X.svelte @@ -0,0 +1,33 @@ + + + + + + + + + + + + diff --git a/src/components/SimpleTimeline/SimpleTimeline.mdx b/src/components/SimpleTimeline/SimpleTimeline.mdx new file mode 100644 index 0000000..7eb4c72 --- /dev/null +++ b/src/components/SimpleTimeline/SimpleTimeline.mdx @@ -0,0 +1,121 @@ +import { Meta, Canvas } from '@storybook/blocks'; + +import * as SimpleTimelineStories from './SimpleTimeline.stories.svelte'; + + + +# SimpleTimeline + +The `SimpleTimeline` component creates a basic timeline with dates, titles and descriptions of events. + +```svelte + + + +``` + + + +## Using with ArchieML docs + +With the graphics kit, you'll likely get your text value from an ArchieML doc. + +```yaml +# Archie ML doc + +type: timeline +# Optional +class: timeline +id: timeline-1 +symbolColour: var(--theme-colour-brand-rules, grey) +dateColour: var(--theme-colour-accent, red) +[.dates] + # date object with events + date: May 10 + [.events] + title: U.S. House passes $40 bln bill to bolster Ukraine against Russian invasion + context: The U.S. House of Representatives approved more than $40 billion more aid for Ukraine on Tuesday, as Congress races to keep military aid flowing and boost the government in Kyiv as it grapples with the Russian invasion. + titleLink: https://www.reuters.com/world/us-house-vote-40-billion-ukraine-aid-package-tuesday-pelosi-2022-05-10/ + [] + +# More dates and events... +[] +``` + +... which you'll pass to the `SimpleTimeline` component. + +```svelte + + + + +``` + + + +# Multiple events + +You can add multiple events to a single date by adding objects to the `events` array. + +```svelte + + + +``` + + diff --git a/src/components/SimpleTimeline/SimpleTimeline.stories.svelte b/src/components/SimpleTimeline/SimpleTimeline.stories.svelte new file mode 100644 index 0000000..bcf8d85 --- /dev/null +++ b/src/components/SimpleTimeline/SimpleTimeline.stories.svelte @@ -0,0 +1,95 @@ + + + + + + + diff --git a/src/components/SimpleTimeline/SimpleTimeline.svelte b/src/components/SimpleTimeline/SimpleTimeline.svelte new file mode 100644 index 0000000..7a89807 --- /dev/null +++ b/src/components/SimpleTimeline/SimpleTimeline.svelte @@ -0,0 +1,135 @@ + + + + +
+ {#each dates as date} +
+ + + +
+ {date.date} +
+ {#each date.events as event} +
+ {#if event.titleLink} + +
+ {event.title} + +
+
+ {:else} +
+ {event.title} +
+ {/if} + {#if event.context} + + {/if} +
+ {/each} +
+ {/each} +
+
+ + diff --git a/src/components/SiteFooter/CompanyLinks.svelte b/src/components/SiteFooter/CompanyLinks.svelte new file mode 100644 index 0000000..3157a6b --- /dev/null +++ b/src/components/SiteFooter/CompanyLinks.svelte @@ -0,0 +1,177 @@ + + +{#if links.social_links} +
+
+
+

Information you can trust

+

{links.company_description}

+
+ +
+
+{/if} + + diff --git a/src/components/SiteFooter/LegalLinks.svelte b/src/components/SiteFooter/LegalLinks.svelte new file mode 100644 index 0000000..df4fd7f --- /dev/null +++ b/src/components/SiteFooter/LegalLinks.svelte @@ -0,0 +1,181 @@ + + +{#if links.ad_links} + +{/if} + + diff --git a/src/components/SiteFooter/QuickLinks.svelte b/src/components/SiteFooter/QuickLinks.svelte new file mode 100644 index 0000000..caac9ed --- /dev/null +++ b/src/components/SiteFooter/QuickLinks.svelte @@ -0,0 +1,265 @@ + + + + +{#if links.latest_links} + +{/if} + + diff --git a/src/components/SiteFooter/SiteFooter.mdx b/src/components/SiteFooter/SiteFooter.mdx new file mode 100644 index 0000000..fb724d8 --- /dev/null +++ b/src/components/SiteFooter/SiteFooter.mdx @@ -0,0 +1,45 @@ +import { Meta } from '@storybook/blocks'; + +import * as SiteFooterStories from './SiteFooter.stories.svelte'; + + + +# SiteFooter + +Reuters dotcom site footer with graphics referrals, ported from [Raptor UI components](https://github.com/tr/rcom-arc_raptor-ui/tree/develop/packages/rcom-raptor-ui_common/src/components/site-footer). + +> **Note:** In the graphics kit, you can find this component in `pages/+page.svelte`. Customise it there for the default page. + +```svelte + + + +``` + +## Dark theme + +Colours are customised by the [`Theme`](?path=/docs/theming-theme--default) component. ([Demo](?path=/story/components-page-furniture-sitefooter--customised-theme)) + +```svelte + + + + + +``` + +## Removing referrals + +Remove graphics referrals. ([Demo](?path=/story/components-page-furniture-sitefooter--remove-referrals)) + +```svelte + + + +``` diff --git a/src/components/SiteFooter/SiteFooter.stories.svelte b/src/components/SiteFooter/SiteFooter.stories.svelte new file mode 100644 index 0000000..d05461a --- /dev/null +++ b/src/components/SiteFooter/SiteFooter.stories.svelte @@ -0,0 +1,29 @@ + + + + + +
+ + + +
+
+ + + + diff --git a/src/components/SiteFooter/SiteFooter.svelte b/src/components/SiteFooter/SiteFooter.svelte new file mode 100644 index 0000000..ece8bd1 --- /dev/null +++ b/src/components/SiteFooter/SiteFooter.svelte @@ -0,0 +1,85 @@ + + + +
+
+ {#if includeReferrals} + + + + {/if} + + + +
+
+ + diff --git a/src/components/SiteFooter/data.json b/src/components/SiteFooter/data.json new file mode 100644 index 0000000..76a8f61 --- /dev/null +++ b/src/components/SiteFooter/data.json @@ -0,0 +1,210 @@ +[ + { + "company_description": "Reuters, the news and media division of Thomson Reuters, is the world’s largest multimedia news provider, reaching billions of people worldwide every day. Reuters provides business, financial, national and international news to professionals via desktop terminals, the world's media organizations, industry events and directly to consumers.", + "disclaimer_link": "https://www.reuters.com/info-pages/disclaimer/", + "copyright_link": "https://www.thomsonreuters.com/en/policies/copyright.html", + "copyright_year": "2025", + "latest_links": [ + { + "text": "Home", + "url": "/", + "self": true + } + ], + "browse_links": [ + { + "text": "World", + "url": "/world/", + "self": true + }, + { + "text": "Business", + "url": "/business/", + "self": true + }, + { + "text": "Legal", + "url": "/legal/", + "self": true + }, + { + "text": "Markets", + "url": "/markets/", + "self": true + }, + { + "text": "Breakingviews", + "url": "/breakingviews/", + "self": true + }, + { + "text": "Technology", + "url": "/technology/", + "self": true + }, + { + "text": "Investigations", + "url": "/investigates/" + }, + { + "text": "Lifestyle", + "url": "/lifestyle/", + "self": true + } + ], + "media_links": [ + { + "text": "Videos", + "url": "https://www.reuters.com/video/", + "symbol": "videos" + }, + { + "text": "Pictures", + "url": "https://www.reuters.com/news/pictures", + "symbol": "pictures" + }, + { + "text": "Graphics", + "url": "https://graphics.reuters.com/", + "symbol": "graphics" + } + ], + "about_links": [ + { + "text": "About Reuters", + "url": "https://www.reutersagency.com/en/about/about-us/" + }, + { + "text": "Careers", + "url": "https://www.thomsonreuters.com/en/careers.html" + }, + { + "text": "Reuters News Agency", + "url": "https://www.reutersagency.com/en/?utm_source=website&utm_medium=reuters&utm_campaign=site-referral&utm_content=us&utm_term=0" + }, + { + "text": "Brand Attribution Guidelines", + "url": "https://www.reutersagency.com/en/about/about-us/brand-attribution-guidelines/" + }, + { + "text": "Reuters Leadership", + "url": "https://www.reutersagency.com/en/about/leadership-team/" + }, + { + "text": "Reuters Fact Check", + "url": "https://www.reuters.com/fact-check/" + }, + { + "text": "Reuters Diversity Report", + "url": "https://www.reuters.com/DiversityReportApril2022" + } + ], + "stay_informed_links": [ + { + "text": "Download the App", + "url": "https://www.reuters.com/tools/mobile/us" + }, + { + "text": "Newsletters", + "url": "https://newslink.reuters.com/join/subscribe" + } + ], + "social_links": [ + { + "type": "twitter", + "url": "https://www.twitter.com/Reuters" + }, + { + "type": "facebook", + "url": "https://www.facebook.com/Reuters" + }, + { + "type": "instagram", + "url": "https://www.instagram.com/Reuters" + }, + { + "type": "youtube", + "url": "https://www.youtube.com/user/ReutersVideo" + }, + { + "type": "linkedin", + "url": "https://www.linkedin.com/company/10256858/" + } + ], + "tr_products": [ + { + "name": "Westlaw", + "description": "Build the strongest argument relying on authoritative content, attorney-editor expertise, and industry defining technology.", + "url": "https://legal.thomsonreuters.com/en/products/westlaw" + }, + { + "name": "Onesource", + "description": "The most comprehensive solution to manage all your complex and ever-expanding tax and compliance needs.", + "url": "https://tax.thomsonreuters.com/en/onesource" + }, + { + "name": "Checkpoint", + "description": "The industry leader for online information for tax, accounting and finance professionals.", + "url": "https://tax.thomsonreuters.com/en/checkpoint" + } + ], + "refinitiv_products": [ + { + "name": "Refinitiv Workspace", + "description": " Access unmatched financial data, news and content in a highly-customised workflow experience on desktop, web and mobile.", + "url": " https://www.refinitiv.com/en/products/refinitiv-workspace" + }, + { + "name": "Refinitiv Data Catalogue", + "description": " Browse an unrivalled portfolio of real-time and historical market data and insights from worldwide sources and experts.", + "url": " https://www.refinitiv.com/en/financial-data" + }, + { + "name": "Refinitiv World-Check", + "description": "Screen for heightened risk individual and entities globally to help uncover hidden risks in business relationships and human networks.", + "url": "https://www.refinitiv.com/en/products/world-check-kyc-screening" + } + ], + "ad_links": [ + { + "text": "Advertise With Us", + "url": "https://www.reutersagency.com/en/services/advertising-solutions/" + }, + { + "text": "Advertising Guidelines", + "url": "/info-pages/advertising-guidelines/" + } + ], + "misc_links": [ + { + "text": "Cookies", + "url": "https://www.thomsonreuters.com/en/privacy-statement.html#cookies" + }, + { + "text": "Terms of Use", + "url": "/info-pages/terms-of-use/" + }, + { + "text": "Privacy", + "url": "https://www.thomsonreuters.com/en/privacy-statement.html" + }, + { + "text": "Digital Accessibility", + "url": "https://www.thomsonreuters.com/en/policies/digital-accessibility-policy.html" + }, + { + "text": "Corrections", + "url": "/info-pages/contact-us/" + }, + { + "text": "Site Feedback", + "url": "https://trdigital.iad1.qualtrics.com/jfe/form/SV_8kte8gArGyCGVhz" + }, + { + "text": "Do Not Sell My Personal Information", + "url": "javascript:window.OneTrust.ToggleInfoDisplay();", + "self": true + } + ] + } +] diff --git a/src/components/SiteFooter/svgs/Facebook.svelte b/src/components/SiteFooter/svgs/Facebook.svelte new file mode 100644 index 0000000..045cd7b --- /dev/null +++ b/src/components/SiteFooter/svgs/Facebook.svelte @@ -0,0 +1,25 @@ + + + + + diff --git a/src/components/SiteFooter/svgs/Graphics.svelte b/src/components/SiteFooter/svgs/Graphics.svelte new file mode 100644 index 0000000..c15bb5f --- /dev/null +++ b/src/components/SiteFooter/svgs/Graphics.svelte @@ -0,0 +1,21 @@ + + + diff --git a/src/components/SiteFooter/svgs/Instagram.svelte b/src/components/SiteFooter/svgs/Instagram.svelte new file mode 100644 index 0000000..1e45cc2 --- /dev/null +++ b/src/components/SiteFooter/svgs/Instagram.svelte @@ -0,0 +1,27 @@ + + + + + diff --git a/src/components/SiteFooter/svgs/LinkedIn.svelte b/src/components/SiteFooter/svgs/LinkedIn.svelte new file mode 100644 index 0000000..bb3505c --- /dev/null +++ b/src/components/SiteFooter/svgs/LinkedIn.svelte @@ -0,0 +1,25 @@ + + + + + diff --git a/src/components/SiteFooter/svgs/Pictures.svelte b/src/components/SiteFooter/svgs/Pictures.svelte new file mode 100644 index 0000000..2d15743 --- /dev/null +++ b/src/components/SiteFooter/svgs/Pictures.svelte @@ -0,0 +1,17 @@ + + + diff --git a/src/components/SiteFooter/svgs/Twitter.svelte b/src/components/SiteFooter/svgs/Twitter.svelte new file mode 100644 index 0000000..1c5e81c --- /dev/null +++ b/src/components/SiteFooter/svgs/Twitter.svelte @@ -0,0 +1,27 @@ + + + + + diff --git a/src/components/SiteFooter/svgs/Videos.svelte b/src/components/SiteFooter/svgs/Videos.svelte new file mode 100644 index 0000000..937bda4 --- /dev/null +++ b/src/components/SiteFooter/svgs/Videos.svelte @@ -0,0 +1,17 @@ + + + diff --git a/src/components/SiteFooter/svgs/YouTube.svelte b/src/components/SiteFooter/svgs/YouTube.svelte new file mode 100644 index 0000000..cadb5e0 --- /dev/null +++ b/src/components/SiteFooter/svgs/YouTube.svelte @@ -0,0 +1,25 @@ + + + + + diff --git a/src/components/SiteHeader/MobileMenu/index.svelte b/src/components/SiteHeader/MobileMenu/index.svelte new file mode 100644 index 0000000..d4be65b --- /dev/null +++ b/src/components/SiteHeader/MobileMenu/index.svelte @@ -0,0 +1,197 @@ + + +{#if isMobileMenuOpen} +
+
+ + +
+ {#each data.sections as section} +
+ {section.name} + {#if section.children} + + {/if} +
+ {/each} +
+{/if} + + diff --git a/src/components/SiteHeader/NavBar/DownArrow.svelte b/src/components/SiteHeader/NavBar/DownArrow.svelte new file mode 100644 index 0000000..24467c9 --- /dev/null +++ b/src/components/SiteHeader/NavBar/DownArrow.svelte @@ -0,0 +1,33 @@ + + + + + diff --git a/src/components/SiteHeader/NavBar/NavDropdown/MoreDropdown.svelte b/src/components/SiteHeader/NavBar/NavDropdown/MoreDropdown.svelte new file mode 100644 index 0000000..51f1650 --- /dev/null +++ b/src/components/SiteHeader/NavBar/NavDropdown/MoreDropdown.svelte @@ -0,0 +1,110 @@ + + + +
+
+ {#each sections as section} +
+ + {section.name} + + {#if section.children} +
    + {#each section.children as sub} +
  • + {sub.name} +
  • + {/each} +
+ {/if} +
+ {/each} +
+
+
+ + diff --git a/src/components/SiteHeader/NavBar/NavDropdown/SectionDropdown.svelte b/src/components/SiteHeader/NavBar/NavDropdown/SectionDropdown.svelte new file mode 100644 index 0000000..49edf9a --- /dev/null +++ b/src/components/SiteHeader/NavBar/NavDropdown/SectionDropdown.svelte @@ -0,0 +1,134 @@ + + + + + + Browse {section.name} + + +
+ {#if splitCount > 0} +
    + {#each section.children.slice(0, splitCount) as sub} +
  • + + {sub.name} + +
  • + {/each} +
+ {/if} +
    + {#each section.children.slice(splitCount) as sub} +
  • + + {sub.name} + +
  • + {/each} +
+
+
+ + diff --git a/src/components/SiteHeader/NavBar/NavDropdown/Spinner/index.svelte b/src/components/SiteHeader/NavBar/NavDropdown/Spinner/index.svelte new file mode 100644 index 0000000..32d6e07 --- /dev/null +++ b/src/components/SiteHeader/NavBar/NavDropdown/Spinner/index.svelte @@ -0,0 +1,49 @@ +
+
+
+
+
+ + diff --git a/src/components/SiteHeader/NavBar/NavDropdown/StoryCard/index.svelte b/src/components/SiteHeader/NavBar/NavDropdown/StoryCard/index.svelte new file mode 100644 index 0000000..1ddde42 --- /dev/null +++ b/src/components/SiteHeader/NavBar/NavDropdown/StoryCard/index.svelte @@ -0,0 +1,94 @@ + + +
+ +
+ {story.title} + +
+ {#if thumbnail && (thumbnail.resizer_url || thumbnail?.renditions?.square?.['120w'])} +
+ {#if thumbnail.resizer_url} + {thumbnail.alt_text} + {:else} + {thumbnail.alt_text} + {/if} +
+ {/if} +
+
+ + diff --git a/src/components/SiteHeader/NavBar/NavDropdown/StoryCard/time.ts b/src/components/SiteHeader/NavBar/NavDropdown/StoryCard/time.ts new file mode 100644 index 0000000..db2455a --- /dev/null +++ b/src/components/SiteHeader/NavBar/NavDropdown/StoryCard/time.ts @@ -0,0 +1,74 @@ +import advancedFormat from 'dayjs/plugin/advancedFormat.js'; +import dayjs from 'dayjs'; +import localizedFormat from 'dayjs/plugin/localizedFormat.js'; +import relativeTime from 'dayjs/plugin/relativeTime.js'; +import timezone from 'dayjs/plugin/timezone.js'; +import updateLocale from 'dayjs/plugin/updateLocale.js'; +import utc from 'dayjs/plugin/utc.js'; + +dayjs.extend(relativeTime); +dayjs.extend(localizedFormat); +dayjs.extend(advancedFormat); +dayjs.extend(utc); +dayjs.extend(timezone); +dayjs.extend(updateLocale); + +dayjs.updateLocale('en', { + relativeTime: { + future: 'in %s', + past: '%s ago', + s: 'a few seconds', + m: 'a min', + mm: '%d min', + h: 'an hour', + hh: '%d hours', + d: 'a day', + dd: '%d days', + M: 'a month', + MM: '%d months', + y: 'a year', + yy: '%d years', + }, +}); + +const getTimeZone = (local: boolean) => { + if (local) { + return dayjs.tz.guess(); + } + + return 'UTC'; +}; + +const diff = ( + dateFrom: Date, + dateTo: number, + measurement: 'day' | 'hour' = 'day' +) => { + return dayjs(dateFrom).diff(dayjs(dateTo), measurement, true); +}; + +const olderThanHour = (dateFrom: Date, dateTo: number, hours = 1) => { + return diff(dateFrom, dateTo, 'hour') < -hours; +}; + +const isSameDay = (dateFrom: Date, dateTo: number) => { + const first = new Date(dateFrom); + const second = new Date(dateTo); + return ( + first.getFullYear() === second.getFullYear() && + first.getMonth() === second.getMonth() && + first.getDate() === second.getDate() + ); +}; + +export const getTime = (datetime: dayjs.ConfigType) => { + const publishTime = dayjs(datetime, { utc: true }); + const showRelativeTime = !olderThanHour(publishTime.toDate(), Date.now()); + const showTime = isSameDay(publishTime.toDate(), Date.now()); + const timezone = getTimeZone(false); + if (showRelativeTime) { + return dayjs().to(publishTime); + } + if (showTime) return dayjs(datetime).tz(timezone).format('h:mm A z'); + return publishTime.format('MMMM D, YYYY'); +}; diff --git a/src/components/SiteHeader/NavBar/NavDropdown/index.svelte b/src/components/SiteHeader/NavBar/NavDropdown/index.svelte new file mode 100644 index 0000000..09ea6c5 --- /dev/null +++ b/src/components/SiteHeader/NavBar/NavDropdown/index.svelte @@ -0,0 +1,248 @@ + + + + + diff --git a/src/components/SiteHeader/NavBar/index.svelte b/src/components/SiteHeader/NavBar/index.svelte new file mode 100644 index 0000000..292ca4f --- /dev/null +++ b/src/components/SiteHeader/NavBar/index.svelte @@ -0,0 +1,268 @@ + + + + + + + diff --git a/src/components/SiteHeader/NavBar/utils/index.ts b/src/components/SiteHeader/NavBar/utils/index.ts new file mode 100644 index 0000000..bbb458b --- /dev/null +++ b/src/components/SiteHeader/NavBar/utils/index.ts @@ -0,0 +1,2 @@ +export const normalizeUrl = (url: string) => + /^http/.test(url) ? url : `https://www.reuters.com${url}`; diff --git a/src/components/SiteHeader/SiteHeader.mdx b/src/components/SiteHeader/SiteHeader.mdx new file mode 100644 index 0000000..abd6348 --- /dev/null +++ b/src/components/SiteHeader/SiteHeader.mdx @@ -0,0 +1,33 @@ +import { Meta } from '@storybook/blocks'; + +import * as SiteHeaderStories from './SiteHeader.stories.svelte'; + + + +# SiteHeader + +Reuters dotcom site header, ported from [Raptor UI components](https://github.com/tr/rcom-arc_raptor-ui/tree/develop/packages/rcom-raptor-ui_common/src/components/site-header). + +> **Note:** In the graphics kit, you can find this component in `pages/+page.svelte`. Customise it there for the default page. + +```svelte + + + +``` + +## Dark theme + +Colours are customised by the [`Theme`](?path=/docs/theming-theme--default) component. ([Demo](?path=/story/components-page-furniture-siteheader--customised-theme)) + +```svelte + + + + + +``` diff --git a/src/components/SiteHeader/SiteHeader.stories.svelte b/src/components/SiteHeader/SiteHeader.stories.svelte new file mode 100644 index 0000000..3fe3f8d --- /dev/null +++ b/src/components/SiteHeader/SiteHeader.stories.svelte @@ -0,0 +1,47 @@ + + + { + const canvas = within(canvasElement); + await userEvent.click(canvas.getByText('More')); + await waitFor(() => + expect(canvas.getByText('Graphics')).toBeInTheDocument() + ); + await userEvent.click(canvas.getByText('World')); + await waitFor(() => expect(canvas.getByText('Europe')).toBeInTheDocument()); + }} +> +
+ +
+
+ + +
+ + + +
+
+ + diff --git a/src/components/SiteHeader/SiteHeader.svelte b/src/components/SiteHeader/SiteHeader.svelte new file mode 100644 index 0000000..a6e3cc3 --- /dev/null +++ b/src/components/SiteHeader/SiteHeader.svelte @@ -0,0 +1,268 @@ + + + +
+ + +
+ + { + isMobileMenuOpen = false; + }} + data={data[0]} +/> + + diff --git a/src/components/SiteHeader/data.json b/src/components/SiteHeader/data.json new file mode 100644 index 0000000..97f4bd6 --- /dev/null +++ b/src/components/SiteHeader/data.json @@ -0,0 +1,347 @@ +[ + { + "sections": [ + { + "id": "/world/", + "url": "/world/", + "name": "World", + "type": "section", + "children": [ + { "id": "/world/africa/", "url": "/world/africa/", "name": "Africa" }, + { + "id": "/world/americas/", + "url": "/world/americas/", + "name": "Americas" + }, + { + "id": "/world/asia-pacific/", + "url": "/world/asia-pacific/", + "name": "Asia Pacific" + }, + { "id": "/world/china/", "url": "/world/china/", "name": "China" }, + { "id": "/world/europe/", "url": "/world/europe/", "name": "Europe" }, + { "id": "/world/india/", "url": "/world/india/", "name": "India" }, + { + "id": "/world/middle-east/", + "url": "/world/middle-east/", + "name": "Middle East" + }, + { "id": "/world/uk/", "url": "/world/uk/", "name": "United Kingdom" }, + { "id": "/world/us/", "url": "/world/us/", "name": "United States" }, + { + "id": "/world/the-great-reboot/", + "url": "/world/the-great-reboot/", + "name": "The Great Reboot" + }, + { + "id": "/world/reuters-next/", + "url": "/world/reuters-next/", + "name": "Reuters Next" + } + ] + }, + { + "id": "/business/", + "url": "/business/", + "name": "Business", + "type": "section", + "children": [ + { + "id": "/business/aerospace-defense/", + "url": "/business/aerospace-defense/", + "name": "Aerospace & Defense" + }, + { + "id": "/business/autos-transportation/", + "url": "/business/autos-transportation/", + "name": "Autos & Transportation" + }, + { + "id": "/business/energy/", + "url": "/business/energy/", + "name": "Energy" + }, + { + "id": "/business/environment/", + "url": "/business/environment/", + "name": "Environment" + }, + { + "id": "/business/finance/", + "url": "/business/finance/", + "name": "Finance" + }, + { + "id": "/business/healthcare-pharmaceuticals/", + "url": "/business/healthcare-pharmaceuticals/", + "name": "Healthcare & Pharmaceuticals" + }, + { + "id": "/business/media-telecom/", + "url": "/business/media-telecom/", + "name": "Media & Telecom" + }, + { + "id": "/business/retail-consumer/", + "url": "/business/retail-consumer/", + "name": "Retail & Consumer" + }, + { + "id": "/business/sustainable-business/", + "url": "/business/sustainable-business/", + "name": "Sustainable Business" + }, + { + "id": "/business/charged/", + "url": "/business/charged/", + "name": "Charged" + }, + { + "id": "/business/future-of-health/", + "url": "/business/future-of-health/", + "name": "Future of Health" + }, + { + "id": "/business/future-of-money/", + "url": "/business/future-of-money/", + "name": "Future of Money" + }, + { + "id": "/business/take-five/", + "url": "/business/take-five/", + "name": "Take Five" + }, + { + "id": "/business/reuters-impact/", + "url": "/business/reuters-impact/", + "name": "Reuters Impact" + }, + { + "id": "/business/davos/", + "url": "/business/davos/", + "name": "Davos" + } + ] + }, + { + "id": "/legal/", + "url": "/legal/", + "name": "Legal", + "type": "section", + "children": [ + { + "id": "/legal/government/", + "url": "/legal/government/", + "name": "Government" + }, + { + "id": "/legal/legalindustry/", + "url": "/legal/legalindustry/", + "name": "Legal Industry" + }, + { + "id": "/legal/litigation/", + "url": "/legal/litigation/", + "name": "Litigation" + }, + { + "id": "/legal/transactional/", + "url": "/legal/transactional/", + "name": "Transactional" + } + ] + }, + { + "id": "/markets/", + "url": "/markets/", + "name": "Markets", + "type": "section", + "children": [ + { + "id": "/markets/asia/", + "url": "/markets/asia/", + "name": "Asian Markets" + }, + { + "id": "/markets/carbon/", + "url": "/markets/carbon/", + "name": "Carbon Markets" + }, + { + "id": "/markets/commodities/", + "url": "/markets/commodities/", + "name": "Commodities" + }, + { + "id": "/markets/currencies/", + "url": "/markets/currencies/", + "name": "Currencies" + }, + { + "id": "/markets/deals/", + "url": "/markets/deals/", + "name": "Deals" + }, + { + "id": "/markets/emerging/", + "url": "/markets/emerging/", + "name": "Emerging Markets" + }, + { + "id": "/markets/europe/", + "url": "/markets/europe/", + "name": "European Markets" + }, + { + "id": "/markets/funds/", + "url": "/markets/funds/", + "name": "Funds" + }, + { + "id": "/markets/global-market-data/", + "url": "/markets/global-market-data/", + "name": "Global Market Data" + }, + { + "id": "/markets/rates-bonds/", + "url": "/markets/rates-bonds/", + "name": "Rates & Bonds" + }, + { + "id": "/markets/stocks/", + "url": "/markets/stocks/", + "name": "Stocks" + }, + { + "id": "/markets/us/", + "url": "/markets/us/", + "name": "U.S. Markets" + }, + { + "id": "/markets/wealth/", + "url": "/markets/wealth/", + "name": "Wealth" + }, + { + "id": "/markets/macromatters/", + "url": "/markets/macromatters/", + "name": "Macro Matters" + } + ] + }, + { + "id": "/breakingviews/", + "url": "/breakingviews/", + "name": "Breakingviews", + "type": "section" + }, + { + "id": "/technology/", + "url": "/technology/", + "name": "Technology", + "type": "section", + "children": [ + { + "id": "/technology/disrupted/", + "url": "/technology/disrupted/", + "name": "Disrupted" + }, + { + "id": "/technology/reuters-momentum/", + "url": "/technology/reuters-momentum/", + "name": "Reuters Momentum" + } + ] + }, + { + "id": "/investigates/", + "url": "https://www.reuters.com/investigates/", + "name": "Investigations", + "type": "link" + }, + { + "id": "/lifestyle/sports/", + "url": "/lifestyle/sports/", + "name": "Sports", + "type": "section", + "children": [ + { + "id": "/lifestyle/sports/athletics/", + "url": "/lifestyle/sports/athletics/", + "name": "Athletics" + }, + { + "id": "/lifestyle/sports/cricket/", + "url": "/lifestyle/sports/cricket/", + "name": "Cricket" + }, + { + "id": "/lifestyle/sports/cycling/", + "url": "/lifestyle/sports/cycling/", + "name": "Cycling" + }, + { + "id": "/lifestyle/sports/golf/", + "url": "/lifestyle/sports/golf/", + "name": "Golf" + }, + { + "id": "/lifestyle/sports/motor-sports/", + "url": "/lifestyle/sports/motor-sports/", + "name": "Motor Sports" + }, + { + "id": "/lifestyle/sports/soccer/", + "url": "/lifestyle/sports/soccer/", + "name": "Soccer" + }, + { + "id": "/lifestyle/sports/tennis/", + "url": "/lifestyle/sports/tennis/", + "name": "Tennis" + } + ] + }, + { + "id": "/lifestyle/", + "url": "/lifestyle/", + "name": "Lifestyle", + "type": "section", + "children": [ + { + "id": "/lifestyle/oddly-enough/", + "url": "/lifestyle/oddly-enough/", + "name": "Oddly Enough" + }, + { + "id": "/lifestyle/science/", + "url": "/lifestyle/science/", + "name": "Science" + } + ] + }, + { + "id": "/graphics/", + "url": "https://graphics.reuters.com/", + "name": "Graphics", + "type": "link" + }, + { + "id": "/pictures/", + "url": "/pictures/", + "name": "Pictures", + "type": "section" + }, + { "id": "/video/", "url": "/video/", "name": "Video", "type": "section" } + ], + "home_url": "/", + "search_url": "/site-search/", + "sign_in_url": "/signin/", + "sign_up_url": "/signup/", + "subscribe_url": "", + "my_account_url": "/myaccount/", + "my_view_url": "/myview/all", + "following_url": "/myview/following-feed", + "saved_url": "/myview/saved" + } +] diff --git a/src/components/SiteHeader/scss/_breakpoints.scss b/src/components/SiteHeader/scss/_breakpoints.scss new file mode 100644 index 0000000..57266b1 --- /dev/null +++ b/src/components/SiteHeader/scss/_breakpoints.scss @@ -0,0 +1,79 @@ +// From FSC: +// Our design layouts are based on 2 artboard sizes: +// 375px for small screens and 1440px for desktop. + +// However, these are snapshots to inform the core layouts. +// Some aspects of the design system adapt to breakpoints between these sizes. + +// Our design layouts therefore show the snapshots for mobile on the 375px artboard and wide on the 1440px artboard. + +// Note that if we refer only to desktop it means all layouts above 1023px, similarly mobile in general is below 1024px. + +// Note that the desktop breakpoint kicks-in at 1024px, which also accommodates iPad/tablet in landscape. +// This is the last size before the layout adapts to the mobile view. + +$maxWidth: 1440px; + +@mixin for-small-mobile { + @media (max-width: 518px) { + @content; + } +} + +@mixin for-mobile { + @media (max-width: 745px) { + @content; + } +} + +@mixin for-tablet { + @media (min-width: 746px) and (max-width: 1023px) { + @content; + } +} + +@mixin for-tablet-up { + @media (min-width: 746px) { + @content; + } +} + +@mixin for-tablet-down { + @media (max-width: 1023px) { + @content; + } +} + +@mixin for-tight-desktop { + @media (min-width: 1024px) and (max-width: 1060px) { + @content; + } +} + +@mixin for-desktop { + @media (min-width: 1024px) { + @content; + } +} + +@mixin for-wide-desktop { + @media (min-width: 1300px) { + @content; + } +} + +@mixin for-extra-wide-desktop { + @media (min-width: 1440px) { + @content; + } +} + +@mixin above-max { + @media (min-width: $maxWidth) { + @content; + } +} + +@mixin max-width { + max-width: $maxWidth; +} diff --git a/src/components/SiteHeader/scss/_colors.scss b/src/components/SiteHeader/scss/_colors.scss new file mode 100644 index 0000000..1f731a5 --- /dev/null +++ b/src/components/SiteHeader/scss/_colors.scss @@ -0,0 +1,25 @@ +// Sheet of project colors +$tr-orange: #fa6400; +$tr-dark-orange: #dc4300; +$tr-light-orange: #ffa100; +$tr-dark-grey: #404040; +$tr-medium-grey: #666666; +$tr-light-grey: #afafaf; +$tr-muted-grey: #d0d0d0; +$tr-hover-background-grey: #f8f8f8; +$tr-light-muted-grey: #f4f4f4; +$tr-ultra-light-grey: #fafafa; +$tr-dark-blue: #005da2; +$tr-light-blue: #0099c4; +$tr-muted-blue: #4386b9; +$tr-lighter-blue: #7facce; +$tr-superlight-blue: #e5eef5; +$tr-dark-purple: #621f95; +$tr-light-purple: #6e3ab7; +$tr-dark-red: #a00000; +$tr-light-red: #dc0a0a; +$tr-dark-green: #387c2b; +$tr-light-green: #77a22d; +$black: #000; +$white: #fff; +$ad-placeholder: #ffb1b1; diff --git a/src/components/SiteHeader/scss/_eases.scss b/src/components/SiteHeader/scss/_eases.scss new file mode 100644 index 0000000..ae36571 --- /dev/null +++ b/src/components/SiteHeader/scss/_eases.scss @@ -0,0 +1,9 @@ +$principleDefaultEase: cubic-bezier(0.25, 0.1, 0.25, 1); +$easeOutExpo: cubic-bezier(0.19, 1, 0.22, 1); +$easeOutCubic: cubic-bezier(0.215, 0.61, 0.355, 1); +$easeInOutCubic: cubic-bezier(0.645, 0.045, 0.355, 1); +$easeInOutQuad: cubic-bezier(0.455, 0.03, 0.515, 0.955); +$easeInQuad: cubic-bezier(0.55, 0.085, 0.68, 0.53); +$easeOutQuad: cubic-bezier(0.25, 0.46, 0.45, 0.94); +$navContainerEase: cubic-bezier(0.25, 0.1, 0.25, 1); +$rtvDefaultEase: cubic-bezier(0.165, 0.84, 0.44, 1); diff --git a/src/components/SiteHeader/scss/_grids.scss b/src/components/SiteHeader/scss/_grids.scss new file mode 100644 index 0000000..74ed066 --- /dev/null +++ b/src/components/SiteHeader/scss/_grids.scss @@ -0,0 +1,158 @@ +@use 'sass:math'; + +@use '_breakpoints.scss' as *; + +@mixin spacing-single($properties, $delta: 1) { + @each $property in $properties { + & { + #{$property}: (math.div(32, 1440) * 100vw * $delta); + } + } + + @include for-tablet-down { + @each $property in $properties { + & { + #{$property}: (math.div(16, 375) * 100vw * $delta); + } + } + } + + @include above-max { + @each $property in $properties { + & { + #{$property}: (32px * $delta); + } + } + } +} + +@mixin spacing-single-half($properties, $delta: 1) { + @each $property in $properties { + & { + #{$property}: (calc(16 / 1440) * 100vw * $delta); + } + } + + @include for-tablet-down { + @each $property in $properties { + & { + #{$property}: (math.div(8, 375) * 100vw * $delta); + } + } + } + + @include above-max { + @each $property in $properties { + & { + #{$property}: (16px * $delta); + } + } + } +} + +@mixin spacing-single-34($properties, $delta: 1) { + @each $property in $properties { + & { + #{$property}: (math.div(24, 1440) * 100vw * $delta); + } + } + + @include for-tablet-down { + @each $property in $properties { + & { + #{$property}: (math.div(12, 375) * 100vw * $delta); + } + } + } + + @include above-max { + @each $property in $properties { + & { + #{$property}: (24px * $delta); + } + } + } +} + +@mixin spacing-150($properties, $delta: 1) { + @each $property in $properties { + & { + #{$property}: (math.div(48, 1440) * 100vw * $delta); + } + } + + @include for-tablet-down { + @each $property in $properties { + & { + #{$property}: (math.div(24, 375) * 100vw * $delta); + } + } + } + + @include above-max { + @each $property in $properties { + & { + #{$property}: (48px * $delta); + } + } + } +} + +@mixin spacing-75($properties, $delta: 1) { + @each $property in $properties { + & { + #{$property}: (math.div(24, 1440) * 100vw * $delta); + } + } + + @include above-max { + @each $property in $properties { + & { + #{$property}: (24px * $delta); + } + } + } +} + +@mixin spacing-50($properties, $delta: 1) { + @each $property in $properties { + & { + #{$property}: (math.div(16, 1440) * 100vw * $delta); + } + } + + @include above-max { + @each $property in $properties { + & { + #{$property}: (16px * $delta); + } + } + } +} + +@mixin responsive-columns($columns, $size: 1fr) { + @content; + + display: grid; + grid-template-columns: repeat($columns, $size); + + // Desktop grid (all screens greater than or equal to 1024px) + @include spacing-single(grid-column-gap); + + // Mobile grid (all screens up to and including 1023px) + @include for-tablet-down { + grid-template-columns: repeat(4, $size); + } +} + +@mixin at-4-columns { + @include for-tablet-down { + @content; + } +} + +@mixin above-4-columns { + @include for-desktop { + @content; + } +} diff --git a/src/components/SiteHeader/scss/_z-indexes.scss b/src/components/SiteHeader/scss/_z-indexes.scss new file mode 100644 index 0000000..c3bb9cb --- /dev/null +++ b/src/components/SiteHeader/scss/_z-indexes.scss @@ -0,0 +1,15 @@ +/* +Several components utilize z-index, the CSS property that helps control layout by providing a third axis to arrange content. We utilize a default z-index scale that’s been designed to properly layer navigation, tooltips and popovers, modals, and more. +These higher values start at an arbitrary number, high and specific enough to ideally avoid conflicts. We need a standard set of these across our layered components—tooltips, popovers, navbars, dropdowns, modals—so we can be reasonably consistent in the behaviors. +To handle overlapping borders within components (e.g., buttons and inputs in input groups), we use low single digit z-index values of 1, 2, and 3 for default, hover, and active states. On hover/focus/active, we bring a particular element to the forefront with a higher z-index value to show their border over the sibling elements. +*/ + +$zindex-dropdown: 1000; +$zindex-sticky: 1020; +$zindex-fixed: 1030; +$zindex-modal-backdrop: 1040; +$zindex-offcanvas: 1050; +$zindex-modal: 1060; +$zindex-popover: 1070; +$zindex-tooltip: 1080; +$zindex-close-button: 1090; diff --git a/src/components/SiteHeader/svgs/Close.svelte b/src/components/SiteHeader/svgs/Close.svelte new file mode 100644 index 0000000..0a6d359 --- /dev/null +++ b/src/components/SiteHeader/svgs/Close.svelte @@ -0,0 +1,18 @@ + + + diff --git a/src/components/SiteHeader/svgs/Menu.svelte b/src/components/SiteHeader/svgs/Menu.svelte new file mode 100644 index 0000000..924657c --- /dev/null +++ b/src/components/SiteHeader/svgs/Menu.svelte @@ -0,0 +1,26 @@ + + + + + diff --git a/src/components/SiteHeadline/SiteHeadline.mdx b/src/components/SiteHeadline/SiteHeadline.mdx new file mode 100644 index 0000000..da70943 --- /dev/null +++ b/src/components/SiteHeadline/SiteHeadline.mdx @@ -0,0 +1,69 @@ +import { Meta, Canvas } from '@storybook/blocks'; + +import * as SiteHeadlineStories from './SiteHeadline.stories.svelte'; + + + +# SiteHeadline + +The `SiteHeadline` component creates a simplified Reuters Graphics headline, loosely modelled off the Reuters.com styles. + +Styles for this headline are intentionally restricted. It is meant to serve as a unifying style for quick-turnaround breaking news pages. + +```svelte + + + +``` + + + +## Using with ArchieML docs + +With the graphics kit, you'll likely get your text value from an ArchieML doc... + +```yaml +# ArchieML doc +section: Global News # Optional +sectionUrl: https://www.reuters.com/graphics/ # Optional +hed: A beautiful page +[authors] +* Samuel Granados +* Dea Bankova +[] +published: 2022-09-12T08:30:00.000Z +updated: +``` + +... which you'll pass to the `SiteHeadline` component. + +```svelte + + + + +``` + + diff --git a/src/components/SiteHeadline/SiteHeadline.stories.svelte b/src/components/SiteHeadline/SiteHeadline.stories.svelte new file mode 100644 index 0000000..0988b41 --- /dev/null +++ b/src/components/SiteHeadline/SiteHeadline.stories.svelte @@ -0,0 +1,49 @@ + + + + + + + + + diff --git a/src/components/SiteHeadline/SiteHeadline.svelte b/src/components/SiteHeadline/SiteHeadline.svelte new file mode 100644 index 0000000..f1d5cc1 --- /dev/null +++ b/src/components/SiteHeadline/SiteHeadline.svelte @@ -0,0 +1,86 @@ + + + + +
+
+ {#if section} +

+ {#if sectionUrl} + {section} + {:else} + {section} + {/if} +

+ {/if} + {#if hed} +

+ {hed} +

+ {/if} +
+ +
+
diff --git a/src/components/Table/Table.svelte b/src/components/Table/Table.svelte index 0cd90bb..c5b534f 100644 --- a/src/components/Table/Table.svelte +++ b/src/components/Table/Table.svelte @@ -292,7 +292,7 @@ diff --git a/src/components/Theme/Theme.svelte b/src/components/Theme/Theme.svelte new file mode 100644 index 0000000..dbea6cb --- /dev/null +++ b/src/components/Theme/Theme.svelte @@ -0,0 +1,55 @@ + + + + + +
+ +
+ + {@render children()} +
+
diff --git a/src/components/Theme/demo/ThemedPage.svelte b/src/components/Theme/demo/ThemedPage.svelte new file mode 100644 index 0000000..57c3236 --- /dev/null +++ b/src/components/Theme/demo/ThemedPage.svelte @@ -0,0 +1,36 @@ + + +
+ + + +
+
+ +
+ + diff --git a/src/components/Theme/themes/common.js b/src/components/Theme/themes/common.js new file mode 100644 index 0000000..65af9e2 --- /dev/null +++ b/src/components/Theme/themes/common.js @@ -0,0 +1,32 @@ +/** @type {Omit} */ +/* Generated from +https://www.fluid-type-scale.com/calculate?minFontSize=18&minWidth=320&minRatio=1.125&maxFontSize=21&maxWidth=1280&maxRatio=1.25&steps=xxs%2Cxs%2Csm%2Cbase%2Clg%2Cxl%2C2xl%2C3xl%2C4xl%2C5xl%2C6xl&baseStep=base&prefix=&decimals=2&useRems=on&remValue=16&previewFont=Noto+Sans&previewText=Almost+before+we+knew+it%2C+we+had+left+the+ground&previewWidth=0 +*/ + +export default { + font: { + family: { + serif: '"Newsreader Text", serif', + 'sans-serif': 'Knowledge, sans-serif', + monospace: + '"Droid Sans Mono", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace', + hed: 'var(--theme-font-family-sans-serif)', + subhed: 'var(--theme-font-family-sans-serif)', + body: 'var(--theme-font-family-serif)', + note: 'var(--theme-font-family-sans-serif)', + }, + size: { + xxs: 'clamp(0.79rem, -0.2vw + 0.83rem, 0.67rem)', + xs: 'clamp(0.89rem, -0.08vw + 0.91rem, 0.84rem)', + sm: 'clamp(1rem, 0.08vw + 0.98rem, 1.05rem)', + base: 'clamp(1.13rem, 0.31vw + 1.06rem, 1.31rem)', + lg: 'clamp(1.27rem, 0.63vw + 1.14rem, 1.64rem)', + xl: 'clamp(1.42rem, 1.04vw + 1.21rem, 2.05rem)', + '2xl': 'clamp(1.6rem, 1.6vw + 1.28rem, 2.56rem)', + '3xl': 'clamp(1.8rem, 2.34vw + 1.33rem, 3.2rem)', + '4xl': 'clamp(2.03rem, 3.3vw + 1.37rem, 4.01rem)', + '5xl': 'clamp(2.28rem, 4.54vw + 1.37rem, 5.01rem)', + '6xl': 'clamp(2.57rem, 6.15vw + 1.33rem, 6.26rem)', + }, + }, +}; diff --git a/src/components/Theme/themes/dark.js b/src/components/Theme/themes/dark.js new file mode 100644 index 0000000..f5d6a33 --- /dev/null +++ b/src/components/Theme/themes/dark.js @@ -0,0 +1,15 @@ +import common from './common.js'; + +/** @type {import('../@types/component').ThemeConfig} */ +export default { + ...common, + colour: { + background: '#2e3440', + 'text-primary': '#ffffff', + 'text-secondary': 'rgb(255 255 255 / 60%)', + accent: '#ef3c2a', + 'brand-logo': '#ffffff', + 'brand-rules': 'rgb(255 255 255 / 25%)', + 'brand-shadow': 'rgb(255 255 255 / 10%)', + }, +}; diff --git a/src/components/Theme/themes/light.js b/src/components/Theme/themes/light.js new file mode 100644 index 0000000..5749c7b --- /dev/null +++ b/src/components/Theme/themes/light.js @@ -0,0 +1,15 @@ +import common from './common.js'; + +/** @type {import('../@types/component').ThemeConfig} */ +export default { + ...common, + colour: { + background: '#ffffff', + 'text-primary': '#404040', + 'text-secondary': '#666666', + accent: ' #d64000', + 'brand-logo': '#d64000', + 'brand-rules': '#d0d0d0', + 'brand-shadow': 'rgba(64, 64, 64, .08)', + }, +}; diff --git a/src/components/Theme/utils/flatten.ts b/src/components/Theme/utils/flatten.ts new file mode 100644 index 0000000..ae84fcf --- /dev/null +++ b/src/components/Theme/utils/flatten.ts @@ -0,0 +1,46 @@ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function isBuffer(obj: any): boolean { + return ( + obj && + obj.constructor && + typeof obj.constructor.isBuffer === 'function' && + obj.constructor.isBuffer(obj) + ); +} + +const transformKey = (key: string): string => key.replace(/[^a-z0-9-]/gi, ''); + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export default function flatten>(target: T) { + const delimiter = '-'; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const output: Record = {}; + + function step( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + object: Record, + prev?: string, + currentDepth = 1 + ): void { + Object.keys(object).forEach(function (key) { + const value = object[key]; + const isArray = Array.isArray(value); + const type = Object.prototype.toString.call(value); + const isbuffer = isBuffer(value); + const isObject = type === '[object Object]' || type === '[object Array]'; + + const newKey = + prev ? prev + delimiter + transformKey(key) : transformKey(key); + + if (!isArray && !isbuffer && isObject && Object.keys(value).length) { + return step(value, newKey, currentDepth + 1); + } + + output[newKey] = value; + }); + } + + step(target); + + return output; +} diff --git a/src/components/Theme/utils/merge.ts b/src/components/Theme/utils/merge.ts new file mode 100644 index 0000000..7a90c23 --- /dev/null +++ b/src/components/Theme/utils/merge.ts @@ -0,0 +1,31 @@ +import type { ThemeConfig, CustomThemeConfig } from '../@types/component'; + +function isObject(item: unknown): item is Record { + return item !== null && typeof item === 'object' && !Array.isArray(item); +} + +/** + * Deep merges theme objects. + */ +export default function merge>( + target: T, + ...sources: (ThemeConfig | CustomThemeConfig)[] +): T { + if (!sources.length) return target; + const source = sources.shift(); + + if (isObject(target) && isObject(source)) { + for (const key in source) { + if (isObject(source[key])) { + if (!target[key]) Object.assign(target, { [key]: {} }); + merge( + target[key] as Record, + source[key] as Record + ); + } else { + Object.assign(target, { [key]: source[key] }); + } + } + } + return merge(target, ...sources); +} diff --git a/src/components/ToolsHeader/ToolsHeader.mdx b/src/components/ToolsHeader/ToolsHeader.mdx new file mode 100644 index 0000000..1247302 --- /dev/null +++ b/src/components/ToolsHeader/ToolsHeader.mdx @@ -0,0 +1,23 @@ +import { Meta, Canvas } from '@storybook/blocks'; + +import * as ToolsHeaderStories from './ToolsHeader.stories.svelte'; + + + +# ToolsHeader + +The `ToolsHeader` component adds a header with the Reuters Graphics logo for internal tooling sites. + +> **Note:** Don't use this component for public pages. + +```svelte + + + + + +``` + + diff --git a/src/components/ToolsHeader/ToolsHeader.stories.svelte b/src/components/ToolsHeader/ToolsHeader.stories.svelte new file mode 100644 index 0000000..1c46b89 --- /dev/null +++ b/src/components/ToolsHeader/ToolsHeader.stories.svelte @@ -0,0 +1,22 @@ + + + +
+ +
+
+ + diff --git a/src/components/ToolsHeader/ToolsHeader.svelte b/src/components/ToolsHeader/ToolsHeader.svelte new file mode 100644 index 0000000..2d68b00 --- /dev/null +++ b/src/components/ToolsHeader/ToolsHeader.svelte @@ -0,0 +1,96 @@ + + + +
+
+ + + +
+ + + {#if children} + + {/if} +
+ + diff --git a/src/components/Video/Video.stories.svelte b/src/components/Video/Video.stories.svelte index 90705f4..2ce5f2a 100644 --- a/src/components/Video/Video.stories.svelte +++ b/src/components/Video/Video.stories.svelte @@ -88,7 +88,7 @@
+ diff --git a/src/lib/Parse.svelte b/src/lib/Parse.svelte new file mode 100644 index 0000000..f668bc1 --- /dev/null +++ b/src/lib/Parse.svelte @@ -0,0 +1,153 @@ + + + + +
+ + + + + {#each content.blocks as block} + + {#if block.type === 'text'} + + + + + + + {:else if block.type === 'photo'} + + + + + {:else if block.type === 'ai-graphic'} + {#if !aiCharts[block.chart]} + + {:else} + {@const AiChart = aiCharts[block.chart]} + + + + {/if} + + + {:else if block.type === 'ai-scroller'} + ({ + background: aiCharts[step.background], + /* backgroundProps: { assetsPath: assets || '/' }, */ + foreground: foregrounds[step.foreground] ?? step.foreground, + altText: step.altText, + }))} + /> + + + {:else} + + {/if} + {/each} + + +
diff --git a/src/lib/ai2svelte/ai-chart.svelte b/src/lib/ai2svelte/ai-chart.svelte new file mode 100644 index 0000000..e3d4c4b --- /dev/null +++ b/src/lib/ai2svelte/ai-chart.svelte @@ -0,0 +1,98 @@ + + + + +
+ + {#if width && width >= 0 && width < 510} +
+
+
+
+ {/if} + + {#if width && width >= 510 && width < 660} +
+
+
+
+ {/if} + + {#if width && width >= 660} +
+
+
+
+ {/if} +
+ + + + + + + diff --git a/src/pages/Squash.svelte b/src/pages/Squash.svelte index 531d939..96d905b 100644 --- a/src/pages/Squash.svelte +++ b/src/pages/Squash.svelte @@ -1,6 +1,13 @@ + +{#each steps as step, i} + {#if showStep(i)} +
+ +
+ {/if} +{/each} + + diff --git a/src/pages/stories/one/Foreground.svelte b/src/pages/stories/one/Foreground.svelte new file mode 100644 index 0000000..ff56476 --- /dev/null +++ b/src/pages/stories/one/Foreground.svelte @@ -0,0 +1,64 @@ + + +{#each steps as step, i} +
+ {#if step.foreground === '' || !step.foreground} + +
+ {#if typeof step.altText === 'string'} +
+ +
+ {/if} + {:else} +
+ {#if typeof step.foreground === 'string'} + + {:else} + + {/if} +
+ {#if typeof step.altText === 'string'} +
+ +
+ {/if} + {/if} +
+{/each} + + diff --git a/src/pages/stories/one/Photo-4439.jpg b/src/pages/stories/one/Photo-4439.jpg new file mode 100755 index 0000000..a1ab7de Binary files /dev/null and b/src/pages/stories/one/Photo-4439.jpg differ diff --git a/src/pages/stories/one/Step01.svelte b/src/pages/stories/one/Step01.svelte new file mode 100644 index 0000000..d914ec8 --- /dev/null +++ b/src/pages/stories/one/Step01.svelte @@ -0,0 +1,18 @@ + + +
+ + diff --git a/src/pages/stories/one/Step02.svelte b/src/pages/stories/one/Step02.svelte new file mode 100644 index 0000000..3ad3dc9 --- /dev/null +++ b/src/pages/stories/one/Step02.svelte @@ -0,0 +1,18 @@ + + +
Two
+ + diff --git a/src/pages/stories/one/ai-foreground.svelte b/src/pages/stories/one/ai-foreground.svelte new file mode 100644 index 0000000..6af40a5 --- /dev/null +++ b/src/pages/stories/one/ai-foreground.svelte @@ -0,0 +1,112 @@ + + +
+ + {#if width && width >= 0} +
+
+
+
+

Likelihood of something happening

+
+ +
+

0-25%

+
+
+

50-75%

+
+
+

75-100%

+
+
+

25-50%

+
+
+ {/if} +
+ + + + + + diff --git a/src/pages/stories/one/ai-scroller-1.svelte b/src/pages/stories/one/ai-scroller-1.svelte new file mode 100644 index 0000000..df60d00 --- /dev/null +++ b/src/pages/stories/one/ai-scroller-1.svelte @@ -0,0 +1,121 @@ + + + + +
+ + {#if width && width >= 1200} +
+
+
+
+ {/if} + + {#if width && width >= 930 && width < 1200} +
+
+
+
+ {/if} + + {#if width && width >= 660 && width < 930} +
+
+
+
+ {/if} + + {#if width && width >= 510 && width < 660} +
+
+
+
+ {/if} + + {#if width && width >= 0 && width < 510} +
+
+
+
+ {/if} +
+ + + + + + + diff --git a/src/pages/stories/one/ai-scroller-2.svelte b/src/pages/stories/one/ai-scroller-2.svelte new file mode 100644 index 0000000..18a5870 --- /dev/null +++ b/src/pages/stories/one/ai-scroller-2.svelte @@ -0,0 +1,310 @@ + + + + +
+ + {#if width && width >= 1200} +
+
+
+
+

This thing here is

+

particularly important

+
+
+ {/if} + + {#if width && width >= 930 && width < 1200} +
+
+
+
+

This thing here is

+

particularly important

+
+
+ {/if} + + {#if width && width >= 660 && width < 930} +
+
+
+
+

This thing here is

+

particularly important

+
+
+ {/if} + + {#if width && width >= 510 && width < 660} +
+
+
+
+

This thing here is

+

particularly important

+
+
+ {/if} + + {#if width && width >= 0 && width < 510} +
+
+
+
+

This thing here is

+

particularly important

+
+
+ {/if} +
+ + + + + + diff --git a/src/pages/stories/one/ai-scroller-3.svelte b/src/pages/stories/one/ai-scroller-3.svelte new file mode 100644 index 0000000..33538e0 --- /dev/null +++ b/src/pages/stories/one/ai-scroller-3.svelte @@ -0,0 +1,310 @@ + + + + +
+ + {#if width && width >= 1200} +
+
+
+
+

Something

+

happened here

+
+
+ {/if} + + {#if width && width >= 930 && width < 1200} +
+
+
+
+

Something

+

happened here

+
+
+ {/if} + + {#if width && width >= 660 && width < 930} +
+
+
+
+

Something

+

happened here

+
+
+ {/if} + + {#if width && width >= 510 && width < 660} +
+
+
+
+

Something

+

happened here

+
+
+ {/if} + + {#if width && width >= 0 && width < 510} +
+
+
+
+

Something

+

happened here

+
+
+ {/if} +
+ + + + + + diff --git a/src/pages/stories/one/images/Body-issues-key-xs.png b/src/pages/stories/one/images/Body-issues-key-xs.png new file mode 100644 index 0000000..e03e5e1 Binary files /dev/null and b/src/pages/stories/one/images/Body-issues-key-xs.png differ diff --git a/src/pages/stories/one/images/step-1-lg.png b/src/pages/stories/one/images/step-1-lg.png new file mode 100644 index 0000000..de1888e Binary files /dev/null and b/src/pages/stories/one/images/step-1-lg.png differ diff --git a/src/pages/stories/one/images/step-1-md.png b/src/pages/stories/one/images/step-1-md.png new file mode 100644 index 0000000..ead091c Binary files /dev/null and b/src/pages/stories/one/images/step-1-md.png differ diff --git a/src/pages/stories/one/images/step-1-sm.png b/src/pages/stories/one/images/step-1-sm.png new file mode 100644 index 0000000..dada63c Binary files /dev/null and b/src/pages/stories/one/images/step-1-sm.png differ diff --git a/src/pages/stories/one/images/step-1-xl.png b/src/pages/stories/one/images/step-1-xl.png new file mode 100644 index 0000000..55823fb Binary files /dev/null and b/src/pages/stories/one/images/step-1-xl.png differ diff --git a/src/pages/stories/one/images/step-1-xs.png b/src/pages/stories/one/images/step-1-xs.png new file mode 100644 index 0000000..26e4449 Binary files /dev/null and b/src/pages/stories/one/images/step-1-xs.png differ diff --git a/src/pages/stories/one/images/step-2-lg.png b/src/pages/stories/one/images/step-2-lg.png new file mode 100644 index 0000000..9619643 Binary files /dev/null and b/src/pages/stories/one/images/step-2-lg.png differ diff --git a/src/pages/stories/one/images/step-2-md.png b/src/pages/stories/one/images/step-2-md.png new file mode 100644 index 0000000..37450dc Binary files /dev/null and b/src/pages/stories/one/images/step-2-md.png differ diff --git a/src/pages/stories/one/images/step-2-sm.png b/src/pages/stories/one/images/step-2-sm.png new file mode 100644 index 0000000..e236862 Binary files /dev/null and b/src/pages/stories/one/images/step-2-sm.png differ diff --git a/src/pages/stories/one/images/step-2-xl.png b/src/pages/stories/one/images/step-2-xl.png new file mode 100644 index 0000000..fa15744 Binary files /dev/null and b/src/pages/stories/one/images/step-2-xl.png differ diff --git a/src/pages/stories/one/images/step-2-xs.png b/src/pages/stories/one/images/step-2-xs.png new file mode 100644 index 0000000..0fef62a Binary files /dev/null and b/src/pages/stories/one/images/step-2-xs.png differ diff --git a/src/pages/stories/one/images/step-3-lg.png b/src/pages/stories/one/images/step-3-lg.png new file mode 100644 index 0000000..9e4f037 Binary files /dev/null and b/src/pages/stories/one/images/step-3-lg.png differ diff --git a/src/pages/stories/one/images/step-3-md.png b/src/pages/stories/one/images/step-3-md.png new file mode 100644 index 0000000..91cfa1a Binary files /dev/null and b/src/pages/stories/one/images/step-3-md.png differ diff --git a/src/pages/stories/one/images/step-3-sm.png b/src/pages/stories/one/images/step-3-sm.png new file mode 100644 index 0000000..876a388 Binary files /dev/null and b/src/pages/stories/one/images/step-3-sm.png differ diff --git a/src/pages/stories/one/images/step-3-xl.png b/src/pages/stories/one/images/step-3-xl.png new file mode 100644 index 0000000..03e47dd Binary files /dev/null and b/src/pages/stories/one/images/step-3-xl.png differ diff --git a/src/pages/stories/one/images/step-3-xs.png b/src/pages/stories/one/images/step-3-xs.png new file mode 100644 index 0000000..26223ca Binary files /dev/null and b/src/pages/stories/one/images/step-3-xs.png differ diff --git a/src/pages/stories/one/index.astro b/src/pages/stories/one/index.astro new file mode 100644 index 0000000..57ef147 --- /dev/null +++ b/src/pages/stories/one/index.astro @@ -0,0 +1,30 @@ +--- +import Layout from '../../../layouts/Layout.astro'; + +import Parse from '@lib/Parse.svelte'; +import { parse } from '@rferl/veronica'; +import ArchieML from './story.aml?raw'; + + +const content = parse(ArchieML); + +// ai2svelte backgrounds +import AiMap1 from './ai-scroller-1.svelte'; +import AiMap2 from './ai-scroller-2.svelte'; +import AiMap3 from './ai-scroller-3.svelte'; + +//photos +import { getImage } from "astro:assets"; +import photoOne from './Photo-4439.jpg'; +const photos = { 'Photo-4439.jpg': photoOne.src }; + +// ai2svelte foreground +import AiForeground from './ai-foreground.svelte'; + +const aiCharts = { AiMap1, AiMap2, AiMap3 }; +const foregrounds = { Foreground1: AiForeground }; +--- + + + + \ No newline at end of file diff --git a/src/pages/stories/one/one.json b/src/pages/stories/one/one.json new file mode 100644 index 0000000..8a3e703 --- /dev/null +++ b/src/pages/stories/one/one.json @@ -0,0 +1,75 @@ +{ + "metadata": { + "id": "cltmvzj5m0000lc089jz22aet", + "name": "graphics-kit-page", + "storyboard": { + "id": "cltmvxt5q0000l908irus4rdd", + "name": "🔒 TEMPLATES" + }, + "lastSynced": "2025-03-26T17:16:03.466Z", + "liveEditing": { + "preview": { + "aml": "https://graphics.thomsonreuters.com/apps/graphics-tools/prod/stories/cltmvzj5m0000lc089jz22aet/preview/story.aml", + "json": "https://graphics.thomsonreuters.com/apps/graphics-tools/prod/stories/cltmvzj5m0000lc089jz22aet/preview/story.json", + "enabled": true + } + } + }, + "story": { + "slug": "ROOT-SLUG/WILD", + "seoTitle": "Page title for search", + "seoDescription": "Page description for search", + "shareTitle": "Page title for social media", + "shareDescription": "Page description for social media", + "shareImgPath": "images/reuters-graphics.jpg", + "shareImgAlt": "Alt text for share image.", + "hed": "A Reuters Graphics page", + "section": "Graphics", + "sectionUrl": "https://www.reuters.com/graphics/", + "authors": ["Jane Doe", "John Doe"], + "publishTime": "2024-04-17T17:00:00.000Z", + "updateTime": "", + "blocks": [ + { + "type": "text", + "text": "Pig short ribs jerky, meatloaf turducken ribeye strip steak bacon pastrami tail pancetta chicken. Turkey landjaeger kevin turducken kielbasa pork loin, filet mignon leberkas meatball cow tenderloin. T-bone meatloaf kielbasa pancetta filet mignon doner. \r\n\r\nPig short ribs jerky, meatloaf turducken ribeye strip steak bacon pastrami tail pancetta chicken. Shankle pork loin burgdoggen, prosciutto beef ribs turducken ball tip.\r\n\r\nBoudin alcatra kevin, jerky swine brisket pastrami tail pork chop drumstick tongue." + }, + { + "type": "ai-graphic", + "chart": "AiMap", + "width": "normal", + "textWidth": "normal", + "title": "Optional title of the graphic", + "description": "Optional chatter describes more about the graphic.", + "notes": "Note: Optional note clarifying something in the data.\r\n\r\nSource: Optional source of the data.", + "altText": "Add a description of the graphic for screen readers. This is invisible on the page." + }, + { + "type": "text", + "text": "Quis ea deserunt tempor. Incididunt ipsum culpa laboris. Aliqua culpa excepteur magna. Fugiat aliqua commodo ea ut dolor. Irure deserunt qui commodo sunt elit deserunt culpa. Amet occaecat ipsum cupidatat proident incididunt officia non ea.\r\n\r\nId incididunt laboris cillum eu. Ullamco ipsum mollit ipsum consequat laboris. Pariatur incididunt sit aute nulla enim excepteur ut." + }, + { + "type": "inline-ad", + "n": "1" + }, + { + "type": "text", + "text": "Dolor qui dolore nulla pariatur sunt fugiat. Fugiat amet voluptate consequat. Dolore consequat proident commodo. Exercitation occaecat deserunt occaecat ea. Reprehenderit incididunt ad quis proident laboris et dolore.\r\n\r\nSint velit elit do sunt ad voluptate nulla dolore. Deserunt aliquip mollit reprehenderit nulla do ad quis ullamco. Deserunt aliquip aute qui eiusmod aliquip ullamco nostrud incididunt." + } + ], + "endNotes": [ + { + "title": "Note", + "text": "Data is current as of today" + }, + { + "title": "Sources", + "text": "Data, Inc." + }, + { + "title": "Edited by", + "text": "Editor, Copyeditor" + } + ] + } +} diff --git a/src/pages/stories/one/story.aml b/src/pages/stories/one/story.aml new file mode 100644 index 0000000..48b89d1 --- /dev/null +++ b/src/pages/stories/one/story.aml @@ -0,0 +1,38 @@ +# ArchieML doc +[blocks] + +type: photo +width: normal +src: Photo-4439.jpg +altText: Boom +caption: Peerless + +type: ai-scroller +id: my-map-scroller +foregroundPosition: left +stackBackground: true + +# Array of step objects + [.steps] + background: AiMap1 + # You can still use a markdown string even if other step/s use a custom foreground component + foreground: #### Step 1 + + Here's where something happend. + :end + altText: A map showing the Upper West side in New York City. + :end + + background: AiMap2 + foreground: Foreground1 # The name of your custom foreground component + altText: The same map now highlights 98th Street. + :end + background: AiMap3 + foreground: #### Step 3 + + ... and now there are multiple protests. + :end + altText: The same map now highlights three locations near 98th Street where something particulary important happened. + :end + [] +[] \ No newline at end of file diff --git a/src/scss/_mixins.scss b/src/styles/_mixins.scss similarity index 100% rename from src/scss/_mixins.scss rename to src/styles/_mixins.scss diff --git a/src/scss/colours/_main.scss b/src/styles/colours/_main.scss similarity index 100% rename from src/scss/colours/_main.scss rename to src/styles/colours/_main.scss diff --git a/src/scss/colours/primary/_amber.scss b/src/styles/colours/primary/_amber.scss similarity index 100% rename from src/scss/colours/primary/_amber.scss rename to src/styles/colours/primary/_amber.scss diff --git a/src/scss/colours/primary/_blue.scss b/src/styles/colours/primary/_blue.scss similarity index 100% rename from src/scss/colours/primary/_blue.scss rename to src/styles/colours/primary/_blue.scss diff --git a/src/scss/colours/primary/_cyan.scss b/src/styles/colours/primary/_cyan.scss similarity index 100% rename from src/scss/colours/primary/_cyan.scss rename to src/styles/colours/primary/_cyan.scss diff --git a/src/scss/colours/primary/_emerald.scss b/src/styles/colours/primary/_emerald.scss similarity index 100% rename from src/scss/colours/primary/_emerald.scss rename to src/styles/colours/primary/_emerald.scss diff --git a/src/scss/colours/primary/_fuchsia.scss b/src/styles/colours/primary/_fuchsia.scss similarity index 100% rename from src/scss/colours/primary/_fuchsia.scss rename to src/styles/colours/primary/_fuchsia.scss diff --git a/src/scss/colours/primary/_green.scss b/src/styles/colours/primary/_green.scss similarity index 100% rename from src/scss/colours/primary/_green.scss rename to src/styles/colours/primary/_green.scss diff --git a/src/scss/colours/primary/_grey.scss b/src/styles/colours/primary/_grey.scss similarity index 100% rename from src/scss/colours/primary/_grey.scss rename to src/styles/colours/primary/_grey.scss diff --git a/src/scss/colours/primary/_indigo.scss b/src/styles/colours/primary/_indigo.scss similarity index 100% rename from src/scss/colours/primary/_indigo.scss rename to src/styles/colours/primary/_indigo.scss diff --git a/src/scss/colours/primary/_lime.scss b/src/styles/colours/primary/_lime.scss similarity index 100% rename from src/scss/colours/primary/_lime.scss rename to src/styles/colours/primary/_lime.scss diff --git a/src/scss/colours/primary/_neutral.scss b/src/styles/colours/primary/_neutral.scss similarity index 100% rename from src/scss/colours/primary/_neutral.scss rename to src/styles/colours/primary/_neutral.scss diff --git a/src/scss/colours/primary/_orange.scss b/src/styles/colours/primary/_orange.scss similarity index 100% rename from src/scss/colours/primary/_orange.scss rename to src/styles/colours/primary/_orange.scss diff --git a/src/scss/colours/primary/_pink.scss b/src/styles/colours/primary/_pink.scss similarity index 100% rename from src/scss/colours/primary/_pink.scss rename to src/styles/colours/primary/_pink.scss diff --git a/src/scss/colours/primary/_purple.scss b/src/styles/colours/primary/_purple.scss similarity index 100% rename from src/scss/colours/primary/_purple.scss rename to src/styles/colours/primary/_purple.scss diff --git a/src/scss/colours/primary/_red.scss b/src/styles/colours/primary/_red.scss similarity index 100% rename from src/scss/colours/primary/_red.scss rename to src/styles/colours/primary/_red.scss diff --git a/src/scss/colours/primary/_rose.scss b/src/styles/colours/primary/_rose.scss similarity index 100% rename from src/scss/colours/primary/_rose.scss rename to src/styles/colours/primary/_rose.scss diff --git a/src/scss/colours/primary/_sky.scss b/src/styles/colours/primary/_sky.scss similarity index 100% rename from src/scss/colours/primary/_sky.scss rename to src/styles/colours/primary/_sky.scss diff --git a/src/scss/colours/primary/_slate.scss b/src/styles/colours/primary/_slate.scss similarity index 100% rename from src/scss/colours/primary/_slate.scss rename to src/styles/colours/primary/_slate.scss diff --git a/src/scss/colours/primary/_stone.scss b/src/styles/colours/primary/_stone.scss similarity index 100% rename from src/scss/colours/primary/_stone.scss rename to src/styles/colours/primary/_stone.scss diff --git a/src/scss/colours/primary/_teal.scss b/src/styles/colours/primary/_teal.scss similarity index 100% rename from src/scss/colours/primary/_teal.scss rename to src/styles/colours/primary/_teal.scss diff --git a/src/scss/colours/primary/_violet.scss b/src/styles/colours/primary/_violet.scss similarity index 100% rename from src/scss/colours/primary/_violet.scss rename to src/styles/colours/primary/_violet.scss diff --git a/src/scss/colours/primary/_yellow.scss b/src/styles/colours/primary/_yellow.scss similarity index 100% rename from src/scss/colours/primary/_yellow.scss rename to src/styles/colours/primary/_yellow.scss diff --git a/src/scss/colours/primary/_zinc.scss b/src/styles/colours/primary/_zinc.scss similarity index 100% rename from src/scss/colours/primary/_zinc.scss rename to src/styles/colours/primary/_zinc.scss diff --git a/src/scss/colours/thematic/_nord.scss b/src/styles/colours/thematic/_nord.scss similarity index 100% rename from src/scss/colours/thematic/_nord.scss rename to src/styles/colours/thematic/_nord.scss diff --git a/src/scss/colours/thematic/_tr.scss b/src/styles/colours/thematic/_tr.scss similarity index 100% rename from src/scss/colours/thematic/_tr.scss rename to src/styles/colours/thematic/_tr.scss diff --git a/src/scss/fonts/_font-faces.scss b/src/styles/fonts/_font-faces.scss similarity index 100% rename from src/scss/fonts/_font-faces.scss rename to src/styles/fonts/_font-faces.scss diff --git a/src/scss/main.scss b/src/styles/main.scss similarity index 100% rename from src/scss/main.scss rename to src/styles/main.scss diff --git a/src/scss/mixins/_block.scss b/src/styles/mixins/_block.scss similarity index 100% rename from src/scss/mixins/_block.scss rename to src/styles/mixins/_block.scss diff --git a/src/scss/mixins/_main.scss b/src/styles/mixins/_main.scss similarity index 100% rename from src/scss/mixins/_main.scss rename to src/styles/mixins/_main.scss diff --git a/src/scss/reset/_main.scss b/src/styles/reset/_main.scss similarity index 95% rename from src/scss/reset/_main.scss rename to src/styles/reset/_main.scss index bc29520..d210083 100644 --- a/src/scss/reset/_main.scss +++ b/src/styles/reset/_main.scss @@ -1,4 +1,4 @@ -@use '../mixins' as *; +@use 'mixins' as *; @forward 'normalize'; @forward 'typography'; diff --git a/src/scss/reset/_normalize.scss b/src/styles/reset/_normalize.scss similarity index 100% rename from src/scss/reset/_normalize.scss rename to src/styles/reset/_normalize.scss diff --git a/src/scss/reset/_typography.scss b/src/styles/reset/_typography.scss similarity index 98% rename from src/scss/reset/_typography.scss rename to src/styles/reset/_typography.scss index 0727c9b..6668e5f 100644 --- a/src/scss/reset/_typography.scss +++ b/src/styles/reset/_typography.scss @@ -1,4 +1,4 @@ -@use '../mixins' as *; +@use 'mixins' as *; html { font-size: 100%; diff --git a/src/scss/token-classes.scss b/src/styles/token-classes.scss similarity index 100% rename from src/scss/token-classes.scss rename to src/styles/token-classes.scss diff --git a/src/scss/tokens/accessibility/_main.scss b/src/styles/tokens/accessibility/_main.scss similarity index 100% rename from src/scss/tokens/accessibility/_main.scss rename to src/styles/tokens/accessibility/_main.scss diff --git a/src/scss/tokens/accessibility/_visibility.scss b/src/styles/tokens/accessibility/_visibility.scss similarity index 100% rename from src/scss/tokens/accessibility/_visibility.scss rename to src/styles/tokens/accessibility/_visibility.scss diff --git a/src/scss/tokens/accessibility/mixins/_main.scss b/src/styles/tokens/accessibility/mixins/_main.scss similarity index 100% rename from src/scss/tokens/accessibility/mixins/_main.scss rename to src/styles/tokens/accessibility/mixins/_main.scss diff --git a/src/scss/tokens/accessibility/mixins/_visibility.scss b/src/styles/tokens/accessibility/mixins/_visibility.scss similarity index 100% rename from src/scss/tokens/accessibility/mixins/_visibility.scss rename to src/styles/tokens/accessibility/mixins/_visibility.scss diff --git a/src/scss/tokens/backgrounds/_background-color.scss b/src/styles/tokens/backgrounds/_background-color.scss similarity index 100% rename from src/scss/tokens/backgrounds/_background-color.scss rename to src/styles/tokens/backgrounds/_background-color.scss diff --git a/src/scss/tokens/backgrounds/_main.scss b/src/styles/tokens/backgrounds/_main.scss similarity index 100% rename from src/scss/tokens/backgrounds/_main.scss rename to src/styles/tokens/backgrounds/_main.scss diff --git a/src/scss/tokens/backgrounds/mixins/_background-color.scss b/src/styles/tokens/backgrounds/mixins/_background-color.scss similarity index 100% rename from src/scss/tokens/backgrounds/mixins/_background-color.scss rename to src/styles/tokens/backgrounds/mixins/_background-color.scss diff --git a/src/scss/tokens/backgrounds/mixins/_main.scss b/src/styles/tokens/backgrounds/mixins/_main.scss similarity index 100% rename from src/scss/tokens/backgrounds/mixins/_main.scss rename to src/styles/tokens/backgrounds/mixins/_main.scss diff --git a/src/scss/tokens/borders/_border-color.scss b/src/styles/tokens/borders/_border-color.scss similarity index 100% rename from src/scss/tokens/borders/_border-color.scss rename to src/styles/tokens/borders/_border-color.scss diff --git a/src/scss/tokens/borders/_border-radius.scss b/src/styles/tokens/borders/_border-radius.scss similarity index 100% rename from src/scss/tokens/borders/_border-radius.scss rename to src/styles/tokens/borders/_border-radius.scss diff --git a/src/scss/tokens/borders/_border-style.scss b/src/styles/tokens/borders/_border-style.scss similarity index 100% rename from src/scss/tokens/borders/_border-style.scss rename to src/styles/tokens/borders/_border-style.scss diff --git a/src/scss/tokens/borders/_border-width.scss b/src/styles/tokens/borders/_border-width.scss similarity index 100% rename from src/scss/tokens/borders/_border-width.scss rename to src/styles/tokens/borders/_border-width.scss diff --git a/src/scss/tokens/borders/_main.scss b/src/styles/tokens/borders/_main.scss similarity index 100% rename from src/scss/tokens/borders/_main.scss rename to src/styles/tokens/borders/_main.scss diff --git a/src/scss/tokens/interactivity/_cursor.scss b/src/styles/tokens/interactivity/_cursor.scss similarity index 100% rename from src/scss/tokens/interactivity/_cursor.scss rename to src/styles/tokens/interactivity/_cursor.scss diff --git a/src/scss/tokens/interactivity/_main.scss b/src/styles/tokens/interactivity/_main.scss similarity index 100% rename from src/scss/tokens/interactivity/_main.scss rename to src/styles/tokens/interactivity/_main.scss diff --git a/src/scss/tokens/interactivity/_pointer-events.scss b/src/styles/tokens/interactivity/_pointer-events.scss similarity index 100% rename from src/scss/tokens/interactivity/_pointer-events.scss rename to src/styles/tokens/interactivity/_pointer-events.scss diff --git a/src/scss/tokens/layout/_box-sizing.scss b/src/styles/tokens/layout/_box-sizing.scss similarity index 100% rename from src/scss/tokens/layout/_box-sizing.scss rename to src/styles/tokens/layout/_box-sizing.scss diff --git a/src/scss/tokens/layout/_display.scss b/src/styles/tokens/layout/_display.scss similarity index 100% rename from src/scss/tokens/layout/_display.scss rename to src/styles/tokens/layout/_display.scss diff --git a/src/scss/tokens/layout/_floats.scss b/src/styles/tokens/layout/_floats.scss similarity index 100% rename from src/scss/tokens/layout/_floats.scss rename to src/styles/tokens/layout/_floats.scss diff --git a/src/scss/tokens/layout/_main.scss b/src/styles/tokens/layout/_main.scss similarity index 100% rename from src/scss/tokens/layout/_main.scss rename to src/styles/tokens/layout/_main.scss diff --git a/src/scss/tokens/layout/_object-fit.scss b/src/styles/tokens/layout/_object-fit.scss similarity index 100% rename from src/scss/tokens/layout/_object-fit.scss rename to src/styles/tokens/layout/_object-fit.scss diff --git a/src/scss/tokens/layout/_object-position.scss b/src/styles/tokens/layout/_object-position.scss similarity index 100% rename from src/scss/tokens/layout/_object-position.scss rename to src/styles/tokens/layout/_object-position.scss diff --git a/src/scss/tokens/layout/_overflow.scss b/src/styles/tokens/layout/_overflow.scss similarity index 100% rename from src/scss/tokens/layout/_overflow.scss rename to src/styles/tokens/layout/_overflow.scss diff --git a/src/scss/tokens/layout/_position.scss b/src/styles/tokens/layout/_position.scss similarity index 100% rename from src/scss/tokens/layout/_position.scss rename to src/styles/tokens/layout/_position.scss diff --git a/src/scss/tokens/layout/flex/_align-content.scss b/src/styles/tokens/layout/flex/_align-content.scss similarity index 100% rename from src/scss/tokens/layout/flex/_align-content.scss rename to src/styles/tokens/layout/flex/_align-content.scss diff --git a/src/scss/tokens/layout/flex/_align-items.scss b/src/styles/tokens/layout/flex/_align-items.scss similarity index 100% rename from src/scss/tokens/layout/flex/_align-items.scss rename to src/styles/tokens/layout/flex/_align-items.scss diff --git a/src/scss/tokens/layout/flex/_align-self.scss b/src/styles/tokens/layout/flex/_align-self.scss similarity index 100% rename from src/scss/tokens/layout/flex/_align-self.scss rename to src/styles/tokens/layout/flex/_align-self.scss diff --git a/src/scss/tokens/layout/flex/_flex-direction.scss b/src/styles/tokens/layout/flex/_flex-direction.scss similarity index 100% rename from src/scss/tokens/layout/flex/_flex-direction.scss rename to src/styles/tokens/layout/flex/_flex-direction.scss diff --git a/src/scss/tokens/layout/flex/_flex-grow.scss b/src/styles/tokens/layout/flex/_flex-grow.scss similarity index 100% rename from src/scss/tokens/layout/flex/_flex-grow.scss rename to src/styles/tokens/layout/flex/_flex-grow.scss diff --git a/src/scss/tokens/layout/flex/_flex-shrink.scss b/src/styles/tokens/layout/flex/_flex-shrink.scss similarity index 100% rename from src/scss/tokens/layout/flex/_flex-shrink.scss rename to src/styles/tokens/layout/flex/_flex-shrink.scss diff --git a/src/scss/tokens/layout/flex/_flex-wrap.scss b/src/styles/tokens/layout/flex/_flex-wrap.scss similarity index 100% rename from src/scss/tokens/layout/flex/_flex-wrap.scss rename to src/styles/tokens/layout/flex/_flex-wrap.scss diff --git a/src/scss/tokens/layout/flex/_flex.scss b/src/styles/tokens/layout/flex/_flex.scss similarity index 100% rename from src/scss/tokens/layout/flex/_flex.scss rename to src/styles/tokens/layout/flex/_flex.scss diff --git a/src/scss/tokens/layout/flex/_justify-content.scss b/src/styles/tokens/layout/flex/_justify-content.scss similarity index 100% rename from src/scss/tokens/layout/flex/_justify-content.scss rename to src/styles/tokens/layout/flex/_justify-content.scss diff --git a/src/scss/tokens/layout/flex/_justify-items.scss b/src/styles/tokens/layout/flex/_justify-items.scss similarity index 100% rename from src/scss/tokens/layout/flex/_justify-items.scss rename to src/styles/tokens/layout/flex/_justify-items.scss diff --git a/src/scss/tokens/layout/flex/_justify-self.scss b/src/styles/tokens/layout/flex/_justify-self.scss similarity index 100% rename from src/scss/tokens/layout/flex/_justify-self.scss rename to src/styles/tokens/layout/flex/_justify-self.scss diff --git a/src/scss/tokens/layout/flex/_main.scss b/src/styles/tokens/layout/flex/_main.scss similarity index 100% rename from src/scss/tokens/layout/flex/_main.scss rename to src/styles/tokens/layout/flex/_main.scss diff --git a/src/scss/tokens/sizing/_height.scss b/src/styles/tokens/sizing/_height.scss similarity index 100% rename from src/scss/tokens/sizing/_height.scss rename to src/styles/tokens/sizing/_height.scss diff --git a/src/scss/tokens/sizing/_main.scss b/src/styles/tokens/sizing/_main.scss similarity index 100% rename from src/scss/tokens/sizing/_main.scss rename to src/styles/tokens/sizing/_main.scss diff --git a/src/scss/tokens/sizing/_max-height.scss b/src/styles/tokens/sizing/_max-height.scss similarity index 100% rename from src/scss/tokens/sizing/_max-height.scss rename to src/styles/tokens/sizing/_max-height.scss diff --git a/src/scss/tokens/sizing/_max-width.scss b/src/styles/tokens/sizing/_max-width.scss similarity index 100% rename from src/scss/tokens/sizing/_max-width.scss rename to src/styles/tokens/sizing/_max-width.scss diff --git a/src/scss/tokens/sizing/_min-height.scss b/src/styles/tokens/sizing/_min-height.scss similarity index 100% rename from src/scss/tokens/sizing/_min-height.scss rename to src/styles/tokens/sizing/_min-height.scss diff --git a/src/scss/tokens/sizing/_min-width.scss b/src/styles/tokens/sizing/_min-width.scss similarity index 100% rename from src/scss/tokens/sizing/_min-width.scss rename to src/styles/tokens/sizing/_min-width.scss diff --git a/src/scss/tokens/sizing/_width.scss b/src/styles/tokens/sizing/_width.scss similarity index 100% rename from src/scss/tokens/sizing/_width.scss rename to src/styles/tokens/sizing/_width.scss diff --git a/src/scss/tokens/spacers/_fluid-margin.scss b/src/styles/tokens/spacers/_fluid-margin.scss similarity index 100% rename from src/scss/tokens/spacers/_fluid-margin.scss rename to src/styles/tokens/spacers/_fluid-margin.scss diff --git a/src/scss/tokens/spacers/_fluid-padding.scss b/src/styles/tokens/spacers/_fluid-padding.scss similarity index 100% rename from src/scss/tokens/spacers/_fluid-padding.scss rename to src/styles/tokens/spacers/_fluid-padding.scss diff --git a/src/scss/tokens/spacers/_main.scss b/src/styles/tokens/spacers/_main.scss similarity index 100% rename from src/scss/tokens/spacers/_main.scss rename to src/styles/tokens/spacers/_main.scss diff --git a/src/scss/tokens/spacers/_margin.scss b/src/styles/tokens/spacers/_margin.scss similarity index 100% rename from src/scss/tokens/spacers/_margin.scss rename to src/styles/tokens/spacers/_margin.scss diff --git a/src/scss/tokens/spacers/_padding.scss b/src/styles/tokens/spacers/_padding.scss similarity index 100% rename from src/scss/tokens/spacers/_padding.scss rename to src/styles/tokens/spacers/_padding.scss diff --git a/src/scss/tokens/spacers/mixins/_fluid-margin.scss b/src/styles/tokens/spacers/mixins/_fluid-margin.scss similarity index 100% rename from src/scss/tokens/spacers/mixins/_fluid-margin.scss rename to src/styles/tokens/spacers/mixins/_fluid-margin.scss diff --git a/src/scss/tokens/spacers/mixins/_fluid-padding.scss b/src/styles/tokens/spacers/mixins/_fluid-padding.scss similarity index 100% rename from src/scss/tokens/spacers/mixins/_fluid-padding.scss rename to src/styles/tokens/spacers/mixins/_fluid-padding.scss diff --git a/src/scss/tokens/spacers/mixins/_main.scss b/src/styles/tokens/spacers/mixins/_main.scss similarity index 100% rename from src/scss/tokens/spacers/mixins/_main.scss rename to src/styles/tokens/spacers/mixins/_main.scss diff --git a/src/scss/tokens/text/_color.scss b/src/styles/tokens/text/_color.scss similarity index 100% rename from src/scss/tokens/text/_color.scss rename to src/styles/tokens/text/_color.scss diff --git a/src/scss/tokens/text/_font-family.scss b/src/styles/tokens/text/_font-family.scss similarity index 100% rename from src/scss/tokens/text/_font-family.scss rename to src/styles/tokens/text/_font-family.scss diff --git a/src/scss/tokens/text/_font-size.scss b/src/styles/tokens/text/_font-size.scss similarity index 100% rename from src/scss/tokens/text/_font-size.scss rename to src/styles/tokens/text/_font-size.scss diff --git a/src/scss/tokens/text/_font-style.scss b/src/styles/tokens/text/_font-style.scss similarity index 100% rename from src/scss/tokens/text/_font-style.scss rename to src/styles/tokens/text/_font-style.scss diff --git a/src/scss/tokens/text/_font-weight.scss b/src/styles/tokens/text/_font-weight.scss similarity index 100% rename from src/scss/tokens/text/_font-weight.scss rename to src/styles/tokens/text/_font-weight.scss diff --git a/src/scss/tokens/text/_letter-spacing.scss b/src/styles/tokens/text/_letter-spacing.scss similarity index 100% rename from src/scss/tokens/text/_letter-spacing.scss rename to src/styles/tokens/text/_letter-spacing.scss diff --git a/src/scss/tokens/text/_line-height.scss b/src/styles/tokens/text/_line-height.scss similarity index 100% rename from src/scss/tokens/text/_line-height.scss rename to src/styles/tokens/text/_line-height.scss diff --git a/src/scss/tokens/text/_main.scss b/src/styles/tokens/text/_main.scss similarity index 100% rename from src/scss/tokens/text/_main.scss rename to src/styles/tokens/text/_main.scss diff --git a/src/scss/tokens/text/_text-align.scss b/src/styles/tokens/text/_text-align.scss similarity index 100% rename from src/scss/tokens/text/_text-align.scss rename to src/styles/tokens/text/_text-align.scss diff --git a/src/scss/tokens/text/_text-decoration.scss b/src/styles/tokens/text/_text-decoration.scss similarity index 100% rename from src/scss/tokens/text/_text-decoration.scss rename to src/styles/tokens/text/_text-decoration.scss diff --git a/src/scss/tokens/text/_text-role.scss b/src/styles/tokens/text/_text-role.scss similarity index 100% rename from src/scss/tokens/text/_text-role.scss rename to src/styles/tokens/text/_text-role.scss diff --git a/src/scss/tokens/text/_text-stroke.scss b/src/styles/tokens/text/_text-stroke.scss similarity index 100% rename from src/scss/tokens/text/_text-stroke.scss rename to src/styles/tokens/text/_text-stroke.scss diff --git a/src/scss/tokens/text/_text-transform.scss b/src/styles/tokens/text/_text-transform.scss similarity index 100% rename from src/scss/tokens/text/_text-transform.scss rename to src/styles/tokens/text/_text-transform.scss diff --git a/src/scss/tokens/text/_vertical-align.scss b/src/styles/tokens/text/_vertical-align.scss similarity index 100% rename from src/scss/tokens/text/_vertical-align.scss rename to src/styles/tokens/text/_vertical-align.scss diff --git a/src/scss/tokens/text/_white-space.scss b/src/styles/tokens/text/_white-space.scss similarity index 100% rename from src/scss/tokens/text/_white-space.scss rename to src/styles/tokens/text/_white-space.scss diff --git a/src/scss/tokens/text/_word-break.scss b/src/styles/tokens/text/_word-break.scss similarity index 100% rename from src/scss/tokens/text/_word-break.scss rename to src/styles/tokens/text/_word-break.scss diff --git a/src/scss/tokens/text/mixins/_color.scss b/src/styles/tokens/text/mixins/_color.scss similarity index 100% rename from src/scss/tokens/text/mixins/_color.scss rename to src/styles/tokens/text/mixins/_color.scss diff --git a/src/scss/tokens/text/mixins/_font-family.scss b/src/styles/tokens/text/mixins/_font-family.scss similarity index 100% rename from src/scss/tokens/text/mixins/_font-family.scss rename to src/styles/tokens/text/mixins/_font-family.scss diff --git a/src/scss/tokens/text/mixins/_font-size.scss b/src/styles/tokens/text/mixins/_font-size.scss similarity index 100% rename from src/scss/tokens/text/mixins/_font-size.scss rename to src/styles/tokens/text/mixins/_font-size.scss diff --git a/src/scss/tokens/text/mixins/_font-weight.scss b/src/styles/tokens/text/mixins/_font-weight.scss similarity index 100% rename from src/scss/tokens/text/mixins/_font-weight.scss rename to src/styles/tokens/text/mixins/_font-weight.scss diff --git a/src/scss/tokens/text/mixins/_letter-spacing.scss b/src/styles/tokens/text/mixins/_letter-spacing.scss similarity index 100% rename from src/scss/tokens/text/mixins/_letter-spacing.scss rename to src/styles/tokens/text/mixins/_letter-spacing.scss diff --git a/src/scss/tokens/text/mixins/_line-height.scss b/src/styles/tokens/text/mixins/_line-height.scss similarity index 100% rename from src/scss/tokens/text/mixins/_line-height.scss rename to src/styles/tokens/text/mixins/_line-height.scss diff --git a/src/scss/tokens/text/mixins/_main.scss b/src/styles/tokens/text/mixins/_main.scss similarity index 100% rename from src/scss/tokens/text/mixins/_main.scss rename to src/styles/tokens/text/mixins/_main.scss diff --git a/src/scss/tokens/text/mixins/_text-role.scss b/src/styles/tokens/text/mixins/_text-role.scss similarity index 100% rename from src/scss/tokens/text/mixins/_text-role.scss rename to src/styles/tokens/text/mixins/_text-role.scss diff --git a/src/scss/tokens/text/mixins/_text-stroke.scss b/src/styles/tokens/text/mixins/_text-stroke.scss similarity index 100% rename from src/scss/tokens/text/mixins/_text-stroke.scss rename to src/styles/tokens/text/mixins/_text-stroke.scss diff --git a/src/scss/tokens/variables/_block.scss b/src/styles/tokens/variables/_block.scss similarity index 100% rename from src/scss/tokens/variables/_block.scss rename to src/styles/tokens/variables/_block.scss diff --git a/src/scss/tokens/variables/_main.scss b/src/styles/tokens/variables/_main.scss similarity index 100% rename from src/scss/tokens/variables/_main.scss rename to src/styles/tokens/variables/_main.scss diff --git a/src/scss/tokens/variables/_theme.scss b/src/styles/tokens/variables/_theme.scss similarity index 100% rename from src/scss/tokens/variables/_theme.scss rename to src/styles/tokens/variables/_theme.scss diff --git a/src/utils/env/index.ts b/src/utils/env/index.ts new file mode 100644 index 0000000..0dfca6a --- /dev/null +++ b/src/utils/env/index.ts @@ -0,0 +1,72 @@ +import { building } from '$app/environment'; + +/** + * Check if the page is being hosted inside the Reuters mobile app. + * + * @example + * ```typescript + * import { page } from "$app/stores"; + * + * isReutersApp($page.url); + * ``` + * @param url URL of current page + * @returns `true` if in the Reuters app + */ +export const isReutersApp = (url: URL) => { + if (building) return false; + return url.searchParams.get('outputType') === 'chromeless'; +}; + +/** + * Check if the page is being hosted on reuters.com. + * + * @example + * ```typescript + * import { page } from "$app/stores"; + * + * isReutersDotcom($page.url); + * ``` + * + * @param url URL of current page + * @returns `true` if on reuters.com and not in an iframe + */ +export const isReutersDotcom = (url: URL) => { + if (typeof window !== 'undefined') { + if (window.self !== window.top) return false; + } + return /\Wreuters\.com$/.test(url.hostname); +}; + +/** + * Check if the page is being hosted on our preview server at graphics.thomsonreuters.com. + * + * @example + * ```typescript + * import { page } from "$app/stores"; + * + * isReutersPreview($page.url); + * ``` + * + * @param url URL of current page + * @returns `true` if on graphics.thomsonreuters.com + */ +export const isReutersPreview = (url: URL) => { + return url.hostname === 'graphics.thomsonreuters.com'; +}; + +/** + * Check if the page is being hosted in development or on our preview server at graphics.thomsonreuters.com. + * + * @example + * ```typescript + * import { page } from "$app/stores"; + * + * isReutersDev($page.url); + * ``` + * + * @param url URL of current page + * @returns `true` if on dev or graphics.thomsonreuters.com + */ +export const isReutersDev = (url: URL) => { + return url.hostname === 'localhost' || isReutersPreview(url); +}; diff --git a/src/utils/graphics/ResponsiveGraphic.astro b/src/utils/graphics/ResponsiveGraphic.astro new file mode 100644 index 0000000..afcbd10 --- /dev/null +++ b/src/utils/graphics/ResponsiveGraphic.astro @@ -0,0 +1,30 @@ +--- +import type { ImageMetadata } from "astro"; +import { getImage } from "astro:assets"; + +interface Props { + width: string | ImageMetadata; +} + +const { width } = Astro.props; + +const mobileImg = await getImage({ + src: mobileImgUrl, + format: "webp", + width: 200, + height: 200, +}); + +const desktopImg = await getImage({ + src: desktopImgUrl, + format: "webp", + width: 800, + height: 200, +}); +--- + + + + + {alt} + \ No newline at end of file diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..1509348 --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,112 @@ +import slug from 'slugify'; + +/** Helper function to generate a random 4-character string */ +export const random4 = () => + Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); + +/** + * Custom function that returns an author page URL. + */ +export const getAuthorPageUrl = (author: string): string => { + const authorSlug = slug(author.trim(), { lower: true }); + return `https://www.reuters.com/authors/${authorSlug}/`; +}; + +/** Formats a string containing a full or 3-letter abbreviated month, AM/PM, and am/pm to match the Reuters style. + * + * All months, full or abbreviated to 3 letters, are formatted to: + * - Jan. + * - Feb. + * - March + * - April] + * - May + * - June + * - July + * - Aug. + * - Sept. + * - Oct. + * - Nov. + * - Dec. + * + * AM and PM are formatted as lowercase. + * + */ +export const prettifyDate = (input: string) => { + // Define an object to map full month names to their Reuters style equivalents + const conversions: { [key: string]: string } = { + // full months + january: 'Jan.', + february: 'Feb.', + august: 'Aug.', + september: 'Sept.', + october: 'Oct.', + november: 'Nov.', + december: 'Dec.', + + // 3-letter abbreviations that need fixing + jan: 'Jan.', + feb: 'Feb.', + mar: 'March', + apr: 'April', + jun: 'June', + jul: 'July', + aug: 'Aug.', + sep: 'Sept.', + oct: 'Oct.', + nov: 'Nov.', + dec: 'Dec.', + }; + + // If the key in conversions is found in the input (case insensitive), replace it with the corresponding value + const formatted = Object.keys(conversions).reduce((acc, key) => { + const regex = new RegExp(`\\b${key}\\b`, 'gi'); // Added 'i' flag for case insensitive + return acc.replace(regex, conversions[key]); + }, input); + + // Fix rogue periods in abbreviations (case insensitive) + const fixedAbbr = formatted + .replace(/\bmar\./gi, 'March') + .replace(/\bmarch\./gi, 'March') + .replace(/\bapr\./gi, 'April') + .replace(/\bapril\./gi, 'April') + .replace(/\bmay\./gi, 'May') + .replace(/\bjune\./gi, 'June') + .replace(/\bjuly\./gi, 'July') + .replace(/\bsep\./gi, 'Sept.'); + + // Replace double periods with a single period + const fixedPeriods = fixedAbbr.replace(/\.{2,}/g, '.'); + + // Fix am/pm formatting + return prettifyAmPm(fixedPeriods); +}; + +const prettifyAmPm = (text: string) => { + return text.replace( + /(\d)\s*(am|AM|pm|PM)\b/g, + (_match, digit, timeDesignator) => { + const formattedDesignator = + timeDesignator.toLowerCase() === 'am' ? 'a.m.' : 'p.m.'; + return `${digit} ${formattedDesignator}`; + } + ); +}; + +/** + * Converts a string into a URL-friendly slug. + * + * @param str The string to be slugified. + * @returns The slugified string. + */ +export const slugify = (str: string) => + slug(str, { lower: true, strict: true }); + +/** Formats a datetime string into a localized time string with hour, minute, and time zone for the dateline */ +export const formatTime = (datetime: string) => + new Date(datetime).toLocaleTimeString([], { + hour: '2-digit', + minute: '2-digit', + timeZoneName: 'short', + }); diff --git a/src/utils/liveEndpoints/index.ts b/src/utils/liveEndpoints/index.ts new file mode 100644 index 0000000..0afca46 --- /dev/null +++ b/src/utils/liveEndpoints/index.ts @@ -0,0 +1,197 @@ +interface ConfigLiveEndpoint { + enabled: boolean; + json: string; + aml: string; +} + +interface StoryConfig { + name: string; + rngsIo: string; + syncPath: string; + preview?: ConfigLiveEndpoint; + public?: ConfigLiveEndpoint; +} + +interface StoryboardConfig { + name: string; + rngsIo: string; + stories: { + [storyId: string]: StoryConfig; + }; +} + +interface StoryClientConfig { + storyboards: { + [storyboardId: string]: StoryboardConfig; + }; +} + +type LiveEndpoint = { + localFile: string; + metadata: { + id: string; + name: string; + storyboard: { + id: string; + name: string; + }; + lastPublished: string; + version: 'preview' | 'public'; + }; + story: unknown; +}; + +/** + * LiveEndpoints connects your app to updating data published independent of your project files. + * + * This is often used in preview stages, but can also be used in production to allow updating a + * published page with new content. + */ +export class LiveEndpoints { + private clientConfig: StoryClientConfig; + private currentUrl: URL; + /** + * @param clientConfig RNGS.io config object, usually imported directly from `rngs-io.json` + * in the root of the project. + * @param currentUrl URL of the current page. + */ + constructor(clientConfig: StoryClientConfig, currentUrl: URL) { + this.clientConfig = clientConfig; + this.currentUrl = currentUrl; + } + + private extractLiveUrlsFromConfig(config: StoryClientConfig) { + const urls: { + url: string; + localFile: string; + version: 'preview' | 'public'; + }[] = []; + + // Loop through each storyboard in the storyboards object + for (const storyboardId in config.storyboards) { + const storyboard = config.storyboards[storyboardId]; + + // Loop through each story in the stories object of the current storyboard + for (const storyId in storyboard.stories) { + const story = storyboard.stories[storyId]; + + // Check both 'preview' and 'public' keys in the story + (['preview', 'public'] as const).forEach((version) => { + const media = story[version]; + // If media exists, is not an empty object, and enabled is true + if (media && Object.keys(media).length !== 0 && media.enabled) { + // If the json URL exists, add it to the urls array + if (media.json) { + urls.push({ + localFile: story.syncPath, + url: media.json, + version, + }); + } + } + }); + } + } + + return urls; + } + + private async fetchLiveUrls(urlsData: { url: string; localFile: string }[]) { + const fetchPromises = urlsData.map( + async (data: { + url: string; + localFile: string; + }): Promise => { + const response = await fetch(data.url); + const story = (await response.json()) as LiveEndpoint; + return { ...story, localFile: data.localFile }; + } + ); + + const result = await Promise.all(fetchPromises); + return result; + } + + private async fetchLiveEndPoints() { + const liveUrls = this.extractLiveUrlsFromConfig(this.clientConfig); + + let liveStories: LiveEndpoint[] = []; + + // Public pages + if (this.currentUrl.hostname === 'reuters.com') { + const versionLiveUrls = liveUrls.filter( + ({ version }) => version === 'public' + ); + liveStories = await this.fetchLiveUrls(versionLiveUrls); + } + + // Preview pages + if (this.currentUrl.hostname === 'graphics.thomsonreuters.com') { + const versionLiveUrls = liveUrls.filter( + ({ version }) => version === 'preview' + ); + liveStories = await this.fetchLiveUrls(versionLiveUrls); + } + + return liveStories; + } + + /** + * Get the latest version of content docs, which may be published remotely. + * + * @example + * ```typescript + * import localContent from '$locales/en/content.json'; + * + * const liveEndpoints = new LiveEndpoints(rngsIoConfig, url); + * const content = await liveEndpoints.getLiveContent('en/content', localContent); + * ``` + * @param localeFilePath Path part of the locale file, e.g., `en/content` for + * `locales/en/content.json` + * @param localContent Local copy of the locale file, imported directly as json. + * @returns The latest content + */ + async getLiveContent(localeFilePath: string, localContent: T): Promise { + const liveEndpoints = await this.fetchLiveEndPoints(); + const pathParts = localeFilePath.split('/'); + if (pathParts.length !== 2) { + throw new Error( + 'Invalid locale file. Must have just two path parts, e.g., "en/content"' + ); + } + + const liveContent = liveEndpoints.find( + (endPoint) => endPoint.localFile === `locales/${localeFilePath}.json` + ); + + // If no live content found, return local content + if (!liveContent) { + return localContent; + } + + // Compare lastPublished dates to determine which content is more recent + const localContentWithMetadata = localContent as { + metadata?: { lastSynced?: string }; + }; + + const liveLastPublished = liveContent.metadata?.lastPublished; + const localLastSynced = localContentWithMetadata.metadata?.lastSynced; + + // If local content doesn't have lastSynced return live content + if (!localLastSynced) { + return liveContent as typeof localContent; + } + + // If live content doesn't have lastPublished, return local content + if (!liveLastPublished) { + return localContent; + } + + // Compare dates - return live content if it's more recent, otherwise return local + const liveDate = new Date(liveLastPublished); + const localDate = new Date(localLastSynced); + + if (liveDate > localDate) return liveContent as typeof localContent; + return localContent; + } +} diff --git a/src/utils/propValidators/index.ts b/src/utils/propValidators/index.ts new file mode 100644 index 0000000..87a6467 --- /dev/null +++ b/src/utils/propValidators/index.ts @@ -0,0 +1,68 @@ +/** + * Coeerce a string to a valid container width. + * + * Used to satisfy type checking. + * + * @param width Width string + * @returns valid container width + */ +export const containerWidth = (width: string) => { + switch (width) { + case 'narrower': + case 'narrow': + case 'normal': + case 'wide': + case 'wider': + case 'widest': + case 'fluid': + return width; + default: + return 'normal'; // Default value if invalid + } +}; + +/** + * Validate inline ad number prop + * @param n string + * @returns Ad number + */ +export const inlineAdNumber = (n: string) => { + switch (n) { + case '1': + case '2': + case '3': + return n; + default: + return '1'; + } +}; + +/** + * Coerce a truth-y value to a proper boolean. + * + * @example + * ``` + * truthyString('true'); // true + * truthyString('f'); // false + * truthrString('yes'); // true + * truthyString('0'); // false + * ``` + * @param value + * @param defaultValue Default value + * @returns `true` or `false` + */ +export const truthy = (value: string, defaultValue = false) => { + // trim and standardise string + const cleaned = value.toLowerCase().trim(); + + const truthyStrings = ['true', 't', 'yes', '1']; + const falsyStrings = ['false', 'f', 'no', '0']; + + if (truthyStrings.includes(cleaned)) { + return true; + } else if (falsyStrings.includes(cleaned)) { + return false; + } else { + return defaultValue; + } +}; diff --git a/svelte.config.js b/svelte.config.js index 522c1ef..d86edd2 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -1,5 +1,19 @@ import { vitePreprocess } from '@astrojs/svelte'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); export default { - preprocess: vitePreprocess(), -} + preprocess: vitePreprocess({ + style: { + css: { + preprocessorOptions: { + scss: { + loadPaths: [path.resolve(__dirname, 'src/styles')] + } + } + } + } + }) +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 8bf91d3..d3572ac 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,14 @@ { "extends": "astro/tsconfigs/strict", + "compilerOptions": { + "paths": { + "@assets/*": ["./src/assets/*"], + "@components/*": ["./src/components/*"], + "@lib/*": ["./src/lib/*"], + "../../styles/*": ["./src/styles/*"], + "@utils/*": ["./src/utils/*"] + } + }, "include": [".astro/types.d.ts", "**/*"], "exclude": ["dist"] }