diff --git a/README.md b/README.md index 532b3ba..dfb0a0a 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ Open [http://localhost:3000](http://localhost:3000) in your browser. 1. **Iframe-based preview** - Uses `srcdoc` with strict sandboxing for security. This shows how the email renders in a modern browser, not specific email clients. -2. **Regex-based CSS extraction** - Simple and fast for the MVP. Could be upgraded to PostCSS for more robust parsing. +2. **PostCSS-based CSS extraction** - Uses PostCSS with safe-parser for robust CSS parsing, handling nested rules, @-rules, and malformed CSS gracefully. 3. **Client-side analysis** - All processing happens in the browser. The caniemail data is fetched once and cached. @@ -116,8 +116,13 @@ For true cross-client preview screenshots, commercial tools like [Litmus](https: - [Next.js 16](https://nextjs.org) - React framework - [TypeScript](https://typescriptlang.org) - Type safety - [Tailwind CSS](https://tailwindcss.com) - Styling +- [PostCSS](https://postcss.org) - CSS parsing - [caniemail.com](https://caniemail.com) - Compatibility data +## Author + +**Dominik Hryshaiev** - [LinkedIn](https://www.linkedin.com/in/domhhv) · [GitHub](https://github.com/domhhv) + ## License MIT @@ -125,5 +130,3 @@ MIT ## Acknowledgments - [caniemail.com](https://www.caniemail.com) for their comprehensive email client support data -- [Resend](https://resend.com) for the interesting challenge -- [Email favicon](https://www.flaticon.com/free-icon/email_9840614?term=mail&page=1&position=53&origin=search&related_id=9840614) is created by [lakonicon](https://www.flaticon.com/authors/lakonicon) on Flaticon diff --git a/src/app/favicon.ico b/src/app/favicon.ico index 9769b2c..6a4c629 100644 Binary files a/src/app/favicon.ico and b/src/app/favicon.ico differ diff --git a/src/app/globals.css b/src/app/globals.css index 9390f63..05247c4 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -44,30 +44,31 @@ } :root { - --radius: 0.625rem; - --background: oklch(1 0 0); - --foreground: oklch(0.129 0.042 264.695); - --card: oklch(1 0 0); - --card-foreground: oklch(0.129 0.042 264.695); - --popover: oklch(1 0 0); - --popover-foreground: oklch(0.129 0.042 264.695); - --primary: oklch(0.208 0.042 265.755); - --primary-foreground: oklch(0.984 0.003 247.858); - --secondary: oklch(0.968 0.007 247.896); - --secondary-foreground: oklch(0.208 0.042 265.755); - --muted: oklch(0.968 0.007 247.896); - --muted-foreground: oklch(0.554 0.046 257.417); - --accent: oklch(0.968 0.007 247.896); - --accent-foreground: oklch(0.208 0.042 265.755); - --destructive: oklch(0.577 0.245 27.325); - --border: oklch(0.929 0.013 255.508); - --input: oklch(0.929 0.013 255.508); - --ring: oklch(0.704 0.04 256.788); - --chart-1: oklch(0.646 0.222 41.116); - --chart-2: oklch(0.6 0.118 184.704); - --chart-3: oklch(0.398 0.07 227.392); - --chart-4: oklch(0.828 0.189 84.429); - --chart-5: oklch(0.769 0.188 70.08); + --radius: 0.5rem; + --background: oklch(0.982 0 161.565); + --foreground: oklch(0.244 0 169.695); + --muted: oklch(0.952 0 165.964); + --muted-foreground: oklch(0.503 0 180); + --popover: oklch(0.991 0 180); + --popover-foreground: oklch(0.244 0 169.695); + --card: oklch(0.991 0 180); + --card-foreground: oklch(0.244 0 169.695); + --border: oklch(0.882 0 180); + --input: oklch(0.955 0 180); + --primary: oklch(0.689 0.19 45.166); + --primary-foreground: oklch(1 0 180); + --secondary: oklch(0.92 0.065 74.362); + --secondary-foreground: oklch(0.35 0.069 40.827); + --accent: oklch(0.931 0 171.254); + --accent-foreground: oklch(0.244 0 169.695); + --destructive: oklch(0.627 0.194 33.336); + --destructive-foreground: oklch(1 0 180); + --ring: oklch(0.745 0.132 54.33); + --chart-1: oklch(0.689 0.19 45.166); + --chart-2: oklch(0.92 0.065 74.362); + --chart-3: oklch(0.931 0 171.254); + --chart-4: oklch(0.936 0.052 74.572); + --chart-5: oklch(0.693 0.195 44.608); --sidebar: oklch(0.984 0.003 247.858); --sidebar-foreground: oklch(0.129 0.042 264.695); --sidebar-primary: oklch(0.208 0.042 265.755); @@ -79,29 +80,31 @@ } .dark { - --background: oklch(0.129 0.042 264.695); - --foreground: oklch(0.984 0.003 247.858); - --card: oklch(0.208 0.042 265.755); - --card-foreground: oklch(0.984 0.003 247.858); - --popover: oklch(0.208 0.042 265.755); - --popover-foreground: oklch(0.984 0.003 247.858); - --primary: oklch(0.929 0.013 255.508); - --primary-foreground: oklch(0.208 0.042 265.755); - --secondary: oklch(0.279 0.041 260.031); - --secondary-foreground: oklch(0.984 0.003 247.858); - --muted: oklch(0.279 0.041 260.031); - --muted-foreground: oklch(0.704 0.04 256.788); - --accent: oklch(0.279 0.041 260.031); - --accent-foreground: oklch(0.984 0.003 247.858); - --destructive: oklch(0.704 0.191 22.216); - --border: oklch(1 0 0 / 10%); - --input: oklch(1 0 0 / 15%); - --ring: oklch(0.551 0.027 264.364); - --chart-1: oklch(0.488 0.243 264.376); - --chart-2: oklch(0.696 0.17 162.48); - --chart-3: oklch(0.769 0.188 70.08); - --chart-4: oklch(0.627 0.265 303.9); - --chart-5: oklch(0.645 0.246 16.439); + --radius: 0.5rem; + --background: oklch(0.178 0 180); + --foreground: oklch(0.949 0 180); + --muted: oklch(0.252 0 180); + --muted-foreground: oklch(0.77 0 141.34); + --popover: oklch(0.213 0 168.69); + --popover-foreground: oklch(0.949 0 180); + --card: oklch(0.213 0 168.69); + --card-foreground: oklch(0.949 0 180); + --border: oklch(0.235 0.011 90.394); + --input: oklch(0.402 0 195.945); + --primary: oklch(0.689 0.19 45.166); + --primary-foreground: oklch(1 0 180); + --secondary: oklch(0.258 0.045 60.789); + --secondary-foreground: oklch(0.925 0.052 66.177); + --accent: oklch(0.285 0 168.69); + --accent-foreground: oklch(0.949 0 180); + --destructive: oklch(0.627 0.194 33.336); + --destructive-foreground: oklch(1 0 180); + --ring: oklch(0.541 0.116 50.039); + --chart-1: oklch(0.689 0.19 45.166); + --chart-2: oklch(0.258 0.045 60.789); + --chart-3: oklch(0.285 0 168.69); + --chart-4: oklch(0.294 0.053 60.297); + --chart-5: oklch(0.693 0.195 44.608); --sidebar: oklch(0.208 0.042 265.755); --sidebar-foreground: oklch(0.984 0.003 247.858); --sidebar-primary: oklch(0.488 0.243 264.376); diff --git a/src/components/custom/email-content-input.tsx b/src/components/custom/email-content-input.tsx index 1fd86b6..a759076 100644 --- a/src/components/custom/email-content-input.tsx +++ b/src/components/custom/email-content-input.tsx @@ -8,7 +8,6 @@ import { useTheme } from 'next-themes'; import * as React from 'react'; import { TooltipButton } from '@/components/custom/tooltip-button'; -import { Button } from '@/components/ui/button'; import { Kbd, KbdGroup } from '@/components/ui/kbd'; import { useHasKeyboard } from '@/hooks/use-has-keyboard'; import { useModifierKeys } from '@/hooks/use-modifier-keys'; @@ -82,16 +81,22 @@ export function EmailContentInput({ onChange, placeholder = 'Paste your HTML ema ) : ( - + )}
diff --git a/src/components/layout/footer.tsx b/src/components/layout/footer.tsx index 5e5b665..54ef0f4 100644 --- a/src/components/layout/footer.tsx +++ b/src/components/layout/footer.tsx @@ -9,20 +9,33 @@ type FooterProps = { export function Footer({ canIEmailData }: FooterProps) { return ( -