Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions uli-website/src/components/molecules/AppShellNew.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import * as React from "react";
import { Grommet, Box } from "grommet";
import { Helmet } from "react-helmet";
import { Theme } from "../atoms/UliCore";
import NavBarNew from "./NavBarNew";
import FooterNew from "./FooterNew";
import { useTranslation } from "react-i18next";
import { useLocation } from "@reach/router";
import Projects from "./Projects";
import UpdatesSection from "./UpdatesSection";
import ResourcesSection from "./ResourcesSection";

export default function AppShellNew({ children }) {
const { i18n } = useTranslation();
const location = useLocation();

/* ---------- Set language from localStorage ---------- */
React.useEffect(() => {
const lang = localStorage.getItem("uli-lang");
if (lang) {
i18n.changeLanguage(lang);
}
}, []);

/* ---------- Page title ---------- */
let fullPath = location.pathname;
if (fullPath.endsWith("/")) {
fullPath = fullPath.slice(0, -1);
}

let title = fullPath.split("/").at(-1);

if (["hi", "en", "ta", "ma"].includes(title)) {
title = "Uli";
}

title = title
.split("-")
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
.join(" ");
Comment on lines +26 to +40
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Ensure a default title for the root path.
When pathname === "/", title becomes an empty string, so the page title/OG title is blank. Add a fallback (e.g., “Uli”).

✅ Suggested fix
-  let title = fullPath.split("/").at(-1);
+  let title = fullPath.split("/").filter(Boolean).at(-1) || "Uli";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let fullPath = location.pathname;
if (fullPath.endsWith("/")) {
fullPath = fullPath.slice(0, -1);
}
let title = fullPath.split("/").at(-1);
if (["hi", "en", "ta", "ma"].includes(title)) {
title = "Uli";
}
title = title
.split("-")
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
.join(" ");
let fullPath = location.pathname;
if (fullPath.endsWith("/")) {
fullPath = fullPath.slice(0, -1);
}
let title = fullPath.split("/").filter(Boolean).at(-1) || "Uli";
if (["hi", "en", "ta", "ma"].includes(title)) {
title = "Uli";
}
title = title
.split("-")
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
.join(" ");
🤖 Prompt for AI Agents
In `@uli-website/src/components/molecules/AppShellNew.jsx` around lines 24 - 38,
The computed title derived from location.pathname can be an empty string for the
root path ("/"), causing blank page/OG titles; modify the logic around
fullPath/title in AppShellNew.jsx (variables fullPath and title) to detect when
title === "" (or fullPath === "/") and set a default like "Uli" before the
language check and capitalization steps so the root page gets a proper fallback
title.


return (
<Grommet theme={Theme}>
<main
style={{
display: "flex",
flexDirection: "column",
minHeight: "100vh",
}}
>
<Helmet>
<meta charSet="utf-8" />
<title>{title}</title>
<link rel="stylesheet" href="/layout.css" />
<link rel="stylesheet" href="https://use.typekit.net/twt1ywc.css" />
<meta property="og:title" content={title} />
<meta name="icon" href="images/favicon-32x32.png" />
<script
Comment on lines +51 to +58
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use <link rel="icon"> instead of a meta tag.
<meta name="icon"> won’t register a favicon in browsers.

✅ Suggested fix
-          <meta name="icon" href="images/favicon-32x32.png" />
+          <link rel="icon" href="images/favicon-32x32.png" />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Helmet>
<meta charSet="utf-8" />
<title>{title}</title>
<link rel="stylesheet" href="/layout.css" />
<link rel="stylesheet" href="https://use.typekit.net/twt1ywc.css" />
<meta property="og:title" content={title} />
<meta name="icon" href="images/favicon-32x32.png" />
<script
<Helmet>
<meta charSet="utf-8" />
<title>{title}</title>
<link rel="stylesheet" href="/layout.css" />
<link rel="stylesheet" href="https://use.typekit.net/twt1ywc.css" />
<meta property="og:title" content={title} />
<link rel="icon" href="images/favicon-32x32.png" />
<script
🤖 Prompt for AI Agents
In `@uli-website/src/components/molecules/AppShellNew.jsx` around lines 49 - 56,
In AppShellNew.jsx inside the Helmet block, replace the incorrect <meta
name="icon" ... /> usage with a proper favicon link element; locate the Helmet
component in the AppShellNew.jsx file and change the meta tag that sets
name="icon" to a link element with rel="icon" and the same href (optionally add
type="image/png") so browsers register the favicon correctly.

defer
data-domain="uli.tattle.co.in"
src="https://plausible.io/js/plausible.js"
></script>
</Helmet>

{/* NEW NAVBAR */}
<NavBarNew />

{/* PAGE CONTENT */}
<Box flex="grow">{children}</Box>
<Projects/>
<UpdatesSection/>
<ResourcesSection/>
Comment on lines +69 to +72
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Page-specific sections are hardcoded inside the app shell.

Projects, UpdatesSection, and ResourcesSection are rendered after {children} on every page that uses AppShellNew. If only the new home page should display these sections, they belong in the page component (e.g., new-home.js), not in the shared shell. Otherwise, navigating to any other page wrapped in AppShellNew will also render these sections.

🤖 Prompt for AI Agents
In `@uli-website/src/components/molecules/AppShellNew.jsx` around lines 69 - 72,
AppShellNew currently hardcodes Projects, UpdatesSection, and ResourcesSection
after {children}, causing those sections to appear on every page; remove these
components from AppShellNew and either (a) move them into the new home page
component (e.g., new-home.js) so only that page renders them, or (b) add a clear
prop/flag on AppShellNew (e.g., showBottomSections) and conditionally render
Projects, UpdatesSection, and ResourcesSection based on that prop; update the
new-home.js page to pass the prop or to include the sections itself and ensure
any references to children in AppShellNew remain unchanged.

{/* FOOTER */}
<FooterNew/>
</main>
</Grommet>
);
}
95 changes: 95 additions & 0 deletions uli-website/src/components/molecules/FooterNew.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import React from "react";
import { Box, Text } from "grommet";

