diff --git a/.github/workflows/info.yml b/.github/workflows/info.yml
index cf63ea7f..16929bdf 100644
--- a/.github/workflows/info.yml
+++ b/.github/workflows/info.yml
@@ -5,15 +5,6 @@ on:
affected_projects:
description: 'JSON array of affected projects'
value: ${{ jobs.Info.outputs.affected_projects }}
- should_run_e2e:
- description: 'Whether to run the e2e because of e2e change'
- value: ${{ contains(fromJSON(jobs.Info.outputs.affected_projects), '@ageorgedev/ageorgedev-e2e') }}
- should_deploy_site:
- description: 'Whether to deploy the site'
- value: ${{ contains(fromJSON(jobs.Info.outputs.affected_projects), '@ageorgedev/ageorgedev') }}
- should_deploy_design_system:
- description: 'Whether to deploy the design-system storybook'
- value: ${{ contains(fromJSON(jobs.Info.outputs.affected_projects), '@ageorgedev/design-docs')}}
production_deploy_matrix:
description: 'JSON array of projects to deploy to production'
value: ${{ jobs.Info.outputs.production_deploy_matrix }}
@@ -42,6 +33,7 @@ jobs:
AFFECTED: ${{ steps.affected_projects.outputs.affected_projects }}
SITE_PROJECT: ${{ vars.VERCEL_PROJECT_AGEORGEDEV }}
DESIGN_PROJECT: ${{ vars.VERCEL_PROJECT_DESIGN_SYSTEM }}
+ GAME_TOOLS_PROJECT: ${{ vars.VERCEL_PROJECT_GAME_TOOLS }}
run: |
matrix=$(echo "$AFFECTED" | jq -c '
. as $p |
@@ -51,6 +43,9 @@ jobs:
else . end |
if ($p | contains(["@ageorgedev/design-docs"])) then
. + [{"vercel_project_name": env.DESIGN_PROJECT}]
+ else . end |
+ if ($p | contains(["@ageorgedev/game-tools"])) then
+ . + [{"vercel_project_name": env.GAME_TOOLS_PROJECT}]
else . end
')
echo "production_deploy_matrix=$matrix" >> "$GITHUB_OUTPUT"
@@ -60,6 +55,7 @@ jobs:
AFFECTED: ${{ steps.affected_projects.outputs.affected_projects }}
SITE_PROJECT: ${{ vars.VERCEL_PROJECT_AGEORGEDEV }}
DESIGN_PROJECT: ${{ vars.VERCEL_PROJECT_DESIGN_SYSTEM }}
+ GAME_TOOLS_PROJECT: ${{ vars.VERCEL_PROJECT_GAME_TOOLS }}
run: |
matrix=$(echo "$AFFECTED" | jq -c '
. as $p |
@@ -69,6 +65,9 @@ jobs:
else . end |
if ($p | contains(["@ageorgedev/design-docs"])) then
. + [{"vercel_project_name": env.DESIGN_PROJECT, "label": "Design System"}]
+ else . end |
+ if ($p | contains(["@ageorgedev/game-tools"])) then
+ . + [{"vercel_project_name": env.GAME_TOOLS_PROJECT, "label": "Game Tools"}]
else . end
')
echo "pr_deploy_matrix=$matrix" >> "$GITHUB_OUTPUT"
diff --git a/.vscode/settings.json b/.vscode/settings.json
index cb336edb..c7d4cdbf 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -9,5 +9,20 @@
"editor.defaultFormatter": "biomejs.biome",
"nxConsole.generateAiAgentRules": true,
"js/ts.preferences.preferTypeOnlyAutoImports": true,
- "js/ts.tsdk.path": "node_modules/typescript/lib"
+ "js/ts.tsdk.path": "node_modules/typescript/lib",
+ "workbench.colorCustomizations": {
+ "commandCenter.border": "#e7e7e799",
+ "sash.hoverBorder": "#8f491a",
+ "statusBar.background": "#643312",
+ "statusBar.foreground": "#e7e7e7",
+ "statusBarItem.hoverBackground": "#8f491a",
+ "statusBarItem.remoteBackground": "#643312",
+ "statusBarItem.remoteForeground": "#e7e7e7",
+ "tab.activeBorder": "#8f491a",
+ "titleBar.activeBackground": "#643312",
+ "titleBar.activeForeground": "#e7e7e7",
+ "titleBar.inactiveBackground": "#64331299",
+ "titleBar.inactiveForeground": "#e7e7e799"
+ },
+ "peacock.color": "#643312"
}
diff --git a/apps/ageorgedev-e2e/playwright.config.ts b/apps/ageorgedev-e2e/playwright.config.ts
index c21707e6..21150f9e 100644
--- a/apps/ageorgedev-e2e/playwright.config.ts
+++ b/apps/ageorgedev-e2e/playwright.config.ts
@@ -1,4 +1,4 @@
-import { defineConfig, devices } from '@playwright/test'
+import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
@@ -11,4 +11,4 @@ export default defineConfig({
use: { ...devices['Desktop Chrome'] },
},
],
-})
+});
diff --git a/apps/ageorgedev-e2e/tests/smoke.spec.ts b/apps/ageorgedev-e2e/tests/smoke.spec.ts
index 45ef1c2f..d786006a 100644
--- a/apps/ageorgedev-e2e/tests/smoke.spec.ts
+++ b/apps/ageorgedev-e2e/tests/smoke.spec.ts
@@ -1,20 +1,20 @@
-import { expect, test } from '@playwright/test'
+import { expect, test } from '@playwright/test';
test('home page renders', async ({ page }) => {
- await page.goto('/')
- await expect(page.getByRole('heading', { name: 'Anish' })).toBeVisible()
- await expect(page.getByRole('heading', { name: 'George' })).toBeVisible()
+ await page.goto('/');
+ await expect(page.getByRole('heading', { name: 'Anish' })).toBeVisible();
+ await expect(page.getByRole('heading', { name: 'George' })).toBeVisible();
await expect(
page.getByRole('heading', { name: 'Web Developer' })
- ).toBeVisible()
-})
+ ).toBeVisible();
+});
test('resume page renders', async ({ page }) => {
- await page.goto('/resume')
- await expect(page.locator('body')).not.toBeEmpty()
-})
+ await page.goto('/resume');
+ await expect(page.locator('body')).not.toBeEmpty();
+});
test('talks page renders', async ({ page }) => {
- await page.goto('/talks')
- await expect(page.locator('body')).not.toBeEmpty()
-})
+ await page.goto('/talks');
+ await expect(page.locator('body')).not.toBeEmpty();
+});
diff --git a/apps/ageorgedev/package.json b/apps/ageorgedev/package.json
index 8fcce42d..c112b506 100644
--- a/apps/ageorgedev/package.json
+++ b/apps/ageorgedev/package.json
@@ -22,7 +22,7 @@
"@phosphor-icons/react": "2.1.10",
"@tanstack/react-router": "1.170.11",
"@tanstack/react-start": "1.168.19",
- "ramda": "0.28.0",
+ "ramda": "0.32.0",
"react": "19.2.7",
"react-dom": "19.2.7",
"remark-frontmatter": "5.0.0",
diff --git a/apps/ageorgedev/postcss.config.js b/apps/ageorgedev/postcss.config.js
index 60bf9096..8984cd25 100644
--- a/apps/ageorgedev/postcss.config.js
+++ b/apps/ageorgedev/postcss.config.js
@@ -8,4 +8,4 @@ export default {
'postcss-nesting': {},
'@tailwindcss/postcss': {},
},
-}
+};
diff --git a/apps/ageorgedev/src/components/GlobalProviders.tsx b/apps/ageorgedev/src/components/GlobalProviders.tsx
index f2af2fb8..9f7816de 100644
--- a/apps/ageorgedev/src/components/GlobalProviders.tsx
+++ b/apps/ageorgedev/src/components/GlobalProviders.tsx
@@ -1,5 +1,5 @@
-import { ThemeProvider } from '@ageorgedev/design-system/theming/ThemeProvider'
+import { ThemeProvider } from '@ageorgedev/design-system/theming/ThemeProvider';
export function GlobalProviders({ children }: React.PropsWithChildren) {
- return {children}
+ return {children};
}
diff --git a/apps/ageorgedev/src/components/HomeAboveFold/HomeAboveFold.spec.tsx b/apps/ageorgedev/src/components/HomeAboveFold/HomeAboveFold.spec.tsx
index 64f526e6..b2e5edd8 100644
--- a/apps/ageorgedev/src/components/HomeAboveFold/HomeAboveFold.spec.tsx
+++ b/apps/ageorgedev/src/components/HomeAboveFold/HomeAboveFold.spec.tsx
@@ -1,25 +1,25 @@
-import { render, screen } from '@testing-library/react'
-import { describe, expect, it } from 'vitest'
-import { HomeAboveFold } from './HomeAboveFold'
+import { render, screen } from '@testing-library/react';
+import { describe, expect, it } from 'vitest';
+import { HomeAboveFold } from './HomeAboveFold';
describe('HomeAboveFold', () => {
it('renders a heading with the title', () => {
- render()
+ render();
expect(
screen.getByRole('heading', { name: /web developer/i })
- ).toBeInTheDocument()
- })
+ ).toBeInTheDocument();
+ });
it('renders the subtitle text', () => {
- render()
+ render();
expect(
screen.getByText(/architecting web experiences since 2016/i)
- ).toBeInTheDocument()
- })
+ ).toBeInTheDocument();
+ });
it('renders social links to GitHub and LinkedIn', () => {
- render()
- expect(screen.getByRole('link', { name: /github/i })).toBeInTheDocument()
- expect(screen.getByRole('link', { name: /linkedin/i })).toBeInTheDocument()
- })
-})
+ render();
+ expect(screen.getByRole('link', { name: /github/i })).toBeInTheDocument();
+ expect(screen.getByRole('link', { name: /linkedin/i })).toBeInTheDocument();
+ });
+});
diff --git a/apps/ageorgedev/src/components/HomeAboveFold/HomeAboveFold.tsx b/apps/ageorgedev/src/components/HomeAboveFold/HomeAboveFold.tsx
index d5422362..9aae3d17 100644
--- a/apps/ageorgedev/src/components/HomeAboveFold/HomeAboveFold.tsx
+++ b/apps/ageorgedev/src/components/HomeAboveFold/HomeAboveFold.tsx
@@ -1,11 +1,11 @@
-import { NameLogo } from '@ageorgedev/design-system/logo/NameLogo'
+import { NameLogo } from '@ageorgedev/design-system/logo/NameLogo';
import {
Heading3,
Heading4,
-} from '@ageorgedev/design-system/typography/typography-components'
-import { cn } from '@ageorgedev/toolbelt/cn'
-import { GithubLogoIcon, LinkedinLogoIcon } from '@phosphor-icons/react/ssr'
-import styles from './HomeAboveFold.module.css'
+} from '@ageorgedev/design-system/typography/typography-components';
+import { cn } from '@ageorgedev/toolbelt/cn';
+import { GithubLogoIcon, LinkedinLogoIcon } from '@phosphor-icons/react/ssr';
+import styles from './HomeAboveFold.module.css';
export function HomeAboveFold() {
return (
@@ -62,5 +62,5 @@ export function HomeAboveFold() {
- )
+ );
}
diff --git a/apps/ageorgedev/src/components/Resume/Skill.spec.tsx b/apps/ageorgedev/src/components/Resume/Skill.spec.tsx
index 920b0f43..e84a4c2f 100644
--- a/apps/ageorgedev/src/components/Resume/Skill.spec.tsx
+++ b/apps/ageorgedev/src/components/Resume/Skill.spec.tsx
@@ -1,28 +1,28 @@
-import { render, screen } from '@testing-library/react'
-import { describe, expect, it } from 'vitest'
-import { Skill } from './Skill'
+import { render, screen } from '@testing-library/react';
+import { describe, expect, it } from 'vitest';
+import { Skill } from './Skill';
describe('Skill', () => {
it('renders the skill name', () => {
- render()
- expect(screen.getByText('TypeScript')).toBeInTheDocument()
- })
+ render();
+ expect(screen.getByText('TypeScript')).toBeInTheDocument();
+ });
it('renders for each skill type without throwing', () => {
- const { rerender } = render()
- expect(screen.getByText('React')).toBeInTheDocument()
+ const { rerender } = render();
+ expect(screen.getByText('React')).toBeInTheDocument();
- rerender()
- expect(screen.getByText('Performance')).toBeInTheDocument()
+ rerender();
+ expect(screen.getByText('Performance')).toBeInTheDocument();
- rerender()
- expect(screen.getByText('Mentoring')).toBeInTheDocument()
- })
+ rerender();
+ expect(screen.getByText('Mentoring')).toBeInTheDocument();
+ });
it('renders for all valid levels without throwing', () => {
- const { rerender } = render()
- rerender()
- rerender()
- expect(screen.getByText('skill')).toBeInTheDocument()
- })
-})
+ const { rerender } = render();
+ rerender();
+ rerender();
+ expect(screen.getByText('skill')).toBeInTheDocument();
+ });
+});
diff --git a/apps/ageorgedev/src/components/Resume/Skill.tsx b/apps/ageorgedev/src/components/Resume/Skill.tsx
index 4bda756e..03061105 100644
--- a/apps/ageorgedev/src/components/Resume/Skill.tsx
+++ b/apps/ageorgedev/src/components/Resume/Skill.tsx
@@ -1,18 +1,18 @@
-import { cn } from '@ageorgedev/toolbelt/cn'
-import { map } from 'ramda'
-import styles from './Skill.module.css'
+import { cn } from '@ageorgedev/toolbelt/cn';
+import { map } from 'ramda';
+import styles from './Skill.module.css';
-type SkillType = 'tool' | 'technique' | 'human'
-type SkillLevel = 3 | 4 | 5
+type SkillType = 'tool' | 'technique' | 'human';
+type SkillLevel = 3 | 4 | 5;
type SkillProps = {
- name: string
- type: SkillType
- level: SkillLevel
-}
+ name: string;
+ type: SkillType;
+ level: SkillLevel;
+};
export function Skill(props: SkillProps) {
- const styleClasses = levelClasses[props.type][props.level]
+ const styleClasses = levelClasses[props.type][props.level];
return (
{props.name}
- )
+ );
}
-type SkillClassMap = Record
+type SkillClassMap = Record;
const primaryAccentColorClasses: SkillClassMap = {
3: 'bg-primary-surface text-primary-onsurface',
4: 'bg-primary-surface-2 text-primary-onsurface-2',
5: `${styles.highSkill} bg-primary-surface-2 text-primary-onsurface-2`, // TODO
-}
+};
const secondaryAccentColorClasses: SkillClassMap = {
3: 'bg-secondary-surface text-secondary-onsurface',
4: 'bg-secondary-surface-2 text-secondary-onsurface-2',
5: `${styles.highSkill} bg-secondary-surface-2 text-secondary-onsurface-2`,
-}
+};
const levelClassesForHuman: SkillClassMap = {
3: 'bg-primary-surface text-primary-onsurface',
4: 'bg-primary-surface-2 text-primary-onsurface-2',
5: `${styles.highSkill} bg-primary-surface-2 text-primary-onsurface-2`, // TODO
-}
+};
const levelClasses: Record = {
tool: primaryAccentColorClasses,
technique: secondaryAccentColorClasses,
human: levelClassesForHuman,
-}
+};
const skillMapper =
(type: SkillType) =>
@@ -58,7 +58,7 @@ const skillMapper =
name,
level,
type,
- })
+ });
export const toolSkills: SkillProps[] = map(skillMapper('tool'), [
['React', 5],
@@ -73,7 +73,7 @@ export const toolSkills: SkillProps[] = map(skillMapper('tool'), [
['AgGrid', 3],
['Node', 4],
['Ramda', 4],
-])
+]);
export const techSkills: SkillProps[] = map(skillMapper('technique'), [
['Performance', 4],
@@ -89,7 +89,7 @@ export const techSkills: SkillProps[] = map(skillMapper('technique'), [
['Functional programming', 5],
['Accessibility', 4],
['CSS animations', 5],
-])
+]);
export const humanSkills: SkillProps[] = map(skillMapper('human'), [
['Mentoring', 4],
@@ -100,10 +100,10 @@ export const humanSkills: SkillProps[] = map(skillMapper('human'), [
['Leadership', 3],
['Stakeholder management', 5],
['Training', 5],
-])
+]);
export const AllSkills: SkillProps[] = [
...toolSkills,
...techSkills,
...humanSkills,
-]
+];
diff --git a/apps/ageorgedev/src/components/Resume/SocialLink.spec.tsx b/apps/ageorgedev/src/components/Resume/SocialLink.spec.tsx
index a41341b8..d98f0c61 100644
--- a/apps/ageorgedev/src/components/Resume/SocialLink.spec.tsx
+++ b/apps/ageorgedev/src/components/Resume/SocialLink.spec.tsx
@@ -1,47 +1,50 @@
-import { render, screen } from '@testing-library/react'
-import { describe, expect, it } from 'vitest'
-import { SocialLink } from './SocialLink'
+import { render, screen } from '@testing-library/react';
+import { describe, expect, it } from 'vitest';
+import { SocialLink } from './SocialLink';
describe('SocialLink', () => {
it('renders an anchor element', () => {
- const { container } = render()
- expect(container.querySelector('a')).toBeInTheDocument()
- })
+ const { container } = render();
+ expect(container.querySelector('a')).toBeInTheDocument();
+ });
it('has target="_blank"', () => {
- const { container } = render()
- expect(container.querySelector('a')).toHaveAttribute('target', '_blank')
- })
+ const { container } = render();
+ expect(container.querySelector('a')).toHaveAttribute('target', '_blank');
+ });
it('has a rel containing noreferrer', () => {
- const { container } = render()
- const rel = container.querySelector('a')?.getAttribute('rel') ?? ''
- expect(rel).toContain('noreferrer')
- })
+ const { container } = render();
+ const rel = container.querySelector('a')?.getAttribute('rel') ?? '';
+ expect(rel).toContain('noreferrer');
+ });
it('links to the correct URL for each type', () => {
const cases: Array<[Parameters[0]['type'], string]> = [
['github', 'https://github.com/bassoGeorge'],
['linkedin', 'https://linkedin.com/in/anishbassogeorge'],
['web', 'https://ageorge.dev'],
- ]
+ ];
for (const [type, expectedHref] of cases) {
- const { container, unmount } = render()
- expect(container.querySelector('a')).toHaveAttribute('href', expectedHref)
- unmount()
+ const { container, unmount } = render();
+ expect(container.querySelector('a')).toHaveAttribute(
+ 'href',
+ expectedHref
+ );
+ unmount();
}
- })
+ });
it('shows display text when full=true', () => {
- render()
- expect(screen.getByText('anishgeorgehb@gmail.com')).toBeInTheDocument()
- })
+ render();
+ expect(screen.getByText('anishgeorgehb@gmail.com')).toBeInTheDocument();
+ });
it('hides display text when full is omitted', () => {
- render()
+ render();
expect(
screen.queryByText('anishgeorgehb@gmail.com')
- ).not.toBeInTheDocument()
- })
-})
+ ).not.toBeInTheDocument();
+ });
+});
diff --git a/apps/ageorgedev/src/components/Resume/SocialLink.tsx b/apps/ageorgedev/src/components/Resume/SocialLink.tsx
index 645e7e45..22081bd6 100644
--- a/apps/ageorgedev/src/components/Resume/SocialLink.tsx
+++ b/apps/ageorgedev/src/components/Resume/SocialLink.tsx
@@ -1,5 +1,5 @@
-import { BodySm } from '@ageorgedev/design-system/typography/typography-components'
-import type { Icon } from '@phosphor-icons/react'
+import { BodySm } from '@ageorgedev/design-system/typography/typography-components';
+import type { Icon } from '@phosphor-icons/react';
import {
EnvelopeIcon,
GithubLogoIcon,
@@ -7,7 +7,7 @@ import {
LinkedinLogoIcon,
MapPinIcon,
PhoneIcon,
-} from '@phosphor-icons/react/ssr'
+} from '@phosphor-icons/react/ssr';
type SocialLinkType =
| 'github'
@@ -15,17 +15,17 @@ type SocialLinkType =
| 'email'
| 'web'
| 'phone'
- | 'location'
+ | 'location';
type SocialLinkProps = {
- type: SocialLinkType
- full?: boolean
-}
+ type: SocialLinkType;
+ full?: boolean;
+};
export function SocialLink({ type, full }: SocialLinkProps) {
- const link = LINKS[type]
- const displayLink = DISPLAY_LINKS[type] ?? link
- const Icon = ICONS[type]
+ const link = LINKS[type];
+ const displayLink = DISPLAY_LINKS[type] ?? link;
+ const Icon = ICONS[type];
return (
{full && {displayLink}}
- )
+ );
}
-const email = 'anishgeorgehb@gmail.com'
-const phone = '+91-999999'
+const email = 'anishgeorgehb@gmail.com';
+const phone = '+91-999999';
const LINKS: Record = {
github: 'https://github.com/bassoGeorge',
@@ -49,13 +49,13 @@ const LINKS: Record = {
web: 'https://ageorge.dev',
phone: `tel:${phone}`,
location: 'https://goo.gl/maps/Q7mUm5VQ5ZuLGV4v7',
-}
+};
const DISPLAY_LINKS: Partial> = {
email,
phone,
location: 'Bengaluru, India',
-}
+};
const ICONS: Record = {
github: GithubLogoIcon,
@@ -64,4 +64,4 @@ const ICONS: Record = {
web: GlobeIcon,
phone: PhoneIcon,
location: MapPinIcon,
-}
+};
diff --git a/apps/ageorgedev/src/mdx-components.tsx b/apps/ageorgedev/src/mdx-components.tsx
index 486273a7..da5eb21b 100644
--- a/apps/ageorgedev/src/mdx-components.tsx
+++ b/apps/ageorgedev/src/mdx-components.tsx
@@ -3,8 +3,8 @@ import {
Heading2,
Heading3,
Heading4,
-} from '@ageorgedev/design-system/typography/typography-components'
-import type { MDXComponents } from 'mdx/types'
+} from '@ageorgedev/design-system/typography/typography-components';
+import type { MDXComponents } from 'mdx/types';
export function useMDXComponents(components: MDXComponents): MDXComponents {
return {
@@ -13,5 +13,5 @@ export function useMDXComponents(components: MDXComponents): MDXComponents {
h2: Heading2,
h3: Heading3,
h4: Heading4,
- }
+ };
}
diff --git a/apps/ageorgedev/src/router.tsx b/apps/ageorgedev/src/router.tsx
index 60f36e5a..c8b5b987 100644
--- a/apps/ageorgedev/src/router.tsx
+++ b/apps/ageorgedev/src/router.tsx
@@ -1,5 +1,5 @@
-import { createRouter as createTanStackRouter } from '@tanstack/react-router'
-import { routeTree } from './routeTree.gen'
+import { createRouter as createTanStackRouter } from '@tanstack/react-router';
+import { routeTree } from './routeTree.gen';
// import { getContext } from './integrations/tanstack-query/root-provider';
@@ -10,12 +10,12 @@ export function getRouter() {
scrollRestoration: true,
defaultPreload: 'intent',
defaultPreloadStaleTime: 0,
- })
- return router
+ });
+ return router;
}
declare module '@tanstack/react-router' {
interface Register {
- router: ReturnType
+ router: ReturnType;
}
}
diff --git a/apps/ageorgedev/src/routes/__root.tsx b/apps/ageorgedev/src/routes/__root.tsx
index a4065322..7abe5d5f 100644
--- a/apps/ageorgedev/src/routes/__root.tsx
+++ b/apps/ageorgedev/src/routes/__root.tsx
@@ -1,18 +1,18 @@
-import { THEME_INIT_SCRIPT } from '@ageorgedev/design-system/theming/theme-init-script'
+import { THEME_INIT_SCRIPT } from '@ageorgedev/design-system/theming/theme-init-script';
import {
createRootRouteWithContext,
HeadContent,
Scripts,
-} from '@tanstack/react-router'
-import { GlobalProviders } from '../components/GlobalProviders'
-import appCss from '../styles.css?url'
+} from '@tanstack/react-router';
+import { GlobalProviders } from '../components/GlobalProviders';
+import appCss from '../styles.css?url';
// import type { QueryClient } from '@tanstack/react-query';
type MyRouterContext = {
// queryClient?: QueryClient;
- temp?: string
-}
+ temp?: string;
+};
export const Route = createRootRouteWithContext()({
head: () => ({
@@ -36,7 +36,7 @@ export const Route = createRootRouteWithContext()({
],
}),
shellComponent: RootDocument,
-})
+});
function RootDocument({ children }: { children: React.ReactNode }) {
return (
@@ -67,5 +67,5 @@ function RootDocument({ children }: { children: React.ReactNode }) {