From 3b0b8dd9fae268690b3f991ea9b27363dab30e4b Mon Sep 17 00:00:00 2001 From: Dominik Hryshaiev Date: Thu, 18 Dec 2025 00:06:59 +0100 Subject: [PATCH] feat: update theme, readme and footer --- README.md | 9 +- src/app/favicon.ico | Bin 15406 -> 15406 bytes src/app/globals.css | 97 +++++++++--------- src/components/custom/email-content-input.tsx | 17 +-- src/components/layout/footer.tsx | 41 +++++--- 5 files changed, 94 insertions(+), 70 deletions(-) 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 9769b2c1a88ea662fdbb193b607468a3716698d8..6a4c62950c6e881a40a2e3ea1e8dd72a7bd20682 100644 GIT binary patch literal 15406 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 literal 15406 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 KbqgG#1^x@*Jb^?2 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 ( -