const FooterNew = () => {
return (
<Box
as="footer"
background="#2b2b2b"
pad={{ vertical: "xlarge", horizontal: "large" }}
gap="large"
>
{/* Stay Updated Section */}
<Box align="center" gap="medium">
<Text
size="48px"
weight="bold"
style={{
color: "#ffffff"
}}
>
Stay Updated
</Text>

<Text
size="medium"
textAlign="center"
style={{ maxWidth: "600px", color: "#eaeaea" }}
>
Short Statement about what Uli is <br />
Use Uli to redact slurs and abusive content, archive problematic
content, and collectively push back against online gender based
violence.
Comment on lines +29 to +32
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Placeholder copy will ship to production.

Line 29 reads like a design-file placeholder ("Short Statement about what Uli is"), not final user-facing text. Please replace it with the intended copy or gate it behind a TODO so it isn't accidentally released.

🤖 Prompt for AI Agents
In `@uli-website/src/components/molecules/FooterNew.jsx` around lines 29 - 32, The
footer contains placeholder text in the FooterNew component ("Short Statement
about what Uli is") which will ship to production; locate that JSX fragment in
FooterNew.jsx and either replace the placeholder with the final user-facing copy
(e.g., a concise sentence describing Uli: "Uli helps redact slurs and abusive
content, archive problematic material, and coordinate responses to online
gender‑based violence.") or, if final copy isn't available, wrap or remove the
placeholder by adding an explicit TODO comment and a safe fallback (empty string
or non-visible comment) so the placeholder text is not rendered in production;
update the JSX in FooterNew accordingly.

</Text>

{/* Email Input */}
<Box
direction="row"
align="center"
width="medium"
pad="small"
background="#f6efe6"
round="xsmall"
justify="between"
>
<Text size="large">✉️</Text>

<Text size="small" color="#666">
your email address
</Text>

<Box
pad={{ horizontal: "medium", vertical: "xsmall" }}
background="#000"
round="xsmall"
>
<Text size="small" color="white">
subscribe
</Text>
</Box>
</Box>
Comment on lines +36 to +60
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Email subscription section is non-functional and inaccessible.

This renders a visual mockup of an email input and subscribe button, but there is no actual <input> element, no <form>, and no click/submit handler. Users (and screen readers) will see what looks like an interactive subscription form but cannot interact with it.

If this is intentionally a placeholder for a future feature, consider either:

  • Adding a TODO comment and visually indicating it's "coming soon," or
  • Wiring it up with a real <input> and <button>/<Anchor> so it's keyboard-navigable and functional.

As-is, the subscribe Box (line 51) is not focusable or activatable via keyboard.

Minimal sketch using real form elements
-        <Box
-          direction="row"
-          align="center"
-          width="medium"
-          pad="small"
-          background="#f6efe6"
-          round="xsmall"
-          justify="between"
-        >
-          <Text size="large">✉️</Text>
-
-          <Text size="small" color="#666">
-            your email address
-          </Text>
-
-          <Box
-            pad={{ horizontal: "medium", vertical: "xsmall" }}
-            background="#000"
-            round="xsmall"
-          >
-            <Text size="small" color="white">
-              subscribe
-            </Text>
-          </Box>
-        </Box>
+        {/* TODO: wire up subscription handler */}
+        <Box
+          as="form"
+          direction="row"
+          align="center"
+          width="medium"
+          pad="small"
+          background="#f6efe6"
+          round="xsmall"
+          justify="between"
+          onSubmit={(e) => { e.preventDefault(); /* handle subscribe */ }}
+        >
+          <Text size="large" aria-hidden="true">✉️</Text>
+          <input
+            type="email"
+            placeholder="your email address"
+            aria-label="Email address"
+            style={{ border: "none", background: "transparent", flex: 1, outline: "none" }}
+          />
+          <button
+            type="submit"
+            style={{
+              padding: "4px 16px",
+              background: "#000",
+              color: "#fff",
+              border: "none",
+              borderRadius: "4px",
+              cursor: "pointer",
+            }}
+          >
+            subscribe
+          </button>
+        </Box>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Box
direction="row"
align="center"
width="medium"
pad="small"
background="#f6efe6"
round="xsmall"
justify="between"
>
<Text size="large">✉️</Text>
<Text size="small" color="#666">
your email address
</Text>
<Box
pad={{ horizontal: "medium", vertical: "xsmall" }}
background="#000"
round="xsmall"
>
<Text size="small" color="white">
subscribe
</Text>
</Box>
</Box>
{/* TODO: wire up subscription handler */}
<Box
as="form"
direction="row"
align="center"
width="medium"
pad="small"
background="#f6efe6"
round="xsmall"
justify="between"
onSubmit={(e) => { e.preventDefault(); /* handle subscribe */ }}
>
<Text size="large" aria-hidden="true">✉️</Text>
<input
type="email"
placeholder="your email address"
aria-label="Email address"
style={{ border: "none", background: "transparent", flex: 1, outline: "none" }}
/>
<button
type="submit"
style={{
padding: "4px 16px",
background: "#000",
color: "#fff",
border: "none",
borderRadius: "4px",
cursor: "pointer",
}}
>
subscribe
</button>
</Box>
🤖 Prompt for AI Agents
In `@uli-website/src/components/molecules/FooterNew.jsx` around lines 36 - 60, The
email subscription area in FooterNew.jsx currently uses non-interactive Box/Text
elements and must be replaced or wrapped with real form controls: add a <form>
element with an <input type="email"> (give it a name/placeholder and aria-label)
and replace the subscribe Box with a <button> (or Anchor with role/button) so it
is keyboard-focusable; implement an onSubmit handler (e.g., handleSubscribe) on
the form that validates the email and triggers the subscription logic or a
noop/coming-soon state, and ensure accessible attributes (aria-live for
success/error, required, and proper focus styles) are included. Use the existing
Box/Text layout but swap the static subscribe Box for an actual button and wire
input/button to the handler so screen readers and keyboard users can interact.

</Box>

{/* Bottom Section */}
<Box
direction="row"
justify="between"
align="center"
margin={{ top: "large" }}
>
<Box>
<Text weight="bold" color="white">
Uli by Tattle
</Text>
<Text size="small" color="#cccccc">
Email & Other contacts
</Text>
</Box>

<Box direction="row" gap="medium">
<Text size="small" color="white">
Link 1
</Text>
<Text size="small" color="white">
Link 2
</Text>
<Text size="small" color="white">
Link 3
</Text>
</Box>
</Box>
Comment on lines +64 to +90
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Bottom section uses placeholder links and won't stack on small viewports.

  1. "Link 1 / 2 / 3" and "Email & Other contacts" are clearly placeholder text — please replace with real content or wire them to config/props.
  2. The direction="row" layout on lines 64 and 79 will overflow or get cramped on narrow screens. Consider making it responsive:
Responsive direction example
      <Box
-       direction="row"
+       direction="row-responsive"
        justify="between"
        align="center"
        margin={{ top: "large" }}
      >
🤖 Prompt for AI Agents
In `@uli-website/src/components/molecules/FooterNew.jsx` around lines 64 - 90, The
footer currently uses placeholder text and hard-coded horizontal layouts which
will overflow on small screens; update the FooterNew component to accept
props/config for contact text (e.g., contactText) and an array of links (e.g.,
links) and render them instead of the hard-coded "Link 1/2/3" and "Email & Other
contacts"; also make the two Box containers that currently use direction="row"
(the outer Box with justify="between" and the inner Box with gap="medium")
responsive—switch to a column layout on small viewports by using Grommet’s
ResponsiveContext or a responsive direction prop (e.g.,
direction={['column','row']} or compute direction from ResponsiveContext) so
items stack vertically on narrow screens while remaining row-aligned on larger
screens.

</Box>
);
};

export default FooterNew;
80 changes: 80 additions & 0 deletions uli-website/src/components/molecules/NavBarNew.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import React from "react";
import { Box, Text, ResponsiveContext } from "grommet";
import { NavLink } from "../atoms/UliCore";
import { navigate } from "gatsby";
import { useContext } from "react";
import navbarBg from "../../images/navbar-bg.svg";


export default function NavBarNew() {
const size = useContext(ResponsiveContext);

return (
<>
{/* ---------- TOP SVG STRIP ---------- */}
<Box
height="20px"
style={{
backgroundImage: `url(${navbarBg})`,
backgroundRepeat: "repeat-x",
backgroundPosition: "top left",
backgroundSize: "auto 20px",
}}
/>

{/* ---------- NAVBAR ---------- */}
<Box
align="center"
background="#fff6e9"
pad={{ vertical: "medium", horizontal: "small" }}
>
<Box
width="xlarge"
direction="row"
align="center"
justify="between"
style={{
position: "relative",
}}
>
{/* ---------- LEFT ---------- */}
<Box direction="row" gap="medium">
<NavLink to="/">
<Text size="small">Tattle</Text>
</NavLink>
</Box>

{/* ---------- CENTER LOGO ---------- */}
<Box
style={{
position: size === "small" ? "static" : "absolute",
left: "50%",
transform: size === "small" ? "none" : "translateX(-50%)",
cursor: "pointer",
}}
onClick={() => navigate("/")}
>
Comment on lines +48 to +56
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "NavBarNew.jsx" -type f

Repository: tattle-made/Uli

Length of output: 111


🏁 Script executed:

cat -n uli-website/src/components/molecules/NavBarNew.jsx | head -80

Repository: tattle-made/Uli

Length of output: 2810


Make the center logo keyboard-accessible.
The clickable Box only handles mouse clicks, so keyboard users cannot activate it. Add keyboard semantics and handlers, or use a proper link/button element. The left and right navigation use NavLink for consistency—this should too.

✅ Suggested fix
          <Box
            style={{
              position: size === "small" ? "static" : "absolute",
              left: "50%",
              transform: size === "small" ? "none" : "translateX(-50%)",
              cursor: "pointer",
            }}
+           role="link"
+           tabIndex={0}
+           aria-label="Uli home"
            onClick={() => navigate("/")}
+           onKeyDown={(e) => {
+             if (e.key === "Enter" || e.key === " ") navigate("/");
+           }}
          >

This violates WCAG 2.1 Level A (2.1.1 Keyboard accessibility) since interactive elements must be operable via keyboard.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Box
style={{
position: size === "small" ? "static" : "absolute",
left: "50%",
transform: size === "small" ? "none" : "translateX(-50%)",
cursor: "pointer",
}}
onClick={() => navigate("/")}
>
<Box
style={{
position: size === "small" ? "static" : "absolute",
left: "50%",
transform: size === "small" ? "none" : "translateX(-50%)",
cursor: "pointer",
}}
role="link"
tabIndex={0}
aria-label="Uli home"
onClick={() => navigate("/")}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") navigate("/");
}}
>
🤖 Prompt for AI Agents
In `@uli-website/src/components/molecules/NavBarNew.jsx` around lines 48 - 56, The
centered logo Box is only clickable via mouse (onClick => navigate("/")) and
must be keyboard-accessible; replace the Box with the same NavLink/NavLink-like
component used for left/right navigation or add proper semantics: give the
element tabIndex={0}, role="link" (or use a button/Link), and handle onKeyDown
to call navigate("/") for Enter/Space, ensuring focus styles remain; update the
element around the navigate("/") call (the Box) to match NavLink usage so
keyboard and screen-reader users can activate it.

<img
src="/Uli_Logo.png"
alt="Uli Logo"
style={{ height: "40px" }}
/>
</Box>

{/* ---------- RIGHT ---------- */}
<Box direction="row" gap="medium">
<NavLink to="/contact">
<Text size="small">Contact</Text>
</NavLink>
<NavLink to="/about">
<Text size="small">About us</Text>
</NavLink>
<NavLink to="/data">
<Text size="small">Data Y</Text>
</NavLink>
</Box>
</Box>
</Box>
</>
);
}
70 changes: 70 additions & 0 deletions uli-website/src/components/molecules/ProjectCard.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React from "react";
import projectImage from "../../images/projectImage.svg";

