Skip to content

Comments

new#1911

Closed
nizzyabi wants to merge 2 commits intostagingfrom
new-zero
Closed

new#1911
nizzyabi wants to merge 2 commits intostagingfrom
new-zero

Conversation

@nizzyabi
Copy link
Collaborator

@nizzyabi nizzyabi commented Aug 4, 2025

READ CAREFULLY THEN REMOVE

Remove bullet points that are not relevant.

PLEASE REFRAIN FROM USING AI TO WRITE YOUR CODE AND PR DESCRIPTION. IF YOU DO USE AI TO WRITE YOUR CODE PLEASE PROVIDE A DESCRIPTION AND REVIEW IT CAREFULLY. MAKE SURE YOU UNDERSTAND THE CODE YOU ARE SUBMITTING USING AI.

  • Pull requests that do not follow these guidelines will be closed without review or comment.
  • If you use AI to write your PR description your pr will be close without review or comment.
  • If you are unsure about anything, feel free to ask for clarification.

Description

Please provide a clear description of your changes.


Type of Change

Please delete options that are not relevant.

  • 🐛 Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • 💥 Breaking change (fix or feature with breaking changes)
  • 📝 Documentation update
  • 🎨 UI/UX improvement
  • 🔒 Security enhancement
  • ⚡ Performance improvement

Areas Affected

Please check all that apply:

  • Email Integration (Gmail, IMAP, etc.)
  • User Interface/Experience
  • Authentication/Authorization
  • Data Storage/Management
  • API Endpoints
  • Documentation
  • Testing Infrastructure
  • Development Workflow
  • Deployment/Infrastructure

Testing Done

Describe the tests you've done:

  • Unit tests added/updated
  • Integration tests added/updated
  • Manual testing performed
  • Cross-browser testing (if UI changes)
  • Mobile responsiveness verified (if UI changes)

Security Considerations

For changes involving data or authentication:

  • No sensitive data is exposed
  • Authentication checks are in place
  • Input validation is implemented
  • Rate limiting is considered (if applicable)

Checklist

  • I have read the CONTRIBUTING document
  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • I have commented my code, particularly in complex areas
  • I have updated the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix/feature works
  • All tests pass locally
  • Any dependent changes are merged and published

Additional Notes

Add any other context about the pull request here.

Screenshots/Recordings

Add screenshots or recordings here if applicable.


By submitting this pull request, I confirm that my contribution is made under the terms of the project's license.


Summary by cubic

Added a new marketing site for Zero with About, Team, Contributors, Pricing, Privacy, Terms, and Blog pages, including new layouts, navigation, and team data. Improved the login and signup flows, updated UI components, and enhanced styling and translations.

  • New Features

    • Added Team, Contributors, and Blog pages with dynamic content and author data.
    • Introduced new signup and login forms with provider support.
    • Implemented employee hover cards and overlapping avatars for team display.
  • Refactors

    • Updated navigation, footer, and layout for a consistent dark theme.
    • Simplified and modernized pricing comparison and feature cards.
    • Improved translation files and added new utility hooks and config.

Summary by CodeRabbit

New Features

  • Introduced a blog section with listing and detail pages, including dynamic content rendering and author profiles.
  • Added a team page showcasing all team members with profile cards.
  • Launched a contributors page displaying GitHub contributors in a clean grid.
  • Added a new signup flow with support for multiple authentication providers.
  • Introduced interactive mesh background and decorative hero elements on the homepage.
  • Added FAQ section to the pricing page with accordion interaction.
  • Introduced hover cards for employee profiles and overlapping avatars UI.

UI/UX Improvements

  • Redesigned About, Privacy, Terms, and Pricing pages for a simplified, dark-themed experience.
  • Updated navigation and footer to be more modular and visually consistent.
  • Overhauled login and 404 pages with modern, branded layouts.
  • Enhanced comparison tables and pricing cards with iconography and clearer calls to action.

Localization

  • Reformatted pluralization entries and removed unused keys in multiple language files for improved consistency.

Chores

  • Added new dependencies for UI components, icons, MDX/markdown support, and utility libraries.
  • Updated Tailwind CSS configuration with new colors, animations, and utilities.

Documentation

  • Added three new blog posts covering product updates, rebranding, and modernization.

Bug Fixes

  • Improved platform detection for keyboard shortcuts to ensure SSR safety.

@jazzberry-ai
Copy link

jazzberry-ai bot commented Aug 4, 2025

An error occured.

This error may be due to rate limits. If this error persists, please email us.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 4, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

This update delivers a major overhaul of the Zero Mail app's UI, structure, and features. It introduces a new blog system, rebrands and simplifies core pages (about, contributors, privacy, terms, pricing), launches a new signup flow, refactors navigation, and implements a dynamic, interactive mesh background on the homepage. Numerous localization files are reformatted, and new utility modules, UI components, and dependencies are added.

Changes

