feat: scaffold initial frontend project structure and marketing layou…#57
feat: scaffold initial frontend project structure and marketing layou…#57muntasiractive wants to merge 3 commits into
Conversation
|
Skipping CodeAnt AI review — this PR is a back-merge between long-lived branches ( If you want to analyze this anyway (e.g. you resolved conflicts with new logic), comment |
📝 WalkthroughWalkthroughThe landing page now uses configurable WebGL and satellite visualizations, Framer Motion and GSAP animations, a closing CTA, reduced-motion handling, updated marketing copy, and additional theme and scrollbar styling. ChangesLanding animation experience
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Visitor
participant LandingPage
participant Lightfall
participant OGL
participant GSAPScrollTrigger
Visitor->>LandingPage: Open and scroll landing page
LandingPage->>Lightfall: Render animated hero background
Lightfall->>OGL: Initialize shader renderer and canvas
LandingPage->>GSAPScrollTrigger: Register section reveal animations
GSAPScrollTrigger->>LandingPage: Animate hero, cards, stats, and CTA
Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 3❌ Failed checks (3 warnings)
✅ Passed checks (2 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
|
||
| interface OrbitalFieldProps { | ||
| prefersReducedMotion: boolean; | ||
| import React, { useEffect, useLayoutEffect, useRef, useState } from "react"; |
There was a problem hiding this comment.
useState is imported but never used after refactoring the prefersReducedMotion state to useReducedMotion() from Framer Motion. This will produce a lint/TypeScript warning and should be removed.
| import React, { useEffect, useLayoutEffect, useRef, useState } from "react"; | |
| import React, { useEffect, useLayoutEffect, useRef } from "react"; |
Prompt To Fix With AI
This is a comment left during a code review.
Path: frontend/src/pages/LandingPage.tsx
Line: 1
Comment:
**Unused `useState` import**
`useState` is imported but never used after refactoring the `prefersReducedMotion` state to `useReducedMotion()` from Framer Motion. This will produce a lint/TypeScript warning and should be removed.
```suggestion
import React, { useEffect, useLayoutEffect, useRef } from "react";
```
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@frontend/src/components/ui/Lightfall.tsx`:
- Around line 228-235: Update the renderer initialization in the Lightfall
effect to detect WebGL availability before calling new Renderer, and handle the
unavailable-context case by exiting through the existing decorative-background
fallback path. Ensure rendererRef and subsequent WebGL-dependent setup are only
used after a valid context is available.
- Around line 306-331: Update the animation loop in Lightfall’s loop callback so
paused state renders one static frame instead of continuously scheduling RAF
callbacks or skipping rendering. Schedule the next requestAnimationFrame only
while active, while preserving the existing render guards and mouse-dampening
behavior.
In `@frontend/src/pages/LandingPage.tsx`:
- Around line 116-133: Update the Lightfall container in LandingPage to render
Lightfall only when prefersReducedMotion is false, rather than merely disabling
mouseInteraction. Remove pointer-events-none from the container so the canvas
can receive pointermove events and restore pointer interaction for
motion-enabled users.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: ac0fce54-8031-4390-9d4d-8c292bad4440
⛔ Files ignored due to path filters (1)
frontend/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (7)
frontend/package.jsonfrontend/src/components/layouts/MarketingLayout.tsxfrontend/src/components/ui/Lightfall.tsxfrontend/src/components/ui/globe.tsxfrontend/src/components/ui/particles.tsxfrontend/src/pages/LandingPage.tsxfrontend/src/styles/index.css
| const renderer = new Renderer({ | ||
| dpr: dpr ?? (typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1), | ||
| alpha: true, | ||
| antialias: true | ||
| }); | ||
| rendererRef.current = renderer; | ||
| const gl = renderer.gl; | ||
| const canvas = gl.canvas as HTMLCanvasElement; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
curl -fsSL https://unpkg.com/ogl@1.0.11/src/core/Renderer.js |
rg -n -C2 'getContext|unable to create webgl context|this\.gl\.renderer'Repository: 7-Blocks/Kepler
Length of output: 685
🏁 Script executed:
#!/bin/bash
sed -n '180,280p' frontend/src/components/ui/Lightfall.tsxRepository: 7-Blocks/Kepler
Length of output: 3182
Handle missing WebGL context before creating Renderer. new Renderer() can throw when WebGL is unavailable, so this decorative effect won’t fall back to the section background.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/ui/Lightfall.tsx` around lines 228 - 235, Update the
renderer initialization in the Lightfall effect to detect WebGL availability
before calling new Renderer, and handle the unavailable-context case by exiting
through the existing decorative-background fallback path. Ensure rendererRef and
subsequent WebGL-dependent setup are only used after a valid context is
available.
| const loop = (t: number) => { | ||
| rafRef.current = requestAnimationFrame(loop); | ||
| uniforms.iTime.value = t * 0.001; | ||
| if (mouseDampening > 0) { | ||
| if (!lastTimeRef.current) lastTimeRef.current = t; | ||
| const dt = (t - lastTimeRef.current) / 1000; | ||
| lastTimeRef.current = t; | ||
| const tau = Math.max(1e-4, mouseDampening); | ||
| let factor = 1 - Math.exp(-dt / tau); | ||
| if (factor > 1) factor = 1; | ||
| const target = mouseTargetRef.current; | ||
| const cur = uniforms.iMouse.value as number[]; | ||
| cur[0] += (target[0] - cur[0]) * factor; | ||
| cur[1] += (target[1] - cur[1]) * factor; | ||
| } else { | ||
| lastTimeRef.current = t; | ||
| } | ||
| if (!paused && programRef.current && meshRef.current) { | ||
| try { | ||
| renderer.render({ scene: meshRef.current }); | ||
| } catch (e) { | ||
| console.error(e); | ||
| } | ||
| } | ||
| }; | ||
| rafRef.current = requestAnimationFrame(loop); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Make paused stop the RAF without blanking the canvas.
Line 307 schedules callbacks continuously, but Line 323 skips every render while paused. Render one static frame when paused and only schedule RAF while active.
Proposed fix
const loop = (t: number) => {
- rafRef.current = requestAnimationFrame(loop);
uniforms.iTime.value = t * 0.001;
// existing mouse dampening logic...
- if (!paused && programRef.current && meshRef.current) {
+ if (programRef.current && meshRef.current) {
try {
renderer.render({ scene: meshRef.current });
} catch (e) {
console.error(e);
}
}
+ rafRef.current = requestAnimationFrame(loop);
};
- rafRef.current = requestAnimationFrame(loop);
+
+ if (paused) {
+ renderer.render({ scene: mesh });
+ } else {
+ rafRef.current = requestAnimationFrame(loop);
+ }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/ui/Lightfall.tsx` around lines 306 - 331, Update the
animation loop in Lightfall’s loop callback so paused state renders one static
frame instead of continuously scheduling RAF callbacks or skipping rendering.
Schedule the next requestAnimationFrame only while active, while preserving the
existing render guards and mouse-dampening behavior.
|
Thank you @muntasiractive for your contribution, we really appreciate the effort you've put into this redesign. The new design looks clean, and we especially like the direction you took with it. However, we already have a strong 3D homepage design (see the attached screenshot), so we aren't planning to replace it at this time. Because of that, I can't merge this PR.
We'd love for you to open another PR that focuses on making the rest of the Kepler web app match the same theme, colors, and overall design language as the current homepage. Please make sure to use Magic UI components throughout where applicable, as that's a requirement for the project. Also, the 7Blocks team really likes the Kepler logo you created, and we'd like to move forward with that logo. Great work on it! Thanks again for your contribution, and we look forward to your next PR. |
|
So, you want me to recreate the home page with a 3D globe in the hero section? |
|
Checkout this @krishkhinchi
|
| const validSats = satData.filter( | ||
| (d) => !isNaN(d.lat) && !isNaN(d.lng) && !isNaN(d.alt) | ||
| ); | ||
| world.particlesData([validSats]); |
There was a problem hiding this comment.
The
particlesData call wraps validSats in an extra array, so globe.gl receives a single-element array whose sole item is the whole SatDataItem[] array. When globe.gl iterates the data and tries to read d["lat"] / d["lng"] / d["alt"] on that array, it gets undefined for all fields, so no satellite particles will ever be rendered.
| world.particlesData([validSats]); | |
| world.particlesData(validSats); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: frontend/src/components/GlobeBackground.tsx
Line: 126
Comment:
The `particlesData` call wraps `validSats` in an extra array, so globe.gl receives a single-element array whose sole item is the whole `SatDataItem[]` array. When globe.gl iterates the data and tries to read `d["lat"]` / `d["lng"]` / `d["alt"]` on that array, it gets `undefined` for all fields, so no satellite particles will ever be rendered.
```suggestion
world.particlesData(validSats);
```
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@frontend/src/components/GlobeBackground.tsx`:
- Around line 96-135: Update the frameTicker loop to accept RAF timestamps and
calculate elapsed time, using that delta to scale globe rotation consistently
across refresh rates. Accumulate elapsed time and only propagate the satellite
catalog and call world.particlesData when the fixed update interval has elapsed,
while continuing to schedule RAF frames and preserving the existing validity
filtering.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: beae4ef5-ba7d-4a73-a591-9fc0fdc4d7f0
⛔ Files ignored due to path filters (1)
frontend/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (4)
frontend/package.jsonfrontend/public/datasets/space-track-leo.txtfrontend/src/components/GlobeBackground.tsxfrontend/src/pages/LandingPage.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
- frontend/src/pages/LandingPage.tsx
- frontend/package.json
| // Time ticker loop | ||
| let time = new Date(); | ||
|
|
||
| const frameTicker = () => { | ||
| if (!isMounted) return; | ||
|
|
||
| time = new Date(+time + TIME_STEP); | ||
|
|
||
| // Update satellite positions | ||
| const gmst = satellite.gstime(time); | ||
| satData.forEach((d) => { | ||
| const eci = satellite.propagate(d.satrec, time); | ||
| if (eci && typeof eci === "object" && eci.position) { | ||
| const position = eci.position as satellite.PositionAndVelocity["position"]; | ||
| if (typeof position === "object" && "x" in position) { | ||
| const gdPos = satellite.eciToGeodetic(position, gmst); | ||
| d.lat = radiansToDegrees(gdPos.latitude); | ||
| d.lng = radiansToDegrees(gdPos.longitude); | ||
| d.alt = gdPos.height / EARTH_RADIUS_KM; | ||
| } | ||
| } else { | ||
| d.lat = NaN; | ||
| d.lng = NaN; | ||
| d.alt = NaN; | ||
| } | ||
| }); | ||
|
|
||
| const validSats = satData.filter( | ||
| (d) => !isNaN(d.lat) && !isNaN(d.lng) && !isNaN(d.alt) | ||
| ); | ||
| world.particlesData([validSats]); | ||
|
|
||
| // Rotate globe slowly | ||
| const currentPov = world.pointOfView(); | ||
| world.pointOfView({ | ||
| lng: currentPov.lng + 0.05, | ||
| }); | ||
|
|
||
| animationFrameRef.current = requestAnimationFrame(frameTicker); | ||
| }; |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
awk 'substr($0, 1, 2) == "1 " { count++ } END { print count }' \
frontend/public/datasets/space-track-leo.txtRepository: 7-Blocks/Kepler
Length of output: 159
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- GlobeBackground.tsx (relevant range) ---'
nl -ba frontend/src/components/GlobeBackground.tsx | sed -n '1,220p'
echo
echo '--- satellite dataset line count / sample shape ---'
python3 - <<'PY'
from pathlib import Path
p = Path('frontend/public/datasets/space-track-leo.txt')
count = 0
sample = []
with p.open() as f:
for i, line in enumerate(f, 1):
if i <= 6:
sample.append(line.rstrip('\n'))
if line.startswith('1 '):
count += 1
print('line_count_starting_with_1_space =', count)
print('first_lines:')
for s in sample:
print(repr(s))
PYRepository: 7-Blocks/Kepler
Length of output: 238
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find the satellite data loader and animation setup in the component.
ast-grep outline frontend/src/components/GlobeBackground.tsx --view expandedRepository: 7-Blocks/Kepler
Length of output: 496
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show the surrounding effect setup to understand how often this runs.
sed -n '1,260p' frontend/src/components/GlobeBackground.tsx | nl -ba | sed -n '1,260p'Repository: 7-Blocks/Kepler
Length of output: 193
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Read the component around the animation loop only.
nl -ba frontend/src/components/GlobeBackground.tsx | sed -n '80,160p'Repository: 7-Blocks/Kepler
Length of output: 193
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- GlobeBackground.tsx around the animation loop ---'
sed -n '1,220p' frontend/src/components/GlobeBackground.tsx | sed -n '1,220p' | awk '{printf "%4d %s\n", NR, $0}' | sed -n '70,170p'
echo
echo '--- Find the dataset loader / fetch path ---'
rg -n "space-track-leo|particlesData|requestAnimationFrame|TIME_STEP|pointOfView|SatDataItem" frontend/src/components/GlobeBackground.tsx frontend/src -SRepository: 7-Blocks/Kepler
Length of output: 6131
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
p = Path('frontend/src/components/GlobeBackground.tsx')
for i, line in enumerate(p.read_text().splitlines(), 1):
if 80 <= i <= 170:
print(f"{i:4d} {line}")
PYRepository: 7-Blocks/Kepler
Length of output: 3227
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
p = Path('frontend/src/components/GlobeBackground.tsx')
for i, line in enumerate(p.read_text().splitlines(), 1):
if 1 <= i <= 220:
if 'particlesData' in line or 'requestAnimationFrame' in line or 'TIME_STEP' in line or 'pointOfView' in line or 'space-track-leo' in line:
print(f"{i:4d} {line}")
PYRepository: 7-Blocks/Kepler
Length of output: 579
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
p = Path('frontend/public/datasets/space-track-leo.txt')
count = 0
valid = 0
with p.open() as f:
for line in f:
if line.startswith('1 '):
count += 1
if line.startswith('2 '):
valid += 1
print('line_count_starting_with_1_space =', count)
print('line_count_starting_with_2_space =', valid)
PYRepository: 7-Blocks/Kepler
Length of output: 235
Throttle globe updates to elapsed time. This propagates the full 16,071-satellite catalog and calls particlesData on every RAF, so the work scales with refresh rate and the animation runs faster on high-Hz displays. Use RAF delta time for rotation and throttle satellite propagation/data uploads to a fixed interval.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/GlobeBackground.tsx` around lines 96 - 135, Update
the frameTicker loop to accept RAF timestamps and calculate elapsed time, using
that delta to scale globe rotation consistently across refresh rates. Accumulate
elapsed time and only propagate the satellite catalog and call
world.particlesData when the fixed update interval has elapsed, while continuing
to schedule RAF frames and preserving the existing validity filtering.
|
@muntasiractive, Do not change the Home page. Instead, use the Home page's design, layout, and styling as the design standard for all other pages to maintain a consistent UI/UX. Additionally, Magic UI must be used throughout the project wherever applicable: its components are mandatory, not optional. |


Description
Redesigned the Home Page to match the About Page’s design language, resolving the merging issues.
Related Issue
Fixed #46
Checklist
Screenshots / Screen Recordings
Breaking Changes
None
ECSoC26 Submission
ECSoC26-L1– BeginnerECSoC26-L2– IntermediateECSoC26-L3– AdvancedSummary by CodeRabbit
Greptile Summary
This PR redesigns the landing page to align with the About page's design language, replacing the previous SVG orbital field with a
LightfallWebGL background and a newGlobeBackgroundcomponent that renders live low-Earth-orbit satellite positions from a TLE dataset.Lightfall.tsx): New OGL-powered WebGL component providing an animated falling-light effect; three new dependencies (ogl,globe.gl,satellite.js) are added to support it and the globe.GlobeBackground.tsx): Fetches and propagates real satellite TLE data each animation frame; contains a data-wrapping bug that prevents particles from rendering (see inline comment).LandingPage.tsx): Replaces the static SVG orbits with the above components, adds Framer Motion parallax/scroll effects, GSAP scroll-triggered entrance animations, a scroll-progress bar, and a new closing CTA section.Confidence Score: 4/5
Safe to merge after fixing the GlobeBackground particle data bug; the rest of the changes are additive UI work.
The
GlobeBackgroundcomponent passes[validSats](an array-wrapped array) toparticlesData, meaning globe.gl sees a single element whoselat/lng/altfields are allundefined— no satellite particles will ever be rendered. Everything else (Lightfall, LandingPage scroll effects, MarketingLayout branding update) is additive and works correctly.frontend/src/components/GlobeBackground.tsx — the one-line
particlesDatafix should be applied before merging to make the satellite visualisation work as intended.Important Files Changed
particlesData([validSats])wraps the data in an extra array so no particles are ever displayed, and the initialsetTimeoutis not cancelled on unmount.pausedandcolors) due to the broaduseEffectdependency array — flagged in prior reviews.useStateimport noted in a prior review.*Firefox scrollbar rule applies app-wide (flagged in prior review).motion/reacttoframer-motionfor consistency; no logic changes.NodeJS.Timeout→ReturnType<typeof setTimeout>for better portability.Sequence Diagram
%%{init: {'theme': 'neutral'}}%% sequenceDiagram participant Browser participant LandingPage participant GlobeBackground participant SpaceTrackTXT as /datasets/space-track-leo.txt participant GlobeGL as globe.gl Browser->>LandingPage: mount LandingPage->>GlobeBackground: render GlobeBackground->>GlobeGL: new Globe(container) GlobeBackground->>SpaceTrackTXT: fetch TLE data SpaceTrackTXT-->>GlobeBackground: raw TLE text GlobeBackground->>GlobeBackground: "parse & filter SatDataItem[]" loop rAF (per frame) GlobeBackground->>GlobeBackground: propagate satellite positions GlobeBackground->>GlobeGL: particlesData(validSats) currently [validSats] GlobeGL-->>Browser: render globe with particles end Browser->>LandingPage: unmount LandingPage->>GlobeBackground: cleanup GlobeBackground->>GlobeBackground: cancelAnimationFrame, clear container%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%% sequenceDiagram participant Browser participant LandingPage participant GlobeBackground participant SpaceTrackTXT as /datasets/space-track-leo.txt participant GlobeGL as globe.gl Browser->>LandingPage: mount LandingPage->>GlobeBackground: render GlobeBackground->>GlobeGL: new Globe(container) GlobeBackground->>SpaceTrackTXT: fetch TLE data SpaceTrackTXT-->>GlobeBackground: raw TLE text GlobeBackground->>GlobeBackground: "parse & filter SatDataItem[]" loop rAF (per frame) GlobeBackground->>GlobeBackground: propagate satellite positions GlobeBackground->>GlobeGL: particlesData(validSats) currently [validSats] GlobeGL-->>Browser: render globe with particles end Browser->>LandingPage: unmount LandingPage->>GlobeBackground: cleanup GlobeBackground->>GlobeBackground: cancelAnimationFrame, clear containerComments Outside Diff (3)
frontend/src/components/ui/Lightfall.tsx, line 284-434 (link)pausedinuseEffectdependency array destroys WebGL context on togglepausedis listed in theuseEffectdependency array, so every time it transitionsfalse → trueor back, the entire WebGL setup (renderer, program, geometry, mesh, canvas) is torn down and rebuilt from scratch. The intended semantic is to skip rendering in the animation loop — not to reset the scene. A consumer that togglespausedwill see the canvas flash and lose any mid-animation state on every toggle.pausedshould be tracked in a ref so the loop can read the latest value without triggering a full teardown.Prompt To Fix With AI
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
frontend/src/components/ui/Lightfall.tsx, line 415-434 (link)colorsarray compared by reference in dependency arraycolorsis an array and is compared by reference in the dependency array. The usage inHeropasses an inline literal —colors={['#00e5ff', '#00707cff', '#9ddfe7ff', '#00e5ff']}— which produces a new reference on every render ofHero. If the parent ever re-renders, the entire WebGL context will be torn down and rebuilt. The array should either be memoized at the call site withuseMemo, orLightfallshould do a deep comparison of thecolorsvalues internally before re-initialising.Prompt To Fix With AI
frontend/src/styles/index.css, line 1132-1137 (link)*scrollbar rule affects the entire applicationThe
scrollbar-width: thin; scrollbar-color: ...rule is applied to the*selector, which overrides scrollbar styling for every element in the app — including the dashboard and any admin UI. Scope this to a marketing-specific class (e.g.,.marketing-layout *) to avoid unintended bleed into non-marketing pages.Prompt To Fix With AI
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix All With AI
Reviews (2): Last reviewed commit: "Merge branch '7-Blocks:main' into main" | Re-trigger Greptile