const pixelCutShape =
"polygon(" +
"0% 20%, 3% 20%, 3% 10%, 6% 10%, 6% 0%, " +
"94% 0%, 94% 10%, 97% 10%, 97% 20%, 100% 20%, " +
"100% 80%, 97% 80%, 97% 90%, 94% 90%, 94% 100%, " +
"6% 100%, 6% 90%, 3% 90%, 3% 80%, 0% 80%" +
")";

const ProjectCard = ({ title, description, reverse }) => {
return (
<div
style={{
background: "#fde9d9",
padding: "10px",
clipPath: pixelCutShape,
}}
>
<div
style={{
backgroundColor: "#fde9d9",
padding: "44px 56px",
clipPath: pixelCutShape,
}}
>
<div
style={{
display: "flex",
flexDirection: reverse ? "row-reverse" : "row",
alignItems: "center",
justifyContent: "space-between",
gap: "48px",
}}
>
{/* LEFT ICON */}
<img
src={projectImage}
alt=""
style={{ width: "180px" }}
/>

{/* TEXT */}
<div style={{ maxWidth: "55%" }}>
<h3 style={{ marginBottom: "16px" }}>{title}</h3>

<p style={{ marginBottom: "24px", lineHeight: "1.6" }}>
{description}
</p>

<div style={{ display: "flex", gap: "18px" }}>
<button style={btnStyle}>Install</button>
<button style={btnStyle}>Install</button>
</div>
Comment on lines +52 to +55
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Wire CTA buttons to real actions instead of hardcoded labels.
Both buttons are static and don’t trigger navigation or actions. If these are meant to be real CTAs (per the “links” requirement), thread labels + href/onClick through props and render them conditionally.

🛠️ Example approach
-const ProjectCard = ({ title, description, reverse }) => {
+const ProjectCard = ({ title, description, reverse, primaryCta, secondaryCta }) => {
...
-            <div style={{ display: "flex", gap: "18px" }}>
-              <button style={btnStyle}>Install</button>
-              <button style={btnStyle}>Install</button>
-            </div>
+            <div style={{ display: "flex", gap: "18px" }}>
+              {primaryCta && (
+                <a href={primaryCta.href} style={btnStyle}>
+                  {primaryCta.label}
+                </a>
+              )}
+              {secondaryCta && (
+                <a href={secondaryCta.href} style={btnStyle}>
+                  {secondaryCta.label}
+                </a>
+              )}
+            </div>
🤖 Prompt for AI Agents
In `@uli-website/src/components/molecules/ProjectCard.jsx` around lines 52 - 55,
ProjectCard currently renders two static "Install" buttons; change this to
accept CTA props (e.g., primaryCta, secondaryCta) that include label and either
href or onClick, use the existing btnStyle for styling, and render each button
conditionally only when its prop is present; for link CTAs render an anchor
(<a>) with href and rel/target as needed, otherwise render a <button> with the
onClick handler, and update the JSX where btnStyle is used so labels come from
primaryCta.label / secondaryCta.label instead of hardcoded text.

</div>
</div>
</div>
</div>
);
};

const btnStyle = {
background: "transparent",
border: "2px dashed #000",
padding: "10px 28px",
cursor: "pointer",
};

export default ProjectCard;
52 changes: 52 additions & 0 deletions uli-website/src/components/molecules/Projects.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from "react";
import { projects } from "../../config/projects";
import ProjectCard from "./ProjectCard";
import topBorder from "../../images/ProjectsTitle.svg";
import sideBorder from "../../images/left-border.svg";

const Projects = () => {
return (
<section
style={{
backgroundColor: "#fff6eb",
position: "relative",
}}
>
{/* 🔝 TOP BORDER */}
<div
style={{
height: "50px",
backgroundImage: `url(${topBorder})`,
backgroundSize: "auto 100%",
}}
/>

{/* ⬅️➡️ SIDE BORDERS WRAPPER */}
<div
style={{
backgroundImage: `url(${sideBorder}), url(${sideBorder})`,
backgroundRepeat: "repeat-y, repeat-y",
backgroundPosition: "left top, right top",
backgroundSize: "auto 100%",
padding: "80px 40px",
}}
>
<div
style={{
maxWidth: "1200px",
margin: "0 auto",
display: "flex",
flexDirection: "column",
gap: "40px",
}}
>
{projects.map((project) => (
<ProjectCard key={project.id} {...project} />
))}
</div>
</div>
</section>
);
};

export default Projects;
Loading