Cohort / File(s) Change Summary
Auth/Login & Signup Overhaul
apps/mail/app/(auth)/login/login-client.tsx, apps/mail/app/(auth)/login/page.tsx, apps/mail/components/login-form.tsx, apps/mail/app/(auth)/signup/error-message.tsx, apps/mail/app/(auth)/signup/page.tsx, apps/mail/components/signup-client.tsx, apps/mail/components/signup-form.tsx
Major refactor of login and signup flows: new conditional rendering for login, new signup page and client, error handling, and modularized social login/signup forms.
Full-width Page Redesigns
apps/mail/app/(full-width)/about.tsx, apps/mail/app/(full-width)/contributors.tsx, apps/mail/app/(full-width)/layout.tsx, apps/mail/app/(full-width)/pricing.tsx, apps/mail/app/(full-width)/privacy.tsx, apps/mail/app/(full-width)/team.tsx, apps/mail/app/(full-width)/terms.tsx
Complete redesign and simplification of about, contributors, pricing, privacy, terms, and new team page. Layout now sets dark theme, updates document title dynamically, and features new investor/team grids and FAQ.
Blog System Addition
apps/mail/app/(routes)/blog/[slug]/+types/page.ts, apps/mail/app/(routes)/blog/[slug]/page.tsx, apps/mail/app/(routes)/blog/page.tsx, apps/mail/lib/mdx-utils.ts, apps/mail/data/employees.ts, apps/mail/hooks/use-page-title.ts, apps/mail/public/blog/posts/*
Introduces a full blog system: blog list, individual post pages, MDX parsing utilities, employee data for author info, and three initial blog posts.
Homepage & Footer Revamp
apps/mail/components/home/HomeContent.tsx, apps/mail/components/home/footer.tsx, apps/mail/public/mesh.json
Homepage now features a dynamic UnicornStudio mesh background, simplified hero content, and a fully reworked, data-driven footer with new badges and social links.
Navigation & UI Components
apps/mail/components/navigation.tsx, apps/mail/components/ui/navigation-menu.tsx, apps/mail/components/ui/accordion.tsx, apps/mail/components/ui/button.tsx, apps/mail/components/ui/employee-hover-card.tsx, apps/mail/components/ui/overlapping-avatars.tsx
Navigation replaced with Radix-based menu, new/updated accordion components (including light/dark variants), button styling tweaks, new employee hover card and overlapping avatars components.
Pricing Components Refactor
apps/mail/components/pricing/comparision.tsx, apps/mail/components/pricing/pricing-card.tsx
Pricing cards and comparison table refactored for new tiers, iconography, and data-driven rendering. Annual billing removed.
Routing & Config Updates
apps/mail/app/routes.ts, apps/mail/lib/site-config.ts, apps/mail/tailwind.config.ts, apps/mail/config/shortcuts.ts, apps/mail/app/root.tsx
Routes reorganized for new layouts and pages, site config updated, Tailwind extended with new colors/animations, platform detection utilities added, and 404 page restyled.
Localization Files
apps/mail/messages/*.json
Pluralization arrays reformatted for readability, minor translation corrections, and removal of unused keys like "openInNewTab" and "snoozed" across all supported languages.
Dependency Additions
apps/mail/package.json, package.json
Adds dependencies for Radix UI, Heroicons, MDX, markdown processing, and classnames.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Browser
    participant ZeroMailApp
    participant UnicornStudio
    participant BlogAPI

    User->>Browser: Navigates to homepage
    Browser->>ZeroMailApp: Loads HomeContent
    ZeroMailApp->>UnicornStudio: Dynamically load and init mesh background
    User->>ZeroMailApp: Clicks "Create a Free Account"
    ZeroMailApp->>ZeroMailApp: Render SignupClient
    ZeroMailApp->>ZeroMailApp: Fetch providers, render signup form

    User->>Browser: Navigates to /blog
    Browser->>ZeroMailApp: Loads BlogPage
    ZeroMailApp->>BlogAPI: fetchAllBlogPosts()
    BlogAPI-->>ZeroMailApp: Blog post list
    ZeroMailApp->>Browser: Render blog grid

    User->>Browser: Clicks blog post
    Browser->>ZeroMailApp: Loads BlogSlugPage
    ZeroMailApp->>BlogAPI: fetchBlogPost(slug)
    BlogAPI-->>ZeroMailApp: Blog post content
    ZeroMailApp->>Browser: Render blog post with author info
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~90+ minutes

Possibly related PRs

  • Mail-0/Zero#564: Also modifies login-client.tsx with error handling and conditional rendering logic—directly related to this PR's login UI changes.
  • Mail-0/Zero#1279: Refactors login-client.tsx with error alert logic, related to this PR's login UI and error handling updates.
  • Mail-0/Zero#1030: Refactors pricing card component and logic, directly related to this PR's pricing UI overhaul.

Suggested reviewers

  • MrgSub

Poem

Zero's new look, a cosmic delight,
Meshes swirl and blog posts ignite.
Contributors shine in a grid so neat,
Pricing's revamped, the team's complete.
Login flows smooth, the signup's a breeze—
Elon says, "This is how you seize
The future of mail, with style and ease." 🚀

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch new-zero

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@nizzyabi nizzyabi marked this pull request as draft August 4, 2025 14:22
@coderabbitai coderabbitai bot requested a review from MrgSub August 4, 2025 14:29
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

cubic analysis

35 issues found across 63 files • Review in cubic

React with 👍 or 👎 to teach cubic. You can also tag @cubic-dev-ai to give feedback, ask questions, or re-run the review.

"archived": "Archiviert",
"failedToArchive": "Archivierung fehlgeschlagen",
"openInNewTab": "In neuem Tab öffnen"
"failedToArchive": "Archivierung fehlgeschlagen"
Copy link
Contributor

Choose a reason for hiding this comment

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

The translation key 'openInNewTab' was removed from the 'mail' object. If this key is still referenced in the UI or code, this will cause missing translations or runtime errors. Please ensure this removal is intentional and update the PR description accordingly, or restore the key if it is still needed.

Prompt for AI agents
Address the following comment on apps/mail/messages/de.json at line 393:

<comment>The translation key &#39;openInNewTab&#39; was removed from the &#39;mail&#39; object. If this key is still referenced in the UI or code, this will cause missing translations or runtime errors. Please ensure this removal is intentional and update the PR description accordingly, or restore the key if it is still needed.</comment>

<file context>
@@ -370,8 +390,7 @@
       &quot;failedToMute&quot;: &quot;Stummschaltung fehlgeschlagen&quot;,
       &quot;failedToUnmute&quot;: &quot;Aufhebung der Stummschaltung fehlgeschlagen&quot;,
       &quot;archived&quot;: &quot;Archiviert&quot;,
-      &quot;failedToArchive&quot;: &quot;Archivierung fehlgeschlagen&quot;,
-      &quot;openInNewTab&quot;: &quot;In neuem Tab öffnen&quot;
+      &quot;failedToArchive&quot;: &quot;Archivierung fehlgeschlagen&quot;
     },
     &quot;units&quot;: {
</file context>
Suggested change
"failedToArchive": "Archivierung fehlgeschlagen"
"failedToArchive": "Archivierung fehlgeschlagen",
"openInNewTab": "In neuem Tab öffnen"

role: "CTO & Co-Founder",
location: {
city: "Vancouver",
country: "BC"
Copy link
Contributor

Choose a reason for hiding this comment

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

"BC" is a province abbreviation, not a country; this makes the location data inaccurate.

Prompt for AI agents
Address the following comment on apps/mail/data/employees.ts at line 46:

<comment>&quot;BC&quot; is a province abbreviation, not a country; this makes the location data inaccurate.</comment>

<file context>
@@ -0,0 +1,184 @@
+export interface Employee {
+  id: string;
+  fullName: string;
+  role: string;
+  location: {
+    city: string;
+    country: string;
+  };
+  timezone: string;
</file context>
Suggested change
country: "BC"
country: "Canada"

role: "CEO & Co-Founder",
location: {
city: "San Francisco",
country: "CA"
Copy link
Contributor

Choose a reason for hiding this comment

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

"CA" is a state abbreviation, not a country; this will surface incorrect location data wherever the record is used.

Prompt for AI agents
Address the following comment on apps/mail/data/employees.ts at line 27:

<comment>&quot;CA&quot; is a state abbreviation, not a country; this will surface incorrect location data wherever the record is used.</comment>

<file context>
@@ -0,0 +1,184 @@
+export interface Employee {
+  id: string;
+  fullName: string;
+  role: string;
+  location: {
+    city: string;
+    country: string;
+  };
+  timezone: string;
</file context>
Suggested change
country: "CA"
country: "USA"

"countPlural=0": "ответы",
"countPlural=one": "# ответ",
"countPlural=other": "# ответов"
"countPlural=other": "# ответы"
Copy link
Contributor

Choose a reason for hiding this comment

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

The plural form for "countPlural=other" in "replies" should be "# ответов" instead of "# ответы" to match correct Russian grammar for pluralization.

Prompt for AI agents
Address the following comment on apps/mail/messages/ru.json at line 340:

<comment>The plural form for &quot;countPlural=other&quot; in &quot;replies&quot; should be &quot;# ответов&quot; instead of &quot;# ответы&quot; to match correct Russian grammar for pluralization.</comment>

<file context>
@@ -312,12 +327,17 @@
     &quot;mail&quot;: {
       &quot;replies&quot;: [
         {
-          &quot;declarations&quot;: [&quot;input count&quot;, &quot;local countPlural = count: plural&quot;],
-          &quot;selectors&quot;: [&quot;countPlural&quot;],
+          &quot;declarations&quot;: [
+            &quot;input count&quot;,
+            &quot;local countPlural = count: plural&quot;
+          ],
</file context>
Suggested change
"countPlural=other": "# ответы"
"countPlural=other": "# ответов"

"countPlural=0": "Добавьте заметки",
"countPlural=one": "# заметка",
"countPlural=other": "# заметок"
"countPlural=other": "# заметки"
Copy link
Contributor

Choose a reason for hiding this comment

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

The plural form for "countPlural=other" in "noteCount" should be "# заметок" instead of "# заметки" to match correct Russian grammar for pluralization.

Prompt for AI agents
Address the following comment on apps/mail/messages/ru.json at line 272:

<comment>The plural form for &quot;countPlural=other&quot; in &quot;noteCount&quot; should be &quot;# заметок&quot; instead of &quot;# заметки&quot; to match correct Russian grammar for pluralization.</comment>

<file context>
@@ -249,12 +259,17 @@
       &quot;search&quot;: &quot;Найти заметки...&quot;,
       &quot;noteCount&quot;: [
         {
-          &quot;declarations&quot;: [&quot;input count&quot;, &quot;local countPlural = count: plural&quot;],
-          &quot;selectors&quot;: [&quot;countPlural&quot;],
+          &quot;declarations&quot;: [
+            &quot;input count&quot;,
+            &quot;local countPlural = count: plural&quot;
+          ],
</file context>
Suggested change
"countPlural=other": "# заметки"
"countPlural=other": "# заметок"

flex items-center justify-center text-xs font-medium text-gray-600 dark:text-gray-300
shadow-lg
`}
style={{ zIndex: 0 }}
Copy link
Contributor

Choose a reason for hiding this comment

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

Rule violated: Detect React performance bottlenecks and rule breaking

Defining object literals directly in JSX props (such as the 'style' prop) causes a new object to be created on every render, leading to unnecessary re-renders. Move the style object outside the JSX or memoize it to avoid performance issues.

Prompt for AI agents
Address the following comment on apps/mail/components/ui/overlapping-avatars.tsx at line 65:

<comment>Defining object literals directly in JSX props (such as the &#39;style&#39; prop) causes a new object to be created on every render, leading to unnecessary re-renders. Move the style object outside the JSX or memoize it to avoid performance issues.</comment>

<file context>
@@ -0,0 +1,74 @@
+import React from &#39;react&#39;;
+import { getAllEmployees, Employee } from &#39;@/data/employees&#39;;
+import { EmployeeHoverCard } from &#39;./employee-hover-card&#39;;
+
+interface OverlappingAvatarsProps {
+  maxDisplay?: number;
+  size?: &#39;sm&#39; | &#39;md&#39; | &#39;lg&#39;;
+  className?: string;
+}
</file context>

{/* Investors Grid */}
<div className="grid grid-cols-2 lg:grid-cols-4 gap-8 justify-items-center">
{investors.map((investor, index) => {
const totalInvestors = investors.length;
Copy link
Contributor

Choose a reason for hiding this comment

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

Rule violated: Prevent Side Effects or Logic Inside Render Functions or Loops

Non-trivial logic (array length calculation, modulo, and conditional logic) is performed inside the React render loop. Move this logic outside the render function or loop to prevent performance issues and repeated computation.

Prompt for AI agents
Address the following comment on apps/mail/app/(full-width)/about.tsx at line 99:

<comment>Non-trivial logic (array length calculation, modulo, and conditional logic) is performed inside the React render loop. Move this logic outside the render function or loop to prevent performance issues and repeated computation.</comment>

<file context>
@@ -1,159 +1,170 @@
-import { Card, CardHeader, CardTitle } from &#39;@/components/ui/card&#39;;
-import { Github, Mail, ArrowLeft } from &#39;lucide-react&#39;;
-import { Navigation } from &#39;@/components/navigation&#39;;
+import { useTheme } from &#39;next-themes&#39;;
+import { useEffect } from &#39;react&#39;;
 import { Button } from &#39;@/components/ui/button&#39;;
-import Footer from &#39;@/components/home/footer&#39;;
 import React from &#39;react&#39;;
 
</file context>

size = 'md',
className = ''
}) => {
const allEmployees = getAllEmployees();
Copy link
Contributor

Choose a reason for hiding this comment

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

Rule violated: Prevent Side Effects or Logic Inside Render Functions or Loops

Calling getAllEmployees() directly inside the render function can cause repeated side effects or performance issues if it is not a pure, trivial function. Move this logic to a useEffect or memoization hook to prevent unnecessary executions.

Prompt for AI agents
Address the following comment on apps/mail/components/ui/overlapping-avatars.tsx at line 16:

<comment>Calling getAllEmployees() directly inside the render function can cause repeated side effects or performance issues if it is not a pure, trivial function. Move this logic to a useEffect or memoization hook to prevent unnecessary executions.</comment>

<file context>
@@ -0,0 +1,74 @@
+import React from &#39;react&#39;;
+import { getAllEmployees, Employee } from &#39;@/data/employees&#39;;
+import { EmployeeHoverCard } from &#39;./employee-hover-card&#39;;
+
+interface OverlappingAvatarsProps {
+  maxDisplay?: number;
+  size?: &#39;sm&#39; | &#39;md&#39; | &#39;lg&#39;;
+  className?: string;
+}
</file context>

) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5">
{blogPosts.map((post, index) => {
const author = post.authorId ? getEmployeeById(post.authorId) : null;
Copy link
Contributor

Choose a reason for hiding this comment

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

Rule violated: Prevent Side Effects or Logic Inside Render Functions or Loops

Calling getEmployeeById inside the render loop can cause repeated side effects or performance issues. Move all data lookups or non-trivial logic outside the render function or loop and precompute them before rendering.

Prompt for AI agents
Address the following comment on apps/mail/app/(routes)/blog/page.tsx at line 68:

<comment>Calling getEmployeeById inside the render loop can cause repeated side effects or performance issues. Move all data lookups or non-trivial logic outside the render function or loop and precompute them before rendering.</comment>

<file context>
@@ -0,0 +1,154 @@
+
+import { Button } from &#39;@/components/ui/button&#39;;
+import { Badge } from &#39;@/components/ui/badge&#39;;
+import { Calendar, Clock, ArrowRight, User } from &#39;lucide-react&#39;;
+import { Link } from &#39;react-router&#39;;
+
+
+import { useTheme } from &#39;next-themes&#39;;
+import { useEffect, useState } from &#39;react&#39;;
</file context>

<div
className="text-[#a1a1aa] leading-relaxed text-lg [&>h1]:text-4xl [&>h1]:font-bold [&>h1]:text-white [&>h1]:mb-6 [&>h1]:mt-12 [&>h1]:leading-tight [&>h2]:text-2xl [&>h2]:font-bold [&>h2]:text-white [&>h2]:mb-5 [&>h2]:mt-10 [&>h2]:leading-tight [&>h3]:text-xl [&>h3]:font-semibold [&>h3]:text-white [&>h3]:mb-4 [&>h3]:mt-8 [&>h3]:leading-tight [&>p]:mb-6 [&>p]:leading-relaxed [&>p]:text-[#a1a1aa] [&>p]:text-lg [&>ul]:mb-6 [&>ul]:ml-6 [&>li]:mb-3 [&>li]:list-disc [&>li]:leading-relaxed [&>li]:text-[#a1a1aa] [&>li]:text-lg [&>strong]:text-white [&>strong]:font-semibold [&>em]:text-white/80 [&>a]:text-blue-400 [&>a]:hover:text-blue-300 [&>a]:underline [&>a]:underline-offset-2 [&>hr]:border-white/10 [&>hr]:my-12"
dangerouslySetInnerHTML={{
__html: post.content
Copy link
Contributor

Choose a reason for hiding this comment

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

Rule violated: Prevent Side Effects or Logic Inside Render Functions or Loops

Extensive Markdown-to-HTML parsing is executed directly inside the component’s render path. This non-trivial processing violates the guideline “Prevent Side Effects or Logic Inside Render Functions or Loops” (section: heavy logic in render) because it runs on every re-render, degrading performance. Move this transformation to useMemo or compute it server-side.

Prompt for AI agents
Address the following comment on apps/mail/app/(routes)/blog/[slug]/page.tsx at line 119:

<comment>Extensive Markdown-to-HTML parsing is executed directly inside the component’s render path. This non-trivial processing violates the guideline “Prevent Side Effects or Logic Inside Render Functions or Loops” (section: heavy logic in render) because it runs on every re-render, degrading performance. Move this transformation to useMemo or compute it server-side.</comment>

<file context>
@@ -0,0 +1,153 @@
+import { useLoaderData } from &#39;react-router&#39;;
+import { useEffect } from &#39;react&#39;;
+import { useTheme } from &#39;next-themes&#39;;
+import { getEmployeeById } from &#39;@/data/employees&#39;;
+import { EmployeeHoverCard } from &#39;@/components/ui/employee-hover-card&#39;;
+import { fetchBlogPost, type BlogPost } from &#39;@/lib/mdx-utils&#39;;
+import type { Route } from &#39;./+types/page&#39;;
+
+export async function clientLoader({ params }: Route.ClientLoaderArgs) {
</file context>

@jazzberry-ai
Copy link

jazzberry-ai bot commented Aug 4, 2025

An error occured.

This error may be due to rate limits. If this error persists, please email us.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 63

🔭 Outside diff range comments (16)
apps/mail/messages/fa.json (4)

168-179: Missing numeric placeholder – users won’t see the count 🚀

The new attachmentCount pattern lists the plural categories but never injects the actual number (#). Persian users will just read “پیوست” regardless of whether there are 2 or 200 attachments. We’re aiming for Mars-grade UX here—let’s actually show the number.

-            "countPlural=one": "پیوست",
-            "countPlural=other": "پیوست"
+            "countPlural=one": "# پیوست",
+            "countPlural=other": "# پیوست"

184-197: Same blind spot for fileCount – number vanishes into the void

Mirror image of the previous issue: the fileCount message drops #, so the figure never leaves the launchpad. Patch it the same way:

-            "countPlural=one": "فایل",
-            "countPlural=other": "فایل"
+            "countPlural=one": "# فایل",
+            "countPlural=other": "# فایل"

330-341: Zero category missing # again

replies zero state is “پاسخ‌ها” with no quantity. Consistency and clarity matter—even at light-speed. Suggest adding a numeral or adopting the pattern from noteCount.

-            "countPlural=0": "پاسخ‌ها",
+            "count=0": "۰ پاسخ",

262-275: Correct zero-case selector and translation in Persian plural

  • Location: apps/mail/messages/fa.json (lines 262–275)
  • Change the invalid countPlural=0 selector (never matches a plural category) to an explicit zero-case on the raw variable.
  • Replace the imperative “افزودن یادداشت‌ها” with the numeric count “۰ یادداشت” for consistency.
           "match": {
-            "countPlural=0": "افزودن یادداشت‌ها",
+            "count=0": "۰ یادداشت",
             "countPlural=one": "# یادداشت",
             "countPlural=other": "# یادداشت"
           }
apps/mail/messages/cs.json (2)

166-179: Missing {count} will hide the number – not very rocket-grade telemetry.

The attachmentCount strings no longer interpolate {count}, so UI will output plain “příloha/příloh” with zero context. Users deserve the actual number.

-            "countPlural=0": "příloh",
-            "countPlural=one": "příloha",
-            "countPlural=other": "příloh"
+            "countPlural=0": "{count} příloh",
+            "countPlural=one": "{count} příloha",
+            "countPlural=other": "{count} příloh"

182-195: Same telemetry gap for fileCount.

Keep the data visible; add {count} as above.

-            "countPlural=0": "souborů",
-            "countPlural=one": "soubor",
-            "countPlural=other": "souborů"
+            "countPlural=0": "{count} souborů",
+            "countPlural=one": "{count} soubor",
+            "countPlural=other": "{count} souborů"
apps/mail/messages/zh_TW.json (1)

392-395: Action Required: Align “openInNewTab” Across All Locales

Our scan shows that the openInNewTab key is still present in several translation files but missing from most others. You’ll need to decide whether to fully remove this UI string or restore it everywhere.

Files containing "openInNewTab" (line 394):

  • apps/mail/messages/tr.json
  • apps/mail/messages/pl.json
  • apps/mail/messages/cs.json
  • apps/mail/messages/zh_TW.json

Next steps (choose one):

  • Remove the "openInNewTab" entry from all above files if the feature has been deprecated.
  • Reintroduce "openInNewTab" with appropriate translations in all other locale files to keep your key sets uniform.
apps/mail/messages/tr.json (1)

338-341: Minor style point – singular already includes “#”.

"countPlural=one": "# yanıt" prints “1 yanıt” fine, but Turkish usually omits the number for singular (“yanıt”). Up to you, but worth a thought.

apps/mail/messages/ja.json (1)

168-190: Plural scaffolding fine, but wording feels off-target.

noteCount falls back to English (“Add notes”, “# note”). Japanese users shouldn't see English here.

-            "countPlural=0": "Add notes"
-            "countPlural=one": "# note"
-            "countPlural=other": "# notes"
+            "countPlural=0": "メモを追加"
+            "countPlural=one": "# 件のメモ"
+            "countPlural=other": "# 件のメモ"

Also applies to: 262-274

apps/mail/messages/ru.json (1)

168-179: Russian declension off-trajectory for plural “attachmentCount”

Numbers >1 should use the genitive plural “вложений”.
Leaving “вложения” after 2,5,7 etc. sounds odd to natives.

-            "countPlural=other": "вложений"
+            "countPlural=other": "вложений"
apps/mail/components/navigation.tsx (3)

2-13: Remove dead imports - we don't carry dead weight to Mars

These imports are no longer used after the refactoring. Clean them up.

 import NavigationMenu from '@/components/ui/navigation-menu';
-import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from '@/components/ui/sheet';
-import { GitHub, Twitter, Discord, LinkedIn, Star } from './icons/icons';
-import { AnimatedNumber } from '@/components/ui/animated-number';
-import { signIn, useSession } from '@/lib/auth-client';
-import { Separator } from '@/components/ui/separator';
-import { useQuery } from '@tanstack/react-query';
-import { Link, useNavigate } from 'react-router';
-import { Button } from '@/components/ui/button';
 import { useState, useEffect } from 'react';
-import { Menu } from 'lucide-react';
 import { cn } from '@/lib/utils';
-import { toast } from 'sonner';

15-75: Remove unused constants - efficiency matters

These resource and link definitions are orphaned code. If they're not used, they're just burning electricity.

Delete all unused constants from lines 15-75 since they're not referenced in the component.


77-102: State management cleanup needed - simplify like SpaceX engines

Multiple unused state variables and API calls. Keep only what's necessary.

 export function Navigation() {
-  const [open, setOpen] = useState(false);
-  const [stars, setStars] = useState(0); // Default fallback value
   const [isScrolled, setIsScrolled] = useState(false);
-  const { data: session } = useSession();
-  const navigate = useNavigate();
-
-  const { data: githubData } = useQuery({
-    queryKey: ['githubStars'],
-    queryFn: async () => {
-      const response = await fetch('https://api.github.com/repos/Mail-0/Zero', {
-        headers: {
-          Accept: 'application/vnd.github.v3+json',
-        },
-      });
-      if (!response.ok) {
-        throw new Error('Failed to fetch GitHub stars');
-      }
-      return response.json() as Promise<GitHubApiResponse>;
-    },
-  });
-
-  useEffect(() => {
-    if (githubData) {
-      setStars(githubData.stargazers_count || 0);
-    }
-  }, [githubData]);
apps/mail/tailwind.config.ts (2)

139-146: Houston, we have a problem - duplicate animation definitions detected

You've defined fadeIn animation twice - once as a keyframe (lines 139-146) and again in the animation list (line 248). This creates a naming collision that could cause unpredictable behavior.

Remove the duplicate or rename one of them:

         gauge_fadeIn: 'gauge_fadeIn 1s ease forwards',
         gauge_fill: 'gauge_fill 1s ease forwards',
         scaleIn: 'scaleIn 200ms ease',
         scaleOut: 'scaleOut 200ms ease',
-        fadeIn: 'fadeIn 200ms ease',
+        fadeInQuick: 'fadeIn 200ms ease',
         fadeOut: 'fadeOut 200ms ease',

Also applies to: 248-248


1-279: Legacy Tailwind config detected - time to upgrade the propulsion system

According to Tailwind v4 best practices, configuration should be done using the @theme directive in CSS files instead of tailwind.config.js. This is like using chemical propulsion when we have electric.

Consider migrating this configuration to CSS using:

/* In your CSS file */
@import "tailwindcss";
@theme {
  /* Your theme configuration here */
}
apps/mail/app/(full-width)/contributors.tsx (1)

68-92: Sequential API calls are like single-stage rockets. Parallelize for speed.

You're fetching contributor pages sequentially when they could be parallel.

const { data: allPages } = useQuery({
  queryFn: async () => {
    const pages = await Promise.all([
      fetch(`https://api.github.com/repos/${REPOSITORY}/contributors?per_page=100&page=1`).then(res => res.json()),
      fetch(`https://api.github.com/repos/${REPOSITORY}/contributors?per_page=100&page=2`).then(res => res.json()),
      fetch(`https://api.github.com/repos/${REPOSITORY}/contributors?per_page=100&page=3`).then(res => res.json())
    ]);
    return pages.flat();
  },
  queryKey: ['all-contributors', REPOSITORY],
});

Comment on lines +128 to +175
const shouldShowSimpleForm = !hasMissingRequiredProviders;

if (shouldShowSimpleForm) {
return (
<>
{/* Left Column - Login Form */}
<div className="flex flex-col h-full">
{/* Logo Section */}
<div className="flex items-center gap-[18px] p-4 pl-6">
<a href="/home" className="flex items-center justify-center w-6 h-6 cursor-pointer">
<div className="w-[48px] h-[48px] cursor-pointer">
<img
src="/white-icon.svg"
alt="Zero"
width={38}
height={38}
className="w-full h-full cursor-pointer"
/>
</div>
</a>
</div>
<div className="flex flex-1 items-center justify-center px-6 md:px-10">
<div className="w-full max-w-xs">
{error && (
<Alert variant="default" className="mb-6 border-orange-500/40 bg-orange-500/10">
<AlertTitle className="text-orange-400">Error</AlertTitle>
<AlertDescription>Failed to log you in. Please try again.</AlertDescription>
</Alert>
)}
<ErrorMessage />
<LoginForm providers={providers} />
</div>
</div>
</div>

{/* Right Column - Image */}
<div className="bg-muted relative hidden lg:block h-full">
<img
src="/couple.jpeg"
alt="Login"
className="absolute inset-0 h-full w-full object-cover brightness-[0.5]"
/>
{/* Overlay gradient for better text readability if needed */}
<div className="absolute inset-0 bg-gradient-to-t from-black/50 to-transparent" />
</div>
</>
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Component complexity approaching escape velocity

This component has grown quite large with multiple conditional rendering paths. Consider extracting the simplified login form into a separate component for better maintainability - just like how we modularize spacecraft systems.

Would you like me to help refactor this into smaller, more focused components?

🤖 Prompt for AI Agents
In apps/mail/app/(auth)/login/login-client.tsx around lines 128 to 175, the
login form rendering block is large and complex, making the component hard to
maintain. To fix this, extract the entire JSX returned inside the
shouldShowSimpleForm condition into a new separate functional component, for
example, SimpleLoginForm. Then replace the original JSX with a call to this new
component. This modularization will improve readability and maintainability by
isolating the login form logic and UI.

Comment on lines 217 to 220
<button
onClick={() => toggleProvider(provider.id)}
className="flex w-full items-center justify-between bg-black/5 px-4 py-3 text-left transition-colors hover:bg-black/10 dark:bg-white/5 dark:hover:bg-white/10"
className="flex w-full items-center justify-between bg-white/5 px-4 py-3 text-left transition-colors hover:bg-white/10"
>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Critical issue: Button without type attribute is like a rocket without a guidance system

The button is missing an explicit type prop. In a form context, this defaults to "submit" which could cause unintended form submissions.

 <button
+  type="button"
   onClick={() => toggleProvider(provider.id)}
   className="flex w-full items-center justify-between bg-white/5 px-4 py-3 text-left transition-colors hover:bg-white/10"
 >
📝 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
<button
onClick={() => toggleProvider(provider.id)}
className="flex w-full items-center justify-between bg-black/5 px-4 py-3 text-left transition-colors hover:bg-black/10 dark:bg-white/5 dark:hover:bg-white/10"
className="flex w-full items-center justify-between bg-white/5 px-4 py-3 text-left transition-colors hover:bg-white/10"
>
<button
type="button"
onClick={() => toggleProvider(provider.id)}
className="flex w-full items-center justify-between bg-white/5 px-4 py-3 text-left transition-colors hover:bg-white/10"
>
🧰 Tools
🪛 Biome (2.1.2)

[error] 217-220: Provide an explicit type prop for the button element.

The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset

(lint/a11y/useButtonType)

🤖 Prompt for AI Agents
In apps/mail/app/(auth)/login/login-client.tsx around lines 217 to 220, the
button element lacks an explicit type attribute, causing it to default to
"submit" in a form context and potentially trigger unintended form submissions.
Add a type="button" attribute to the button element to ensure it does not submit
the form when clicked.

Comment on lines +23 to +27
useEffect(() => {
if (error && isErrorToast(error)) {
toast.error(m[`errorMessages.${error}`]());
}
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Houston, we have a major problem with this useEffect!

This useEffect is missing its dependency array, which means it runs on every render. This could create an infinite loop of toast notifications - like having a rocket engine that keeps firing after reaching orbit!

   useEffect(() => {
     if (error && isErrorToast(error)) {
       toast.error(m[`errorMessages.${error}`]());
     }
-  });
+  }, [error]);
📝 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
useEffect(() => {
if (error && isErrorToast(error)) {
toast.error(m[`errorMessages.${error}`]());
}
});
useEffect(() => {
if (error && isErrorToast(error)) {
toast.error(m[`errorMessages.${error}`]());
}
}, [error]);
🤖 Prompt for AI Agents
In apps/mail/app/(auth)/signup/error-message.tsx around lines 23 to 27, the
useEffect hook is missing a dependency array, causing it to run on every render
and potentially trigger an infinite loop of toast notifications. Fix this by
adding a dependency array that includes all variables used inside the effect,
specifically 'error' and 'm', to ensure the effect only runs when these values
change.

Comment on lines +5 to +15
export async function clientLoader() {
const isProd = !import.meta.env.DEV;

const response = await fetch(import.meta.env.VITE_PUBLIC_BACKEND_URL + '/api/public/providers');
const data = (await response.json()) as { allProviders: any[] };

return {
allProviders: data.allProviders,
isProd,
};
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

This clientLoader needs more robust error handling, my friend!

Like launching rockets, we need backup systems when things go wrong. The fetch request could fail spectacularly and we're not catching it. Also, using any[] for providers is like designing a rocket without specifications - technically works but not optimal for long-term success.

 export async function clientLoader() {
   const isProd = !import.meta.env.DEV;

-  const response = await fetch(import.meta.env.VITE_PUBLIC_BACKEND_URL + '/api/public/providers');
-  const data = (await response.json()) as { allProviders: any[] };
+  try {
+    const response = await fetch(import.meta.env.VITE_PUBLIC_BACKEND_URL + '/api/public/providers');
+    if (!response.ok) {
+      throw new Error(`HTTP error! status: ${response.status}`);
+    }
+    const data = (await response.json()) as { allProviders: AuthProvider[] };
+    
+    return {
+      allProviders: data.allProviders,
+      isProd,
+    };
+  } catch (error) {
+    console.error('Failed to fetch providers:', error);
+    return {
+      allProviders: [],
+      isProd,
+    };
+  }
-
-  return {
-    allProviders: data.allProviders,
-    isProd,
-  };
 }
📝 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
export async function clientLoader() {
const isProd = !import.meta.env.DEV;
const response = await fetch(import.meta.env.VITE_PUBLIC_BACKEND_URL + '/api/public/providers');
const data = (await response.json()) as { allProviders: any[] };
return {
allProviders: data.allProviders,
isProd,
};
}
export async function clientLoader() {
const isProd = !import.meta.env.DEV;
try {
const response = await fetch(
import.meta.env.VITE_PUBLIC_BACKEND_URL + '/api/public/providers',
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = (await response.json()) as { allProviders: AuthProvider[] };
return {
allProviders: data.allProviders,
isProd,
};
} catch (error) {
console.error('Failed to fetch providers:', error);
return {
allProviders: [],
isProd,
};
}
}
🤖 Prompt for AI Agents
In apps/mail/app/(auth)/signup/page.tsx lines 5 to 15, the clientLoader function
lacks error handling for the fetch request and uses a generic any[] type for
providers. To fix this, wrap the fetch call in a try-catch block to handle
potential errors gracefully, returning a fallback or error state if the fetch
fails. Also, define a proper TypeScript interface for the provider objects
instead of using any[], and use that interface to type the allProviders array
for better type safety and maintainability.

Comment on lines +6 to +14
// Route to title mapping
const routeTitles: Record<string, string> = {
'/about': 'About - Zero',
'/contributors': 'Contributors - Zero',
'/pricing': 'Pricing - Zero',
'/privacy': 'Privacy - Zero',
'/terms': 'Terms - Zero',
'/team': 'Team - Zero',
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

We're missing the main blog route in our title mapping constellation!

The /blog route itself isn't included in the routeTitles mapping, which means it'll fall back to just "Zero". That's like having a sign for every Mars colony except Mars itself.

 const routeTitles: Record<string, string> = {
   '/about': 'About - Zero',
   '/contributors': 'Contributors - Zero',
   '/pricing': 'Pricing - Zero',
   '/privacy': 'Privacy - Zero',
   '/terms': 'Terms - Zero',
   '/team': 'Team - Zero',
+  '/blog': 'Blog - Zero',
 };
📝 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
// Route to title mapping
const routeTitles: Record<string, string> = {
'/about': 'About - Zero',
'/contributors': 'Contributors - Zero',
'/pricing': 'Pricing - Zero',
'/privacy': 'Privacy - Zero',
'/terms': 'Terms - Zero',
'/team': 'Team - Zero',
};
// Route to title mapping
const routeTitles: Record<string, string> = {
'/about': 'About - Zero',
'/contributors': 'Contributors - Zero',
'/pricing': 'Pricing - Zero',
'/privacy': 'Privacy - Zero',
'/terms': 'Terms - Zero',
'/team': 'Team - Zero',
'/blog': 'Blog - Zero',
};
🤖 Prompt for AI Agents
In apps/mail/app/(full-width)/layout.tsx between lines 6 and 14, the routeTitles
object lacks an entry for the main '/blog' route, causing it to default to just
"Zero" instead of a descriptive title. Add a key-value pair for '/blog' with an
appropriate title like 'Blog - Zero' to the routeTitles mapping to ensure the
blog route has a proper title.

Comment on lines 328 to 341
"replies": [
{
"declarations": ["input count", "local countPlural = count: plural"],
"selectors": ["countPlural"],
"declarations": [
"input count",
"local countPlural = count: plural"
],
"selectors": [
"countPlural"
],
"match": {
"countPlural=0": "arquivos",
"countPlural=one": "# resposta",
"countPlural=other": "# respostas"
"countPlural=one": "arquivo",
"countPlural=other": "arquivos"
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Wrong payload – “replies” translated as “arquivos”.

We’re asking for “respostas”, got “files” instead. Portuguese speakers will be puzzled.

-            "countPlural=0": "arquivos",
-            "countPlural=one": "arquivo",
-            "countPlural=other": "arquivos"
+            "countPlural=0": "respostas",
+            "countPlural=one": "resposta",
+            "countPlural=other": "respostas"

Let’s land the right word before users think their mails turned into attachments.

📝 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
"replies": [
{
"declarations": ["input count", "local countPlural = count: plural"],
"selectors": ["countPlural"],
"declarations": [
"input count",
"local countPlural = count: plural"
],
"selectors": [
"countPlural"
],
"match": {
"countPlural=0": "arquivos",
"countPlural=one": "# resposta",
"countPlural=other": "# respostas"
"countPlural=one": "arquivo",
"countPlural=other": "arquivos"
}
"replies": [
{
"declarations": [
"input count",
"local countPlural = count: plural"
],
"selectors": [
"countPlural"
],
"match": {
"countPlural=0": "respostas",
"countPlural=one": "resposta",
"countPlural=other": "respostas"
}
🤖 Prompt for AI Agents
In apps/mail/messages/pt.json around lines 328 to 341, the translation for
"replies" is incorrectly given as "arquivos" (files). Replace all instances of
"arquivos" in the match object with the correct Portuguese word for replies,
"respostas", to ensure the translation matches the intended meaning.

Comment on lines +262 to 273
"declarations": [
"input count",
"local countPlural = count: plural"
],
"selectors": [
"countPlural"
],
"match": {
"countPlural=0": "Добавьте заметки",
"countPlural=one": "# заметка",
"countPlural=other": "# заметок"
"countPlural=other": "# заметки"
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

“noteCount” lost proper genitive form

Same linguistic physics as above – “# заметки” is grammatically wrong after most numerals. Use “заметок”.

-            "countPlural=other": "# заметки"
+            "countPlural=other": "# заметок"
📝 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
"declarations": [
"input count",
"local countPlural = count: plural"
],
"selectors": [
"countPlural"
],
"match": {
"countPlural=0": "Добавьте заметки",
"countPlural=one": "# заметка",
"countPlural=other": "# заметок"
"countPlural=other": "# заметки"
}
"declarations": [
"input count",
"local countPlural = count: plural"
],
"selectors": [
"countPlural"
],
"match": {
"countPlural=0": "Добавьте заметки",
"countPlural=one": "# заметка",
"countPlural=other": "# заметок"
}
🤖 Prompt for AI Agents
In apps/mail/messages/ru.json around lines 262 to 273, the plural form for
"noteCount" uses "заметки," which is grammatically incorrect in this context.
Update the "countPlural=other" selector to use the genitive plural form
"заметок" instead of "заметки" to ensure proper Russian grammar after numerals.

Comment on lines +330 to 341
"declarations": [
"input count",
"local countPlural = count: plural"
],
"selectors": [
"countPlural"
],
"match": {
"countPlural=0": "ответы",
"countPlural=one": "# ответ",
"countPlural=other": "# ответов"
"countPlural=other": "# ответы"
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Plural gravity mis-aligned for “replies”

The other-case should read “ответов”, not “ответы”.

-            "countPlural=other": "# ответы"
+            "countPlural=other": "# ответов"
📝 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
"declarations": [
"input count",
"local countPlural = count: plural"
],
"selectors": [
"countPlural"
],
"match": {
"countPlural=0": "ответы",
"countPlural=one": "# ответ",
"countPlural=other": "# ответов"
"countPlural=other": "# ответы"
}
"declarations": [
"input count",
"local countPlural = count: plural"
],
"selectors": [
"countPlural"
],
"match": {
"countPlural=0": "ответы",
"countPlural=one": "# ответ",
"countPlural=other": "# ответов"
}
🤖 Prompt for AI Agents
In apps/mail/messages/ru.json around lines 330 to 341, the plural form for the
"other" case of "replies" is incorrect; it currently reads "ответы" but should
be "ответов". Update the "countPlural=other" value in the "match" object to
"ответов" to correctly reflect the plural grammar.

Comment on lines +428 to 430
"snoozed": "已推迟"
},
"settings": {
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Translation update looks good, but check UI fit.

"snoozed": "已推迟" is shorter – nice. Just make sure the sidebar width still accommodates other long-form locales.

🤖 Prompt for AI Agents
In apps/mail/messages/zh_CN.json around lines 428 to 430, the translation for
"snoozed" was shortened to "已推迟". Verify in the UI that this shorter text does
not cause layout issues, especially in the sidebar, and confirm that the sidebar
width still properly accommodates longer translations from other locales without
truncation or overflow.

@@ -0,0 +1 @@
{"history":[{"breakpoints":[],"visible":true,"aspectRatio":1,"userDownsample":1,"layerType":"effect","type":"gradient","usesPingPong":false,"speed":0.25,"trackMouse":0,"mouseMomentum":0,"texture":false,"animating":false,"isMask":0,"compiledFragmentShaders":["#version 300 es\nprecision highp float;in vec2 vTextureCoord;uniform vec2 uMousePos;vec3 getColor(int index) { switch(index) { case 0: return vec3(0, 0, 0); case 1: return vec3(0, 0, 0); case 2: return vec3(0, 0, 0); case 3: return vec3(0, 0, 0); case 4: return vec3(0, 0, 0); case 5: return vec3(0, 0, 0); case 6: return vec3(0, 0, 0); case 7: return vec3(0, 0, 0); case 8: return vec3(0, 0, 0); case 9: return vec3(0, 0, 0); case 10: return vec3(0, 0, 0); case 11: return vec3(0, 0, 0); case 12: return vec3(0, 0, 0); case 13: return vec3(0, 0, 0); case 14: return vec3(0, 0, 0); case 15: return vec3(0, 0, 0); default: return vec3(0.0); } }const float PI = 3.14159265;vec2 rotate(vec2 coord, float angle) { float s = sin(angle); float c = cos(angle); return vec2( coord.x * c - coord.y * s, coord.x * s + coord.y * c ); }out vec4 fragColor;vec3 getColor(vec2 uv) {return vec3(0, 0, 0); }void main() {vec2 uv = vTextureCoord; vec2 pos = vec2(0.5, 0.5) + mix(vec2(0), (uMousePos-0.5), 0.0000); uv -= pos; uv /= (0.5000*2.); uv = rotate(uv, (0.0000 - 0.5) * 2. * PI); vec4 color = vec4(getColor(uv), 1.); fragColor = color; }"],"compiledVertexShaders":["#version 300 es\nprecision mediump float;in vec3 aVertexPosition; in vec2 aTextureCoord;uniform mat4 uMVMatrix; uniform mat4 uPMatrix;out vec2 vTextureCoord; out vec3 vVertexPosition;void main() { gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); vTextureCoord = aTextureCoord; }"],"data":{"downSample":0.5,"depth":false,"uniforms":{},"isBackground":true},"id":"effect"},{"breakpoints":[],"visible":true,"aspectRatio":1,"userDownsample":1,"layerType":"effect","type":"beam","usesPingPong":false,"thickness":1,"speed":0.25,"trackMouse":0,"mouseMomentum":0,"texture":false,"animating":false,"isMask":0,"states":{"appear":[{"local":{"pendingChanges":{},"changeDebouncer":null,"dragSession":null},"type":"appear","id":"b9b3db10-dab0-492e-8ec5-00ee7bd43239","prop":"thickness","transition":{"duration":1000,"delay":0,"ease":"easeInOutQuart"},"complete":false,"progress":0,"value":0,"endValue":1,"initialized":false,"breakpoints":[],"loop":"none","loopDelay":0,"uniformData":{"type":"1f","name":"uThickness"}}],"scroll":[],"hover":[]},"compiledFragmentShaders":["#version 300 es\nprecision highp float; precision highp int;in vec2 vTextureCoord;uniform sampler2D uTexture;uniform float uThickness; uniform float uTime;uniform vec2 uMousePos; vec3 blend (int blendMode, vec3 src, vec3 dst) { return src + dst; }uint fibonacciHash(uint x) { const uint FIB_HASH = 2654435769u; uint hash = x * FIB_HASH; hash ^= hash >> 16; hash *= 0x85ebca6bu; hash ^= hash >> 13; hash *= 0xc2b2ae35u; hash ^= hash >> 16; return hash; }float randFibo(vec2 xy) { uint x_bits = floatBitsToUint(xy.x); uint y_bits = floatBitsToUint(xy.y); uint y_hash = fibonacciHash(y_bits); uint x_xor_y = x_bits ^ y_hash; uint final_hash = fibonacciHash(x_xor_y); return float(final_hash) / float(0xffffffffu); }vec3 Tonemap_tanh(vec3 x) { x = clamp(x, -40.0, 40.0); return (exp(x) - exp(-x)) / (exp(x) + exp(-x)); }out vec4 fragColor;const float PI = 3.14159265359; const float TWO_PI = 2.0 * PI;float luma(vec3 color) { return dot(color, vec3(0.299, 0.587, 0.114)); }vec3 drawLine(vec2 uv, vec2 center, float scale, float angle) { float radAngle = -angle * TWO_PI; float phase = fract(uTime * 0.01 + 0.5000) * (3. * max(1., scale)) - (1.5 * max(1., scale));vec2 direction = vec2(cos(radAngle), sin(radAngle));vec2 centerToPoint = uv - center;float projection = dot(centerToPoint, direction);float distToLine = length(centerToPoint - projection * direction);float lineRadius = uThickness * 0.25; float brightness = lineRadius / (1. - smoothstep(0.4, 0., distToLine + 0.02));float glowRadius = scale; float glow = smoothstep(glowRadius, 0.0, abs(projection - phase));return brightness * (1.-distToLine)*(1.-distToLine) * vec3(1, 1, 1) * glow; }vec3 getBeam(vec2 uv) { vec2 pos = vec2(0.5, 0.5) + mix(vec2(0), (uMousePos-0.5), 0.0000); return drawLine(uv, pos, 1.0000, 0.9990); }void main() { vec2 uv = vTextureCoord; vec4 bg = texture(uTexture, uv);vec3 beam = getBeam(uv); float dither = (randFibo(gl_FragCoord.xy) - 0.5) / 255.0;vec3 blended = blend(1, Tonemap_tanh(beam), bg.rgb); vec3 result = mix(bg.rgb, blended, 1.0000); result += dither;vec4 color = vec4(result, max(bg.a, luma(beam))); fragColor = color;}"],"compiledVertexShaders":["#version 300 es\nprecision mediump float;in vec3 aVertexPosition; in vec2 aTextureCoord;uniform mat4 uMVMatrix; uniform mat4 uPMatrix; uniform mat4 uTextureMatrix;out vec2 vTextureCoord; out vec3 vVertexPosition;void main() { gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); vTextureCoord = (uTextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy; }"],"data":{"depth":false,"uniforms":{},"isBackground":false},"id":"effect1"},{"breakpoints":[],"visible":true,"aspectRatio":1,"userDownsample":1,"layerType":"effect","type":"flowField","usesPingPong":false,"speed":0.15,"trackMouse":0,"mouseMomentum":0,"texture":false,"animating":true,"isMask":0,"compiledFragmentShaders":["#version 300 es\nprecision mediump float;in vec3 vVertexPosition; in vec2 vTextureCoord;uniform sampler2D uTexture; uniform float uTime; uniform vec2 uMousePos; uniform vec2 uResolution; float ease (int easingFunc, float t) { return t; }vec3 hash33(vec3 p3) { p3 = fract(p3 * vec3(0.1031, 0.11369, 0.13787)); p3 += dot(p3, p3.yxz + 19.19); return -1.0 + 2.0 * fract(vec3( (p3.x + p3.y) * p3.z, (p3.x + p3.z) * p3.y, (p3.y + p3.z) * p3.x )); }float perlin_noise(vec3 p) { vec3 pi = floor(p); vec3 pf = p - pi;vec3 w = pf * pf * (3.0 - 2.0 * pf);float n000 = dot(pf - vec3(0.0, 0.0, 0.0), hash33(pi + vec3(0.0, 0.0, 0.0))); float n100 = dot(pf - vec3(1.0, 0.0, 0.0), hash33(pi + vec3(1.0, 0.0, 0.0))); float n010 = dot(pf - vec3(0.0, 1.0, 0.0), hash33(pi + vec3(0.0, 1.0, 0.0))); float n110 = dot(pf - vec3(1.0, 1.0, 0.0), hash33(pi + vec3(1.0, 1.0, 0.0))); float n001 = dot(pf - vec3(0.0, 0.0, 1.0), hash33(pi + vec3(0.0, 0.0, 1.0))); float n101 = dot(pf - vec3(1.0, 0.0, 1.0), hash33(pi + vec3(1.0, 0.0, 1.0))); float n011 = dot(pf - vec3(0.0, 1.0, 1.0), hash33(pi + vec3(0.0, 1.0, 1.0))); float n111 = dot(pf - vec3(1.0, 1.0, 1.0), hash33(pi + vec3(1.0, 1.0, 1.0)));float nx00 = mix(n000, n100, w.x); float nx01 = mix(n001, n101, w.x); float nx10 = mix(n010, n110, w.x); float nx11 = mix(n011, n111, w.x);float nxy0 = mix(nx00, nx10, w.y); float nxy1 = mix(nx01, nx11, w.y);float nxyz = mix(nxy0, nxy1, w.z);return nxyz; }const float MAX_ITERATIONS = 16.; vec2 flow (in vec2 st) { float aspectRatio = uResolution.x/uResolution.y;vec2 mPos = vec2(0.5, 0.5) + mix(vec2(0), (uMousePos-0.5), 0.0000); vec2 pos = mix(vec2(0.5, 0.5), mPos, floor(1.0000)); float dist = ease(0, max(0.,1.-distance(st * vec2(aspectRatio, 1), mPos * vec2(aspectRatio, 1)) * 4. * (1. - 1.0000)));float sprd = (0.5000 + 0.01) / ((aspectRatio + 1.) / 2.); float amt = 0.3500 * 0.01 * dist; if(amt <= 0.) { return st; }for (float i = 0.; i < MAX_ITERATIONS; i++) { vec2 scaled = (st-0.5) * vec2(aspectRatio, 1) + (1. - pos); float perlin = perlin_noise(vec3((scaled-0.5) * (5. * sprd), 0.0000*5. + uTime/60.))-0.5; float ang = (perlin * (360. * (0.5000 * 6.))) * 3.1415926 / 180.; st += vec2(cos(ang), sin(ang)) * amt; st = clamp(st, 0., 1.); }return st; }out vec4 fragColor;void main() { vec2 uv = vTextureCoord; vec4 color = texture(uTexture, mix(uv, flow(uv), 1.0000)); fragColor = color;}"],"compiledVertexShaders":["#version 300 es\nprecision mediump float;in vec3 aVertexPosition; in vec2 aTextureCoord;uniform mat4 uMVMatrix; uniform mat4 uPMatrix; uniform mat4 uTextureMatrix;out vec2 vTextureCoord; out vec3 vVertexPosition;void main() { gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); vTextureCoord = (uTextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy; }"],"data":{"depth":false,"uniforms":{},"isBackground":false},"id":"effect2"},{"breakpoints":[],"visible":true,"aspectRatio":1,"userDownsample":1,"layerType":"effect","type":"mouse","usesPingPong":true,"mouseMomentum":0,"texture":false,"animating":false,"isMask":0,"compiledFragmentShaders":["#version 300 es\nprecision highp float;in vec2 vTextureCoord; in vec3 vVertexPosition;uniform sampler2D uTexture; uniform sampler2D uPingPongTexture; uniform vec2 uResolution;const float PI = 3.1415926; const float ITERATIONS = 24.0;out vec4 fragColor;vec3 rgb2hsv(vec3 c) { vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));float d = q.x - min(q.w, q.y); float e = 1.0e-10; return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); }vec3 chromatic_aberration(vec3 color, vec2 uv, vec2 offset) { vec4 left = texture(uTexture, uv - offset); vec4 right = texture(uTexture, uv + offset);color.r = left.r; color.b = right.b;return color; }vec2 pixelate(vec2 uv) { float aspectRatio = uResolution.x / uResolution.y; float scale = 0.1500 / 2.0; vec2 modulate = mod(vec2(uv.x * aspectRatio, uv.y) - 0.5, (scale + 0.01) / 12.0); return vec2( uv.x - modulate.x / aspectRatio + (0.08333 * scale) / 2.0, uv.y - modulate.y + (0.08333 * scale) / 2.0 ); }vec2 angleToDir(float angle) { float rad = angle * 2.0 * PI; return vec2(cos(rad), sin(rad)); }vec4 pixelTrail(vec2 uv, vec2 mouseDir, float strength) { vec4 color = vec4(0); vec2 distorted = mouseDir * 0.4; vec2 pixelated = uv - distorted; color = texture(uTexture, pixelated); color.rgb = chromatic_aberration(color.rgb, pixelated, distorted * 0.6100 * 0.12); return color; }vec4 getTrailColor(vec2 uv, vec2 mouseDir, float strength) { vec4 color = vec4(0); float aspectRatio = uResolution.x / uResolution.y;return pixelTrail(uv, mouseDir, strength); }void main() { vec2 uv = vTextureCoord; vec2 pingpongUv = uv;pingpongUv = pixelate(pingpongUv);vec3 mouseRgb = texture(uPingPongTexture, pingpongUv).rgb; vec3 mouseTrail = rgb2hsv(mouseRgb);float angle = mouseTrail.x; float strength = mouseTrail.z * (0.4500 * 2.0); vec2 direction = angleToDir(angle); vec2 mouseDir = direction * strength;vec4 color = getTrailColor(uv, mouseDir, strength);fragColor = color; }","#version 300 es\nprecision highp float;in vec3 vVertexPosition; in vec2 vTextureCoord;uniform sampler2D uPingPongTexture; uniform vec2 uPreviousMousePos;uniform vec2 uMousePos; uniform vec2 uResolution;const float PI = 3.1415926; const float TWOPI = 6.2831852;out vec4 fragColor;vec3 hsv2rgb(vec3 c) { vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); }void main() { float aspectRatio = uResolution.x / uResolution.y; vec2 uv = vTextureCoord; vec2 correctedUv = uv * vec2(aspectRatio, 1.0);vec2 dir = (uMousePos - uPreviousMousePos) * vec2(aspectRatio, 1.0); float dist = length(dir); dir = normalize(dir);float rad = 0.3400 * 0.4 * mix(aspectRatio, 1.0, 0.5); float angle = atan(dir.y, dir.x); if (angle < 0.0) angle += TWOPI;float distLine = distance(uPreviousMousePos, uMousePos);float t = clamp(dot(correctedUv - uPreviousMousePos * vec2(aspectRatio, 1.0), dir) / dist, 0.0, 1.0); vec2 closestPoint = mix(uPreviousMousePos, uMousePos, t) * vec2(aspectRatio, 1.0); float distanceToLine = distance(correctedUv, closestPoint);float s = smoothstep(rad, rad * 0.0000, distanceToLine);s = s * s;vec3 color = vec3(angle / TWOPI, 1.0, 1.0); vec3 mouseColor = hsv2rgb(color);vec2 sampleUv = mix(uv, uv / (1.0 + 0.0000 * 0.03) + 0.0000 * 0.015, 0.0000); vec3 lastFrameColor = texture(uPingPongTexture, sampleUv).rgb; lastFrameColor = pow(lastFrameColor, vec3(2.2)); mouseColor = pow(mouseColor, vec3(2.2)); float intensity = min(0.7, dist * 10.0) * s * 0.4; vec3 draw = mix(lastFrameColor, mouseColor, intensity); draw *= pow(0.2100, 0.2); draw = pow(draw, vec3(1.0/2.2)); fragColor = vec4(draw, 1.0); }"],"compiledVertexShaders":["#version 300 es\nprecision mediump float;in vec3 aVertexPosition; in vec2 aTextureCoord;uniform mat4 uMVMatrix; uniform mat4 uPMatrix; uniform mat4 uTextureMatrix;out vec2 vTextureCoord; out vec3 vVertexPosition;void main() { gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); vTextureCoord = (uTextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy; }","#version 300 es\nprecision mediump float;in vec3 aVertexPosition; in vec2 aTextureCoord;uniform mat4 uMVMatrix; uniform mat4 uPMatrix;out vec2 vTextureCoord; out vec3 vVertexPosition;void main() { gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); vTextureCoord = aTextureCoord; }"],"data":{"depth":false,"uniforms":{},"isBackground":false},"id":"effect3"},{"breakpoints":[],"visible":true,"aspectRatio":1,"userDownsample":1,"layerType":"effect","type":"diffuse","usesPingPong":false,"speed":0.25,"trackMouse":0,"mouseMomentum":0,"texture":false,"animating":false,"isMask":0,"compiledFragmentShaders":["#version 300 es\nprecision highp float; precision highp int;in vec2 vTextureCoord;uniform sampler2D uTexture;uniform float uTime; uniform float xy;uniform vec2 uMousePos; uniform vec2 uResolution;float ease (int easingFunc, float t) { return t * t; }uint fibonacciHash(uint x) { const uint FIB_HASH = 2654435769u; uint hash = x * FIB_HASH; hash ^= hash >> 16; hash *= 0x85ebca6bu; hash ^= hash >> 13; hash *= 0xc2b2ae35u; hash ^= hash >> 16; return hash; }float randFibo(vec2 xy) { uint x_bits = floatBitsToUint(xy.x); uint y_bits = floatBitsToUint(xy.y); uint y_hash = fibonacciHash(y_bits); uint x_xor_y = x_bits ^ y_hash; uint final_hash = fibonacciHash(x_xor_y); return float(final_hash) / float(0xffffffffu); }const float MAX_ITERATIONS = 24.; const float PI = 3.14159265; const float TWOPI = 6.2831853;out vec4 fragColor;void main() { vec2 uv = vTextureCoord; vec2 pos = vec2(0.5, 0.5) + mix(vec2(0), (uMousePos-0.5), 0.0000); float aspectRatio = uResolution.x/uResolution.y; float delta = fract(floor(uTime)/20.); float angle, rotation, amp; float inner = distance(uv * vec2(aspectRatio, 1), pos * vec2(aspectRatio, 1)); float outer = max(0., 1.-distance(uv * vec2(aspectRatio, 1), pos * vec2(aspectRatio, 1))); float amount = 0.0300 * 2.;vec2 mPos = vec2(0.5, 0.5) + mix(vec2(0), (uMousePos-0.5), 0.0000); pos = vec2(0.5, 0.5); float dist = ease(1, max(0.,1.-distance(uv * vec2(aspectRatio, 1), mPos * vec2(aspectRatio, 1)) * 4. * (1. - 1.0000)));amount *= dist;vec4 col; if(amount <= 0.001) { col = texture(uTexture, uv); } else { vec4 result = vec4(0); float threshold = max(1. - 0.6400, 2./MAX_ITERATIONS); const float invMaxIterations = 1.0 / float(MAX_ITERATIONS);vec2 dir = vec2(0.6100 / aspectRatio, 1.-0.6100) * amount * 0.4; float iterations = 0.0; for(float i = 1.; i <= MAX_ITERATIONS; i++) { float th = i * invMaxIterations; if(th > threshold) break;float random1 = randFibo(uv + th + delta); float random2 = randFibo(uv + th * 2. + delta); float random3 = randFibo(uv + th * 3. + delta); vec2 ranPoint = vec2(random1 * 2. - 1., random2 * 2. - 1.) * mix(1., random3, 0.8); result += texture(uTexture, uv + ranPoint * dir); iterations += 1.0; }result /= max(1.0, iterations); col = result; } fragColor = col;}"],"compiledVertexShaders":["#version 300 es\nprecision mediump float;in vec3 aVertexPosition; in vec2 aTextureCoord;uniform mat4 uMVMatrix; uniform mat4 uPMatrix; uniform mat4 uTextureMatrix;out vec2 vTextureCoord; out vec3 vVertexPosition;void main() { gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); vTextureCoord = (uTextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy; }"],"data":{"depth":false,"uniforms":{},"isBackground":false},"id":"effect4"},{"breakpoints":[],"visible":true,"aspectRatio":1,"userDownsample":1,"layerType":"effect","type":"blur","usesPingPong":false,"trackMouse":0,"mouseMomentum":0,"texture":false,"animating":false,"isMask":0,"compiledFragmentShaders":["#version 300 es\nprecision highp float; precision highp int;in vec3 vVertexPosition; in vec2 vTextureCoord;uniform sampler2D uTexture; uniform vec2 uMousePos; uniform vec2 uResolution; float ease (int easingFunc, float t) { return t; }out vec4 fragColor;const int kernelSize = 36;vec4 BoxBlur(sampler2D tex, vec2 uv, vec2 direction) { vec4 color = vec4(0.0);vec2 pos = vec2(0.5, 0.5) + mix(vec2(0), (uMousePos-0.5), 0.0000); float inner = distance(uv, pos); float outer = max(0., 1.-distance(uv, pos)); float amount = 1.0000 * ease(0, mix(inner, outer, 0.5000)); for (int i = 0; i < kernelSize; i++) { float x = float(i - kernelSize / 2) * amount/144.; color += texture(tex, uv + vec2(x) * direction * vec2(0.5000, 1. - 0.5000)); } return color/float(kernelSize); }vec4 blur(vec2 uv, vec2 direction) { return BoxBlur(uTexture, uv, direction); }void main() { vec2 uv = vTextureCoord; vec4 color = vec4(0); int dir = 0 % 2; vec2 direction = dir == 1 ? vec2(0, uResolution.x/uResolution.y) : vec2(1, 0);color = blur(uv, direction); fragColor = color;}","#version 300 es\nprecision highp float; precision highp int;in vec3 vVertexPosition; in vec2 vTextureCoord;uniform sampler2D uTexture; uniform vec2 uMousePos; uniform vec2 uResolution; float ease (int easingFunc, float t) { return t; }out vec4 fragColor;const int kernelSize = 36;vec4 BoxBlur(sampler2D tex, vec2 uv, vec2 direction) { vec4 color = vec4(0.0);vec2 pos = vec2(0.5, 0.5) + mix(vec2(0), (uMousePos-0.5), 0.0000); float inner = distance(uv, pos); float outer = max(0., 1.-distance(uv, pos)); float amount = 1.0000 * ease(0, mix(inner, outer, 0.5000)); for (int i = 0; i < kernelSize; i++) { float x = float(i - kernelSize / 2) * amount/144.; color += texture(tex, uv + vec2(x) * direction * vec2(0.5000, 1. - 0.5000)); } return color/float(kernelSize); }vec4 blur(vec2 uv, vec2 direction) { return BoxBlur(uTexture, uv, direction); }void main() { vec2 uv = vTextureCoord; vec4 color = vec4(0); int dir = 1 % 2; vec2 direction = dir == 1 ? vec2(0, uResolution.x/uResolution.y) : vec2(1, 0);color = blur(uv, direction); fragColor = color;}","#version 300 es\nprecision highp float; precision highp int;in vec3 vVertexPosition; in vec2 vTextureCoord;uniform sampler2D uTexture; uniform vec2 uMousePos; uniform vec2 uResolution; float ease (int easingFunc, float t) { return t; }out vec4 fragColor;const int kernelSize = 36;vec4 BoxBlur(sampler2D tex, vec2 uv, vec2 direction) { vec4 color = vec4(0.0);vec2 pos = vec2(0.5, 0.5) + mix(vec2(0), (uMousePos-0.5), 0.0000); float inner = distance(uv, pos); float outer = max(0., 1.-distance(uv, pos)); float amount = 1.0000 * ease(0, mix(inner, outer, 0.5000)); for (int i = 0; i < kernelSize; i++) { float x = float(i - kernelSize / 2) * amount/144.; color += texture(tex, uv + vec2(x) * direction * vec2(0.5000, 1. - 0.5000)); } return color/float(kernelSize); }vec4 blur(vec2 uv, vec2 direction) { return BoxBlur(uTexture, uv, direction); }void main() { vec2 uv = vTextureCoord; vec4 color = vec4(0); int dir = 2 % 2; vec2 direction = dir == 1 ? vec2(0, uResolution.x/uResolution.y) : vec2(1, 0);color = blur(uv, direction); fragColor = color;}","#version 300 es\nprecision highp float; precision highp int;in vec3 vVertexPosition; in vec2 vTextureCoord;uniform sampler2D uTexture; uniform vec2 uMousePos; uniform vec2 uResolution; float ease (int easingFunc, float t) { return t; } uint fibonacciHash(uint x) { const uint FIB_HASH = 2654435769u; uint hash = x * FIB_HASH; hash ^= hash >> 16; hash *= 0x85ebca6bu; hash ^= hash >> 13; hash *= 0xc2b2ae35u; hash ^= hash >> 16; return hash; }float randFibo(vec2 xy) { uint x_bits = floatBitsToUint(xy.x); uint y_bits = floatBitsToUint(xy.y); uint y_hash = fibonacciHash(y_bits); uint x_xor_y = x_bits ^ y_hash; uint final_hash = fibonacciHash(x_xor_y); return float(final_hash) / float(0xffffffffu); }out vec4 fragColor;const int kernelSize = 36;vec4 BoxBlur(sampler2D tex, vec2 uv, vec2 direction) { vec4 color = vec4(0.0);vec2 pos = vec2(0.5, 0.5) + mix(vec2(0), (uMousePos-0.5), 0.0000); float inner = distance(uv, pos); float outer = max(0., 1.-distance(uv, pos)); float amount = 1.0000 * ease(0, mix(inner, outer, 0.5000)); for (int i = 0; i < kernelSize; i++) { float x = float(i - kernelSize / 2) * amount/144.; color += texture(tex, uv + vec2(x) * direction * vec2(0.5000, 1. - 0.5000)); } return color/float(kernelSize); }vec4 blur(vec2 uv, vec2 direction) { return BoxBlur(uTexture, uv, direction); }void main() { vec2 uv = vTextureCoord; vec4 color = vec4(0); int dir = 3 % 2; vec2 direction = dir == 1 ? vec2(0, uResolution.x/uResolution.y) : vec2(1, 0);color = blur(uv, direction);float dither = (randFibo(gl_FragCoord.xy) - 0.5) / 255.0; color.rgb += dither; fragColor = color;}"],"compiledVertexShaders":["#version 300 es\nprecision mediump float;in vec3 aVertexPosition; in vec2 aTextureCoord;uniform mat4 uMVMatrix; uniform mat4 uPMatrix; uniform mat4 uTextureMatrix;out vec2 vTextureCoord; out vec3 vVertexPosition;void main() { gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); vTextureCoord = (uTextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy; }"],"data":{"downSample":0.25,"depth":false,"uniforms":{},"isBackground":false,"passes":[{"prop":"vertical","value":1,"downSample":0.25},{"prop":"vertical","value":2,"downSample":0.5},{"prop":"vertical","value":3,"downSample":0.5}]},"id":"effect5"},{"breakpoints":[],"visible":true,"aspectRatio":1,"userDownsample":1,"layerType":"effect","type":"blinds","usesPingPong":false,"speed":0,"trackMouse":0,"mouseMomentum":0,"texture":false,"animating":true,"isMask":0,"compiledFragmentShaders":["#version 300 es\nprecision highp float; in vec2 vTextureCoord;uniform sampler2D uTexture; uniform float uTime;uniform vec2 uMousePos; uniform vec2 uResolution;float ease (int easingFunc, float t) { return t * t; }const float STEPS = 10.0; const float PI = 3.14159265359;mat2 rot(float a) { return mat2(cos(a), -sin(a), sin(a), cos(a)); }vec3 chromatic_abberation(vec2 st, vec2 aberrated) { vec4 red = vec4(0); vec4 blue = vec4(0); vec4 green = vec4(0);float invSteps = 1.0 / STEPS; float invStepsHalf = invSteps * 0.5;for(float i = 1.0; i <= STEPS; i++) { vec2 offset = aberrated * (i * invSteps); red += texture(uTexture, st - offset) * invSteps; blue += texture(uTexture, st + offset) * invSteps; green += texture(uTexture, st - offset * 0.5) * invStepsHalf; green += texture(uTexture, st + offset * 0.5) * invStepsHalf; }return vec3(red.r, green.g, blue.b); }vec2 scaleAspect(vec2 st, float aspectRatio) { return st * vec2(aspectRatio, 1.0); }vec2 unscaleAspect(vec2 st) { float aspectRatio = uResolution.x / uResolution.y; return st * vec2(1.0/aspectRatio, 1.0); }vec2 rotate(vec2 st, float angle) { float s = sin(angle); float c = cos(angle); mat2 rot = mat2(c, -s, s, c); return rot * st; }struct StructFunc { vec2 st; vec3 distort; };StructFunc style0(vec2 st, vec2 pos, float divisions, float dist, float amount, vec3 first, vec3 second, vec3 third) { float segment = fract((st.y + 1. - pos.y - 1. + uTime * 0.01) * divisions); segment = mix(segment, smoothstep(0.0, 0.5, segment) - smoothstep(0.5, 1.0, segment), 0.0000); vec3 distort = mix(mix(first, second, segment * 2.), mix(second, third, (segment - 0.5) / (1. - 0.5)), step(0.5, segment)); st.y -= pow(distort.r, dist) / 10. * amount; st.y += pow(distort.b, dist) / 10. * amount;st = rot(0.1053 * 2. * PI) * (st - pos) + pos; st = unscaleAspect(st);return StructFunc(st, distort); }StructFunc getStyle(vec2 st, vec2 pos, float divisions, float dist, float amount, vec3 first, vec3 second, vec3 third) { return style0(st, pos, divisions, dist, amount, first, second, third); }vec4 blinds(vec2 st, float mDist) { float aspectRatio = uResolution.x / uResolution.y; vec2 pos = vec2(0.5, 0.5) + mix(vec2(0), (uMousePos - 0.5), 0.0000) * floor(1.0000); pos = scaleAspect(pos, aspectRatio); st = scaleAspect(st, aspectRatio);st = rotate(st - pos, -1. * 0.1053 * 2.0 * PI) + pos;vec3 first = vec3(1, 0, 0); vec3 second = vec3(0, 1, 0); vec3 third = vec3(0, 0, 1); float divisions = 2. + 0.1400 * 30.; float dist = 0.8400 * 4. + 1.; float amount = 1.8000 * mDist;StructFunc result = getStyle(st, pos, divisions, dist, amount, first, second, third); vec4 color = texture(uTexture, result.st);vec2 offset = vec2(pow(result.distort.r, dist), pow(result.distort.b, dist)) * vec2(0.1) * amount; color.rgb = chromatic_abberation(result.st, offset * 1.0000);return color; }out vec4 fragColor;void main() { vec2 uv = vTextureCoord; float aspectRatio = uResolution.x / uResolution.y;vec2 mPos = vec2(0.5, 0.5) + mix(vec2(0), (uMousePos - 0.5), 0.0000); vec2 pos = mix(vec2(0.5, 0.5), mPos, floor(1.0000)); float mDist = ease(1, max(0., 1. - distance(uv * vec2(aspectRatio, 1), mPos * vec2(aspectRatio, 1)) * 4. * (1. - 1.0000)));vec4 col = blinds(uv, mDist); fragColor = col;}"],"compiledVertexShaders":["#version 300 es\nprecision mediump float;in vec3 aVertexPosition; in vec2 aTextureCoord;uniform mat4 uMVMatrix; uniform mat4 uPMatrix; uniform mat4 uTextureMatrix;out vec2 vTextureCoord; out vec3 vVertexPosition;void main() { gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); vTextureCoord = (uTextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy; }"],"data":{"depth":false,"uniforms":{},"isBackground":false},"id":"effect6"},{"breakpoints":[],"visible":true,"aspectRatio":1,"userDownsample":1,"layerType":"effect","type":"retro_screen","usesPingPong":false,"speed":0.25,"trackMouse":0,"mouseMomentum":0,"texture":false,"animating":false,"isMask":0,"compiledFragmentShaders":["#version 300 es\nprecision mediump float;in vec2 vTextureCoord; uniform sampler2D uTexture; uniform float uTime;uniform vec2 uResolution;out vec4 fragColor;vec3 styleOne(vec2 curvedUV) { float size = max(3.0 / 1080.0, 0.028 * (1.0 - 0.7700)); float aspectRatio = uResolution.x / uResolution.y; float aspectCorrection = mix(aspectRatio, 1./aspectRatio, 0.5); vec2 cellSize = vec2(size / aspectRatio, size) * aspectCorrection;vec2 staggeredUV = curvedUV; if (mod(floor(curvedUV.x / cellSize.x), 2.0) > 0.5) { staggeredUV.y += 0.5 * cellSize.y; }vec2 cellCoords = floor(staggeredUV / cellSize) * cellSize;vec2 unstaggerOffset = vec2(0.0); if (mod(floor(curvedUV.x / cellSize.x), 2.0) > 0.5) { unstaggerOffset.y = -0.5 * cellSize.y; }vec2 sampleCoord = cellCoords + 0.5 * cellSize + unstaggerOffset; vec4 texColor = texture(uTexture, sampleCoord);vec2 staggeredCellPos = mod(staggeredUV, cellSize) / cellSize;float segmentWidth = 0.5; vec3 finalColor = vec3(0.0);float distCoord = staggeredCellPos.x;float distRed = abs(distCoord - segmentWidth * 0.5); float distGreen = abs(distCoord - segmentWidth * 1.); float distBlue = abs(distCoord - segmentWidth * 1.5);distRed = min(distRed, 1.0 - distRed); distGreen = min(distGreen, 1.0 - distGreen); distBlue = min(distBlue, 1.0 - distBlue);float softness = 0.75 * segmentWidth; float redFactor = smoothstep(softness, 0.0, distRed * 1.05); float greenFactor = smoothstep(softness, 0.0, distGreen * 1.1); float blueFactor = smoothstep(softness, 0.0, distBlue * 0.9);vec3 blurColor = vec3(0.0); float blurFactor = 1.0 / 9.0; for (int dx = -1; dx <= 1; dx++) { for (int dy = -1; dy <= 1; dy++) { vec2 offset = vec2(float(dx), dy) * cellSize * 0.8100; blurColor += texture(uTexture, sampleCoord + offset).rgb * blurFactor; } } finalColor.r = redFactor * blurColor.r * (3. * 0.3800); finalColor.g = greenFactor * blurColor.g * (3. * 0.3800); finalColor.b = blueFactor * blurColor.b * (3. * 0.3800);float edgeWidth = 0.05; vec2 edgeDistance = abs(staggeredCellPos - 0.5); float edgeFactor = smoothstep(0.45 - edgeWidth, 0.5, max(edgeDistance.x, edgeDistance.y)); edgeFactor = ((1.0 - edgeFactor) + 0.2); finalColor = finalColor * edgeFactor; finalColor = floor(finalColor * 16.0000) / 16.0000;float flicker = 1.0+0.03*cos(sampleCoord.x/6e1 + uTime*2e1); finalColor *= mix(1., flicker, 0.6500);return finalColor; }void main() { vec3 finalColor;vec4 color = texture(uTexture, vTextureCoord);if(color.a == 0.) { fragColor = vec4(0); return; }finalColor = styleOne(vTextureCoord);vec4 bg = texture(uTexture, vTextureCoord);vec4 col = mix(bg, vec4(finalColor, bg.a), 1.0000); fragColor = col;}"],"compiledVertexShaders":["#version 300 es\nprecision mediump float;in vec3 aVertexPosition; in vec2 aTextureCoord;uniform mat4 uMVMatrix; uniform mat4 uPMatrix; uniform mat4 uTextureMatrix;out vec2 vTextureCoord; out vec3 vVertexPosition;void main() { gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); vTextureCoord = (uTextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy; }"],"data":{"depth":false,"uniforms":{},"isBackground":false},"id":"effect7"},{"breakpoints":[],"visible":true,"aspectRatio":1,"userDownsample":1,"layerType":"effect","type":"noiseField","usesPingPong":false,"speed":0.1,"texture":false,"animating":true,"mouseMomentum":0,"isMask":0,"compiledFragmentShaders":["#version 300 es\nprecision mediump float;in vec3 vVertexPosition; in vec2 vTextureCoord; uniform sampler2D uTexture;out vec4 fragColor;void main() { vec2 uv = vTextureCoord; uv = vec2( 1. - uv.x, 1. - uv.y ); vec4 color = texture(uTexture, uv); fragColor = color;}"],"compiledVertexShaders":["#version 300 es\nprecision mediump float;in vec3 aVertexPosition; in vec2 aTextureCoord; uniform mat4 uMVMatrix; uniform mat4 uPMatrix; out vec3 vVertexPosition; out vec2 vTextureCoord;uniform float uTime; uniform vec2 uResolution;vec3 hash33(vec3 p3) { p3 = fract(p3 * vec3(0.1031, 0.11369, 0.13787)); p3 += dot(p3, p3.yxz + 19.19); return -1.0 + 2.0 * fract(vec3( (p3.x + p3.y) * p3.z, (p3.x + p3.z) * p3.y, (p3.y + p3.z) * p3.x )); }float perlin_noise(vec3 p) { vec3 pi = floor(p); vec3 pf = p - pi;vec3 w = pf * pf * (3.0 - 2.0 * pf);float n000 = dot(pf - vec3(0.0, 0.0, 0.0), hash33(pi + vec3(0.0, 0.0, 0.0))); float n100 = dot(pf - vec3(1.0, 0.0, 0.0), hash33(pi + vec3(1.0, 0.0, 0.0))); float n010 = dot(pf - vec3(0.0, 1.0, 0.0), hash33(pi + vec3(0.0, 1.0, 0.0))); float n110 = dot(pf - vec3(1.0, 1.0, 0.0), hash33(pi + vec3(1.0, 1.0, 0.0))); float n001 = dot(pf - vec3(0.0, 0.0, 1.0), hash33(pi + vec3(0.0, 0.0, 1.0))); float n101 = dot(pf - vec3(1.0, 0.0, 1.0), hash33(pi + vec3(1.0, 0.0, 1.0))); float n011 = dot(pf - vec3(0.0, 1.0, 1.0), hash33(pi + vec3(0.0, 1.0, 1.0))); float n111 = dot(pf - vec3(1.0, 1.0, 1.0), hash33(pi + vec3(1.0, 1.0, 1.0)));float nx00 = mix(n000, n100, w.x); float nx01 = mix(n001, n101, w.x); float nx10 = mix(n010, n110, w.x); float nx11 = mix(n011, n111, w.x);float nxy0 = mix(nx00, nx10, w.y); float nxy1 = mix(nx01, nx11, w.y);float nxyz = mix(nxy0, nxy1, w.z);return nxyz; }mat4 rotation(float angle) { return mat4( vec4( cos(angle), -sin(angle), 0.0, 0.0 ), vec4( sin(angle), cos(angle), 0.0, 0.0 ), vec4( 0.0, 0.0, 1.0, 0.0 ), vec4( 0.0, 0.0, 0.0, 1.0 ) ); } void main() { vec3 vertexPosition = aVertexPosition; vec3 waveCoord = aVertexPosition; float cumval = 0.; float spr = (0.0700 + 1.) / ((uResolution.x/uResolution.y + 1.) * 0.5) * 10.; float time = 0.0000 * 10. + uTime * 0.05;float value = perlin_noise(vec3(((waveCoord.xy * 0.0700 * 10.) + (vec2(0.5, 0.5) - 0.5) * 20. * 0.0700) * (vec2(0.2, 1.)), time)) * 0.2300;waveCoord.z = 0.; waveCoord.y += mix(value, smoothstep(-1., 0., value) - 1., 0.5000); waveCoord.x += value * 0.02; if(vertexPosition.x == 1.) { waveCoord.x = 1.; } if(vertexPosition.x == -1.) { waveCoord.x = -1.; } if(vertexPosition.y == 1.) { waveCoord.y = 1.; } if(vertexPosition.y == -1.) { waveCoord.y = -1.; }gl_Position = uPMatrix * uMVMatrix * rotation(radians(180.0)) * vec4(waveCoord, 1.);vTextureCoord = aTextureCoord - vec2(0., mix(value, smoothstep(-1., 0., value) - 1., 0.5000)); vVertexPosition = vertexPosition; }"],"data":{"depth":false,"uniforms":{},"isBackground":false,"heightSegments":300,"widthSegments":300},"id":"effect8"},{"breakpoints":[],"visible":true,"aspectRatio":1,"userDownsample":1,"layerType":"effect","type":"godrays","usesPingPong":false,"trackMouse":0,"mouseMomentum":0,"texture":false,"animating":false,"isMask":0,"compiledFragmentShaders":["#version 300 es\nprecision highp float; precision highp int;in vec3 vVertexPosition; in vec2 vTextureCoord;uniform sampler2D uTexture;const float MAX_ITERATIONS = 64.; const float PI2 = 6.28318530718; const float EPSILON = 0.0001; const float stepFactor = 0.0098;vec4 getBrightAreas(vec2 uv) { vec4 color = texture(uTexture, uv); float lum = dot(color.rgb, vec3(0.299, 0.587, 0.114)); color = color * smoothstep(0.6600 - 0.1, 0.6600, lum); return color; }vec4 getColor(vec2 uv) { return getBrightAreas(uv); }out vec4 fragColor;void main() { vec2 uv = vTextureCoord; vec4 color = getColor(uv); if(0 == 2) { fragColor = color;} else { fragColor = color; } }","#version 300 es\nprecision highp float; precision highp int;in vec3 vVertexPosition; in vec2 vTextureCoord;uniform sampler2D uTexture;uniform vec2 uMousePos;uint fibonacciHash(uint x) { const uint FIB_HASH = 2654435769u; uint hash = x * FIB_HASH; hash ^= hash >> 16; hash *= 0x85ebca6bu; hash ^= hash >> 13; hash *= 0xc2b2ae35u; hash ^= hash >> 16; return hash; }float randFibo(vec2 xy) { uint x_bits = floatBitsToUint(xy.x); uint y_bits = floatBitsToUint(xy.y); uint y_hash = fibonacciHash(y_bits); uint x_xor_y = x_bits ^ y_hash; uint final_hash = fibonacciHash(x_xor_y); return float(final_hash) / float(0xffffffffu); }const float MAX_ITERATIONS = 64.; const float PI2 = 6.28318530718; const float EPSILON = 0.0001; const float stepFactor = 0.0098;vec4 godRays(vec2 st, float decay) { vec3 color = vec3(0); float offset = (0.25 + min(1., 0.5200)) * stepFactor; vec2 pos = vec2(0.5, 0.5) - mix(vec2(0), (vec2(1. - uMousePos.x, 1. - uMousePos.y) - 0.5), 0.0000); float weight = 1.0; float bnoz = randFibo(st) * 0.5200; float distanceInfo = 0.0; vec2 marchPos = st;for (float i = 0.0; i < MAX_ITERATIONS; i += 4.0) { for (float j = 0.0; j < 4.0; j++) { float bno = randFibo(st + vec2(i/MAX_ITERATIONS + j/4.0)) * 0.5200; vec2 offbno = vec2(cos(bno) - 0.5, sin(bno) - 0.5); float x = min(0.999, (i + j) * offset) + bnoz * 0.02; float y = min(0.999, (i + j)); marchPos = st * (1.0 - x) + vec2(x * 0.5) + (pos - 0.5) * x + offbno * 0.02 * 1.0000 * x; color += texture(uTexture, marchPos).rgb * weight; distanceInfo += y * weight; weight *= decay; if(weight < 0.01) break; } } return vec4(color / MAX_ITERATIONS, distance(st, marchPos)); }vec4 getGodRays(vec2 uv) { if(0.3400 == 0.) { return vec4(0); } vec4 rays = godRays(uv, 0.972); rays.rgb *= vec3(1, 0.8196078431372549, 0.596078431372549); vec4 color; color.rgb = rays.rgb; color.a = rays.a; return color; }vec4 getColor(vec2 uv) { return getGodRays(uv); }out vec4 fragColor;void main() { vec2 uv = vTextureCoord; vec4 color = getColor(uv); if(1 == 2) { fragColor = color;} else { fragColor = color; } }","#version 300 es\nprecision highp float; precision highp int;in vec3 vVertexPosition; in vec2 vTextureCoord;uniform sampler2D uTexture; uniform sampler2D uBgTexture; uniform sampler2D uBlueNoise;uniform vec2 uResolution;const float MAX_ITERATIONS = 64.; const float PI2 = 6.28318530718; const float EPSILON = 0.0001; const float stepFactor = 0.0098;float luma(vec4 color) { return dot(color.rgb, vec3(0.299, 0.587, 0.114)); }float getBlueNoiseOffset(vec2 st) { ivec2 texSize = ivec2(512, 512); vec4 blueNoise = texelFetch(uBlueNoise, ivec2(fract(st * (uResolution)/vec2(texSize) * vec2(texSize.x/texSize.y, 1.0)) * vec2(texSize)) % texSize, 0); return mod((blueNoise.r - 0.5) * PI2, PI2); }vec4 composite(vec2 uv) { vec4 godrays = texture(uTexture, uv); float distanceInfo = godrays.a; float luminance = luma(godrays); float blueNoise = getBlueNoiseOffset(uv) - 0.5; vec2 circNoise = vec2(cos(blueNoise), sin(blueNoise)); float brightnessScale = (1. - (luminance + 0.25)); vec2 offset = circNoise * 0.05 * pow(brightnessScale, 3.) * distanceInfo * 2.; vec4 color = texture(uTexture, uv + offset); vec4 bg = texture(uBgTexture, uv);color.rgb = bg.rgb + (color.rgb * 2.9 * 0.3400 + blueNoise * 0.001); color.a = bg.a + color.r; return color; }vec4 getColor(vec2 uv) { return composite(uv); }out vec4 fragColor;void main() { vec2 uv = vTextureCoord; vec4 color = getColor(uv); if(2 == 2) { fragColor = color;} else { fragColor = color; } }"],"compiledVertexShaders":["#version 300 es\nprecision mediump float;in vec3 aVertexPosition; in vec2 aTextureCoord;uniform mat4 uMVMatrix; uniform mat4 uPMatrix; uniform mat4 uTextureMatrix;out vec2 vTextureCoord; out vec3 vVertexPosition;void main() { gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); vTextureCoord = (uTextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy; }"],"data":{"depth":false,"uniforms":{},"isBackground":false,"passes":[{"prop":"pass","value":1,"downSample":0.5},{"prop":"pass","value":2,"includeBg":true}],"texture":{"src":"https://assets.unicorn.studio/media/blue_noise_med.png","sampler":"uBlueNoise"}},"id":"effect9"},{"breakpoints":[],"visible":true,"aspectRatio":1,"userDownsample":1,"layerType":"effect","type":"duotone","usesPingPong":false,"texture":false,"animating":false,"mouseMomentum":0,"isMask":0,"compiledFragmentShaders":["#version 300 es\nprecision mediump float; in vec3 vVertexPosition; in vec2 vTextureCoord; uniform sampler2D uTexture;out vec4 fragColor; void main() { vec2 uv = vTextureCoord; vec4 color = texture(uTexture, uv); float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114)); vec3 duotoneColor = mix(vec3(0, 0, 0), vec3(1, 1, 1), gray); color = vec4(mix(color.rgb, duotoneColor, 1.0000), color.a); fragColor = color;}"],"compiledVertexShaders":["#version 300 es\nprecision mediump float;in vec3 aVertexPosition; in vec2 aTextureCoord;uniform mat4 uMVMatrix; uniform mat4 uPMatrix; uniform mat4 uTextureMatrix;out vec2 vTextureCoord; out vec3 vVertexPosition;void main() { gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); vTextureCoord = (uTextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy; }"],"data":{"depth":false,"uniforms":{},"isBackground":false},"id":"effect10"},{"breakpoints":[],"visible":true,"aspectRatio":1,"userDownsample":1,"layerType":"effect","type":"bloom","usesPingPong":false,"texture":false,"animating":false,"mouseMomentum":0,"isMask":0,"compiledFragmentShaders":["#version 300 es\nprecision highp float; precision highp int;in vec3 vVertexPosition; in vec2 vTextureCoord;uniform sampler2D uTexture;out vec4 fragColor;float luma(vec4 color) { return dot(color.rgb, vec3(0.299, 0.587, 0.114)); }vec4 thresholdPass(vec4 color) { color.rgb = pow(color.rgb, vec3(1.0/2.2)); color.rgb = 1.2 * (color.rgb - 0.5) + 0.5; vec4 bloom = color * smoothstep(0.5000 - 0.1, 0.5000, luma(color)); return vec4(bloom.rgb, color.a); }vec4 getColor(vec4 color) { return thresholdPass(color); }void main() { vec2 uv = vTextureCoord; vec4 color = texture(uTexture, uv); fragColor = getColor(color); }","#version 300 es\nprecision highp float; precision highp int;in vec3 vVertexPosition; in vec2 vTextureCoord;uniform sampler2D uTexture;uniform vec2 uResolution;out vec4 fragColor;float getExponentialWeight(int index) { switch(index) { case 0: return 1.0000000000; case 1: return 0.7165313106; case 2: return 0.5134171190; case 3: return 0.3678794412; case 4: return 0.2636050919; case 5: return 0.1888756057; case 6: return 0.1353352832; case 7: return 0.0969670595; case 8: return 0.0694877157; default: return 0.0; } }vec4 blur(vec2 uv, bool vertical, float radius, bool diamond) { vec4 color = vec4(0.0); float total_weight = 0.0; float aspectRatio = uResolution.x/uResolution.y;vec2 dir; if (diamond) { dir = vertical ? vec2(1, 1) : vec2(1, -1); } else { dir = vertical ? vec2(0, 1) : vec2(1, 0); } dir *= vec2(0.5000, 1. - 0.5000); dir.x /= aspectRatio; vec4 center = texture(uTexture, uv); float center_weight = getExponentialWeight(0); color += center * center_weight; total_weight += center_weight;radius *= 0.7200; for (int i = 1; i <= 8; i++) { float weight = getExponentialWeight(i); float offset = mix(0.015, 0.025, radius) * float(i)/8.; vec4 sample1 = texture(uTexture, uv + offset * dir); vec4 sample2 = texture(uTexture, uv - offset * dir); color += (sample1 + sample2) * weight; total_weight += 2.0 * weight; }return color / total_weight; }vec4 blurPass(vec2 uv, bool vertical, float radius, float intensity, bool diamond) { return blur(uv, vertical, radius, diamond); }vec4 getColor(vec4 color) { return blurPass(vTextureCoord, false, 40., 1.25, true); }void main() { vec2 uv = vTextureCoord; vec4 color = texture(uTexture, uv); fragColor = getColor(color); }","#version 300 es\nprecision highp float; precision highp int;in vec3 vVertexPosition; in vec2 vTextureCoord;uniform sampler2D uTexture; uniform sampler2D uBgTexture;uniform vec2 uResolution;out vec4 fragColor;float luma(vec4 color) { return dot(color.rgb, vec3(0.299, 0.587, 0.114)); }float getExponentialWeight(int index) { switch(index) { case 0: return 1.0000000000; case 1: return 0.7165313106; case 2: return 0.5134171190; case 3: return 0.3678794412; case 4: return 0.2636050919; case 5: return 0.1888756057; case 6: return 0.1353352832; case 7: return 0.0969670595; case 8: return 0.0694877157; default: return 0.0; } }vec4 blur(vec2 uv, bool vertical, float radius, bool diamond) { vec4 color = vec4(0.0); float total_weight = 0.0; float aspectRatio = uResolution.x/uResolution.y;vec2 dir; if (diamond) { dir = vertical ? vec2(1, 1) : vec2(1, -1); } else { dir = vertical ? vec2(0, 1) : vec2(1, 0); } dir *= vec2(0.5000, 1. - 0.5000); dir.x /= aspectRatio; vec4 center = texture(uTexture, uv); float center_weight = getExponentialWeight(0); color += center * center_weight; total_weight += center_weight;radius *= 0.7200; for (int i = 1; i <= 8; i++) { float weight = getExponentialWeight(i); float offset = mix(0.015, 0.025, radius) * float(i)/8.; vec4 sample1 = texture(uTexture, uv + offset * dir); vec4 sample2 = texture(uTexture, uv - offset * dir); color += (sample1 + sample2) * weight; total_weight += 2.0 * weight; }return color / total_weight; }vec4 thresholdPass(vec4 color) { color.rgb = pow(color.rgb, vec3(1.0/2.2)); color.rgb = 1.2 * (color.rgb - 0.5) + 0.5; vec4 bloom = color * smoothstep(0.5000 - 0.1, 0.5000, luma(color)); return vec4(bloom.rgb, color.a); }vec4 blurCombinePass(vec2 uv, bool vertical, float radius, float intensity, bool diamond) { vec4 blurred = blur(uv, vertical, radius, diamond); return (thresholdPass(texture(uBgTexture, uv)) * 0.5 + blurred * intensity); }vec4 getColor(vec4 color) { return blurCombinePass(vTextureCoord, true, 40., 1.25, true); }void main() { vec2 uv = vTextureCoord; vec4 color = texture(uTexture, uv); fragColor = getColor(color); }","#version 300 es\nprecision highp float; precision highp int;in vec3 vVertexPosition; in vec2 vTextureCoord;uniform sampler2D uTexture;uniform vec2 uResolution;out vec4 fragColor;float getExponentialWeight(int index) { switch(index) { case 0: return 1.0000000000; case 1: return 0.7165313106; case 2: return 0.5134171190; case 3: return 0.3678794412; case 4: return 0.2636050919; case 5: return 0.1888756057; case 6: return 0.1353352832; case 7: return 0.0969670595; case 8: return 0.0694877157; default: return 0.0; } }vec4 blur(vec2 uv, bool vertical, float radius, bool diamond) { vec4 color = vec4(0.0); float total_weight = 0.0; float aspectRatio = uResolution.x/uResolution.y;vec2 dir; if (diamond) { dir = vertical ? vec2(1, 1) : vec2(1, -1); } else { dir = vertical ? vec2(0, 1) : vec2(1, 0); } dir *= vec2(0.5000, 1. - 0.5000); dir.x /= aspectRatio; vec4 center = texture(uTexture, uv); float center_weight = getExponentialWeight(0); color += center * center_weight; total_weight += center_weight;radius *= 0.7200; for (int i = 1; i <= 8; i++) { float weight = getExponentialWeight(i); float offset = mix(0.015, 0.025, radius) * float(i)/8.; vec4 sample1 = texture(uTexture, uv + offset * dir); vec4 sample2 = texture(uTexture, uv - offset * dir); color += (sample1 + sample2) * weight; total_weight += 2.0 * weight; }return color / total_weight; }vec4 blurPass(vec2 uv, bool vertical, float radius, float intensity, bool diamond) { return blur(uv, vertical, radius, diamond); }vec4 getColor(vec4 color) { return blurPass(vTextureCoord, false, 15., 1.1, true); }void main() { vec2 uv = vTextureCoord; vec4 color = texture(uTexture, uv); fragColor = getColor(color); }","#version 300 es\nprecision highp float; precision highp int;in vec3 vVertexPosition; in vec2 vTextureCoord;uniform sampler2D uTexture; uniform sampler2D uBgTexture;uniform vec2 uResolution;out vec4 fragColor;float luma(vec4 color) { return dot(color.rgb, vec3(0.299, 0.587, 0.114)); }float getExponentialWeight(int index) { switch(index) { case 0: return 1.0000000000; case 1: return 0.7165313106; case 2: return 0.5134171190; case 3: return 0.3678794412; case 4: return 0.2636050919; case 5: return 0.1888756057; case 6: return 0.1353352832; case 7: return 0.0969670595; case 8: return 0.0694877157; default: return 0.0; } }vec4 blur(vec2 uv, bool vertical, float radius, bool diamond) { vec4 color = vec4(0.0); float total_weight = 0.0; float aspectRatio = uResolution.x/uResolution.y;vec2 dir; if (diamond) { dir = vertical ? vec2(1, 1) : vec2(1, -1); } else { dir = vertical ? vec2(0, 1) : vec2(1, 0); } dir *= vec2(0.5000, 1. - 0.5000); dir.x /= aspectRatio; vec4 center = texture(uTexture, uv); float center_weight = getExponentialWeight(0); color += center * center_weight; total_weight += center_weight;radius *= 0.7200; for (int i = 1; i <= 8; i++) { float weight = getExponentialWeight(i); float offset = mix(0.015, 0.025, radius) * float(i)/8.; vec4 sample1 = texture(uTexture, uv + offset * dir); vec4 sample2 = texture(uTexture, uv - offset * dir); color += (sample1 + sample2) * weight; total_weight += 2.0 * weight; }return color / total_weight; }vec4 thresholdPass(vec4 color) { color.rgb = pow(color.rgb, vec3(1.0/2.2)); color.rgb = 1.2 * (color.rgb - 0.5) + 0.5; vec4 bloom = color * smoothstep(0.5000 - 0.1, 0.5000, luma(color)); return vec4(bloom.rgb, color.a); }vec4 blurCombinePass(vec2 uv, bool vertical, float radius, float intensity, bool diamond) { vec4 blurred = blur(uv, vertical, radius, diamond); return (thresholdPass(texture(uBgTexture, uv)) * 0.5 + blurred * intensity); }vec4 getColor(vec4 color) { return blurCombinePass(vTextureCoord, true, 15., 1.1, true); }void main() { vec2 uv = vTextureCoord; vec4 color = texture(uTexture, uv); fragColor = getColor(color); }","#version 300 es\nprecision highp float; precision highp int;in vec3 vVertexPosition; in vec2 vTextureCoord;uniform sampler2D uTexture;uniform vec2 uResolution;out vec4 fragColor;float getExponentialWeight(int index) { switch(index) { case 0: return 1.0000000000; case 1: return 0.7165313106; case 2: return 0.5134171190; case 3: return 0.3678794412; case 4: return 0.2636050919; case 5: return 0.1888756057; case 6: return 0.1353352832; case 7: return 0.0969670595; case 8: return 0.0694877157; default: return 0.0; } }vec4 blur(vec2 uv, bool vertical, float radius, bool diamond) { vec4 color = vec4(0.0); float total_weight = 0.0; float aspectRatio = uResolution.x/uResolution.y;vec2 dir; if (diamond) { dir = vertical ? vec2(1, 1) : vec2(1, -1); } else { dir = vertical ? vec2(0, 1) : vec2(1, 0); } dir *= vec2(0.5000, 1. - 0.5000); dir.x /= aspectRatio; vec4 center = texture(uTexture, uv); float center_weight = getExponentialWeight(0); color += center * center_weight; total_weight += center_weight;radius *= 0.7200; for (int i = 1; i <= 8; i++) { float weight = getExponentialWeight(i); float offset = mix(0.015, 0.025, radius) * float(i)/8.; vec4 sample1 = texture(uTexture, uv + offset * dir); vec4 sample2 = texture(uTexture, uv - offset * dir); color += (sample1 + sample2) * weight; total_weight += 2.0 * weight; }return color / total_weight; }vec4 blurPass(vec2 uv, bool vertical, float radius, float intensity, bool diamond) { return blur(uv, vertical, radius, diamond); }vec4 getColor(vec4 color) { return blurPass(vTextureCoord, false, 7.5, 1., false); }void main() { vec2 uv = vTextureCoord; vec4 color = texture(uTexture, uv); fragColor = getColor(color); }","#version 300 es\nprecision highp float; precision highp int;in vec3 vVertexPosition; in vec2 vTextureCoord;uniform sampler2D uTexture;uniform vec2 uResolution;out vec4 fragColor;float getExponentialWeight(int index) { switch(index) { case 0: return 1.0000000000; case 1: return 0.7165313106; case 2: return 0.5134171190; case 3: return 0.3678794412; case 4: return 0.2636050919; case 5: return 0.1888756057; case 6: return 0.1353352832; case 7: return 0.0969670595; case 8: return 0.0694877157; default: return 0.0; } }vec4 blur(vec2 uv, bool vertical, float radius, bool diamond) { vec4 color = vec4(0.0); float total_weight = 0.0; float aspectRatio = uResolution.x/uResolution.y;vec2 dir; if (diamond) { dir = vertical ? vec2(1, 1) : vec2(1, -1); } else { dir = vertical ? vec2(0, 1) : vec2(1, 0); } dir *= vec2(0.5000, 1. - 0.5000); dir.x /= aspectRatio; vec4 center = texture(uTexture, uv); float center_weight = getExponentialWeight(0); color += center * center_weight; total_weight += center_weight;radius *= 0.7200; for (int i = 1; i <= 8; i++) { float weight = getExponentialWeight(i); float offset = mix(0.015, 0.025, radius) * float(i)/8.; vec4 sample1 = texture(uTexture, uv + offset * dir); vec4 sample2 = texture(uTexture, uv - offset * dir); color += (sample1 + sample2) * weight; total_weight += 2.0 * weight; }return color / total_weight; }vec4 blurPass(vec2 uv, bool vertical, float radius, float intensity, bool diamond) { return blur(uv, vertical, radius, diamond); }vec4 getColor(vec4 color) { return blurPass(vTextureCoord, true, 7.5, 1., false); }void main() { vec2 uv = vTextureCoord; vec4 color = texture(uTexture, uv); fragColor = getColor(color); }","#version 300 es\nprecision highp float; precision highp int;in vec3 vVertexPosition; in vec2 vTextureCoord;uniform sampler2D uTexture; uniform sampler2D uBgTexture;uint fibonacciHash(uint x) { const uint FIB_HASH = 2654435769u; uint hash = x * FIB_HASH; hash ^= hash >> 16; hash *= 0x85ebca6bu; hash ^= hash >> 13; hash *= 0xc2b2ae35u; hash ^= hash >> 16; return hash; }float randFibo(vec2 xy) { uint x_bits = floatBitsToUint(xy.x); uint y_bits = floatBitsToUint(xy.y); uint y_hash = fibonacciHash(y_bits); uint x_xor_y = x_bits ^ y_hash; uint final_hash = fibonacciHash(x_xor_y); return float(final_hash) / float(0xffffffffu); }out vec4 fragColor;float luma(vec4 color) { return dot(color.rgb, vec3(0.299, 0.587, 0.114)); }vec4 finalPass(vec4 bloomColor) { float dither = (randFibo(gl_FragCoord.xy) - 0.5) / 255.0; bloomColor.rgb *= vec3(1, 1, 1); bloomColor.rgb += dither; bloomColor.a = luma(bloomColor); vec4 sceneColor = texture(uBgTexture, vTextureCoord); vec4 finalColor = mix(sceneColor, sceneColor + bloomColor, 0.2400 * 1.75); return finalColor; }vec4 getColor(vec4 color) { return finalPass(color); }void main() { vec2 uv = vTextureCoord; vec4 color = texture(uTexture, uv); fragColor = getColor(color); }"],"compiledVertexShaders":["#version 300 es\nprecision mediump float;in vec3 aVertexPosition; in vec2 aTextureCoord;uniform mat4 uMVMatrix; uniform mat4 uPMatrix; uniform mat4 uTextureMatrix;out vec2 vTextureCoord; out vec3 vVertexPosition;void main() { gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); vTextureCoord = (uTextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy; }"],"data":{"downSample":0.5,"depth":false,"uniforms":{},"isBackground":false,"passes":[{"prop":"pass","value":1,"downSample":0.25},{"prop":"pass","value":2,"downSample":0.25,"includeBg":true},{"prop":"pass","value":3,"downSample":0.25},{"prop":"pass","value":4,"downSample":0.25,"includeBg":true},{"prop":"pass","value":5,"downSample":0.5},{"prop":"pass","value":6,"downSample":0.5,"includeBg":true},{"prop":"pass","value":7,"downSample":1,"includeBg":true}]},"id":"effect11"},{"breakpoints":[],"visible":true,"aspectRatio":1,"userDownsample":1,"layerType":"effect","type":"chromab","usesPingPong":false,"speed":0.25,"trackMouse":0,"mouseMomentum":0,"texture":false,"animating":false,"isMask":0,"compiledFragmentShaders":["#version 300 es\nprecision highp float;in vec3 vVertexPosition; in vec2 vTextureCoord;uniform sampler2D uTexture; uniform float uTime;uniform vec2 uMousePos;out vec4 fragColor; const float PI = 3.1415926;void main() { vec2 uv = vTextureCoord; vec2 pos = vec2(0.1227741330834114, 0.5805998125585754) + mix(vec2(0), (uMousePos-0.5), 0.0000); float angle = ((0.0000 + uTime * 0.05) * 360.0) * PI / 180.0; vec2 rotation = vec2(sin(angle), cos(angle)); vec2 aberrated;aberrated = 0.2200 * rotation * 0.03 * mix(1.0, distance(uv, pos) * (1.0 + 0.0000), 0.0000); vec4 left = vec4(0); vec4 right = vec4(0); vec4 center = vec4(0);float steps = max(2., min(float(6), 24.)); float invSteps = 1.0 / (steps + 1.0);for (float i = 0.0; i <= steps; i++) { vec2 offset = aberrated * (i * invSteps); left += texture(uTexture, uv - offset) * invSteps; right += texture(uTexture, uv + offset) * invSteps; } for (float i = 0.0; i <= steps; i++) { vec2 offset = aberrated * ((i / steps) - 0.5); center += texture(uTexture, uv + offset) * invSteps; }vec4 color = texture(uTexture, uv); if(0 == 0) { color.r = left.r; color.g = mix(color.g, center.g, float(1)); color.b = right.b; } else if(0 == 1) { color.r = mix(color.r, center.r, float(1)); color.g = left.g; color.b = right.b; } else if(0 == 2) { color.r = right.r; color.g = left.g; color.b = mix(color.b, center.b, float(1)); }color.a = max(max(left.a, center.a), right.a); fragColor = color;}"],"compiledVertexShaders":["#version 300 es\nprecision mediump float;in vec3 aVertexPosition; in vec2 aTextureCoord;uniform mat4 uMVMatrix; uniform mat4 uPMatrix; uniform mat4 uTextureMatrix;out vec2 vTextureCoord; out vec3 vVertexPosition;void main() { gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); vTextureCoord = (uTextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy; }"],"data":{"depth":false,"uniforms":{},"isBackground":false},"id":"effect12"},{"breakpoints":[],"visible":true,"aspectRatio":1,"userDownsample":1,"layerType":"effect","type":"grain","usesPingPong":false,"speed":0.5,"texture":false,"animating":true,"mouseMomentum":0,"isMask":0,"compiledFragmentShaders":["#version 300 es\nprecision highp float; precision highp int;in vec3 vVertexPosition; in vec2 vTextureCoord;uniform sampler2D uTexture; uniform float uTime; uniform vec2 uResolution; vec3 blend (int blendMode, vec3 src, vec3 dst) { return vec3((dst.x <= 0.5) ? (2.0 * src.x * dst.x) : (1.0 - 2.0 * (1.0 - dst.x) * (1.0 - src.x)), (dst.y <= 0.5) ? (2.0 * src.y * dst.y) : (1.0 - 2.0 * (1.0 - dst.y) * (1.0 - src.y)), (dst.z <= 0.5) ? (2.0 * src.z * dst.z) : (1.0 - 2.0 * (1.0 - dst.z) * (1.0 - src.z))); } uint fibonacciHash(uint x) { const uint FIB_HASH = 2654435769u; uint hash = x * FIB_HASH; hash ^= hash >> 16; hash *= 0x85ebca6bu; hash ^= hash >> 13; hash *= 0xc2b2ae35u; hash ^= hash >> 16; return hash; }float randFibo(vec2 xy) { uint x_bits = floatBitsToUint(xy.x); uint y_bits = floatBitsToUint(xy.y); uint y_hash = fibonacciHash(y_bits); uint x_xor_y = x_bits ^ y_hash; uint final_hash = fibonacciHash(x_xor_y); return float(final_hash) / float(0xffffffffu); }out vec4 fragColor;void main() { vec2 uv = vTextureCoord; vec4 color = texture(uTexture, uv);if(color.a == 0.) { fragColor = vec4(0); return; }vec2 st = uv; vec3 grainRGB = vec3(0);st *= uResolution;float delta = fract((floor(uTime)/20.));if(0 == 1) { grainRGB = vec3( randFibo(st + vec2(1, 2) + delta), randFibo(st + vec2(2, 3) + delta), randFibo(st + vec2(3, 4) + delta) ); } else { grainRGB = vec3(randFibo(st + vec2(delta))); } color.rgb = mix(color.rgb, blend(5, grainRGB, color.rgb), 0.1500); fragColor = color;}"],"compiledVertexShaders":["#version 300 es\nprecision mediump float;in vec3 aVertexPosition; in vec2 aTextureCoord;uniform mat4 uMVMatrix; uniform mat4 uPMatrix; uniform mat4 uTextureMatrix;out vec2 vTextureCoord; out vec3 vVertexPosition;void main() { gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); vTextureCoord = (uTextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy; }"],"data":{"depth":false,"uniforms":{},"isBackground":false},"id":"effect13"},{"breakpoints":[],"visible":true,"aspectRatio":1,"userDownsample":1,"layerType":"effect","type":"vignette","usesPingPong":false,"trackMouse":0,"mouseMomentum":0,"texture":false,"animating":false,"isMask":0,"compiledFragmentShaders":["#version 300 es\nprecision highp float; in vec3 vVertexPosition; in vec2 vTextureCoord; uniform sampler2D uTexture; uniform vec2 uResolution;out vec4 fragColor; mat2 rot(float a) { return mat2(cos(a),-sin(a),sin(a),cos(a)); } void main() { vec2 uv = vTextureCoord; vec4 color = texture(uTexture, uv); float luma = dot(color.rgb, vec3(0.299, 0.587, 0.114)); float displacement = (luma - 0.5) * 0.0000 * 0.5; vec2 aspectRatio = vec2(uResolution.x/uResolution.y, 1.0); vec2 skew = vec2(0.5000, 1.0 - 0.5000); float halfRadius = 0.5000 * 0.5; float innerEdge = halfRadius - 1.0000 * halfRadius * 0.5; float outerEdge = halfRadius + 1.0000 * halfRadius * 0.5; vec2 pos = vec2(0.5006944444444444, 0.5); const float TWO_PI = 6.28318530718; vec2 scaledUV = uv * aspectRatio * rot(0.0000 * TWO_PI) * skew; vec2 scaledPos = pos * aspectRatio * rot(0.0000 * TWO_PI) * skew; float radius = distance(scaledUV, scaledPos); float falloff = smoothstep(innerEdge + displacement, outerEdge + displacement, radius); vec3 finalColor;finalColor = mix(color.rgb, mix(color.rgb, vec3(0, 0, 0), 1.0000), falloff); color = mix(color * (1.-falloff), vec4(finalColor * color.a, color.a), 1.0000); fragColor = color;}"],"compiledVertexShaders":["#version 300 es\nprecision mediump float;in vec3 aVertexPosition; in vec2 aTextureCoord;uniform mat4 uMVMatrix; uniform mat4 uPMatrix; uniform mat4 uTextureMatrix;out vec2 vTextureCoord; out vec3 vVertexPosition;void main() { gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); vTextureCoord = (uTextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy; }"],"data":{"depth":false,"uniforms":{},"isBackground":false},"id":"effect14"}],"options":{"name":"Copy of zero (8)","fps":60,"dpi":1.5,"scale":1,"includeLogo":false,"isProduction":false},"version":"1.4.29","id":"u6y1wqsDX7oNKKlkVPpN"} No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

1 MB JSON shader payload—consider async loading or CDN caching

Shipping this monster inline will inflate first paint and possibly break CSP with the external https://assets.unicorn.studio/… texture.
Recommend hosting the JSON & texture on your own CDN and lazy-loading after the main UI is interactive.

🤖 Prompt for AI Agents
In apps/mail/public/mesh.json at line 1, the entire large JSON shader payload is
included inline, which can significantly slow down the initial page load and may
cause Content Security Policy (CSP) issues due to external texture URLs. To fix
this, move the JSON file and its associated textures to your own CDN or static
asset hosting, then implement lazy loading or asynchronous fetching of this JSON
after the main UI has loaded and become interactive. This will reduce the
initial payload size and improve performance and security compliance.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Review continued from previous batch...

</Button>
</a>
</div>
<main className="relative flex flex-col overflow-x-hidden bg-[#000000] px-2 flex-1">
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Use semantic color utility instead of hex value

Same issue as before. We need consistency across the codebase like we need reusable rockets.

-    <main className="relative flex flex-col overflow-x-hidden bg-[#000000] px-2 flex-1">
+    <main className="relative flex flex-col overflow-x-hidden bg-black px-2 flex-1">
📝 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
<main className="relative flex flex-col overflow-x-hidden bg-[#000000] px-2 flex-1">
<main className="relative flex flex-col overflow-x-hidden bg-black px-2 flex-1">
🤖 Prompt for AI Agents
In apps/mail/app/(full-width)/about.tsx at line 14, replace the hardcoded hex
color value #000000 in the className with the appropriate semantic color utility
class defined in the project's design system or tailwind config to maintain
consistency and reusability of colors across the codebase.

Comment on lines +59 to +86
<h2 className="text-3xl font-bold text-white mb-6 text-center">Our Team</h2>

<div className="text-center mb-8">
<p className="text-white/70 text-lg mb-6">
Zero is a team with one mission: bring agents to email.
</p>

<Button
className="rounded-[10px] px-4 py-1 h-9 bg-white text-[14px] font-medium leading-[1.43] text-[#262626] hover:bg-white/90 transition-colors"
onClick={() => window.location.href = '/team'}
>
Meet the rest of the team
</Button>
</div>


<div className="aspect-[16/9] relative overflow-hidden bg-white/5 rounded-xl border max-w-2xl mx-auto">
<img
src="/founders.jpg"
alt="Adam and Nizar, Zero founders"
className="w-full h-full object-cover"
/>
</div>
<p className="text-xs text-white/50 text-center mt-3">
Adam and Nizar at Y Combinator Demo Day
</p>
</section> */}

Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Remove commented code

Dead code is like fossil fuels - we need to move on. If this isn't needed, delete it.

🤖 Prompt for AI Agents
In apps/mail/app/(full-width)/about.tsx between lines 59 and 86, there is a
large block of commented-out code representing a team section. Since this code
is not currently needed and is considered dead code, remove the entire commented
section to clean up the file and improve maintainability.

Comment on lines +105 to +110
if (isInLastRow && lastRowCount === 2) {
const positionInLastRow = index - (totalInvestors - lastRowCount);
if (positionInLastRow === 0) gridPositionClass = 'lg:col-start-2';
if (positionInLastRow === 1) gridPositionClass = 'lg:col-start-3';
}

Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Add explanatory comment for grid centering logic

This grid positioning wizardry needs documentation. Future developers shouldn't need to reverse-engineer this like it's alien technology.

                const totalInvestors = investors.length;
                const investorsPerRow = 4;
                const lastRowCount = totalInvestors % investorsPerRow;
                const isInLastRow = index >= totalInvestors - lastRowCount;
                
+               // Center the last row if it has exactly 2 investors by adjusting grid column start positions
                let gridPositionClass = '';
                if (isInLastRow && lastRowCount === 2) {
                  const positionInLastRow = index - (totalInvestors - lastRowCount);
                  if (positionInLastRow === 0) gridPositionClass = 'lg:col-start-2';
                  if (positionInLastRow === 1) gridPositionClass = 'lg:col-start-3';
                }
📝 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 gridPositionClass = '';
if (isInLastRow && lastRowCount === 2) {
const positionInLastRow = index - (totalInvestors - lastRowCount);
if (positionInLastRow === 0) gridPositionClass = 'lg:col-start-2';
if (positionInLastRow === 1) gridPositionClass = 'lg:col-start-3';
}
// Center the last row if it has exactly 2 investors by adjusting grid column start positions
let gridPositionClass = '';
if (isInLastRow && lastRowCount === 2) {
const positionInLastRow = index - (totalInvestors - lastRowCount);
if (positionInLastRow === 0) gridPositionClass = 'lg:col-start-2';
if (positionInLastRow === 1) gridPositionClass = 'lg:col-start-3';
}
🤖 Prompt for AI Agents
In apps/mail/app/(full-width)/about.tsx around lines 105 to 110, add a clear
explanatory comment above the grid positioning logic that describes why and how
the gridPositionClass is set for centering the last row when it has exactly two
items. Explain the calculation of positionInLastRow and how it determines the
column start classes to center the items in the grid layout. This will help
future developers understand the purpose and mechanism of this code without
needing to reverse-engineer it.

Comment on lines +169 to +170
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Remove trailing empty lines

These empty lines are like unnecessary meetings - they waste space and serve no purpose.

  
];
-

-
📝 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
];
🤖 Prompt for AI Agents
In apps/mail/app/(full-width)/about.tsx around lines 169 to 170, remove the
trailing empty lines at the end of the file to eliminate unnecessary whitespace
and keep the code clean.

const [showAllContributors, setShowAllContributors] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const REPOSITORY = 'Mail-0/Zero';
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Hardcoded repository like a fixed trajectory. Make it configurable.

The repository should be an environment variable for flexibility.

-const REPOSITORY = 'Mail-0/Zero';
+const REPOSITORY = process.env.NEXT_PUBLIC_GITHUB_REPOSITORY || 'Mail-0/Zero';
📝 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
const REPOSITORY = 'Mail-0/Zero';
- const REPOSITORY = 'Mail-0/Zero';
+ const REPOSITORY = process.env.NEXT_PUBLIC_GITHUB_REPOSITORY || 'Mail-0/Zero';
🤖 Prompt for AI Agents
In apps/mail/app/(full-width)/contributors.tsx at line 58, the repository name
is hardcoded as 'Mail-0/Zero'. Replace this hardcoded string with a reference to
an environment variable to make the repository configurable. Use a suitable
environment variable access method (e.g., process.env.REPOSITORY) and ensure to
provide a fallback or handle the case where the environment variable is not set.

Comment on lines +45 to +56
<a
className="flex h-full w-full select-none flex-col justify-end rounded-lg p-[25px] no-underline outline-none"
style={{
backgroundImage: "url('/couple.jpeg')",
backgroundSize: "cover",
backgroundPosition: "center",
backgroundRepeat: "no-repeat"
}}
href="/"
>

</a>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add screen reader accessible content to image links

These empty links are like sending a rocket to space without telemetry - nobody knows what's happening. Add aria-labels or screen reader text.

                      <a
                        className="flex h-full w-full select-none flex-col justify-end rounded-lg p-[25px] no-underline outline-none"
                        style={{
                          backgroundImage: "url('/couple.jpeg')",
                          backgroundSize: "cover",
                          backgroundPosition: "center",
                          backgroundRepeat: "no-repeat"
                        }}
                        href="/"
+                       aria-label="Navigate to home page"
                      >
-                        
+                        <span className="sr-only">Navigate to home page</span>
                      </a>

Apply similar fixes to the other empty links with background images.

Also applies to: 85-96, 177-188

🧰 Tools
🪛 Biome (2.1.2)

[error] 45-56: Provide screen reader accessible content when using a elements.

All links on a page should have content that is accessible to screen readers.
Accessible content refers to digital content that is designed and structured in a way that makes it easy for people with disabilities to access, understand, and interact with using assistive technologies.
Follow these links for more information,
WCAG 2.4.4
WCAG 4.1.2

(lint/a11y/useAnchorContent)

🤖 Prompt for AI Agents
In apps/mail/components/ui/navigation-menu.tsx at lines 45 to 56, the anchor tag
with a background image is empty and lacks accessible content, making it
invisible to screen readers. Add an aria-label attribute describing the link's
purpose or include visually hidden text inside the anchor to provide screen
reader accessible content. Apply the same fix to the similar empty links with
background images at lines 85-96 and 177-188.

Comment on lines +30 to +70
function parseFrontmatter(content: string): { metadata: BlogPostMetadata; content: string } {
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);

if (!frontmatterMatch) {
throw new Error('No frontmatter found in MDX file');
}

const [, frontmatterStr, markdownContent] = frontmatterMatch;
const metadata: any = {};

// Parse YAML-like frontmatter
frontmatterStr.split('\n').forEach(line => {
const colonIndex = line.indexOf(':');
if (colonIndex > 0) {
const key = line.substring(0, colonIndex).trim();
let value = line.substring(colonIndex + 1).trim();

// Remove quotes
value = value.replace(/^["']|["']$/g, '');

// Handle arrays (for authorIds)
if (value.startsWith('[') && value.endsWith(']')) {
const arrayContent = value.slice(1, -1);
if (arrayContent) {
metadata[key] = arrayContent.split(',').map(item => item.trim().replace(/^["']|["']$/g, ''));
return; // Skip the normal assignment below
} else {
metadata[key] = [];
return; // Skip the normal assignment below
}
}

metadata[key] = value;
}
});

return {
metadata: metadata as BlogPostMetadata,
content: markdownContent.trim()
};
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Replace manual YAML parsing with a proper parser

Rolling your own YAML parser is like building your own rocket engine when Merlin already exists. This implementation doesn't handle edge cases like multiline values, special characters, or nested structures.

Use a battle-tested YAML parser:

import { parse } from 'yaml';

function parseFrontmatter(content: string): { metadata: BlogPostMetadata; content: string } {
  const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
  
  if (!frontmatterMatch) {
    throw new Error('No frontmatter found in MDX file');
  }

  const [, frontmatterStr, markdownContent] = frontmatterMatch;
  const metadata = parse(frontmatterStr) as BlogPostMetadata;

  return {
    metadata,
    content: markdownContent.trim()
  };
}
🤖 Prompt for AI Agents
In apps/mail/lib/mdx-utils.ts between lines 30 and 70, replace the manual YAML
frontmatter parsing logic with a proper YAML parser like the 'yaml' package.
Import the 'parse' function from 'yaml' and use it to parse the frontmatter
string instead of splitting and manually processing lines. This will handle
multiline values, special characters, and nested structures correctly and
simplify the code.

const { readFileSync, readdirSync } = await import('fs');
const { join } = await import('path');

const postsDirectory = join(process.cwd(), 'apps/mail/blog/posts');
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix incorrect blog posts directory path

According to the AI summary, blog posts are stored in apps/mail/public/blog/posts/, not apps/mail/blog/posts. This is like aiming for Mars but pointing at Venus.

-    const postsDirectory = join(process.cwd(), 'apps/mail/blog/posts');
+    const postsDirectory = join(process.cwd(), 'apps/mail/public/blog/posts');
📝 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
const postsDirectory = join(process.cwd(), 'apps/mail/blog/posts');
const postsDirectory = join(process.cwd(), 'apps/mail/public/blog/posts');
🤖 Prompt for AI Agents
In apps/mail/lib/mdx-utils.ts at line 86, the path to the blog posts directory
is incorrect; it currently points to 'apps/mail/blog/posts' but should point to
'apps/mail/public/blog/posts'. Update the join function argument to reflect the
correct directory path by inserting 'public' in the path string.

const { readFileSync } = await import('fs');
const { join } = await import('path');

const filePath = join(process.cwd(), 'apps/mail/blog/posts', `${slug}.mdx`);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Critical: Path traversal vulnerability and incorrect directory

Two issues here that need immediate attention - it's like having both a hull breach AND wrong navigation coordinates.

-    const filePath = join(process.cwd(), 'apps/mail/blog/posts', `${slug}.mdx`);
+    // Sanitize slug to prevent path traversal
+    const sanitizedSlug = slug.replace(/[^a-zA-Z0-9-]/g, '');
+    const filePath = join(process.cwd(), 'apps/mail/public/blog/posts', `${sanitizedSlug}.mdx`);
📝 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
const filePath = join(process.cwd(), 'apps/mail/blog/posts', `${slug}.mdx`);
// Sanitize slug to prevent path traversal
const sanitizedSlug = slug.replace(/[^a-zA-Z0-9-]/g, '');
const filePath = join(process.cwd(), 'apps/mail/public/blog/posts', `${sanitizedSlug}.mdx`);
🤖 Prompt for AI Agents
In apps/mail/lib/mdx-utils.ts at line 124, the construction of filePath using
join with process.cwd() and user-provided slug is vulnerable to path traversal
attacks and points to an incorrect directory. To fix this, sanitize and validate
the slug input to prevent directory traversal (e.g., disallow '..' or absolute
paths), and ensure the base directory is correctly set to the intended safe
folder for blog posts. Use path resolution methods to enforce that the final
resolved path remains within the allowed directory.

Comment on lines +169 to +175
// In a real app, you might have an API endpoint that lists all available posts
const knownSlugs = [
'a-faster-zero',
'zero-modernized-email',
'rebranding-zero-email',
// Add more as you create them
];
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

🛠️ Refactor suggestion

Hardcoded slugs won't scale - need dynamic discovery

This is like manually updating Tesla's software in each car instead of OTA updates. We need a scalable solution.

Create an API endpoint that returns available blog posts dynamically, or generate a manifest file during build time. Want me to implement a proper blog post discovery system?

🤖 Prompt for AI Agents
In apps/mail/lib/mdx-utils.ts around lines 169 to 175, the hardcoded list of
knownSlugs is not scalable. Replace this static array with a dynamic solution by
either creating an API endpoint that fetches available blog post slugs at
runtime or generating a manifest file during the build process that lists all
blog posts. This will automate slug discovery and eliminate manual updates.

@MrgSub MrgSub closed this Aug 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants